- plugin: *.plugin.js with the store header and start/stop on api.scope, storage, observe, onKey, the UI kit (toast with action, ui.h settings dialog) and api.theme/--wb-* vars; --ts makes a TypeScript project typed by the waybetter package (build keeps the header first) - theme: JSON with "$schema" -> theme.schema.json, palette derived like themeFromColors, font roles (family, tweet), presets - app: Tauri 2 + waybetter-host + runtime + shell models: SiteSpec, SiteAdapter, shell/tab-chrome/ site-tab capabilities, tabs_sync, plugin store, a site-only command; presets for youtube.com, kick.com, twitch.tv with placeholder selectors; host crate via submodule, path or git - tests generate into temp dirs: headers via parse_meta (new plugin_meta example), plugin code against createCorePluginApi's members, themes against the schema, app TS with tsc against the built package, app Rust with cargo metadata/check/test - wired into bun run check (typecheck incl. checkJs, build:packages, test:create) and pack:check
73 lines
3.1 KiB
JavaScript
73 lines
3.1 KiB
JavaScript
// Names and small validators shared by the generators. Pure; no I/O.
|
|
|
|
/** A usage error: shown to the user as-is, exit code 1, no stack. */
|
|
export class UsageError extends Error {}
|
|
|
|
/** `Hide Trends!` -> `hide-trends` (ASCII letters and digits, dashes between). */
|
|
export function slugify(value, max = 64) {
|
|
return String(value)
|
|
.normalize('NFKD')
|
|
.replace(/\p{M}/gu, '')
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
.slice(0, max)
|
|
.replace(/-+$/, '');
|
|
}
|
|
|
|
/** `hide trends 2` -> `HideTrends2`; always a valid JS/Rust type identifier. */
|
|
export function pascalCase(value) {
|
|
const words = String(value).normalize('NFKD').replace(/\p{M}/gu, '').replace(/[^A-Za-z0-9]+/g, ' ').trim().split(/\s+/).filter(Boolean);
|
|
const name = words.map(word => word[0].toUpperCase() + word.slice(1)).join('');
|
|
return /^[A-Za-z]/.test(name) ? name : `Plugin${name}`;
|
|
}
|
|
|
|
/** `wayBetter for Kick` -> `wayBetterForKick` (camelCase, for `data-*` keys). */
|
|
export function camelCase(value) {
|
|
const name = pascalCase(value);
|
|
return name[0].toLowerCase() + name.slice(1);
|
|
}
|
|
|
|
/**
|
|
* A value for a `@name` / `@description` / ... plugin header line, or a theme string. The plugin
|
|
* store reads each header line up to its end, drops control characters and keeps 300 characters
|
|
* (waybetter-host `plugins::parse_meta`), so values are one line and cannot end the comment.
|
|
*/
|
|
export function headerValue(label, value, { max = 300, required = false } = {}) {
|
|
const text = String(value ?? '').trim();
|
|
if (required && !text) throw new UsageError(`${label} is required.`);
|
|
if (/[\u0000-\u001f\u007f]/.test(text)) throw new UsageError(`${label} must be one line of text.`);
|
|
if (text.includes('*/')) throw new UsageError(`${label} cannot contain "*/".`);
|
|
if (text.length > max) throw new UsageError(`${label} is longer than ${max} characters.`);
|
|
return text;
|
|
}
|
|
|
|
/** Semver-ish plugin version (`1.0.0`, `0.3.1-beta.2`). */
|
|
export function versionValue(value) {
|
|
const text = String(value ?? '1.0.0').trim() || '1.0.0';
|
|
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(text)) throw new UsageError(`Version "${text}" is not like 1.0.0.`);
|
|
return text;
|
|
}
|
|
|
|
export const HEX = /^#[0-9a-fA-F]{6}$/;
|
|
|
|
/** `#abc` / `abc123` / `#ABC123` -> `#aabbcc` style lowercase six-digit hex. */
|
|
export function hexValue(label, value) {
|
|
let text = String(value ?? '').trim().toLowerCase();
|
|
if (!text.startsWith('#')) text = `#${text}`;
|
|
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(text);
|
|
if (short) text = `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;
|
|
if (!HEX.test(text)) throw new UsageError(`${label} must be a hex color like #1d9bf0 (got "${value}").`);
|
|
return text;
|
|
}
|
|
|
|
/** A JS/TS string literal. */
|
|
export const str = value => JSON.stringify(String(value));
|
|
|
|
/** A Rust string literal (JSON escapes are valid Rust escapes for the characters we allow). */
|
|
export function rustStr(value) {
|
|
return JSON.stringify(String(value)).replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => `\\u{${hex}}`);
|
|
}
|
|
|
|
/** Joins lines with `\n` and ends the file with one newline. */
|
|
export const lines = (...parts) => `${parts.flat().join('\n').replace(/\n+$/, '')}\n`;
|