// THE TWO FILES GENERATED FROM THE SETTINGS SCHEMA: settings.json.example and // the key table SETTINGS.md, both at the repo root. // // Pure renderers — `common/bin/settings-example.ts` writes them, and // `settingsDocs.test.ts` asserts the committed files are byte-identical to what // these return, so neither can be edited by hand without the test failing. // // Everything comes from `siteSettingsSchema` (./settingsSchema.ts): the keys and // their order from its shape, the defaults from `defaultSiteSettings()`, the // prose from each field's `.describe()`. Changing a default or a description is // a schema edit followed by regenerating, never an edit here. import { ARCHIVE_ORG_SETTINGS_FIELD_DOCS, ARCHIVE_STORAGE_SETTINGS_FIELD_DOCS, ATTRIBUTION_SETTINGS_FIELD_DOCS, BACKFILL_SETTINGS_FIELD_DOCS, BUILD_PIPELINE_SETTINGS_FIELD_DOCS, DIARIZATION_SETTINGS_FIELD_DOCS, PACING_SETTINGS_FIELD_DOCS, PUBLISH_SETTINGS_FIELD_DOCS, DIGEST_SETTINGS_FIELD_DOCS, SAVED_VIDEO_BACKUP_SETTINGS_FIELD_DOCS, SEEDER_SETTINGS_FIELD_DOCS, SOCIAL_LINK_FIELD_DOCS, SOCIAL_SETTINGS_FIELD_DOCS, X_SOCIAL_SETTINGS_FIELD_DOCS, SYNC_SCHEDULER_SETTINGS_FIELD_DOCS, defaultSiteSettings, siteSettingsSchema, type SiteSettings, } from "./settingsSchema"; import { AUTO_QUEUE_MATCH_FIELD_DOCS, AUTO_QUEUE_NODE_FIELD_DOCS, AUTO_QUEUE_POLICY_FIELD_DOCS, LANES, } from "./autoQueueTypes"; import { CHANNEL_AUTO_PAUSE_FIELD_DOCS, CHANNEL_FOCUS_FIELD_DOCS, CHANNEL_PRIORITY_ENTRY_FIELD_DOCS, CHANNEL_PRIORITY_FIELD_DOCS, } from "./channelPriority"; import { LLM_WORKER_CONFIG_FIELD_DOCS, REMOTE_WORKER_CONFIG_FIELD_DOCS, WORKER_FIELD_DOCS, } from "./workers"; import { APP_INSTANCE_CONFIG_FIELD_DOCS } from "./transcriptionApps"; import { DIGEST_APP_CONFIG_FIELD_DOCS } from "./digest"; import { STORAGE_LOCATION_FIELD_DOCS, STORAGE_SETTINGS_FIELD_DOCS, STORAGE_VOLUME_FIELD_DOCS, } from "./storageLocations"; import { HEALTH_TIMING_DEFAULTS, STORAGE_HEALTH_SETTINGS_FIELD_DOCS, } from "./storageHealthTimings"; // `workers` IS LEFT OUT OF THE EXAMPLE, and that is the one place the example // is not the literal default object. Its default is `[]`, and a settings.json // that SPELLS `workers: []` READS as "no transcription workers" — until the // next save, when writeSettings' worker shadow synthesizes one from // `transcriptionApp`; in between, auto-transcribe does nothing, silently. A file // that does not name the key gets that worker list synthesized on read (see // getSettings), which is what a template copied to settings.json should give. export const EXAMPLE_OMITTED_KEYS = ["workers"] as const; export function renderSettingsExample(): string { const d = defaultSiteSettings() as Record; for (const key of EXAMPLE_OMITTED_KEYS) delete d[key]; return JSON.stringify(d, null, 2) + "\n"; } export function isScalar(v: unknown): boolean { return v === null || typeof v !== "object"; } // The default as a table cell: a scalar inline, an empty container inline, // anything larger by reference to its section. export function defaultCell(v: unknown): string { if (isScalar(v)) return "`" + JSON.stringify(v) + "`"; const json = JSON.stringify(v); if (json === "[]" || json === "{}") return "`" + json + "`"; return Array.isArray(v) ? "list — see below" : "object — see below"; } // A description inside a table cell: one line, pipes escaped, paragraphs kept. export function cell(text: string): string { return text.replace(/\|/g, "\\|").replace(/\n\n/g, "

").replace(/\n/g, " "); } // One nested key table. `defaults(key)` answers the Default column; a table of // per-entry fields (list items, map values, tree nodes) has no defaults — each // entry spells its own — and says so. export type KeyTable = { path: string; docs: Readonly>; defaults?: (key: string) => string; }; export function fromObject(obj: unknown): (key: string) => string { const r = (obj ?? {}) as Record; return (key) => (key in r ? defaultCell(r[key]) : "absent"); } // A lane-policy field's default can differ per lane (`held`, `order`, `root`), // and that difference is exactly what a reader needs to see. function perLane(d: SiteSettings): (key: string) => string { return (key) => { const cells = LANES.map((lane) => { const policy = d.autoQueue[lane] as Record; return key in policy ? defaultCell(policy[key]) : "absent"; }); if (cells.every((c) => c === cells[0])) return cells[0]; return LANES.map((lane, i) => `${lane} ${cells[i]}`).join("
"); }; } export function blockTables(d: SiteSettings): Partial> { return { transcriptionApps: [ { path: "transcriptionApps.", docs: APP_INSTANCE_CONFIG_FIELD_DOCS }, ], workers: [ { path: "workers[]", docs: WORKER_FIELD_DOCS }, { path: "workers[].config", docs: APP_INSTANCE_CONFIG_FIELD_DOCS }, { path: "workers[].remote", docs: REMOTE_WORKER_CONFIG_FIELD_DOCS }, { path: "workers[].llm", docs: LLM_WORKER_CONFIG_FIELD_DOCS }, ], pacing: [ { path: "pacing", docs: PACING_SETTINGS_FIELD_DOCS, defaults: fromObject(d.pacing), }, ], archiveStorage: [ { path: "archiveStorage", docs: ARCHIVE_STORAGE_SETTINGS_FIELD_DOCS, defaults: fromObject(d.archiveStorage), }, ], syncScheduler: [ { path: "syncScheduler", docs: SYNC_SCHEDULER_SETTINGS_FIELD_DOCS, defaults: fromObject(d.syncScheduler), }, ], autoQueue: [ { path: "autoQueue.", docs: AUTO_QUEUE_POLICY_FIELD_DOCS, defaults: perLane(d), }, { path: "autoQueue..root (tree nodes)", docs: AUTO_QUEUE_NODE_FIELD_DOCS }, { path: "autoQueue..root … .match", docs: AUTO_QUEUE_MATCH_FIELD_DOCS }, ], channelPriority: [ { path: "channelPriority", docs: CHANNEL_PRIORITY_FIELD_DOCS, defaults: fromObject(d.channelPriority), }, { path: "channelPriority.focus", docs: CHANNEL_FOCUS_FIELD_DOCS }, { path: "channelPriority.channels.", docs: CHANNEL_PRIORITY_ENTRY_FIELD_DOCS }, { path: "channelPriority.channels..autoPaused", docs: CHANNEL_AUTO_PAUSE_FIELD_DOCS, }, ], social: [ { path: "social", docs: SOCIAL_SETTINGS_FIELD_DOCS, defaults: fromObject(d.social) }, { path: "social.x", docs: X_SOCIAL_SETTINGS_FIELD_DOCS, // Absent from the default block: the source is resolved at read time // and only a chosen one is written. defaults: fromObject(d.social.x), }, ], socialLinks: [{ path: "socialLinks[]", docs: SOCIAL_LINK_FIELD_DOCS }], savedVideoBackup: [ { path: "savedVideoBackup", docs: SAVED_VIDEO_BACKUP_SETTINGS_FIELD_DOCS, defaults: fromObject(d.savedVideoBackup), }, ], storage: [ { path: "storage", docs: STORAGE_SETTINGS_FIELD_DOCS, defaults: fromObject(d.storage), }, { path: "storage.locations[]", docs: STORAGE_LOCATION_FIELD_DOCS }, { path: "storage.locations[].volume", docs: STORAGE_VOLUME_FIELD_DOCS }, // Absent from the default block (only a tuned value is written), so the // Default column is each timing's default, not the block's. { path: "storage.health", docs: STORAGE_HEALTH_SETTINGS_FIELD_DOCS, defaults: fromObject(HEALTH_TIMING_DEFAULTS), }, ], buildPipeline: [ { path: "buildPipeline", docs: BUILD_PIPELINE_SETTINGS_FIELD_DOCS, defaults: fromObject(d.buildPipeline), }, ], digest: [ { path: "digest", docs: DIGEST_SETTINGS_FIELD_DOCS, defaults: fromObject(d.digest) }, { path: "digest.apps.", docs: DIGEST_APP_CONFIG_FIELD_DOCS }, ], diarization: [ { path: "diarization", docs: DIARIZATION_SETTINGS_FIELD_DOCS, defaults: fromObject(d.diarization), }, ], backfill: [ { path: "backfill", docs: BACKFILL_SETTINGS_FIELD_DOCS, defaults: fromObject(d.backfill), }, ], attribution: [ { path: "attribution", docs: ATTRIBUTION_SETTINGS_FIELD_DOCS, defaults: fromObject(d.attribution), }, ], archiveOrg: [ { path: "archiveOrg", docs: ARCHIVE_ORG_SETTINGS_FIELD_DOCS, defaults: fromObject(d.archiveOrg), }, ], publish: [ { path: "publish", docs: PUBLISH_SETTINGS_FIELD_DOCS, defaults: fromObject(d.publish), }, ], seeder: [ { path: "seeder", docs: SEEDER_SETTINGS_FIELD_DOCS, defaults: fromObject(d.seeder), }, ], }; } // Shared with lib/fileSchemaDocs.ts (SITE.md, CHANNEL.md). export function renderTable(out: string[], table: KeyTable): void { out.push(`#### \`${table.path}\``); out.push(""); if (table.defaults) { out.push("| Key | Default | Description |"); out.push("|---|---|---|"); for (const [key, text] of Object.entries(table.docs)) { out.push(`| \`${key}\` | ${table.defaults(key)} | ${cell(text)} |`); } } else { out.push("Per entry — each entry spells its own values."); out.push(""); out.push("| Key | Description |"); out.push("|---|---|"); for (const [key, text] of Object.entries(table.docs)) { out.push(`| \`${key}\` | ${cell(text)} |`); } } out.push(""); } export function renderSettingsMarkdown(): string { const d = defaultSiteSettings(); const values = d as Record; const shape = siteSettingsSchema.shape as Record< string, { description?: string } >; const tables = blockTables(d); const keys = Object.keys(shape); const out: string[] = []; out.push("# settings.json keys"); out.push(""); out.push( "", ); out.push(""); out.push( "Global operational settings shared by every site this editor powers, " + "persisted to `settings.json` at the repo root (or `$SETTINGS_FILE`). " + "Per-site presentation lives in `sites//site.json`. Every key is " + "optional: a missing key reads as its default, an ill-typed one is " + "coerced to its default or clamped, and an unknown one is dropped on the " + "next save.", ); out.push(""); out.push( "Regenerate this file and `settings.json.example` with " + "`pnpm --filter yt-dlp-transcript-common exec tsx bin/settings-example.ts`.", ); out.push(""); out.push( "`settings.json.example` is the default object with one key left out, " + "`workers`: a file that does not name it gets a worker list synthesized " + "from `transcriptionApp` on read. A file that spells `workers: []` READS " + "as no transcription at all — until the next save, when the writer " + "synthesizes a worker the same way.", ); out.push(""); out.push( "A copied example PINS every default it spells — including each lane's " + "`autoQueue..held` — so a default changed in a later release will " + "not reach that file. Delete any key you would rather have track the " + "defaults.", ); out.push(""); out.push("| Key | Default |"); out.push("|---|---|"); for (const key of keys) { out.push(`| [\`${key}\`](#${key.toLowerCase()}) | ${defaultCell(values[key])} |`); } out.push(""); for (const key of keys) { out.push(`## \`${key}\``); out.push(""); out.push(shape[key].description ?? ""); out.push(""); const v = values[key]; if (isScalar(v)) { out.push(`Default: \`${JSON.stringify(v)}\``); out.push(""); } else { for (const table of tables[key as keyof SiteSettings] ?? []) { renderTable(out, table); } out.push("Default:"); out.push(""); out.push("```json"); out.push(JSON.stringify(v, null, 2)); out.push("```"); out.push(""); } } return out.join("\n"); }