@takuhon/cli 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -2
- package/dist/index.d.ts +22 -0
- package/dist/index.js +1594 -18
- package/dist/index.js.map +1 -1
- package/dist/init.js +3 -3
- package/dist/init.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/validate-command.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `@takuhon/cli` entry point — the `takuhon` command.\n *\n * At present this exposes `--version` / `--help`, the `validate` command, and\n * a pointer to `create-takuhon` for scaffolding. The dev / sync / export /\n * migrate / restore subcommands land in subsequent releases. The bare-name\n * `takuhon` npm package (`packages/takuhon/`) redirects here via a 4-line\n * shim, so `npm i -g takuhon && takuhon --help` and `npm i -g @takuhon/cli\n * && takuhon --help` give the same output.\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { runValidate } from './validate-command.js';\n\n// Source the reported version from package.json (read at runtime relative to\n// this module) so `takuhon --version` can never drift from the published\n// release — there is no hand-maintained version literal to fall out of sync.\nconst pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as {\n version: string;\n};\nconst VERSION = pkg.version;\n\nconst HELP = `takuhon ${VERSION}\n\nTakuhon — open-source portable profile API server.\n\nUsage:\n takuhon --version Show the installed CLI version\n takuhon --help Show this help\n\nCommands:\n takuhon validate [path] Validate a takuhon.json (default: ./takuhon.json)\n\nScaffolding a new profile project:\n npx create-takuhon my-profile\n npx create-takuhon my-profile --license CC-BY-4.0\n\nSubcommands (dev / sync / export / migrate / restore) are planned\nfor a future release. Track progress at:\n\n https://github.com/takuhon-dev/takuhon\n`;\n\nfunction main(argv: readonly string[]): number {\n const first = argv[0];\n\n if (first === '--version' || first === '-v') {\n process.stdout.write(`${VERSION}\\n`);\n return 0;\n }\n\n if (first === undefined || first === '--help' || first === '-h') {\n process.stdout.write(HELP);\n return 0;\n }\n\n if (first === 'validate') {\n const { code, stdout, stderr } = runValidate(argv.slice(1));\n if (stdout) process.stdout.write(stdout);\n if (stderr) process.stderr.write(stderr);\n return code;\n }\n\n process.stderr.write(\n `takuhon: unknown command '${first}'\\n` +\n `Run \\`takuhon --help\\` for usage. For scaffolding a new project, use \\`create-takuhon\\`.\\n`,\n );\n return 2;\n}\n\nprocess.exit(main(process.argv.slice(2)));\n","/**\n * `takuhon validate [path]` — validate a `takuhon.json` against `@takuhon/core`.\n *\n * All argument handling and the validation itself live here as pure,\n * side-effect-free functions so the whole command is unit-testable: `index.ts`\n * runs `process.exit(main(...))` at module top level and is therefore not\n * import-safe. `runValidate` reads the target file itself but returns its\n * output as strings plus an exit code, leaving the actual stdout/stderr writes\n * and `process.exit` to the caller.\n *\n * Exit codes:\n * 0 — the document is valid (or `--help` was requested)\n * 1 — the document was read and parsed but failed schema validation\n * 2 — the command could not run: bad arguments, a missing/unreadable file,\n * or a file that is not valid JSON (operational errors, distinct from an\n * invalid-but-readable document)\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { validate } from '@takuhon/core';\n\n/** Default profile filename, resolved relative to the current working directory. */\nconst DEFAULT_PATH = 'takuhon.json';\n\nconst USAGE = `Usage: takuhon validate [path]\n\nValidate a takuhon.json against the takuhon schema. With no path, validates\n./takuhon.json in the current working directory.\n\nExit codes: 0 = valid, 1 = invalid, 2 = file missing / unreadable / not JSON.\n`;\n\nexport interface ValidateOutcome {\n /** Process exit code (see module docstring). */\n readonly code: number;\n /** Text destined for stdout (empty when there is nothing to print). */\n readonly stdout: string;\n /** Text destined for stderr (empty when there is nothing to print). */\n readonly stderr: string;\n}\n\n/**\n * Run `takuhon validate` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"validate\"`).\n *\n * Handles `--help` / `-h` and rejects extra positionals, then validates the\n * `takuhon.json` at the single optional path argument (default\n * `./takuhon.json`). Never throws and never writes to the process streams —\n * the caller renders the returned `stdout` / `stderr` and exits with `code`.\n */\nexport function runValidate(args: readonly string[] = []): ValidateOutcome {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n if (args.length > 1) {\n return {\n code: 2,\n stdout: '',\n stderr:\n 'takuhon: `validate` takes at most one path argument.\\n' +\n 'Run `takuhon validate --help` for usage.\\n',\n };\n }\n\n return validateFile(args[0]);\n}\n\n/** Read, parse, and schema-validate the profile at `pathArg` (default `./takuhon.json`). */\nfunction validateFile(pathArg?: string): ValidateOutcome {\n const target = pathArg ?? DEFAULT_PATH;\n\n let raw: string;\n try {\n raw = readFileSync(target, 'utf8');\n } catch {\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: cannot read '${target}'. Pass a path, or run from a directory containing a takuhon.json.\\n`,\n };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: '${target}' is not valid JSON: ${detail}\\n`,\n };\n }\n\n const result = validate(data);\n if (result.ok) {\n return {\n code: 0,\n stdout: `${target}: valid (schemaVersion ${result.data.schemaVersion}).\\n`,\n stderr: '',\n };\n }\n\n const count = result.errors.length;\n const lines = result.errors.map((error) => ` ${error.pointer || '/'}: ${error.message}`);\n return {\n code: 1,\n stdout: '',\n stderr: `${target}: invalid (${count} error${count === 1 ? '' : 's'}):\\n${lines.join('\\n')}\\n`,\n };\n}\n"],"mappings":";;;AAaA,SAAS,gBAAAA,qBAAoB;;;ACK7B,SAAS,oBAAoB;AAE7B,SAAS,gBAAgB;AAGzB,IAAM,eAAe;AAErB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BP,SAAS,YAAY,OAA0B,CAAC,GAAoB;AACzE,MAAI,KAAK,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,MAAM;AAC5C,WAAO,EAAE,MAAM,GAAG,QAAQ,OAAO,QAAQ,GAAG;AAAA,EAC9C;AAEA,MAAI,KAAK,SAAS,GAAG;AACnB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QACE;AAAA,IAEJ;AAAA,EACF;AAEA,SAAO,aAAa,KAAK,CAAC,CAAC;AAC7B;AAGA,SAAS,aAAa,SAAmC;AACvD,QAAM,SAAS,WAAW;AAE1B,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,QAAQ,MAAM;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,yBAAyB,MAAM;AAAA;AAAA,IACzC;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,aAAa,MAAM,wBAAwB,MAAM;AAAA;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,IAAI;AAC5B,MAAI,OAAO,IAAI;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,GAAG,MAAM,0BAA0B,OAAO,KAAK,aAAa;AAAA;AAAA,MACpE,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,QAAQ,OAAO,OAAO;AAC5B,QAAM,QAAQ,OAAO,OAAO,IAAI,CAAC,UAAU,KAAK,MAAM,WAAW,GAAG,KAAK,MAAM,OAAO,EAAE;AACxF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ,GAAG,MAAM,cAAc,KAAK,SAAS,UAAU,IAAI,KAAK,GAAG;AAAA,EAAO,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA,EAC5F;AACF;;;AD5FA,IAAM,MAAM,KAAK,MAAMC,cAAa,IAAI,IAAI,mBAAmB,YAAY,GAAG,GAAG,MAAM,CAAC;AAGxF,IAAM,UAAU,IAAI;AAEpB,IAAM,OAAO,WAAW,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqB/B,SAAS,KAAK,MAAiC;AAC7C,QAAM,QAAQ,KAAK,CAAC;AAEpB,MAAI,UAAU,eAAe,UAAU,MAAM;AAC3C,YAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AACnC,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,UAAa,UAAU,YAAY,UAAU,MAAM;AAC/D,YAAQ,OAAO,MAAM,IAAI;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,YAAY;AACxB,UAAM,EAAE,MAAM,QAAQ,OAAO,IAAI,YAAY,KAAK,MAAM,CAAC,CAAC;AAC1D,QAAI,OAAQ,SAAQ,OAAO,MAAM,MAAM;AACvC,QAAI,OAAQ,SAAQ,OAAO,MAAM,MAAM;AACvC,WAAO;AAAA,EACT;AAEA,UAAQ,OAAO;AAAA,IACb,6BAA6B,KAAK;AAAA;AAAA;AAAA,EAEpC;AACA,SAAO;AACT;AAEA,QAAQ,KAAK,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;","names":["readFileSync","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/build-command.ts","../src/backup.ts","../src/site.ts","../src/build-html.ts","../src/dev-command.ts","../src/export-command.ts","../src/import-command.ts","../src/migrate-command.ts","../src/restore-command.ts","../src/validate-command.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `@takuhon/cli` entry point — the `takuhon` command.\n *\n * Exposes `--version` / `--help`, the local profile commands (`validate`,\n * `migrate`, `restore`, `export`, `import`, `build`, `dev`), and a pointer to\n * `create-takuhon` for scaffolding. The `sync` subcommand lands in a subsequent\n * release.\n *\n * `main` is pure (returns an exit code, never calls `process.exit`); the only\n * place that exits the process is {@link run}, invoked either when this module\n * is the entry script or by the bare-name `takuhon` package's `bin.mjs`, which\n * imports and calls `run()`. Keeping `process.exit` at that single boundary\n * lets tests import this module without terminating the test runner.\n */\n\nimport { readFileSync, realpathSync } from 'node:fs';\nimport { stdin, stdout } from 'node:process';\nimport { createInterface } from 'node:readline/promises';\nimport { fileURLToPath } from 'node:url';\n\nimport { runBuild } from './build-command.js';\nimport { runDev } from './dev-command.js';\nimport { runExport } from './export-command.js';\nimport { runImport } from './import-command.js';\nimport { runMigrate } from './migrate-command.js';\nimport { runRestore } from './restore-command.js';\nimport { runValidate } from './validate-command.js';\n\n// Source the reported version from package.json (read at runtime relative to\n// this module) so `takuhon --version` can never drift from the published\n// release — there is no hand-maintained version literal to fall out of sync.\nconst pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as {\n version: string;\n};\nconst VERSION = pkg.version;\n\nconst HELP = `takuhon ${VERSION}\n\nTakuhon — open-source portable profile API server.\n\nUsage:\n takuhon --version Show the installed CLI version\n takuhon --help Show this help\n\nCommands:\n takuhon validate [path] Validate a takuhon.json (default: ./takuhon.json)\n takuhon migrate [path] [--to <v>] Forward-migrate a takuhon.json to a newer schema\n version (default target: latest). Backs up first.\n Add --out <file> to write elsewhere, --dry-run to preview.\n takuhon restore --from <backup> Restore a profile from a backup (prompts before\n overwriting; pass --yes to skip).\n takuhon export [path] [--output <f>] Serialise a takuhon.json to stdout (or --output file).\n takuhon import <file> [path] Import an exported profile into a takuhon.json,\n migrating to the current schema version. Backs up first.\n takuhon build [path] [--output <d>] Render a takuhon.json into a static site (HTML +\n JSON-LD, one page per locale). --base-url <url> adds\n absolute canonical/hreflang links.\n takuhon dev [path] [--port <n>] Serve a takuhon.json as a local static preview,\n re-rendered on each request (default port: 4321).\n --base-url <url> adds canonical/hreflang links.\n\nScaffolding a new profile project:\n npx create-takuhon my-profile\n npx create-takuhon my-profile --license CC-BY-4.0\n\nThe sync subcommand is planned for a future release.\nTrack progress at:\n\n https://github.com/takuhon-dev/takuhon\n`;\n\nasync function main(argv: readonly string[]): Promise<number> {\n const first = argv[0];\n\n if (first === '--version' || first === '-v') {\n process.stdout.write(`${VERSION}\\n`);\n return 0;\n }\n\n if (first === undefined || first === '--help' || first === '-h') {\n process.stdout.write(HELP);\n return 0;\n }\n\n if (first === 'validate') {\n return emit(runValidate(argv.slice(1)));\n }\n\n if (first === 'migrate') {\n return emit(runMigrate(argv.slice(1)));\n }\n\n if (first === 'export') {\n return emit(runExport(argv.slice(1)));\n }\n\n if (first === 'build') {\n return emit(runBuild(argv.slice(1)));\n }\n\n if (first === 'dev') {\n // `dev` runs a long-lived server and streams its own output, so it does not\n // go through `emit` (a one-shot result writer); it returns the exit code\n // directly and resolves only on graceful shutdown.\n return runDev(argv.slice(1));\n }\n\n if (first === 'import') {\n return emit(runImport(argv.slice(1)));\n }\n\n if (first === 'restore') {\n // Only offer an interactive prompt on a real TTY; otherwise `runRestore`\n // refuses to overwrite without `--yes`, which is the safe default for\n // pipelines.\n const confirm = stdin.isTTY ? promptConfirm : undefined;\n return emit(await runRestore(argv.slice(1), { confirm }));\n }\n\n process.stderr.write(\n `takuhon: unknown command '${first}'\\n` +\n `Run \\`takuhon --help\\` for usage. For scaffolding a new project, use \\`create-takuhon\\`.\\n`,\n );\n return 2;\n}\n\n/** Write a command outcome's streams and return its exit code. */\nfunction emit(outcome: { code: number; stdout: string; stderr: string }): number {\n if (outcome.stdout) process.stdout.write(outcome.stdout);\n if (outcome.stderr) process.stderr.write(outcome.stderr);\n return outcome.code;\n}\n\n/** Interactive [y/N] confirmation used by `restore` on a TTY. */\nasync function promptConfirm(message: string): Promise<boolean> {\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n const answer = await rl.question(`${message} `);\n return /^y(es)?$/i.test(answer.trim());\n } finally {\n rl.close();\n }\n}\n\n/**\n * Process entry point: run {@link main} and exit with its code. This is the\n * only function that calls `process.exit`. Exported so the bare-name\n * `takuhon` package can invoke it after importing this module.\n */\nexport async function run(argv: readonly string[] = process.argv.slice(2)): Promise<void> {\n try {\n process.exit(await main(argv));\n } catch (error) {\n process.stderr.write(`takuhon: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exit(1);\n }\n}\n\n/** True when this module was started directly (`node …/index.js`). */\nfunction isEntrypoint(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n return false;\n }\n}\n\nif (isEntrypoint()) {\n void run();\n}\n","/**\n * `takuhon build [path] [--output <dir>] [--base-url <url>]` — render a\n * `takuhon.json` into a deployable static site (Spec §13 Static Edition).\n *\n * Mirrors the other command runners: {@link runBuild} is a pure function that\n * reads the source itself and returns its output as strings plus an exit code.\n * The render pipeline reuses `@takuhon/core` only: validate → normalize →\n * `applyPublicPrivacyFilter` (so the static site honours `meta.privacy` exactly\n * like the live API) → for each available locale `resolveLocale` →\n * {@link renderProfileHtml}. The default locale is written to `<dir>/index.html`\n * and every other locale to `<dir>/<locale>/index.html`.\n *\n * Canonical / hreflang links require absolute URLs, so they are emitted only\n * when `--base-url` is supplied; the human locale switcher always uses\n * depth-correct relative links. Asset files are referenced by URL as-is and\n * never copied (out of scope; assets are remote per the schema). Writes are\n * atomic via {@link writeFileAtomic}.\n *\n * Exit codes:\n * 0 — site generated (or `--help`)\n * 1 — the source was read but is not a valid takuhon profile\n * 2 — the command could not run: bad arguments, a missing/unreadable file,\n * a non-JSON file, or a failed write\n */\n\nimport { mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { applyPublicPrivacyFilter, normalize, validate } from '@takuhon/core';\n\nimport { writeFileAtomic } from './backup.js';\nimport { generateSite } from './site.js';\n\nconst DEFAULT_PATH = 'takuhon.json';\nconst DEFAULT_OUTPUT = 'dist';\n\nconst USAGE = `Usage: takuhon build [path] [--output <dir>] [--base-url <url>]\n\nRender a takuhon.json into a static site (one HTML page per locale, with\nbuild-time Schema.org JSON-LD). With no path, builds ./takuhon.json.\n\nOptions:\n --output <dir> Output directory (default: ${DEFAULT_OUTPUT}). The default\n locale is written to <dir>/index.html and each other locale\n to <dir>/<locale>/index.html.\n --base-url <url> Site origin (e.g. https://me.example). Enables absolute\n canonical and hreflang links; without it those are omitted.\n\nThe public privacy filter is applied (meta.privacy is honoured). Asset URLs are\nreferenced as-is and are not copied. The output directory is written into, not\ncleaned — use a dedicated/empty directory so stale pages do not linger.\n\nExit codes: 0 = built, 1 = source is not a valid profile,\n2 = bad arguments / file missing / unreadable / not JSON / write failed.\n`;\n\nexport interface BuildOutcome {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\ninterface ParsedArgs {\n path: string;\n output: string;\n baseUrl?: string;\n}\n\n/**\n * Run `takuhon build` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"build\"`).\n */\nexport function runBuild(args: readonly string[] = []): BuildOutcome {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n const parsed = parseArgs(args);\n if ('error' in parsed) {\n return {\n code: 2,\n stdout: '',\n stderr: `${parsed.error}\\nRun \\`takuhon build --help\\` for usage.\\n`,\n };\n }\n\n return buildSite(parsed);\n}\n\nfunction parseArgs(args: readonly string[]): ParsedArgs | { error: string } {\n let path: string | undefined;\n let output: string | undefined;\n let baseUrl: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n\n if (arg === '--output' || arg === '--base-url') {\n const value = args[i + 1];\n if (value === undefined || value === '' || value.startsWith('-')) {\n return { error: `takuhon: \\`${arg}\\` requires a value.` };\n }\n if (arg === '--output') output = value;\n else baseUrl = value;\n i++;\n continue;\n }\n if (arg.startsWith('--output=')) {\n const value = arg.slice('--output='.length);\n if (value === '') return { error: 'takuhon: `--output` requires a value.' };\n output = value;\n continue;\n }\n if (arg.startsWith('--base-url=')) {\n const value = arg.slice('--base-url='.length);\n if (value === '') return { error: 'takuhon: `--base-url` requires a value.' };\n baseUrl = value;\n continue;\n }\n if (arg.startsWith('-')) {\n return { error: `takuhon: unknown option \\`${arg}\\` for \\`build\\`.` };\n }\n if (path !== undefined) {\n return { error: 'takuhon: `build` takes at most one path argument.' };\n }\n path = arg;\n }\n\n if (baseUrl !== undefined && !isHttpUrl(baseUrl)) {\n return { error: 'takuhon: `--base-url` must be an absolute http(s) URL.' };\n }\n\n return {\n path: path ?? DEFAULT_PATH,\n output: output ?? DEFAULT_OUTPUT,\n // Drop any trailing slash so URL joins are predictable.\n baseUrl: baseUrl?.replace(/\\/+$/, ''),\n };\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\nfunction buildSite(parsed: ParsedArgs): BuildOutcome {\n const { path, output, baseUrl } = parsed;\n\n let raw: string;\n try {\n raw = readFileSync(path, 'utf8');\n } catch {\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: cannot read '${path}'. Pass a path, or run from a directory containing a takuhon.json.\\n`,\n };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: '${path}' is not valid JSON: ${detail}\\n` };\n }\n\n const result = validate(data);\n if (!result.ok) {\n const lines = result.errors.map((e) => ` ${e.pointer || '/'}: ${e.message}`);\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: '${path}' is not a valid takuhon profile; refusing to build:\\n${lines.join('\\n')}\\n`,\n };\n }\n\n const filtered = applyPublicPrivacyFilter(normalize(result.data));\n\n const written: string[] = [];\n try {\n for (const page of generateSite(filtered, { baseUrl })) {\n const outFile = join(output, page.file);\n mkdirSync(dirname(outFile), { recursive: true });\n writeFileAtomic(outFile, page.html);\n written.push(outFile);\n }\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: failed to write the site: ${detail}\\n` };\n }\n\n const summary = written.map((w) => ` ${w}`).join('\\n');\n return {\n code: 0,\n stdout: `built ${written.length} page${written.length === 1 ? '' : 's'} from ${path}:\\n${summary}\\n`,\n stderr: '',\n };\n}\n","/**\n * Local backup helpers shared by `takuhon migrate` and `takuhon restore`.\n *\n * Backups are written to a `.takuhon-backups/` directory co-located with the\n * profile file being mutated, rather than a `~/.takuhon/backups/{slug}/`\n * tree: a local `takuhon.json` operated on by path has no guaranteed slug,\n * and the restore flow documented in operational-lifecycle §4.1 / §4.3 reads\n * and writes `.takuhon-backups/...` relative to the working file. Keeping the\n * backups beside the source also mirrors the Vercel / Static adapter layout\n * (§3.1), which is the closest analogue for a local file workflow.\n *\n * Naming follows §3.1 (`takuhon-backup-v{version}-{timestamp}.json`) and the\n * pre-restore convention of §4.1 (`pre-restore-{timestamp}.json`). Timestamps\n * are ISO 8601 basic format in UTC at second precision; {@link createBackup}\n * implements the §3.3 overwrite guard by falling back to millisecond\n * precision when a same-second name already exists.\n */\n\nimport { mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\n\n/** Directory (relative to the target file) that holds local backups. */\nexport const BACKUP_DIR_NAME = '.takuhon-backups';\n\n/**\n * Thrown by {@link createBackup} when a backup cannot be written without\n * overwriting an existing file even after disambiguating with milliseconds.\n */\nexport class BackupError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'BackupError';\n }\n}\n\n/**\n * Format `date` as a compact ISO 8601 basic-format UTC timestamp.\n *\n * @example\n * compactTimestamp(new Date('2026-05-11T12:00:00.123Z')) // \"20260511T120000Z\"\n * compactTimestamp(new Date('2026-05-11T12:00:00.123Z'), true) // \"20260511T120000.123Z\"\n */\nexport function compactTimestamp(date: Date, withMillis = false): string {\n const iso = date.toISOString(); // e.g. \"2026-05-11T12:00:00.123Z\"\n const trimmed = withMillis ? iso : iso.replace(/\\.\\d{3}Z$/, 'Z');\n // Drop the extended-format separators; the millisecond `.` (if kept) stays.\n return trimmed.replace(/[-:]/g, '');\n}\n\n/** Build the migration pre-backup filename for a given source `version`. */\nexport function migrateBackupName(version: string, date: Date, withMillis = false): string {\n return `takuhon-backup-v${version}-${compactTimestamp(date, withMillis)}.json`;\n}\n\n/** Build the pre-restore backup filename (operational-lifecycle §4.1). */\nexport function preRestoreName(date: Date, withMillis = false): string {\n return `pre-restore-${compactTimestamp(date, withMillis)}.json`;\n}\n\n/** Build the pre-import backup filename (operational-lifecycle §5.3 step 3). */\nexport function preImportName(date: Date, withMillis = false): string {\n return `pre-import-${compactTimestamp(date, withMillis)}.json`;\n}\n\n/** Resolve the `.takuhon-backups/` directory beside `targetPath`. */\nexport function backupDirFor(targetPath: string): string {\n return join(dirname(targetPath), BACKUP_DIR_NAME);\n}\n\n/**\n * Write `content` into `.takuhon-backups/` beside `targetPath`, returning the\n * path actually written.\n *\n * `name(withMillis)` yields the second-precision filename when `false` and a\n * millisecond-precision variant when `true`. The directory is created if\n * absent and writes use the `wx` flag so an existing file is never\n * overwritten (operational-lifecycle §3.3): a same-second collision retries\n * once at millisecond precision, and a further collision throws\n * {@link BackupError}.\n */\nexport function createBackup(params: {\n targetPath: string;\n content: string;\n name: (withMillis: boolean) => string;\n}): string {\n const dir = backupDirFor(params.targetPath);\n mkdirSync(dir, { recursive: true });\n\n const primary = join(dir, params.name(false));\n try {\n writeFileSync(primary, params.content, { flag: 'wx' });\n return primary;\n } catch (error) {\n if (!isAlreadyExists(error)) throw error;\n }\n\n const fallback = join(dir, params.name(true));\n try {\n writeFileSync(fallback, params.content, { flag: 'wx' });\n return fallback;\n } catch (error) {\n if (isAlreadyExists(error)) {\n throw new BackupError(\n `backup target already exists and could not be disambiguated: ${fallback}`,\n { cause: error },\n );\n }\n throw error;\n }\n}\n\nfunction isAlreadyExists(error: unknown): boolean {\n return (\n typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 'EEXIST'\n );\n}\n\n/**\n * Write `content` to `target` atomically: stage it in a sibling temp file and\n * `rename` it into place. A `rename` within a directory is atomic, so a reader\n * never observes a half-written profile and an interrupted write (crash,\n * ENOSPC) cannot truncate the existing file — it is replaced whole or not at\n * all. The temp file is removed on failure. Pairs with the backup-before-write\n * ordering in the migrate / restore commands.\n */\nexport function writeFileAtomic(target: string, content: string): void {\n const tmp = join(dirname(target), `.${basename(target)}.${process.pid}.tmp`);\n try {\n writeFileSync(tmp, content, 'utf8');\n renameSync(tmp, target);\n } catch (error) {\n rmSync(tmp, { force: true });\n throw error;\n }\n}\n","/**\n * Shared static-site generation for `takuhon build` and `takuhon dev`.\n *\n * {@link generateSite} turns one validated, normalized, privacy-filtered profile\n * into the full set of pages the static surface exposes: one per available\n * locale. `build` writes each page's {@link SitePage.file} to disk; `dev` serves\n * each page's {@link SitePage.route} from memory. Keeping the per-locale loop,\n * the locale switcher links, and the canonical/hreflang logic here means both\n * commands share a single source of truth for the site's structure — the only\n * difference between them is disk write vs. in-memory serve.\n *\n * This reuses `@takuhon/core` only (validate/normalize/resolve happen upstream;\n * this module just resolves each locale and renders), so it stays bundler-free\n * and unit-testable as a pure function.\n */\n\nimport type { NormalizedTakuhon } from '@takuhon/core';\nimport { resolveLocale } from '@takuhon/core';\n\nimport { renderProfileHtml, type Alternate, type LocaleLink } from './build-html.js';\n\n/** One generated page: a serve route, a relative output file, and its HTML. */\nexport interface SitePage {\n /** URL path used by `dev` (default locale at `/`, others at `/<locale>/`). */\n readonly route: string;\n /** Output path used by `build`, relative to the output dir. */\n readonly file: string;\n /** Rendered HTML document. */\n readonly html: string;\n}\n\nexport interface GenerateOptions {\n /**\n * Site origin (e.g. `https://me.example`). When set, pages carry absolute\n * canonical + hreflang links; when absent those are omitted (the human locale\n * switcher is always relative either way).\n */\n readonly baseUrl?: string;\n}\n\n/**\n * Generate every page for a profile: the default locale first, then the rest,\n * de-duplicated. Schema.org JSON-LD is emitted unless `settings.enableJsonLd`\n * is explicitly `false`.\n */\nexport function generateSite(\n profile: NormalizedTakuhon,\n options: GenerateOptions = {},\n): SitePage[] {\n const { baseUrl } = options;\n const defaultLocale = profile.settings.defaultLocale;\n // Default locale first, then the rest, de-duplicated.\n const locales = [...new Set([defaultLocale, ...profile.settings.availableLocales])];\n const jsonLd = profile.settings.enableJsonLd !== false;\n\n return locales.map((locale) => {\n const localized = resolveLocale(profile, locale);\n const isDefault = locale === defaultLocale;\n\n const localeNav: LocaleLink[] = locales.map((to) => ({\n locale: to,\n href: localeHref(locale, to, defaultLocale),\n current: to === locale,\n }));\n const canonicalUrl = baseUrl ? absoluteUrl(baseUrl, locale, defaultLocale) : undefined;\n const alternates: Alternate[] = baseUrl ? buildAlternates(baseUrl, locales, defaultLocale) : [];\n\n const html = renderProfileHtml({ localized, canonicalUrl, alternates, localeNav, jsonLd });\n return {\n route: isDefault ? '/' : `/${locale}/`,\n file: isDefault ? 'index.html' : `${locale}/index.html`,\n html,\n };\n });\n}\n\n/** Absolute URL for a locale's page (default locale lives at the site root). */\nfunction absoluteUrl(baseUrl: string, locale: string, defaultLocale: string): string {\n return locale === defaultLocale ? `${baseUrl}/` : `${baseUrl}/${locale}/`;\n}\n\n/** hreflang alternates for every locale plus an `x-default` pointing at the default. */\nfunction buildAlternates(\n baseUrl: string,\n locales: readonly string[],\n defaultLocale: string,\n): Alternate[] {\n const alternates: Alternate[] = locales.map((locale) => ({\n hreflang: locale,\n href: absoluteUrl(baseUrl, locale, defaultLocale),\n }));\n alternates.push({\n hreflang: 'x-default',\n href: absoluteUrl(baseUrl, defaultLocale, defaultLocale),\n });\n return alternates;\n}\n\n/**\n * Depth-correct relative link from the page for `from` to the page for `to`,\n * for the human locale switcher. Always relative (independent of `baseUrl`)\n * so the switcher works regardless of where the site is hosted: the default\n * locale lives at the root, every other locale one directory deep.\n */\nfunction localeHref(from: string, to: string, defaultLocale: string): string {\n const fromRoot = from === defaultLocale;\n const toRoot = to === defaultLocale;\n if (fromRoot) return toRoot ? './' : `${to}/`;\n return toRoot ? '../' : `../${to}/`;\n}\n","/**\n * Pure HTML rendering for `takuhon build`.\n *\n * {@link renderProfileHtml} turns one locale-resolved {@link LocalizedTakuhon}\n * into a complete, self-contained static HTML document: semantic markup for\n * every profile section, an inline stylesheet, optional Schema.org JSON-LD,\n * and the `<head>` metadata (`<title>`, description, canonical, hreflang\n * alternates) the caller supplies.\n *\n * This is a deliberately separate, simpler surface from the React\n * `@takuhon/ui` (which is delivered as CSS-Modules components needing a\n * bundler). It reuses only `@takuhon/core` and has no DOM/browser dependency,\n * so it renders in plain Node and is unit-testable as a pure string function.\n *\n * Security: every piece of profile-derived text is escaped before it reaches\n * the markup ({@link escapeHtml}), and the JSON-LD payload is `<`/`>`/`&`\n * unicode-escaped so it cannot break out of its `<script>` element.\n */\n\nimport { generateJsonLd } from '@takuhon/core';\nimport type { LocalizedTakuhon } from '@takuhon/core';\n\ntype LocalizedProfile = LocalizedTakuhon['profile'];\n\n/** One entry in the human-facing locale switcher. */\nexport interface LocaleLink {\n locale: string;\n href: string;\n current: boolean;\n}\n\n/** One `<link rel=\"alternate\" hreflang>` entry. */\nexport interface Alternate {\n hreflang: string;\n href: string;\n}\n\nexport interface RenderInput {\n /** The locale-resolved document to render. */\n localized: LocalizedTakuhon;\n /** Absolute canonical URL for this page (only when `--base-url` was given). */\n canonicalUrl?: string;\n /** hreflang alternates (empty when no base URL is available). */\n alternates: readonly Alternate[];\n /** Human locale switcher links (rendered only when more than one locale). */\n localeNav: readonly LocaleLink[];\n /** Whether to emit Schema.org JSON-LD (mirrors `settings.enableJsonLd`). */\n jsonLd: boolean;\n}\n\n/** Escape text for use in HTML element content or double/single-quoted attributes. */\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/** Unicode-escape `<`, `>`, `&` so a JSON-LD payload cannot break out of `<script>`. */\nfunction escapeJsonLd(json: string): string {\n return json.replace(/</g, '\\\\u003c').replace(/>/g, '\\\\u003e').replace(/&/g, '\\\\u0026');\n}\n\n/**\n * Return `url` only when its scheme is safe to place in an `href`/`src`, else\n * `undefined`. Relative, protocol-relative, fragment, and query URLs (no\n * scheme) are allowed; among absolute URLs only `http:`, `https:`, and\n * `mailto:` are. This blocks `javascript:`, `data:`, `vbscript:`, etc. — the\n * schema validates only a generic URI, so a hostile document could otherwise\n * smuggle an executable scheme into the generated page.\n */\nfunction safeUrl(url: string): string | undefined {\n const trimmed = url.trim();\n const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(trimmed)?.[1]?.toLowerCase();\n if (scheme === undefined) return trimmed; // relative / protocol-relative / fragment\n return scheme === 'http' || scheme === 'https' || scheme === 'mailto' ? trimmed : undefined;\n}\n\nconst CSS = `:root{--fg:#1a1a1a;--muted:#666;--accent:#0b5fff;--line:#e5e5e5}\n*{box-sizing:border-box}\nbody{margin:0;color:var(--fg);font:16px/1.6 system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif;background:#fff}\nmain{max-width:42rem;margin:0 auto;padding:2rem 1.25rem}\na{color:var(--accent)}\nh1{font-size:1.9rem;margin:.2rem 0}\nh2{font-size:1.15rem;margin:2rem 0 .75rem;padding-bottom:.3rem;border-bottom:1px solid var(--line)}\nh3{font-size:1rem;margin:0}\nheader .avatar{width:96px;height:96px;border-radius:50%;object-fit:cover}\n.tagline{font-size:1.1rem;color:var(--muted);margin:.2rem 0}\n.location{color:var(--muted);margin:.2rem 0}\n.bio{margin:.75rem 0}\nul{padding:0;margin:0;list-style:none}\n.entries>li{margin:0 0 1.1rem}\n.sub{margin:.1rem 0;font-weight:600}\n.meta{margin:.1rem 0;color:var(--muted);font-size:.9rem}\n.links{display:flex;flex-wrap:wrap;gap:.5rem 1rem;margin:.75rem 0}\n.skills,.tags{display:flex;flex-wrap:wrap;gap:.4rem}\n.skills>li,.tags>li{background:#f2f2f2;border-radius:1rem;padding:.15rem .6rem;font-size:.85rem}\n.rec{margin:0 0 1.1rem}\n.rec blockquote{margin:0;padding-left:.9rem;border-left:3px solid var(--line)}\n.rec figcaption{color:var(--muted);font-size:.9rem;margin-top:.3rem}\nnav.locales{display:flex;gap:.75rem;margin-bottom:1rem;font-size:.9rem}\nfooter.powered{max-width:42rem;margin:0 auto;padding:1.5rem 1.25rem;color:var(--muted);font-size:.85rem}`;\n\ninterface EntryView {\n heading: string;\n sub?: string;\n dates?: string;\n body?: string;\n url?: string;\n tags?: readonly string[];\n}\n\n/** Format a YearMonth range; `null` end or `isCurrent` renders as \"Present\". */\nfunction dateRange(start?: string, end?: string | null, isCurrent?: boolean): string {\n const left = start ?? '';\n const right = isCurrent === true || end === null ? 'Present' : (end ?? '');\n if (left && right) return `${left} – ${right}`;\n return left || right;\n}\n\nfunction nonEmpty(values: readonly (string | undefined)[], separator: string): string | undefined {\n const joined = values\n .filter((v): v is string => typeof v === 'string' && v.length > 0)\n .join(separator);\n return joined.length > 0 ? joined : undefined;\n}\n\nfunction renderEntry(entry: EntryView): string {\n const href = entry.url ? safeUrl(entry.url) : undefined;\n const heading = href\n ? `<a href=\"${escapeHtml(href)}\">${escapeHtml(entry.heading)}</a>`\n : escapeHtml(entry.heading);\n const parts = [`<h3>${heading}</h3>`];\n if (entry.sub) parts.push(`<p class=\"sub\">${escapeHtml(entry.sub)}</p>`);\n if (entry.dates) parts.push(`<p class=\"meta\">${escapeHtml(entry.dates)}</p>`);\n if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);\n if (entry.tags && entry.tags.length > 0) {\n parts.push(\n `<ul class=\"tags\">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join('')}</ul>`,\n );\n }\n return `<li>${parts.join('')}</li>`;\n}\n\n/** Render a `<section>` of entries, or `''` when there are none. */\nfunction entryList(title: string, entries: readonly EntryView[]): string {\n if (entries.length === 0) return '';\n return `<section><h2>${escapeHtml(title)}</h2><ul class=\"entries\">${entries\n .map(renderEntry)\n .join('')}</ul></section>`;\n}\n\nfunction renderHeader(p: LocalizedProfile): string {\n const parts: string[] = [];\n const avatarSrc = p.avatar?.url ? safeUrl(p.avatar.url) : undefined;\n if (avatarSrc) {\n parts.push(\n `<img class=\"avatar\" src=\"${escapeHtml(avatarSrc)}\" alt=\"${escapeHtml(p.avatar?.alt ?? '')}\">`,\n );\n }\n parts.push(`<h1>${escapeHtml(p.displayName)}</h1>`);\n if (p.tagline) parts.push(`<p class=\"tagline\">${escapeHtml(p.tagline)}</p>`);\n if (p.location?.display) parts.push(`<p class=\"location\">${escapeHtml(p.location.display)}</p>`);\n if (p.bio) parts.push(`<p class=\"bio\">${escapeHtml(p.bio)}</p>`);\n return `<header>${parts.join('')}</header>`;\n}\n\nfunction renderLinks(links: LocalizedTakuhon['links']): string {\n if (links.length === 0) return '';\n const items = links\n .map((l) => {\n const text = escapeHtml(l.label ?? l.url);\n const href = safeUrl(l.url);\n return href ? `<li><a href=\"${escapeHtml(href)}\">${text}</a></li>` : `<li>${text}</li>`;\n })\n .join('');\n return `<nav aria-label=\"Links\"><ul class=\"links\">${items}</ul></nav>`;\n}\n\nfunction renderSkills(skills: LocalizedTakuhon['skills']): string {\n if (skills.length === 0) return '';\n const items = skills.map((s) => `<li>${escapeHtml(s.label)}</li>`).join('');\n return `<section><h2>Skills</h2><ul class=\"skills\">${items}</ul></section>`;\n}\n\nfunction renderLanguages(languages: LocalizedTakuhon['languages']): string {\n if (languages.length === 0) return '';\n const items = languages\n .map((l) => `<li>${escapeHtml(`${l.displayName ?? l.language} — ${l.proficiency}`)}</li>`)\n .join('');\n return `<section><h2>Languages</h2><ul class=\"entries\">${items}</ul></section>`;\n}\n\nfunction renderRecommendations(recs: LocalizedTakuhon['recommendations']): string {\n if (recs.length === 0) return '';\n const items = recs\n .map((r) => {\n const authorHref = r.author.url ? safeUrl(r.author.url) : undefined;\n const name = authorHref\n ? `<a href=\"${escapeHtml(authorHref)}\">${escapeHtml(r.author.name)}</a>`\n : escapeHtml(r.author.name);\n const caption = [name, r.author.headline ? escapeHtml(r.author.headline) : '']\n .filter(Boolean)\n .join(', ');\n const rel = r.relationship ? ` (${escapeHtml(r.relationship)})` : '';\n return `<figure class=\"rec\"><blockquote>${escapeHtml(r.body)}</blockquote><figcaption>— ${caption}${rel}</figcaption></figure>`;\n })\n .join('');\n return `<section><h2>Recommendations</h2>${items}</section>`;\n}\n\nfunction renderContact(contact: LocalizedTakuhon['contact']): string {\n const items: string[] = [];\n if (contact.email) {\n items.push(\n `<li><a href=\"mailto:${escapeHtml(contact.email)}\">${escapeHtml(contact.email)}</a></li>`,\n );\n }\n const formHref = contact.formUrl ? safeUrl(contact.formUrl) : undefined;\n if (formHref) {\n items.push(`<li><a href=\"${escapeHtml(formHref)}\">Contact form</a></li>`);\n }\n if (items.length === 0) return '';\n return `<section><h2>Contact</h2><ul class=\"entries\">${items.join('')}</ul></section>`;\n}\n\nfunction renderJsonLdScript(data: LocalizedTakuhon): string {\n const payload = JSON.stringify(generateJsonLd(data));\n return `<script type=\"application/ld+json\">${escapeJsonLd(payload)}</script>`;\n}\n\nfunction renderLocaleNav(localeNav: readonly LocaleLink[]): string {\n const items = localeNav\n .map((l) =>\n l.current\n ? `<span aria-current=\"true\">${escapeHtml(l.locale)}</span>`\n : `<a href=\"${escapeHtml(l.href)}\">${escapeHtml(l.locale)}</a>`,\n )\n .join('');\n return `<nav class=\"locales\" aria-label=\"Language\">${items}</nav>`;\n}\n\n/** Render a complete static HTML document for one locale-resolved profile. */\nexport function renderProfileHtml(input: RenderInput): string {\n const d = input.localized;\n const p = d.profile;\n const description = p.tagline ?? p.bio ?? '';\n\n const head = [\n '<meta charset=\"utf-8\">',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">',\n `<title>${escapeHtml(p.displayName)}</title>`,\n description\n ? `<meta name=\"description\" content=\"${escapeHtml(description.slice(0, 300))}\">`\n : '',\n input.canonicalUrl ? `<link rel=\"canonical\" href=\"${escapeHtml(input.canonicalUrl)}\">` : '',\n ...input.alternates.map(\n (a) =>\n `<link rel=\"alternate\" hreflang=\"${escapeHtml(a.hreflang)}\" href=\"${escapeHtml(a.href)}\">`,\n ),\n input.jsonLd ? renderJsonLdScript(d) : '',\n `<style>${CSS}</style>`,\n ]\n .filter(Boolean)\n .join('\\n ');\n\n const body = [\n input.localeNav.length > 1 ? renderLocaleNav(input.localeNav) : '',\n renderHeader(p),\n renderLinks(d.links),\n entryList(\n 'Experience',\n d.careers.map((c) => ({\n heading: c.role,\n sub: c.organization,\n dates: dateRange(c.startDate, c.endDate, c.isCurrent),\n body: c.description,\n url: c.url,\n })),\n ),\n entryList(\n 'Projects',\n d.projects.map((x) => ({\n heading: x.title,\n dates: dateRange(x.startDate, x.endDate),\n body: x.description,\n url: x.url,\n tags: x.tags,\n })),\n ),\n renderSkills(d.skills),\n entryList(\n 'Education',\n d.education.map((e) => {\n const degree = nonEmpty([e.degree, e.fieldOfStudy], ', ');\n return {\n heading: degree ?? e.institution,\n sub: degree ? e.institution : undefined,\n dates: dateRange(e.startDate, e.endDate, e.isCurrent),\n body: e.description,\n url: e.url,\n };\n }),\n ),\n entryList(\n 'Certifications',\n d.certifications.map((c) => ({\n heading: c.title,\n sub: c.issuingOrganization,\n dates: dateRange(c.issueDate, c.expirationDate),\n url: c.url,\n })),\n ),\n entryList(\n 'Publications',\n d.publications.map((x) => ({\n heading: x.title,\n sub: nonEmpty([x.publisher, x.coAuthors?.join(', ')], ' · '),\n dates: dateRange(x.date),\n body: x.description,\n url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : undefined),\n })),\n ),\n entryList(\n 'Honors & awards',\n d.honors.map((x) => ({\n heading: x.title,\n sub: x.issuer,\n dates: dateRange(x.date),\n body: x.description,\n url: x.url,\n })),\n ),\n entryList(\n 'Memberships',\n d.memberships.map((x) => ({\n heading: x.role ?? x.organization,\n sub: x.role ? x.organization : undefined,\n dates: dateRange(x.startDate, x.endDate, x.isCurrent),\n body: x.description,\n url: x.url,\n })),\n ),\n entryList(\n 'Volunteering',\n d.volunteering.map((x) => ({\n heading: x.role,\n sub: nonEmpty([x.organization, x.cause], ' · '),\n dates: dateRange(x.startDate, x.endDate, x.isCurrent),\n body: x.description,\n url: x.url,\n })),\n ),\n entryList(\n 'Courses',\n d.courses.map((x) => ({\n heading: x.title,\n sub: x.provider,\n dates: dateRange(x.completionDate),\n body: x.description,\n url: x.certificateUrl,\n })),\n ),\n entryList(\n 'Patents',\n d.patents.map((x) => ({\n heading: x.title,\n sub: nonEmpty([x.patentNumber, x.office, x.status, x.coInventors?.join(', ')], ' · '),\n dates: dateRange(x.filingDate ?? x.grantDate),\n body: x.description,\n url: x.url,\n })),\n ),\n entryList(\n 'Test scores',\n d.testScores.map((x) => ({\n heading: `${x.title}: ${x.score}`,\n dates: dateRange(x.date),\n body: x.description,\n url: x.url,\n })),\n ),\n renderLanguages(d.languages),\n renderRecommendations(d.recommendations),\n renderContact(d.contact),\n ]\n .filter(Boolean)\n .join('\\n');\n\n const footer =\n d.settings.showPoweredBy === true ? '<footer class=\"powered\">Powered by takuhon</footer>' : '';\n\n return (\n `<!DOCTYPE html>\\n<html lang=\"${escapeHtml(d.resolvedLocale)}\">\\n<head>\\n ${head}\\n</head>\\n` +\n `<body>\\n<main>\\n${body}\\n</main>\\n${footer ? `${footer}\\n` : ''}</body>\\n</html>\\n`\n );\n}\n","/**\n * `takuhon dev [path] [--port <n>] [--base-url <url>]` — serve a `takuhon.json`\n * as a local static preview (Spec §14.1 Phase 1 \"edit → preview\").\n *\n * This is the in-memory companion of `takuhon build`: it renders the same\n * per-locale surface (via the shared {@link generateSite}) but serves it over\n * `node:http` instead of writing to disk — no bundler, no Hono/Vite/React, no\n * remote/secret coupling. The source is re-read and re-rendered on every\n * request, so editing `takuhon.json` and reloading the browser shows changes\n * with no watch machinery; an invalid file is served as a live error page so it\n * can be fixed in place.\n *\n * A long-running server does not fit the pure `runXxx → {code,stdout,stderr}`\n * shape of the other commands, so the testable logic is split from the I/O:\n * {@link loadSiteState} (source → route map), {@link resolveRoute},\n * {@link contentType}, and {@link handleRequest} are pure; {@link createDevServer}\n * and {@link runDev} are the thin `node:http` wrapper around them.\n *\n * Exit codes:\n * 0 — served, then stopped cleanly (Ctrl-C), or `--help`\n * 2 — the command could not run: bad arguments, a missing/unreadable file,\n * or the port was already in use\n */\n\nimport { readFileSync } from 'node:fs';\nimport { createServer, type Server } from 'node:http';\n\nimport { applyPublicPrivacyFilter, normalize, validate } from '@takuhon/core';\n\nimport { escapeHtml } from './build-html.js';\nimport { generateSite } from './site.js';\n\nconst DEFAULT_PATH = 'takuhon.json';\nconst DEFAULT_PORT = 4321;\n\nconst USAGE = `Usage: takuhon dev [path] [--port <n>] [--base-url <url>]\n\nServe a takuhon.json as a local static preview (one page per locale) — the same\nsurface \\`takuhon build\\` produces. With no path, serves ./takuhon.json. The file\nis re-read and re-rendered on every request, so edit it and reload the browser\nto see changes. Stop with Ctrl-C.\n\nOptions:\n --port <n> Port to listen on (default: ${DEFAULT_PORT}).\n --base-url <url> Site origin (e.g. https://me.example). Enables absolute\n canonical and hreflang links; without it those are omitted.\n\nThe public privacy filter is applied (meta.privacy is honoured). An invalid\ntakuhon.json is served as an error page so you can fix it and reload.\n\nExit codes: 0 = served then stopped, 2 = bad arguments / file missing /\nunreadable / port in use.\n`;\n\n/** Injectable output sinks so tests can capture streams without a real TTY. */\nexport interface DevDeps {\n stdout?: (text: string) => void;\n stderr?: (text: string) => void;\n}\n\ninterface ParsedArgs {\n path: string;\n port: number;\n baseUrl?: string;\n}\n\n/**\n * The current render state of the source file. Recomputed per request so edits\n * are reflected on reload; a read/parse/validate failure becomes a served error\n * page rather than crashing the server.\n */\nexport type SiteState =\n | { readonly ok: true; readonly pages: ReadonlyMap<string, string> }\n | { readonly ok: false; readonly status: number; readonly message: string };\n\n/** A fully-resolved HTTP response, independent of the `node:http` socket. */\nexport interface DevResponse {\n readonly status: number;\n readonly contentType: string;\n readonly body: string;\n}\n\n/**\n * Read, validate, and render `path` into a route→HTML map. Any failure (missing\n * file, bad JSON, invalid profile) yields a 500 state carrying a human-readable\n * message — validation errors are formatted with their JSON Pointers, mirroring\n * `takuhon build`. The public privacy filter is applied, exactly as `build` does.\n */\nexport function loadSiteState(path: string, baseUrl?: string): SiteState {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf8');\n } catch {\n return { ok: false, status: 500, message: `cannot read '${path}'.` };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { ok: false, status: 500, message: `'${path}' is not valid JSON: ${detail}` };\n }\n\n const result = validate(data);\n if (!result.ok) {\n const lines = result.errors.map((e) => ` ${e.pointer || '/'}: ${e.message}`);\n return {\n ok: false,\n status: 500,\n message: `'${path}' is not a valid takuhon profile:\\n${lines.join('\\n')}`,\n };\n }\n\n const filtered = applyPublicPrivacyFilter(normalize(result.data));\n const pages = new Map(generateSite(filtered, { baseUrl }).map((p) => [p.route, p.html]));\n return { ok: true, pages };\n}\n\n/**\n * Normalize a request path to a site route key. A trailing `index.html` is\n * stripped and a trailing slash is added, so `/ja`, `/ja/`, and\n * `/ja/index.html` all map to `/ja/` (and `/`, ``, `/index.html` to `/`).\n */\nexport function resolveRoute(urlPath: string): string {\n let p = urlPath;\n try {\n p = decodeURIComponent(urlPath);\n } catch {\n // Keep the raw path on malformed percent-encoding; it will simply 404.\n }\n p = p.replace(/\\/index\\.html$/, '/');\n if (p === '' || p === '/') return '/';\n if (!p.startsWith('/')) p = `/${p}`;\n if (!p.endsWith('/')) p = `${p}/`;\n return p;\n}\n\n/**\n * Content-type for a served route. The static surface is HTML-only (the build\n * emits one HTML page per locale and no assets/JSON), so this is constant; it is\n * a named function to keep the response shape ready should the surface grow.\n */\nexport function contentType(_route: string): string {\n return 'text/html; charset=utf-8';\n}\n\n/** Resolve a request to a response against the current {@link SiteState}. Pure. */\nexport function handleRequest(method: string, urlPath: string, state: SiteState): DevResponse {\n if (method !== 'GET' && method !== 'HEAD') {\n return { status: 405, contentType: 'text/plain; charset=utf-8', body: 'Method Not Allowed\\n' };\n }\n if (!state.ok) {\n return {\n status: state.status,\n contentType: contentType('/'),\n body: renderErrorPage(state.message),\n };\n }\n const route = resolveRoute(urlPath);\n const html = state.pages.get(route);\n if (html === undefined) {\n return {\n status: 404,\n contentType: contentType(route),\n body: renderNotFoundPage(route, [...state.pages.keys()]),\n };\n }\n return { status: 200, contentType: contentType(route), body: html };\n}\n\n/**\n * Create the preview server. Each request recomputes the site state (so edits\n * are live on reload) and delegates to the pure {@link handleRequest}. Not\n * started here — the caller binds a port. Importing this module has no side\n * effects.\n */\nexport function createDevServer(opts: { path: string; baseUrl?: string }): Server {\n return createServer((req, res) => {\n const method = req.method ?? 'GET';\n const state = loadSiteState(opts.path, opts.baseUrl);\n const response = handleRequest(method, pathnameOf(req.url ?? '/'), state);\n res.writeHead(response.status, { 'Content-Type': response.contentType });\n if (method === 'HEAD') res.end();\n else res.end(response.body);\n });\n}\n\n/**\n * Run `takuhon dev`. Resolves with the process exit code: it stays pending while\n * the server runs and resolves on graceful shutdown (Ctrl-C). Argument and\n * missing-file errors return before any port is bound.\n */\nexport async function runDev(args: readonly string[] = [], deps: DevDeps = {}): Promise<number> {\n const out = deps.stdout ?? ((text: string) => void process.stdout.write(text));\n const err = deps.stderr ?? ((text: string) => void process.stderr.write(text));\n\n if (args[0] === '--help' || args[0] === '-h') {\n out(USAGE);\n return 0;\n }\n\n const parsed = parseArgs(args);\n if ('error' in parsed) {\n err(`${parsed.error}\\nRun \\`takuhon dev --help\\` for usage.\\n`);\n return 2;\n }\n\n // Fast-fail only when the source is missing/unreadable — there is nothing to\n // preview. A present-but-invalid file is served as a live error page instead,\n // so it can be fixed without restarting the server.\n try {\n readFileSync(parsed.path, 'utf8');\n } catch {\n err(\n `takuhon: cannot read '${parsed.path}'. Pass a path, or run from a directory containing a takuhon.json.\\n`,\n );\n return 2;\n }\n\n const server = createDevServer({ path: parsed.path, baseUrl: parsed.baseUrl });\n\n return await new Promise<number>((resolve) => {\n let closing = false;\n const shutdown = (): void => {\n if (closing) return;\n closing = true;\n process.removeListener('SIGINT', shutdown);\n process.removeListener('SIGTERM', shutdown);\n server.close(() => resolve(0));\n // Drop keep-alive sockets so close() does not hang (Node >= 18.2).\n server.closeAllConnections();\n };\n\n server.once('error', (error: NodeJS.ErrnoException) => {\n // The signal handlers are only registered once listening starts, so a\n // bind failure (e.g. EADDRINUSE) has none to detach.\n if (error.code === 'EADDRINUSE') {\n err(`takuhon: port ${parsed.port} is already in use; pass --port <n> to choose another.\\n`);\n } else {\n err(`takuhon: ${error.message}\\n`);\n }\n resolve(2);\n });\n\n // Bind to loopback only: a local preview must not expose draft profile\n // content (or the dev error pages) to the rest of the network.\n server.listen(parsed.port, '127.0.0.1', () => {\n out(\n `takuhon dev: serving ${parsed.path} at http://localhost:${parsed.port}/ (Ctrl-C to stop)\\n`,\n );\n // Surface a current validation problem up front; the browser shows it too.\n const state = loadSiteState(parsed.path, parsed.baseUrl);\n if (!state.ok) {\n err(\n `takuhon dev: ${parsed.path} is not a valid profile yet; the preview will show the error until it is fixed.\\n`,\n );\n }\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n });\n });\n}\n\nfunction parseArgs(args: readonly string[]): ParsedArgs | { error: string } {\n let path: string | undefined;\n let portRaw: string | undefined;\n let baseUrl: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n\n if (arg === '--port' || arg === '--base-url') {\n const value = args[i + 1];\n if (value === undefined || value === '' || value.startsWith('-')) {\n return { error: `takuhon: \\`${arg}\\` requires a value.` };\n }\n if (arg === '--port') portRaw = value;\n else baseUrl = value;\n i++;\n continue;\n }\n if (arg.startsWith('--port=')) {\n const value = arg.slice('--port='.length);\n if (value === '') return { error: 'takuhon: `--port` requires a value.' };\n portRaw = value;\n continue;\n }\n if (arg.startsWith('--base-url=')) {\n const value = arg.slice('--base-url='.length);\n if (value === '') return { error: 'takuhon: `--base-url` requires a value.' };\n baseUrl = value;\n continue;\n }\n if (arg.startsWith('-')) {\n return { error: `takuhon: unknown option \\`${arg}\\` for \\`dev\\`.` };\n }\n if (path !== undefined) {\n return { error: 'takuhon: `dev` takes at most one path argument.' };\n }\n path = arg;\n }\n\n let port = DEFAULT_PORT;\n if (portRaw !== undefined) {\n const parsedPort = parsePort(portRaw);\n if (parsedPort === undefined) {\n return {\n error: `takuhon: \\`--port\\` must be an integer between 1 and 65535 (got \\`${portRaw}\\`).`,\n };\n }\n port = parsedPort;\n }\n\n if (baseUrl !== undefined && !isHttpUrl(baseUrl)) {\n return { error: 'takuhon: `--base-url` must be an absolute http(s) URL.' };\n }\n\n return {\n path: path ?? DEFAULT_PATH,\n port,\n // Drop any trailing slash so URL joins are predictable.\n baseUrl: baseUrl?.replace(/\\/+$/, ''),\n };\n}\n\nfunction parsePort(value: string): number | undefined {\n if (!/^\\d+$/.test(value)) return undefined;\n const n = Number(value);\n return Number.isInteger(n) && n >= 1 && n <= 65535 ? n : undefined;\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\n/** Extract the path portion of a request URL, dropping any query/fragment. */\nfunction pathnameOf(url: string): string {\n try {\n return new URL(url, 'http://localhost').pathname;\n } catch {\n return url;\n }\n}\n\n/** Minimal HTML document used by the dev-only error and 404 pages. */\nfunction devPage(title: string, body: string): string {\n return (\n `<!DOCTYPE html>\\n<html lang=\"en\">\\n<head>\\n` +\n `<meta charset=\"utf-8\">\\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\\n` +\n `<title>${escapeHtml(title)}</title>\\n` +\n `<style>body{margin:0;font:16px/1.6 system-ui,-apple-system,sans-serif;color:#1a1a1a}` +\n `main{max-width:42rem;margin:2rem auto;padding:0 1.25rem}` +\n `pre{background:#f6f6f6;padding:1rem;border-radius:.4rem;overflow:auto;white-space:pre-wrap}` +\n `code{background:#f2f2f2;padding:.1rem .3rem;border-radius:.2rem}</style>\\n` +\n `</head>\\n<body>\\n<main>\\n${body}\\n</main>\\n</body>\\n</html>\\n`\n );\n}\n\nfunction renderErrorPage(message: string): string {\n return devPage(\n 'takuhon dev — error',\n `<h1>takuhon dev</h1>\\n<p>The profile could not be rendered:</p>\\n` +\n `<pre>${escapeHtml(message)}</pre>\\n<p>Fix the file and reload.</p>`,\n );\n}\n\nfunction renderNotFoundPage(route: string, routes: readonly string[]): string {\n const links = routes\n .map((r) => `<li><a href=\"${escapeHtml(r)}\">${escapeHtml(r)}</a></li>`)\n .join('');\n return devPage(\n 'takuhon dev — 404',\n `<h1>404</h1>\\n<p>No page for <code>${escapeHtml(route)}</code>.</p>\\n` +\n `<p>Available pages:</p>\\n<ul>${links}</ul>`,\n );\n}\n","/**\n * `takuhon export [path] [--output <file>]` — serialise a `takuhon.json` into\n * its transport form for transfer or archival.\n *\n * Mirrors the other command runners: {@link runExport} is a pure function that\n * reads the source itself but returns its output as strings plus an exit code.\n * It validates the source first (an export should be a valid transport\n * artifact), then delegates to core's `exportTakuhon`, which deep-clones and\n * refreshes `meta.updatedAt` (the round-trip exception in\n * operational-lifecycle §5.1). The source file is only read, never modified.\n *\n * Output defaults to stdout so the command composes in a pipeline; `--output`\n * writes a file atomically instead. Asset embedding (`--embed-assets`) is a\n * remote-storage concern and is deferred — it is rejected with a clear message\n * rather than silently ignored.\n *\n * Exit codes:\n * 0 — exported (to stdout or `--output`)\n * 1 — the source was read but is not a valid takuhon profile\n * 2 — the command could not run: bad arguments, a missing/unreadable file,\n * a file that is not valid JSON, or a failed `--output` write\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { exportTakuhon, validate } from '@takuhon/core';\nimport type { Takuhon } from '@takuhon/core';\n\nimport { writeFileAtomic } from './backup.js';\n\n/** Default profile filename, resolved relative to the current working directory. */\nconst DEFAULT_PATH = 'takuhon.json';\n\nconst USAGE = `Usage: takuhon export [path] [--output <file>]\n\nSerialise a takuhon.json into its transport form and print it to stdout, or\nwrite it to a file with --output. With no path, exports ./takuhon.json in the\ncurrent working directory.\n\nOptions:\n --output <file> Write the export to <file> instead of stdout (atomic).\n\nExit codes: 0 = exported, 1 = source is not a valid profile,\n2 = bad arguments / file missing / unreadable / not JSON / write failed.\n`;\n\nexport interface ExportOutcome {\n /** Process exit code (see module docstring). */\n readonly code: number;\n /** Text destined for stdout (empty when there is nothing to print). */\n readonly stdout: string;\n /** Text destined for stderr (empty when there is nothing to print). */\n readonly stderr: string;\n}\n\ninterface ParsedArgs {\n path: string;\n output?: string;\n}\n\n/**\n * Run `takuhon export` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"export\"`).\n */\nexport function runExport(args: readonly string[] = []): ExportOutcome {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n const parsed = parseArgs(args);\n if ('error' in parsed) {\n return {\n code: 2,\n stdout: '',\n stderr: `${parsed.error}\\nRun \\`takuhon export --help\\` for usage.\\n`,\n };\n }\n\n return exportFile(parsed);\n}\n\nfunction parseArgs(args: readonly string[]): ParsedArgs | { error: string } {\n let path: string | undefined;\n let output: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n\n if (arg === '--embed-assets') {\n return {\n error:\n 'takuhon: --embed-assets is not supported yet; assets are remote and asset embedding is deferred.',\n };\n }\n if (arg === '--output') {\n const value = args[i + 1];\n if (value === undefined || value === '' || value.startsWith('-')) {\n return { error: 'takuhon: `--output` requires a value.' };\n }\n output = value;\n i++;\n continue;\n }\n if (arg.startsWith('--output=')) {\n const value = arg.slice('--output='.length);\n if (value === '') {\n return { error: 'takuhon: `--output` requires a value.' };\n }\n output = value;\n continue;\n }\n if (arg.startsWith('-')) {\n return { error: `takuhon: unknown option \\`${arg}\\` for \\`export\\`.` };\n }\n if (path !== undefined) {\n return { error: 'takuhon: `export` takes at most one path argument.' };\n }\n path = arg;\n }\n\n return { path: path ?? DEFAULT_PATH, output };\n}\n\nfunction exportFile(parsed: ParsedArgs): ExportOutcome {\n const { path, output } = parsed;\n\n // Export produces a separate transport artifact; refuse to write it over the\n // source (which would mutate the source via the refreshed meta.updatedAt).\n if (output !== undefined && resolve(output) === resolve(path)) {\n return {\n code: 2,\n stdout: '',\n stderr:\n `takuhon: --output '${output}' is the source file; export writes a separate artifact.\\n` +\n 'Omit --output to print to stdout, or choose a different file.\\n',\n };\n }\n\n let raw: string;\n try {\n raw = readFileSync(path, 'utf8');\n } catch {\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: cannot read '${path}'. Pass a path, or run from a directory containing a takuhon.json.\\n`,\n };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: '${path}' is not valid JSON: ${detail}\\n` };\n }\n\n const result = validate(data);\n if (!result.ok) {\n const lines = result.errors.map((e) => ` ${e.pointer || '/'}: ${e.message}`);\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: '${path}' is not a valid takuhon profile; refusing to export:\\n${lines.join('\\n')}\\n`,\n };\n }\n\n // Export the validated source faithfully — only meta.updatedAt is refreshed\n // (operational-lifecycle §5.1). We pass the parsed document rather than\n // `result.data` so `validate`'s coercion of absent optional arrays into `[]`\n // does not silently rewrite the export.\n const content = `${JSON.stringify(exportTakuhon(data as Takuhon), null, 2)}\\n`;\n\n if (output === undefined) {\n // No --output: the serialised profile is the command's stdout.\n return { code: 0, stdout: content, stderr: '' };\n }\n\n try {\n writeFileAtomic(output, content);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: failed to write '${output}': ${detail}\\n` };\n }\n\n return { code: 0, stdout: `exported ${path} -> ${output}\\n`, stderr: '' };\n}\n","/**\n * `takuhon import <file> [path]` — load a previously exported profile into a\n * local `takuhon.json` (operational-lifecycle §5.3 / §17.2).\n *\n * Like the other command runners this returns its output as strings plus an\n * exit code. The flow follows §5.3: read the input, migrate it forward to the\n * current schema version if it is older (core's `importTakuhon` deliberately\n * does *not* auto-migrate, so the CLI composes `migrateTakuhon` +\n * `importTakuhon`), validate the result, back up the current profile, then\n * write the imported document. Unlike `restore` (a verbatim reproduction),\n * `import` writes the migrated-and-validated document re-serialised in\n * canonical form.\n *\n * Import overwrites the target, but a backup is taken first, so — matching the\n * spec, which mandates a confirmation prompt only for `restore` — there is no\n * interactive prompt. Asset expansion and edge-cache purge (§5.3 steps 5-6)\n * are remote concerns and do not apply to this local-file command.\n *\n * Exit codes:\n * 0 — imported\n * 1 — the input was read but cannot be imported (missing/unusable\n * schemaVersion, no migration path to the current version, or the\n * result failed validation)\n * 2 — the command could not run: bad arguments, a missing/unreadable input\n * or target, a non-JSON input, or a failed write\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport {\n ImportError,\n MigrationError,\n SCHEMA_VERSION,\n importTakuhon,\n migrateTakuhon,\n} from '@takuhon/core';\nimport type { Takuhon } from '@takuhon/core';\n\nimport { BackupError, createBackup, preImportName, writeFileAtomic } from './backup.js';\n\n/** Default profile filename, resolved relative to the current working directory. */\nconst DEFAULT_PATH = 'takuhon.json';\n\nconst USAGE = `Usage: takuhon import <file> [path]\n\nLoad a previously exported profile from <file> into a local takuhon.json,\nmigrating it to the current schema version if needed. With no path, writes\n./takuhon.json in the current working directory.\n\nThe current profile (if any) is backed up to\n.takuhon-backups/pre-import-<timestamp>.json before being overwritten.\n\nExit codes: 0 = imported, 1 = input cannot be imported (invalid / unsupported\nversion), 2 = bad arguments / file missing / unreadable / not JSON / write failed.\n`;\n\nexport interface ImportOutcome {\n /** Process exit code (see module docstring). */\n readonly code: number;\n /** Text destined for stdout (empty when there is nothing to print). */\n readonly stdout: string;\n /** Text destined for stderr (empty when there is nothing to print). */\n readonly stderr: string;\n}\n\n/** Injectable dependencies, so tests can pin the backup timestamp. */\nexport interface ImportDeps {\n /** Clock used for the pre-import backup filename. Defaults to `() => new Date()`. */\n now?: () => Date;\n}\n\ninterface ParsedArgs {\n file: string;\n path: string;\n}\n\n/**\n * Run `takuhon import` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"import\"`).\n */\nexport function runImport(args: readonly string[] = [], deps: ImportDeps = {}): ImportOutcome {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n const parsed = parseArgs(args);\n if ('error' in parsed) {\n return {\n code: 2,\n stdout: '',\n stderr: `${parsed.error}\\nRun \\`takuhon import --help\\` for usage.\\n`,\n };\n }\n\n const now = deps.now ?? (() => new Date());\n return importFile(parsed, now);\n}\n\nfunction parseArgs(args: readonly string[]): ParsedArgs | { error: string } {\n const positionals: string[] = [];\n\n for (const arg of args) {\n if (arg.startsWith('-')) {\n return { error: `takuhon: unknown option \\`${arg}\\` for \\`import\\`.` };\n }\n positionals.push(arg);\n }\n\n if (positionals.length === 0) {\n return { error: 'takuhon: `import` requires an input <file>.' };\n }\n if (positionals.length > 2) {\n return { error: 'takuhon: `import` takes at most an input <file> and a target path.' };\n }\n\n return { file: positionals[0]!, path: positionals[1] ?? DEFAULT_PATH };\n}\n\nfunction importFile(parsed: ParsedArgs, now: () => Date): ImportOutcome {\n const { file, path } = parsed;\n\n let raw: string;\n try {\n raw = readFileSync(file, 'utf8');\n } catch {\n return { code: 2, stdout: '', stderr: `takuhon: cannot read '${file}'.\\n` };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: '${file}' is not valid JSON: ${detail}\\n` };\n }\n\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: '${file}' is not a takuhon profile (expected a JSON object).\\n`,\n };\n }\n\n const source = (data as { schemaVersion?: unknown }).schemaVersion;\n if (typeof source !== 'string' || source.length === 0) {\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: '${file}' has no usable schemaVersion; cannot import.\\n`,\n };\n }\n\n // Bring the input up to the current schema version (§5.3 step 2). core's\n // importTakuhon does not auto-migrate, so we migrate first when needed.\n let candidate: unknown = data;\n if (source !== SCHEMA_VERSION) {\n try {\n candidate = migrateTakuhon(data as Takuhon, SCHEMA_VERSION);\n } catch (error) {\n if (error instanceof MigrationError) {\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: cannot import '${file}': ${error.message}.\\n`,\n };\n }\n throw error;\n }\n }\n\n let imported: Takuhon;\n try {\n imported = importTakuhon(candidate as Takuhon);\n } catch (error) {\n if (error instanceof ImportError) {\n const lines = (error.errors ?? []).map((e) => ` ${e.pointer || '/'}: ${e.message}`);\n const detail = lines.length > 0 ? `:\\n${lines.join('\\n')}` : '.';\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: '${file}' is not a valid takuhon profile; refusing to import${detail}\\n`,\n };\n }\n throw error;\n }\n\n // Back up the current profile before overwriting it (§5.3 step 3). A missing\n // target is fine — there is nothing to preserve on a fresh import.\n let currentRaw: string | undefined;\n try {\n currentRaw = readFileSync(path, 'utf8');\n } catch (error) {\n if (!isNotFound(error)) {\n return { code: 2, stdout: '', stderr: `takuhon: cannot read current profile '${path}'.\\n` };\n }\n }\n\n let savedPath: string | undefined;\n if (currentRaw !== undefined) {\n // One timestamp for the backup name (createBackup may call the builder\n // twice on a same-second collision).\n const stamp = now();\n try {\n savedPath = createBackup({\n targetPath: path,\n content: currentRaw,\n name: (withMillis) => preImportName(stamp, withMillis),\n });\n } catch (error) {\n const detail = error instanceof BackupError ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: refusing to import into '${path}' — pre-import backup failed: ${detail}\\n`,\n };\n }\n }\n\n try {\n writeFileAtomic(path, `${JSON.stringify(imported, null, 2)}\\n`);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: failed to write '${path}': ${detail}\\n` };\n }\n\n const lines = [`imported ${file} -> ${path} (schemaVersion ${imported.schemaVersion})`];\n if (savedPath !== undefined) {\n lines.push(` previous profile saved to ${savedPath}`);\n }\n return { code: 0, stdout: `${lines.join('\\n')}\\n`, stderr: '' };\n}\n\nfunction isNotFound(error: unknown): boolean {\n return (\n typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 'ENOENT'\n );\n}\n","/**\n * `takuhon migrate [path] [--to <version>] [--out <file>] [--dry-run]` —\n * forward-migrate a `takuhon.json` to a newer schema version.\n *\n * Mirrors `validate-command.ts`: {@link runMigrate} is a pure,\n * side-effect-light function that reads/writes files itself but returns its\n * output as strings plus an exit code, leaving stdout/stderr writes and\n * `process.exit` to the entry layer. The source version is read from the\n * file's own `schemaVersion` (the file is the source of truth), so the\n * command takes only `--to`; the default target is the latest schema this\n * build of `@takuhon/core` ships ({@link SCHEMA_VERSION}).\n *\n * The transform is intentionally minimal — `migrateTakuhon` then a re-`validate`\n * gate, with no `meta.updatedAt` touch and no `normalize` pass — so a\n * migration stays lossless (operational-lifecycle §2.6) and its diff is\n * limited to the schema-shape change. Before an in-place write the original\n * bytes are backed up beside the file (§3.1 / §3.3). Migrations are\n * forward-only (§2.4); downgrades surface as a clear error pointing at\n * `takuhon restore`.\n *\n * Exit codes:\n * 0 — migrated, already at the target (no-op), `--dry-run`, or `--help`\n * 1 — the document was read but cannot be migrated to the target (no\n * forward path / downgrade / unusable schemaVersion) or the migrated\n * result unexpectedly failed re-validation\n * 2 — the command could not run: bad arguments, an out-of-window `--to`, a\n * missing/unreadable file, or a file that is not valid JSON\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport {\n MigrationError,\n SCHEMA_VERSION,\n SUPPORTED_SCHEMA_VERSIONS,\n migrateTakuhon,\n validate,\n} from '@takuhon/core';\nimport type { Takuhon } from '@takuhon/core';\n\nimport {\n BackupError,\n backupDirFor,\n createBackup,\n migrateBackupName,\n writeFileAtomic,\n} from './backup.js';\n\n/** Default profile filename, resolved relative to the current working directory. */\nconst DEFAULT_PATH = 'takuhon.json';\n\nconst USAGE = `Usage: takuhon migrate [path] [--to <version>] [--out <file>] [--dry-run]\n\nForward-migrate a takuhon.json to a newer schema version. The source version\nis read from the file's own schemaVersion. With no path, migrates\n./takuhon.json in the current working directory.\n\nOptions:\n --to <version> Target schema version (default: ${SCHEMA_VERSION}).\n One of: ${SUPPORTED_SCHEMA_VERSIONS.join(', ')}.\n --out <file> Write the result to <file> instead of in place. The source\n file is left unchanged and no backup is created.\n --dry-run Report the planned migration and backup path; write nothing.\n\nBefore an in-place write the current file is backed up to .takuhon-backups/\nbeside it. Migrations are forward-only; to move to an older schema, restore\nfrom a backup with \\`takuhon restore\\`.\n\nExit codes: 0 = migrated / already current / dry-run, 1 = cannot migrate,\n2 = bad arguments / file missing / unreadable / not JSON.\n`;\n\nexport interface MigrateOutcome {\n /** Process exit code (see module docstring). */\n readonly code: number;\n /** Text destined for stdout (empty when there is nothing to print). */\n readonly stdout: string;\n /** Text destined for stderr (empty when there is nothing to print). */\n readonly stderr: string;\n}\n\n/** Injectable dependencies, so tests can pin the backup timestamp. */\nexport interface MigrateDeps {\n /** Clock used for backup filenames. Defaults to `() => new Date()`. */\n now?: () => Date;\n}\n\ninterface ParsedArgs {\n path: string;\n to: string;\n out?: string;\n dryRun: boolean;\n}\n\n/**\n * Run `takuhon migrate` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"migrate\"`). Never throws\n * and never writes to the process streams.\n */\nexport function runMigrate(args: readonly string[] = [], deps: MigrateDeps = {}): MigrateOutcome {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n const parsed = parseArgs(args);\n if ('error' in parsed) {\n return {\n code: 2,\n stdout: '',\n stderr: `${parsed.error}\\nRun \\`takuhon migrate --help\\` for usage.\\n`,\n };\n }\n\n const now = deps.now ?? (() => new Date());\n return migrateFile(parsed, now);\n}\n\nfunction parseArgs(args: readonly string[]): ParsedArgs | { error: string } {\n let path: string | undefined;\n let to: string | undefined;\n let out: string | undefined;\n let dryRun = false;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n\n if (arg === '--dry-run') {\n dryRun = true;\n continue;\n }\n if (arg === '--to' || arg === '--out') {\n const value = args[i + 1];\n if (value === undefined || value.startsWith('-')) {\n return { error: `takuhon: \\`${arg}\\` requires a value.` };\n }\n if (arg === '--to') to = value;\n else out = value;\n i++;\n continue;\n }\n if (arg.startsWith('--to=')) {\n to = arg.slice('--to='.length);\n continue;\n }\n if (arg.startsWith('--out=')) {\n out = arg.slice('--out='.length);\n continue;\n }\n if (arg.startsWith('-')) {\n return { error: `takuhon: unknown option \\`${arg}\\` for \\`migrate\\`.` };\n }\n if (path !== undefined) {\n return { error: 'takuhon: `migrate` takes at most one path argument.' };\n }\n path = arg;\n }\n\n const target = to ?? SCHEMA_VERSION;\n if (!(SUPPORTED_SCHEMA_VERSIONS as readonly string[]).includes(target)) {\n return {\n error: `takuhon: unsupported --to version \"${target}\". Supported: ${SUPPORTED_SCHEMA_VERSIONS.join(', ')}.`,\n };\n }\n\n return { path: path ?? DEFAULT_PATH, to: target, out, dryRun };\n}\n\nfunction migrateFile(parsed: ParsedArgs, now: () => Date): MigrateOutcome {\n const { path, to: target, out, dryRun } = parsed;\n\n let raw: string;\n try {\n raw = readFileSync(path, 'utf8');\n } catch {\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: cannot read '${path}'. Pass a path, or run from a directory containing a takuhon.json.\\n`,\n };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: '${path}' is not valid JSON: ${detail}\\n` };\n }\n\n const source = (data as { schemaVersion?: unknown }).schemaVersion;\n if (typeof source !== 'string' || source.length === 0) {\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: '${path}' has no usable schemaVersion; cannot determine what to migrate from.\\n`,\n };\n }\n\n if (source === target) {\n return {\n code: 0,\n stdout: `${path}: already at schemaVersion ${target}; nothing to do.\\n`,\n stderr: '',\n };\n }\n\n // Back up the original bytes only for a genuine in-place write. `--out` to a\n // different name leaves the source untouched, so a backup would be redundant.\n // The write is atomic (write-temp-then-rename), which replaces the target\n // *name* rather than following it, so even an `--out` symlink that points at\n // the source cannot clobber the source unbacked — it just replaces the link.\n const writeTarget = out ?? path;\n const inPlace = resolve(writeTarget) === resolve(path);\n\n let migrated: Takuhon;\n try {\n migrated = migrateTakuhon(data as Takuhon, target);\n } catch (error) {\n if (error instanceof MigrationError) {\n return {\n code: 1,\n stdout: '',\n stderr:\n `takuhon: cannot migrate '${path}': ${error.message}.\\n` +\n 'Migrations are forward-only; to move to an older schema, restore from a backup with `takuhon restore`.\\n',\n };\n }\n throw error;\n }\n\n // Re-validate as a structural safety gate. The bundled validator accepts the\n // entire supported window, so a non-latest `--to` still passes here;\n // `migrateTakuhon` is what guarantees the result carries the requested target\n // shape (it stops the chain at `target`). This catches only a migration that\n // produced something the schema rejects outright.\n const revalidated = validate(migrated);\n if (!revalidated.ok) {\n const lines = revalidated.errors.map((e) => ` ${e.pointer || '/'}: ${e.message}`);\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: migrated '${path}' to ${target} but the result failed validation:\\n${lines.join('\\n')}\\n`,\n };\n }\n\n // One timestamp per run, shared by the dry-run preview and the real backup so\n // the reported and actual backup paths cannot drift.\n const stamp = now();\n\n // `--dry-run` previews only after the migration is proven feasible above, so\n // an impossible migration (e.g. a downgrade) still reports exit 1 rather than\n // a misleading \"would migrate\".\n if (dryRun) {\n const lines = [`${path}: would migrate ${source} -> ${target}`, ` write: ${writeTarget}`];\n if (inPlace) {\n lines.push(` backup: ${backupDirFor(path)}/${migrateBackupName(source, stamp)}`);\n } else {\n lines.push(' (source left unchanged; no backup created)');\n }\n return { code: 0, stdout: `${lines.join('\\n')}\\n`, stderr: '' };\n }\n\n let backupPath: string | undefined;\n if (inPlace) {\n try {\n backupPath = createBackup({\n targetPath: path,\n content: raw,\n name: (withMillis) => migrateBackupName(source, stamp, withMillis),\n });\n } catch (error) {\n const detail = error instanceof BackupError ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: refusing to migrate '${path}' — backup failed: ${detail}\\n`,\n };\n }\n }\n\n try {\n writeFileAtomic(writeTarget, `${JSON.stringify(migrated, null, 2)}\\n`);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: failed to write '${writeTarget}': ${detail}\\n`,\n };\n }\n\n const lines = [`migrated ${path}: ${source} -> ${target}`];\n if (inPlace) {\n lines.push(` backup: ${backupPath}`);\n } else {\n lines.push(` wrote: ${writeTarget} (source left unchanged; no backup created)`);\n }\n return { code: 0, stdout: `${lines.join('\\n')}\\n`, stderr: '' };\n}\n","/**\n * `takuhon restore --from <backup> [path] [--yes]` — overwrite a profile with\n * a previously saved backup (operational-lifecycle §4.1 / §4.3).\n *\n * Like the other command runners this returns its output as strings plus an\n * exit code rather than touching the process streams. It is `async` because\n * the confirmation prompt is interactive: the entry layer injects a `confirm`\n * callback that reads from the terminal, while tests inject a deterministic\n * predicate (or pass `--yes` to skip the prompt entirely).\n *\n * Restore is destructive, so the flow is deliberately careful:\n * 1. read and schema-`validate` the backup — never restore an invalid file;\n * 2. confirm (unless `--yes`); a non-interactive run with neither `--yes`\n * nor an injected `confirm` refuses rather than overwriting silently;\n * 3. back up the current profile to `.takuhon-backups/pre-restore-…json`;\n * 4. write the backup's bytes to the target verbatim (a faithful\n * reproduction; `validate` is only the gate, not a rewrite).\n *\n * Edge-cache purge (§4.1 step 4) is a remote-adapter concern and does not\n * apply to this local-file command.\n *\n * Exit codes:\n * 0 — restored, aborted at the prompt, or `--help`\n * 1 — the backup was read but failed schema validation\n * 2 — the command could not run: bad arguments, a missing `--from`, an\n * unreadable backup or current profile, a non-JSON backup, or a refusal\n * to overwrite without confirmation\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { validate } from '@takuhon/core';\nimport type { Takuhon } from '@takuhon/core';\n\nimport {\n BackupError,\n backupDirFor,\n createBackup,\n preRestoreName,\n writeFileAtomic,\n} from './backup.js';\n\n/** Default profile filename, resolved relative to the current working directory. */\nconst DEFAULT_PATH = 'takuhon.json';\n\nconst USAGE = `Usage: takuhon restore --from <backup> [path] [--yes]\n\nOverwrite a profile with a previously saved backup. With no path, restores\n./takuhon.json in the current working directory.\n\nOptions:\n --from <backup> Backup file to restore from (required).\n --yes, -y Skip the confirmation prompt.\n\nThe backup is schema-validated first, and the current profile is saved to\n.takuhon-backups/pre-restore-<timestamp>.json before being overwritten.\n\nExit codes: 0 = restored / aborted, 1 = backup failed validation,\n2 = bad arguments / file missing / unreadable / not JSON / unconfirmed.\n`;\n\nexport interface RestoreOutcome {\n /** Process exit code (see module docstring). */\n readonly code: number;\n /** Text destined for stdout (empty when there is nothing to print). */\n readonly stdout: string;\n /** Text destined for stderr (empty when there is nothing to print). */\n readonly stderr: string;\n}\n\n/** Injectable dependencies for deterministic tests and interactive prompting. */\nexport interface RestoreDeps {\n /** Clock used for the pre-restore backup filename. Defaults to `() => new Date()`. */\n now?: () => Date;\n /**\n * Confirmation prompt. Receives the rendered message and resolves to the\n * user's decision. Omitted in non-interactive contexts, where restore then\n * refuses unless `--yes` was passed.\n */\n confirm?: (message: string) => boolean | Promise<boolean>;\n}\n\ninterface ParsedArgs {\n from: string;\n path: string;\n yes: boolean;\n}\n\n/**\n * Run `takuhon restore` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"restore\"`).\n */\nexport async function runRestore(\n args: readonly string[] = [],\n deps: RestoreDeps = {},\n): Promise<RestoreOutcome> {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n const parsed = parseArgs(args);\n if ('error' in parsed) {\n return {\n code: 2,\n stdout: '',\n stderr: `${parsed.error}\\nRun \\`takuhon restore --help\\` for usage.\\n`,\n };\n }\n\n const now = deps.now ?? (() => new Date());\n return restoreFile(parsed, now, deps.confirm);\n}\n\nfunction parseArgs(args: readonly string[]): ParsedArgs | { error: string } {\n let from: string | undefined;\n let path: string | undefined;\n let yes = false;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n\n if (arg === '--yes' || arg === '-y') {\n yes = true;\n continue;\n }\n if (arg === '--from') {\n const value = args[i + 1];\n if (value === undefined || value.startsWith('-')) {\n return { error: 'takuhon: `--from` requires a value.' };\n }\n from = value;\n i++;\n continue;\n }\n if (arg.startsWith('--from=')) {\n from = arg.slice('--from='.length);\n continue;\n }\n if (arg.startsWith('-')) {\n return { error: `takuhon: unknown option \\`${arg}\\` for \\`restore\\`.` };\n }\n if (path !== undefined) {\n return { error: 'takuhon: `restore` takes at most one path argument.' };\n }\n path = arg;\n }\n\n if (from === undefined || from.length === 0) {\n return { error: 'takuhon: `restore` requires `--from <backup>`.' };\n }\n\n return { from, path: path ?? DEFAULT_PATH, yes };\n}\n\nasync function restoreFile(\n parsed: ParsedArgs,\n now: () => Date,\n confirm: RestoreDeps['confirm'],\n): Promise<RestoreOutcome> {\n const { from, path, yes } = parsed;\n\n let backupRaw: string;\n try {\n backupRaw = readFileSync(from, 'utf8');\n } catch {\n return { code: 2, stdout: '', stderr: `takuhon: cannot read backup '${from}'.\\n` };\n }\n\n let backupData: unknown;\n try {\n backupData = JSON.parse(backupRaw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: backup '${from}' is not valid JSON: ${detail}\\n`,\n };\n }\n\n const result = validate(backupData);\n if (!result.ok) {\n const lines = result.errors.map((e) => ` ${e.pointer || '/'}: ${e.message}`);\n return {\n code: 1,\n stdout: '',\n stderr: `takuhon: backup '${from}' is not a valid takuhon profile; refusing to restore:\\n${lines.join('\\n')}\\n`,\n };\n }\n\n // Read the current profile up front: its existence shapes the confirmation\n // message and decides whether a pre-restore backup is needed.\n let currentRaw: string | undefined;\n try {\n currentRaw = readFileSync(path, 'utf8');\n } catch (error) {\n if (!isNotFound(error)) {\n return { code: 2, stdout: '', stderr: `takuhon: cannot read current profile '${path}'.\\n` };\n }\n }\n\n // One timestamp per run, shared by the prompt preview and the actual\n // pre-restore backup so the reported and written paths cannot drift.\n const stamp = now();\n const preRestorePath = `${backupDirFor(path)}/${preRestoreName(stamp)}`;\n\n if (!yes) {\n if (!confirm) {\n return {\n code: 2,\n stdout: '',\n stderr:\n `takuhon: refusing to overwrite '${path}' without confirmation.\\n` +\n 'Re-run interactively, or pass `--yes` to skip the prompt.\\n',\n };\n }\n const decided = await confirm(\n confirmationMessage(path, from, result.data, currentRaw, preRestorePath),\n );\n if (!decided) {\n return { code: 0, stdout: 'Aborted; no changes made.\\n', stderr: '' };\n }\n }\n\n let savedPath: string | undefined;\n if (currentRaw !== undefined) {\n try {\n savedPath = createBackup({\n targetPath: path,\n content: currentRaw,\n name: (withMillis) => preRestoreName(stamp, withMillis),\n });\n } catch (error) {\n const detail = error instanceof BackupError ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: refusing to restore '${path}' — pre-restore backup failed: ${detail}\\n`,\n };\n }\n }\n\n // Write the backup's bytes verbatim: restore is a faithful reproduction of\n // the saved state, and `validate` above was only the gate.\n try {\n writeFileAtomic(path, backupRaw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return { code: 2, stdout: '', stderr: `takuhon: failed to write '${path}': ${detail}\\n` };\n }\n\n const lines = [`restored ${path} from ${from}`];\n if (savedPath !== undefined) {\n lines.push(` previous profile saved to ${savedPath}`);\n }\n return { code: 0, stdout: `${lines.join('\\n')}\\n`, stderr: '' };\n}\n\nfunction confirmationMessage(\n path: string,\n from: string,\n data: Takuhon,\n currentRaw: string | undefined,\n preRestorePath: string,\n): string {\n const when = typeof data.meta.updatedAt === 'string' ? ` (from ${data.meta.updatedAt})` : '';\n const preNote =\n currentRaw !== undefined\n ? `Your current profile will be saved as ${preRestorePath}.`\n : `(no existing profile at ${path} to preserve)`;\n return (\n `This will overwrite the profile at ${path} with the backup ${from}${when}.\\n` +\n `${preNote}\\n` +\n 'Continue? [y/N]'\n );\n}\n\nfunction isNotFound(error: unknown): boolean {\n return (\n typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 'ENOENT'\n );\n}\n","/**\n * `takuhon validate [path]` — validate a `takuhon.json` against `@takuhon/core`.\n *\n * All argument handling and the validation itself live here as pure,\n * side-effect-free functions so the whole command is unit-testable: `index.ts`\n * runs `process.exit(main(...))` at module top level and is therefore not\n * import-safe. `runValidate` reads the target file itself but returns its\n * output as strings plus an exit code, leaving the actual stdout/stderr writes\n * and `process.exit` to the caller.\n *\n * Exit codes:\n * 0 — the document is valid (or `--help` was requested)\n * 1 — the document was read and parsed but failed schema validation\n * 2 — the command could not run: bad arguments, a missing/unreadable file,\n * or a file that is not valid JSON (operational errors, distinct from an\n * invalid-but-readable document)\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { validate } from '@takuhon/core';\n\n/** Default profile filename, resolved relative to the current working directory. */\nconst DEFAULT_PATH = 'takuhon.json';\n\nconst USAGE = `Usage: takuhon validate [path]\n\nValidate a takuhon.json against the takuhon schema. With no path, validates\n./takuhon.json in the current working directory.\n\nExit codes: 0 = valid, 1 = invalid, 2 = file missing / unreadable / not JSON.\n`;\n\nexport interface ValidateOutcome {\n /** Process exit code (see module docstring). */\n readonly code: number;\n /** Text destined for stdout (empty when there is nothing to print). */\n readonly stdout: string;\n /** Text destined for stderr (empty when there is nothing to print). */\n readonly stderr: string;\n}\n\n/**\n * Run `takuhon validate` against the arguments that follow the subcommand\n * (i.e. `process.argv.slice(2)` minus the leading `\"validate\"`).\n *\n * Handles `--help` / `-h` and rejects extra positionals, then validates the\n * `takuhon.json` at the single optional path argument (default\n * `./takuhon.json`). Never throws and never writes to the process streams —\n * the caller renders the returned `stdout` / `stderr` and exits with `code`.\n */\nexport function runValidate(args: readonly string[] = []): ValidateOutcome {\n if (args[0] === '--help' || args[0] === '-h') {\n return { code: 0, stdout: USAGE, stderr: '' };\n }\n\n if (args.length > 1) {\n return {\n code: 2,\n stdout: '',\n stderr:\n 'takuhon: `validate` takes at most one path argument.\\n' +\n 'Run `takuhon validate --help` for usage.\\n',\n };\n }\n\n return validateFile(args[0]);\n}\n\n/** Read, parse, and schema-validate the profile at `pathArg` (default `./takuhon.json`). */\nfunction validateFile(pathArg?: string): ValidateOutcome {\n const target = pathArg ?? DEFAULT_PATH;\n\n let raw: string;\n try {\n raw = readFileSync(target, 'utf8');\n } catch {\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: cannot read '${target}'. Pass a path, or run from a directory containing a takuhon.json.\\n`,\n };\n }\n\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n return {\n code: 2,\n stdout: '',\n stderr: `takuhon: '${target}' is not valid JSON: ${detail}\\n`,\n };\n }\n\n const result = validate(data);\n if (result.ok) {\n return {\n code: 0,\n stdout: `${target}: valid (schemaVersion ${result.data.schemaVersion}).\\n`,\n stderr: '',\n };\n }\n\n const count = result.errors.length;\n const lines = result.errors.map((error) => ` ${error.pointer || '/'}: ${error.message}`);\n return {\n code: 1,\n stdout: '',\n stderr: `${target}: invalid (${count} error${count === 1 ? '' : 's'}):\\n${lines.join('\\n')}\\n`,\n };\n}\n"],"mappings":"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","names":["readFileSync","mkdirSync","dirname","join","join","mkdirSync","dirname","readFileSync","applyPublicPrivacyFilter","normalize","validate","DEFAULT_PATH","USAGE","readFileSync","validate","applyPublicPrivacyFilter","normalize","parseArgs","resolve","isHttpUrl","readFileSync","validate","DEFAULT_PATH","USAGE","parseArgs","readFileSync","validate","readFileSync","DEFAULT_PATH","USAGE","parseArgs","readFileSync","lines","readFileSync","resolve","MigrationError","SCHEMA_VERSION","migrateTakuhon","validate","DEFAULT_PATH","USAGE","SCHEMA_VERSION","parseArgs","readFileSync","resolve","migrateTakuhon","MigrationError","validate","lines","readFileSync","validate","DEFAULT_PATH","USAGE","parseArgs","readFileSync","validate","lines","isNotFound","readFileSync","validate","DEFAULT_PATH","USAGE","readFileSync"]}
|
package/dist/init.js
CHANGED
|
@@ -168,9 +168,9 @@ function buildPackageJson(opts) {
|
|
|
168
168
|
deploy: "wrangler deploy"
|
|
169
169
|
},
|
|
170
170
|
dependencies: {
|
|
171
|
-
"@takuhon/api": "^0.
|
|
172
|
-
"@takuhon/cloudflare": "^0.
|
|
173
|
-
"@takuhon/core": "^0.
|
|
171
|
+
"@takuhon/api": "^0.8.0",
|
|
172
|
+
"@takuhon/cloudflare": "^0.8.0",
|
|
173
|
+
"@takuhon/core": "^0.8.0",
|
|
174
174
|
hono: "^4.0.0"
|
|
175
175
|
},
|
|
176
176
|
devDependencies: {
|
package/dist/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/init.ts","../src/licenses.ts","../src/prompts.ts","../src/scaffold/index.ts","../src/scaffold/env-example.ts","../src/scaffold/gitignore.ts","../src/scaffold/package-json.ts","../src/scaffold/readme.ts","../src/scaffold/takuhon-json.ts","../src/scaffold/tsconfig-json.ts","../src/scaffold/worker-index-ts.ts","../src/scaffold/wrangler-toml.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Entry point for `npx create-takuhon` (and `pnpm create takuhon`).\n *\n * Usage:\n * create-takuhon <target-dir> [--license <spdxId>]\n *\n * The positional target directory is required; it must not already exist (we\n * refuse to overwrite). `--license` skips the interactive picker for CI /\n * automation use.\n */\n\nimport { basename, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { cancel, intro, outro } from '@clack/prompts';\n\nimport { buildContentLicense, isValidSpdxInput } from './licenses.js';\nimport { promptLicense } from './prompts.js';\nimport { TargetDirectoryExistsError, writeProject } from './scaffold/index.js';\nimport { isValidWorkerName } from './scaffold/wrangler-toml.js';\n\ninterface CliArgs {\n readonly targetArg: string | undefined;\n readonly extraPositionals: readonly string[];\n readonly license: string | undefined;\n readonly help: boolean;\n}\n\nfunction parseCliArgs(argv: readonly string[]): CliArgs {\n const { values, positionals } = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n license: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n return {\n targetArg: positionals[0],\n extraPositionals: positionals.slice(1),\n license: values.license,\n help: values.help === true,\n };\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `Usage: create-takuhon <target-dir> [--license <spdxId>]\\n` +\n `\\n` +\n `Scaffolds a Takuhon profile deployment in <target-dir>.\\n` +\n `\\n` +\n `Options:\\n` +\n ` --license <spdxId> Skip the interactive license prompt and use the\\n` +\n ` given SPDX identifier (e.g. CC-BY-4.0, MIT,\\n` +\n ` Proprietary). Useful for CI / automation.\\n` +\n ` -h, --help Show this help.\\n`,\n );\n}\n\nasync function main(argv: readonly string[]): Promise<number> {\n let parsed: CliArgs;\n try {\n parsed = parseCliArgs(argv);\n } catch (err) {\n process.stderr.write(`${(err as Error).message}\\n\\n`);\n printHelp();\n return 2;\n }\n\n if (parsed.help) {\n printHelp();\n return 0;\n }\n\n if (parsed.targetArg === undefined) {\n process.stderr.write(`Error: missing target directory argument.\\n\\n`);\n printHelp();\n return 2;\n }\n\n if (parsed.extraPositionals.length > 0) {\n process.stderr.write(\n `Error: unexpected extra arguments: ${parsed.extraPositionals.join(' ')}\\n\\n`,\n );\n printHelp();\n return 2;\n }\n\n const targetDir = resolve(process.cwd(), parsed.targetArg);\n const projectName = basename(targetDir);\n\n if (!isValidWorkerName(projectName)) {\n process.stderr.write(\n `Error: target directory basename \"${projectName}\" is not a valid Cloudflare Worker name.\\n` +\n `Names must be lowercase, start and end with a letter or digit, and contain only ` +\n `letters, digits, and hyphens (max 63 chars).\\n`,\n );\n return 2;\n }\n\n intro('create-takuhon');\n\n let spdxId: string;\n if (parsed.license !== undefined) {\n const raw = parsed.license.trim();\n if (!isValidSpdxInput(raw)) {\n cancel(`Invalid --license value: \"${parsed.license}\"`);\n return 2;\n }\n spdxId = raw;\n } else {\n const result = await promptLicense();\n if ('cancelled' in result) {\n return 130;\n }\n spdxId = result.spdxId;\n }\n\n const license = buildContentLicense(spdxId);\n\n try {\n await writeProject({ targetDir, projectName, license });\n } catch (err) {\n if (err instanceof TargetDirectoryExistsError) {\n cancel(`Target directory already exists: ${parsed.targetArg}`);\n return 1;\n }\n throw err;\n }\n\n outro(\n `Created ${projectName} (license: ${spdxId}).\\n` +\n `\\n` +\n `Next steps:\\n` +\n ` cd ${parsed.targetArg}\\n` +\n ` # 1. Edit takuhon.json with your profile data\\n` +\n ` # 2. Provision Cloudflare KV: wrangler kv namespace create TAKUHON_KV\\n` +\n ` # 3. Set admin token: openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN\\n` +\n ` pnpm install\\n` +\n ` pnpm dev`,\n );\n\n return 0;\n}\n\nvoid main(process.argv.slice(2))\n .then((code) => {\n process.exit(code);\n })\n .catch((err: unknown) => {\n // Render only the message — full stack traces can leak host-machine\n // absolute paths and monorepo-internal layout into a published\n // binary's stderr output.\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n","/**\n * Content-license metadata and helpers for the `create-takuhon` scaffolding\n * flow.\n *\n * The four entries in `LICENSE_OPTIONS` are the curated choices the\n * interactive picker offers. \"Custom\" is handled separately by the prompt\n * flow as a free-form SPDX text input. `buildContentLicense` shapes the\n * chosen identifier into the `meta.contentLicense` fragment that lands in\n * the generated `takuhon.json`. The Takuhon profile JSON Schema accepts any\n * SPDX expression in `meta.contentLicense.spdxId`; unknown identifiers fall\n * through with `spdxId` only, and downstream UI rendering is best-effort.\n */\n\n/** A single row in the interactive license selector. */\nexport interface LicenseOption {\n /** Short label shown as the selectable label in the prompt. */\n readonly label: string;\n /** Longer hint shown alongside the label in the prompt UI. */\n readonly hint: string;\n /** SPDX identifier written to `takuhon.json` `meta.contentLicense.spdxId`. */\n readonly spdxId: string;\n /** Canonical license URL. Omitted for `Proprietary`. */\n readonly url?: string;\n}\n\n/**\n * The four curated license choices shown by the interactive picker. \"Custom\"\n * is not in this list because it triggers a free-form text prompt rather than\n * mapping to a fixed SPDX identifier here.\n */\nexport const LICENSE_OPTIONS: readonly LicenseOption[] = [\n {\n label: 'CC BY 4.0',\n hint: 'Allow reuse with attribution',\n spdxId: 'CC-BY-4.0',\n url: 'https://creativecommons.org/licenses/by/4.0/',\n },\n {\n label: 'CC BY-NC 4.0',\n hint: 'Non-commercial reuse with attribution',\n spdxId: 'CC-BY-NC-4.0',\n url: 'https://creativecommons.org/licenses/by-nc/4.0/',\n },\n {\n label: 'CC0',\n hint: 'Public domain',\n spdxId: 'CC0-1.0',\n url: 'https://creativecommons.org/publicdomain/zero/1.0/',\n },\n {\n label: 'Proprietary',\n hint: 'All rights reserved',\n spdxId: 'Proprietary',\n },\n];\n\n/**\n * Canonical URL lookup for SPDX identifiers we recognise but don't list in\n * the interactive picker. Used by `buildContentLicense` so that a user who\n * passes `--license CC-BY-SA-4.0` (or selects it via `Custom`) still gets a\n * usable `url` field in the generated `takuhon.json`.\n *\n * URLs for non-CC SPDX identifiers point at the canonical SPDX license page\n * (`spdx.org/licenses/<id>.html`). Creative Commons identifiers use the\n * `creativecommons.org` deed URL because that is the page humans expect to\n * land on (the SPDX page is metadata-only).\n */\nconst KNOWN_URL_BY_SPDX: Readonly<Record<string, string>> = {\n 'CC0-1.0': 'https://creativecommons.org/publicdomain/zero/1.0/',\n 'CC-BY-4.0': 'https://creativecommons.org/licenses/by/4.0/',\n 'CC-BY-SA-4.0': 'https://creativecommons.org/licenses/by-sa/4.0/',\n 'CC-BY-ND-4.0': 'https://creativecommons.org/licenses/by-nd/4.0/',\n 'CC-BY-NC-4.0': 'https://creativecommons.org/licenses/by-nc/4.0/',\n 'CC-BY-NC-SA-4.0': 'https://creativecommons.org/licenses/by-nc-sa/4.0/',\n 'CC-BY-NC-ND-4.0': 'https://creativecommons.org/licenses/by-nc-nd/4.0/',\n MIT: 'https://spdx.org/licenses/MIT.html',\n};\n\n/** Shape written to `takuhon.json` under `meta.contentLicense`. */\nexport interface ContentLicenseFragment {\n readonly spdxId: string;\n readonly url?: string;\n readonly rights?: string;\n}\n\n/**\n * Translate a chosen SPDX identifier into the `meta.contentLicense` fragment\n * for `takuhon.json`.\n *\n * - `Proprietary` is given a `rights` sentinel and no `url`.\n * - Known SPDX identifiers get a canonical `url`.\n * - Anything else is written as `{ spdxId }` only — the schema accepts\n * arbitrary SPDX expressions, and UI rendering is best-effort.\n */\nexport function buildContentLicense(spdxId: string): ContentLicenseFragment {\n if (spdxId === 'Proprietary') {\n return {\n spdxId: 'Proprietary',\n rights: 'All rights reserved. Contact owner for usage permission.',\n };\n }\n const url = KNOWN_URL_BY_SPDX[spdxId];\n if (url !== undefined) {\n return { spdxId, url };\n }\n return { spdxId };\n}\n\n/**\n * Light syntactic validation for a user-entered SPDX expression (the\n * `Custom` prompt branch and the `--license` flag).\n *\n * We accept any non-empty string composed of the characters that appear in\n * canonical SPDX identifiers and boolean expressions (alphanumerics, `.`,\n * `-`, `+`, parentheses, and literal spaces). Tabs and newlines are rejected\n * because real SPDX expressions never contain them. Full SPDX expression\n * parsing (`MIT OR (Apache-2.0 AND CC-BY-4.0)`) is intentionally out of\n * scope — we trust the user and let the JSON Schema's downstream validation\n * flag obvious problems.\n */\nexport function isValidSpdxInput(input: string): boolean {\n const trimmed = input.trim();\n if (trimmed === '') return false;\n return /^[A-Za-z0-9.\\-+() ]+$/.test(trimmed);\n}\n","/**\n * Interactive prompt flow for `create-takuhon`, built on `@clack/prompts`.\n *\n * A single-choice picker offers the curated `LICENSE_OPTIONS` plus a\n * `Custom` row that opens a free-form SPDX text input. Cancellation\n * (Ctrl+C or pressing Esc on a clack prompt) returns a sentinel result\n * instead of throwing so the caller can exit cleanly with a specific status\n * code.\n */\n\nimport { cancel, isCancel, select, text } from '@clack/prompts';\n\nimport { LICENSE_OPTIONS, isValidSpdxInput } from './licenses.js';\n\n/** Returned by {@link promptLicense} on successful completion. */\nexport interface PromptLicenseResult {\n /** SPDX identifier to write into `meta.contentLicense.spdxId`. */\n readonly spdxId: string;\n}\n\n/** Returned when the user cancels (Ctrl+C / Esc) instead of completing. */\nexport interface PromptCancelled {\n readonly cancelled: true;\n}\n\nconst CUSTOM_SENTINEL = '__custom__';\n\n/**\n * Run the license picker. Resolves to either the selected SPDX identifier or\n * a cancellation sentinel; never throws on user cancel.\n */\nexport async function promptLicense(): Promise<PromptLicenseResult | PromptCancelled> {\n const choice = await select<string>({\n message: 'Choose a license for your profile content:',\n options: [\n ...LICENSE_OPTIONS.map((opt) => ({\n value: opt.spdxId,\n label: opt.label,\n hint: opt.hint,\n })),\n { value: CUSTOM_SENTINEL, label: 'Custom', hint: 'enter SPDX identifier' },\n ],\n });\n\n if (isCancel(choice)) {\n cancel('Aborted.');\n return { cancelled: true };\n }\n\n if (choice !== CUSTOM_SENTINEL) {\n return { spdxId: choice };\n }\n\n const customRaw = await text({\n message: 'Enter the SPDX identifier:',\n placeholder: 'e.g. MIT, Apache-2.0, CC-BY-SA-4.0',\n validate(value): string | undefined {\n if (!isValidSpdxInput(value)) {\n return 'Must be a non-empty SPDX-like expression (alphanumerics, ., -, +, parentheses, spaces).';\n }\n return undefined;\n },\n });\n\n if (isCancel(customRaw)) {\n cancel('Aborted.');\n return { cancelled: true };\n }\n\n return { spdxId: customRaw.trim() };\n}\n","/**\n * Top-level scaffolding orchestrator for `create-takuhon`.\n *\n * `writeProject` is the single entry point used by `init.ts`. It creates the\n * target directory (must not already exist), then writes the eight files\n * that make up the scaffold: `takuhon.json`, `wrangler.toml`, `package.json`,\n * `README.md`, `.gitignore`, `.env.example`, `tsconfig.json`, and\n * `src/index.ts` (the Cloudflare Worker entry composed via\n * `createTakuhonWorker` from `@takuhon/cloudflare`).\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\nimport { renderEnvExample } from './env-example.js';\nimport { renderGitignore } from './gitignore.js';\nimport { renderPackageJson } from './package-json.js';\nimport { renderReadme } from './readme.js';\nimport { renderTakuhonJson } from './takuhon-json.js';\nimport { renderTsconfigJson } from './tsconfig-json.js';\nimport { renderWorkerIndexTs } from './worker-index-ts.js';\nimport { renderWranglerToml } from './wrangler-toml.js';\n\nexport interface WriteProjectOptions {\n /** Absolute path of the directory to create. Must not exist. */\n readonly targetDir: string;\n /** Used for the npm `name`, Worker name, and README header. */\n readonly projectName: string;\n /** Chosen content license (already mapped via `buildContentLicense`). */\n readonly license: ContentLicenseFragment;\n}\n\nexport interface WriteProjectResult {\n /** Relative paths of files that were written (in the order written). */\n readonly files: readonly string[];\n}\n\n/**\n * Error thrown when the target directory already exists. The caller is\n * expected to render a friendly message that references the user-supplied\n * path; the `code` is stable for tests. The absolute `targetDir` is exposed\n * as a field rather than embedded in the message so that bubbling the error\n * up through generic logging does not leak filesystem layout.\n */\nexport class TargetDirectoryExistsError extends Error {\n override readonly name = 'TargetDirectoryExistsError';\n readonly code = 'TARGET_EXISTS' as const;\n constructor(readonly targetDir: string) {\n super('Target directory already exists.');\n }\n}\n\n/**\n * Create the project directory and write the scaffolded files. Order is\n * deterministic so callers (and tests) can rely on it.\n */\nexport async function writeProject(opts: WriteProjectOptions): Promise<WriteProjectResult> {\n const { targetDir, projectName, license } = opts;\n\n // mkdir with recursive: false fails if the directory exists; we surface a\n // typed error so the CLI entry can render a friendly message.\n try {\n await mkdir(targetDir, { recursive: false });\n } catch (err) {\n if (isNodeErrnoException(err) && err.code === 'EEXIST') {\n throw new TargetDirectoryExistsError(targetDir);\n }\n throw err;\n }\n\n const files: { readonly path: string; readonly content: string }[] = [\n { path: 'takuhon.json', content: renderTakuhonJson(license) },\n { path: 'wrangler.toml', content: renderWranglerToml(projectName) },\n { path: 'package.json', content: renderPackageJson({ projectName }) },\n { path: 'README.md', content: renderReadme({ projectName, license }) },\n { path: '.gitignore', content: renderGitignore() },\n { path: '.env.example', content: renderEnvExample() },\n { path: 'tsconfig.json', content: renderTsconfigJson() },\n { path: 'src/index.ts', content: renderWorkerIndexTs() },\n ];\n\n for (const { path, content } of files) {\n const fullPath = join(targetDir, path);\n const parent = dirname(fullPath);\n if (parent !== targetDir) {\n await mkdir(parent, { recursive: true });\n }\n await writeFile(fullPath, content, 'utf8');\n }\n\n return { files: files.map((f) => f.path) };\n}\n\nfunction isNodeErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * Generator for the `.env.example` file in a freshly scaffolded project.\n *\n * The Wrangler-managed secrets (admin bearer token) are *not* read from `.env`\n * at deploy time — `wrangler secret put` is the source of truth. The\n * `.env.example` is therefore documentation: it lists the variables a developer\n * may want locally (e.g. for scripting or for the wrangler CLI itself) and\n * links each to its production provisioning path.\n */\n\nexport function renderEnvExample(): string {\n return `# ----------------------------------------------------------------\n# Local development variables for this Takuhon deployment.\n#\n# Production secrets (admin token) are provisioned via:\n# wrangler secret put TAKUHON_ADMIN_TOKEN\n# This file is a hint for local tooling, not a runtime source.\n# ----------------------------------------------------------------\n\n# Admin bearer token for /api/admin/* endpoints.\n# Generate with: openssl rand -base64 32\n# Then provision with:\n# echo \"$TAKUHON_ADMIN_TOKEN\" | wrangler secret put TAKUHON_ADMIN_TOKEN\nTAKUHON_ADMIN_TOKEN=\n\n# Comma-separated Origin allowlist for browser-originating admin requests.\n# Set in wrangler.toml [vars] for production. Local example:\n# TAKUHON_ADMIN_ORIGIN=https://admin.example.com,https://localhost:8787\nTAKUHON_ADMIN_ORIGIN=\n\n# Cloudflare account id (look up via \\`wrangler whoami\\`). Only needed if you\n# script Cloudflare API calls outside Wrangler itself.\nCLOUDFLARE_ACCOUNT_ID=\n`;\n}\n","/**\n * Generator for the scaffolded project's `.gitignore`.\n *\n * Covers Node, Wrangler local cache, and environment files. Kept conservative\n * — no opinions about lockfile preferences (the user picks their package\n * manager) or editor folders (per-editor patterns belong in a global ignore).\n */\n\nexport function renderGitignore(): string {\n return `# Dependencies\nnode_modules/\n\n# Wrangler local cache (do not commit; contains bundled output and tmp files)\n.wrangler/\n\n# Environment variables (commit \\`.env.example\\` instead)\n.env\n.env.local\n.env.*.local\n\n# Logs\n*.log\nnpm-debug.log*\npnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# macOS\n.DS_Store\n`;\n}\n","/**\n * Generator for the scaffolded project's `package.json`.\n *\n * Pinned dependency versions are caret ranges matched to the current\n * minor of the published `@takuhon/*` packages. Under 0.x semver, a caret\n * does not span minor versions, so the range must move forward with each\n * `@takuhon/core` minor release to keep scaffolded projects on the\n * matching schema generation. `hono` is included as a direct dependency for\n * projects that extend the Worker with their own Hono routes; the generated\n * `src/index.ts` reaches Takuhon's handlers through `@takuhon/cloudflare`\n * rather than importing `hono` directly. `wrangler` is a devDependency since\n * it's the deploy / dev-server tool.\n */\n\nexport interface PackageJsonOptions {\n /** Used for the npm `name` field. */\n readonly projectName: string;\n}\n\nexport function buildPackageJson(opts: PackageJsonOptions): Record<string, unknown> {\n return {\n name: opts.projectName,\n version: '0.0.0',\n private: true,\n type: 'module',\n description: 'Takuhon profile deployment.',\n scripts: {\n dev: 'wrangler dev',\n deploy: 'wrangler deploy',\n },\n dependencies: {\n '@takuhon/api': '^0.6.0',\n '@takuhon/cloudflare': '^0.6.0',\n '@takuhon/core': '^0.6.0',\n hono: '^4.0.0',\n },\n devDependencies: {\n wrangler: '^4.0.0',\n },\n engines: {\n node: '>=22.0.0',\n },\n };\n}\n\nexport function renderPackageJson(opts: PackageJsonOptions): string {\n return `${JSON.stringify(buildPackageJson(opts), null, 2)}\\n`;\n}\n","/**\n * Generator for the README.md placed in a freshly scaffolded project.\n *\n * Aims to give a first-time user enough to (1) fill in their profile data,\n * (2) provision Cloudflare KV + admin secret, and (3) `pnpm dev` / `pnpm\n * deploy` once the upstream `@takuhon/*` packages are reachable. Cross-\n * references the published `@takuhon/cloudflare` adapter README as the\n * next-step reference for the route map.\n */\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\nexport interface ReadmeOptions {\n readonly projectName: string;\n readonly license: ContentLicenseFragment;\n}\n\n/**\n * Human-readable license attribution line for the README footer. Uses the\n * canonical URL when available; falls back to plain SPDX (or\n * \"All rights reserved\" for Proprietary).\n */\nfunction formatLicenseLine(license: ContentLicenseFragment): string {\n if (license.spdxId === 'Proprietary') {\n return '**Proprietary** — all rights reserved.';\n }\n if (license.url !== undefined) {\n return `[\\`${license.spdxId}\\`](${license.url})`;\n }\n return `\\`${license.spdxId}\\``;\n}\n\nexport function renderReadme(opts: ReadmeOptions): string {\n const { projectName, license } = opts;\n const licenseLine = formatLicenseLine(license);\n return `# ${projectName}\n\nA [Takuhon](https://github.com/takuhon-dev/takuhon) profile deployment, running on Cloudflare Workers.\n\n> **Status**: pre-deploy. Edit \\`takuhon.json\\`, provision Cloudflare KV, then \\`pnpm deploy\\`.\n\n> **Heads-up — Takuhon is in a pre-publish phase.**\n>\n> The \\`@takuhon/api\\`, \\`@takuhon/core\\`, and \\`@takuhon/cloudflare\\` packages\n> referenced in \\`package.json\\` are not yet on the npm registry.\n> \\`pnpm install\\` will fail in this directory until they ship. See *Develop*\n> below for the workspace-link recipe in the meantime.\n\n## What is Takuhon?\n\nTakuhon lets you own your profile as a portable JSON document and publish it as a mobile-first profile page plus a public API (JSON-LD for AI agents and search engines included).\n\n## Setup\n\n1. **Edit your profile.** Open \\`takuhon.json\\` and replace the sample fields (\\`profile.displayName\\`, \\`links\\`, \\`careers\\`, \\`projects\\`, \\`skills\\`) with your own.\n\n2. **Create the Cloudflare KV namespaces** and copy the returned ids into \\`wrangler.toml\\`:\n\n \\`\\`\\`sh\n wrangler kv namespace create TAKUHON_KV\n wrangler kv namespace create TAKUHON_KV --preview\n \\`\\`\\`\n\n3. **Provision the admin token** as a Wrangler secret (used by \\`/api/admin/*\\`):\n\n \\`\\`\\`sh\n openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN\n \\`\\`\\`\n\n Leaving the secret unset disables admin writes entirely (every \\`PUT\\` / \\`DELETE\\` returns 401).\n\n## Develop\n\nThe Worker entry at \\`src/index.ts\\` composes the takuhon adapter via\n\\`createTakuhonWorker\\` and serves your \\`takuhon.json\\` as the fallback when\nKV has no stored profile yet. Until the \\`@takuhon/*\\` packages are on npm,\nclone the upstream repo and link them into this directory (substitute\nyour own absolute path for \\`<takuhon-repo-path>\\`):\n\n\\`\\`\\`sh\ngit clone https://github.com/takuhon-dev/takuhon <takuhon-repo-path>\n( cd <takuhon-repo-path> && pnpm install && pnpm build )\npnpm link <takuhon-repo-path>/packages/api <takuhon-repo-path>/packages/core <takuhon-repo-path>/adapters/cloudflare\n\\`\\`\\`\n\nThen:\n\n\\`\\`\\`sh\npnpm dev # runs \\`wrangler dev\\` locally\n\\`\\`\\`\n\nThe full route map (public + admin) is documented in the [\\`@takuhon/cloudflare\\` README](https://github.com/takuhon-dev/takuhon/tree/main/adapters/cloudflare#readme).\n\n## Deploy\n\n\\`\\`\\`sh\npnpm deploy # runs \\`wrangler deploy\\`\n\\`\\`\\`\n\n## License\n\nProfile content (\\`takuhon.json\\`) is licensed under ${licenseLine}\n\nThe deployment code is your own; pick a license appropriate for it.\n`;\n}\n","/**\n * Generator for the `takuhon.json` file written into a freshly scaffolded\n * project.\n *\n * The template is a copy of `examples/minimal-profile/takuhon.json` from the\n * monorepo. It is inlined here as a TypeScript constant rather than imported\n * from `examples/` so the published `@takuhon/cli` npm package does not need\n * to ship the examples directory.\n *\n * Only `meta.contentLicense` is rewritten per the user's choice; everything\n * else is the canonical minimal profile (one career, one project, three\n * skills, `en` locale only) that downstream `@takuhon/core` validation\n * already accepts (see `packages/core/src/__tests__/examples-fixtures.test.ts`).\n */\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\n/**\n * Build the `takuhon.json` payload as a deterministic, schema-valid object.\n * `meta.contentLicense` is filled from the supplied fragment so each\n * generated project carries the user's chosen SPDX identifier (and `url` /\n * `rights` where applicable).\n */\nexport function buildTakuhonJson(license: ContentLicenseFragment): unknown {\n return {\n schemaVersion: '0.4.0',\n profile: {\n displayName: {\n en: 'Sam Lee',\n },\n },\n links: [\n {\n id: 'github',\n type: 'github',\n url: 'https://example.com/github/sam-lee',\n featured: true,\n },\n ],\n careers: [\n {\n id: 'first-job',\n organization: {\n en: 'Example Co.',\n },\n role: {\n en: 'Junior Software Engineer',\n },\n startDate: '2026-04',\n endDate: null,\n isCurrent: true,\n },\n ],\n projects: [\n {\n id: 'personal-homepage',\n title: {\n en: 'Personal homepage',\n },\n },\n ],\n skills: [\n { id: 'html', label: 'HTML' },\n { id: 'css', label: 'CSS' },\n { id: 'javascript', label: 'JavaScript' },\n ],\n contact: {},\n settings: {\n defaultLocale: 'en',\n availableLocales: ['en'],\n },\n meta: {\n contentLicense: { ...license },\n },\n };\n}\n\n/** Serialise to a UTF-8 string with trailing newline (POSIX-friendly). */\nexport function renderTakuhonJson(license: ContentLicenseFragment): string {\n return `${JSON.stringify(buildTakuhonJson(license), null, 2)}\\n`;\n}\n","/**\n * Generator for the scaffolded project's `tsconfig.json`.\n *\n * Minimal configuration that makes the generated `src/index.ts` typecheck\n * with `tsc --noEmit` in a freshly scaffolded directory. The settings\n * mirror the upstream takuhon monorepo defaults (ES2022 target, ESNext\n * modules, strict mode, `noUncheckedIndexedAccess`) and explicitly enable\n * the two flags the Worker entry depends on:\n *\n * - `resolveJsonModule` — so `import takuhonJson from '../takuhon.json'`\n * resolves at type-check time.\n * - `moduleResolution: \"Bundler\"` — so the Wrangler / esbuild-style\n * resolution (no need for explicit file extensions) is the source of\n * truth that matches what `wrangler dev` actually runs.\n *\n * `noEmit: true` keeps `tsc` purely a type-checker; Wrangler's bundler\n * produces the deployable artifact.\n */\n\nexport function renderTsconfigJson(): string {\n const config = {\n compilerOptions: {\n target: 'ES2022',\n lib: ['ES2022'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n resolveJsonModule: true,\n isolatedModules: true,\n verbatimModuleSyntax: true,\n strict: true,\n noUncheckedIndexedAccess: true,\n noImplicitOverride: true,\n noFallthroughCasesInSwitch: true,\n skipLibCheck: true,\n noEmit: true,\n },\n include: ['src/**/*'],\n exclude: ['node_modules'],\n };\n return `${JSON.stringify(config, null, 2)}\\n`;\n}\n","/**\n * Generator for `src/index.ts` — the Cloudflare Worker entry file written\n * into a freshly scaffolded project.\n *\n * The generated file uses `createTakuhonWorker()` from `@takuhon/cloudflare`\n * so the scaffolded project does not need to know the internal wiring\n * (Hono router, KV-backed storage, edge cache purger, console audit logger).\n * The user's own `takuhon.json` is loaded via an ES module JSON import,\n * validated once at module load, and the resulting `Takuhon` value is\n * served as the fallback when KV has no stored profile yet.\n *\n * `wrangler.toml`'s `main` field already points at `src/index.ts`, and\n * `nodejs_compat` is enabled, so the file works under `wrangler dev` /\n * `wrangler deploy` without further configuration.\n */\n\nexport function renderWorkerIndexTs(): string {\n return `import { createTakuhonWorker } from '@takuhon/cloudflare';\nimport { validate } from '@takuhon/core';\n\n// Use the project's own takuhon.json as the fallback served when KV has no\n// stored profile yet. Validated once at module load so a malformed profile\n// fails fast rather than at first request.\nimport takuhonJson from '../takuhon.json' with { type: 'json' };\n\nconst fallback = validate(takuhonJson);\nif (!fallback.ok) {\n throw new Error(\n 'Project takuhon.json failed validation: ' +\n fallback.errors.map((e) => \\`\\${e.pointer}: \\${e.message}\\`).join('; '),\n );\n}\n\nconst fallbackTakuhon = fallback.data;\n\nexport default createTakuhonWorker({\n fallback: () => fallbackTakuhon,\n});\n`;\n}\n","/**\n * Generator for `wrangler.toml` in a freshly scaffolded project.\n *\n * The layout mirrors the reference Cloudflare adapter shipped in this\n * monorepo: a single KV namespace `TAKUHON_KV`, placeholder ids the user\n * fills in after running `wrangler kv namespace create`, and the\n * `TAKUHON_ADMIN_ORIGIN` var defaulting to \"\" (disabled). The admin bearer\n * token is provisioned as a Wrangler secret and is therefore not in this file.\n */\n\n/**\n * Validate a Cloudflare Worker name.\n *\n * Cloudflare itself accepts mixed-case DNS labels (1–63 chars, must not\n * start or end with a hyphen). This validator additionally enforces a\n * lowercase convention chosen for the scaffolder so that the resulting\n * `workers.dev` subdomain is predictable and matches the npm `name` field.\n */\nexport function isValidWorkerName(name: string): boolean {\n return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name);\n}\n\n/**\n * Render `wrangler.toml` for the given project name.\n *\n * Throws if `projectName` is not a valid Cloudflare Worker name. Callers are\n * responsible for sanitising/validating earlier in the prompt flow.\n */\nexport function renderWranglerToml(projectName: string): string {\n if (!isValidWorkerName(projectName)) {\n throw new Error(\n `Invalid Cloudflare Worker name: \"${projectName}\". Names must be lowercase, ` +\n `start with a letter or digit, and contain only letters, digits, and hyphens (max 63 chars).`,\n );\n }\n return `name = \"${projectName}\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2026-05-01\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n# Replace the placeholder ids after running:\n# wrangler kv namespace create TAKUHON_KV\n# wrangler kv namespace create TAKUHON_KV --preview\n[[kv_namespaces]]\nbinding = \"TAKUHON_KV\"\nid = \"REPLACE_WITH_PRODUCTION_NAMESPACE_ID\"\npreview_id = \"REPLACE_WITH_PREVIEW_NAMESPACE_ID\"\n\n# Admin Origin allowlist (comma-separated). Empty value disables the check.\n# Example for production: TAKUHON_ADMIN_ORIGIN = \"https://admin.example.com\"\n#\n# The admin bearer token MUST be provisioned as a Wrangler secret, never in\n# this file:\n# wrangler secret put TAKUHON_ADMIN_TOKEN\n# Use 32+ bytes of entropy, e.g. \\`openssl rand -base64 32\\`.\n[vars]\nTAKUHON_ADMIN_ORIGIN = \"\"\n`;\n}\n"],"mappings":";;;AAaA,SAAS,UAAU,eAAe;AAClC,SAAS,iBAAiB;AAE1B,SAAS,UAAAA,SAAQ,OAAO,aAAa;;;ACc9B,IAAM,kBAA4C;AAAA,EACvD;AAAA,IACE,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EACV;AACF;AAaA,IAAM,oBAAsD;AAAA,EAC1D,WAAW;AAAA,EACX,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,KAAK;AACP;AAkBO,SAAS,oBAAoB,QAAwC;AAC1E,MAAI,WAAW,eAAe;AAC5B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AACA,QAAM,MAAM,kBAAkB,MAAM;AACpC,MAAI,QAAQ,QAAW;AACrB,WAAO,EAAE,QAAQ,IAAI;AAAA,EACvB;AACA,SAAO,EAAE,OAAO;AAClB;AAcO,SAAS,iBAAiB,OAAwB;AACvD,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,YAAY,GAAI,QAAO;AAC3B,SAAO,wBAAwB,KAAK,OAAO;AAC7C;;;AClHA,SAAS,QAAQ,UAAU,QAAQ,YAAY;AAe/C,IAAM,kBAAkB;AAMxB,eAAsB,gBAAgE;AACpF,QAAM,SAAS,MAAM,OAAe;AAAA,IAClC,SAAS;AAAA,IACT,SAAS;AAAA,MACP,GAAG,gBAAgB,IAAI,CAAC,SAAS;AAAA,QAC/B,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,MAAM,IAAI;AAAA,MACZ,EAAE;AAAA,MACF,EAAE,OAAO,iBAAiB,OAAO,UAAU,MAAM,wBAAwB;AAAA,IAC3E;AAAA,EACF,CAAC;AAED,MAAI,SAAS,MAAM,GAAG;AACpB,WAAO,UAAU;AACjB,WAAO,EAAE,WAAW,KAAK;AAAA,EAC3B;AAEA,MAAI,WAAW,iBAAiB;AAC9B,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,YAAY,MAAM,KAAK;AAAA,IAC3B,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS,OAA2B;AAClC,UAAI,CAAC,iBAAiB,KAAK,GAAG;AAC5B,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,MAAI,SAAS,SAAS,GAAG;AACvB,WAAO,UAAU;AACjB,WAAO,EAAE,WAAW,KAAK;AAAA,EAC3B;AAEA,SAAO,EAAE,QAAQ,UAAU,KAAK,EAAE;AACpC;;;AC3DA,SAAS,OAAO,iBAAiB;AACjC,SAAS,SAAS,YAAY;;;ACFvB,SAAS,mBAA2B;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBT;;;AC1BO,SAAS,kBAA0B;AACxC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBT;;;ACXO,SAAS,iBAAiB,MAAmD;AAClF,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,MACP,KAAK;AAAA,MACL,QAAQ;AAAA,IACV;AAAA,IACA,cAAc;AAAA,MACZ,gBAAgB;AAAA,MAChB,uBAAuB;AAAA,MACvB,iBAAiB;AAAA,MACjB,MAAM;AAAA,IACR;AAAA,IACA,iBAAiB;AAAA,MACf,UAAU;AAAA,IACZ;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,kBAAkB,MAAkC;AAClE,SAAO,GAAG,KAAK,UAAU,iBAAiB,IAAI,GAAG,MAAM,CAAC,CAAC;AAAA;AAC3D;;;ACzBA,SAAS,kBAAkB,SAAyC;AAClE,MAAI,QAAQ,WAAW,eAAe;AACpC,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAQ,QAAW;AAC7B,WAAO,MAAM,QAAQ,MAAM,OAAO,QAAQ,GAAG;AAAA,EAC/C;AACA,SAAO,KAAK,QAAQ,MAAM;AAC5B;AAEO,SAAS,aAAa,MAA6B;AACxD,QAAM,EAAE,aAAa,QAAQ,IAAI;AACjC,QAAM,cAAc,kBAAkB,OAAO;AAC7C,SAAO,KAAK,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uDAkE8B,WAAW;AAAA;AAAA;AAAA;AAIlE;;;AClFO,SAAS,iBAAiB,SAA0C;AACzE,SAAO;AAAA,IACL,eAAe;AAAA,IACf,SAAS;AAAA,MACP,aAAa;AAAA,QACX,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,KAAK;AAAA,QACL,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,cAAc;AAAA,UACZ,IAAI;AAAA,QACN;AAAA,QACA,MAAM;AAAA,UACJ,IAAI;AAAA,QACN;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,QACE,IAAI;AAAA,QACJ,OAAO;AAAA,UACL,IAAI;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,EAAE,IAAI,QAAQ,OAAO,OAAO;AAAA,MAC5B,EAAE,IAAI,OAAO,OAAO,MAAM;AAAA,MAC1B,EAAE,IAAI,cAAc,OAAO,aAAa;AAAA,IAC1C;AAAA,IACA,SAAS,CAAC;AAAA,IACV,UAAU;AAAA,MACR,eAAe;AAAA,MACf,kBAAkB,CAAC,IAAI;AAAA,IACzB;AAAA,IACA,MAAM;AAAA,MACJ,gBAAgB,EAAE,GAAG,QAAQ;AAAA,IAC/B;AAAA,EACF;AACF;AAGO,SAAS,kBAAkB,SAAyC;AACzE,SAAO,GAAG,KAAK,UAAU,iBAAiB,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA;AAC9D;;;AC7DO,SAAS,qBAA6B;AAC3C,QAAM,SAAS;AAAA,IACb,iBAAiB;AAAA,MACf,QAAQ;AAAA,MACR,KAAK,CAAC,QAAQ;AAAA,MACd,QAAQ;AAAA,MACR,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,MACtB,QAAQ;AAAA,MACR,0BAA0B;AAAA,MAC1B,oBAAoB;AAAA,MACpB,4BAA4B;AAAA,MAC5B,cAAc;AAAA,MACd,QAAQ;AAAA,IACV;AAAA,IACA,SAAS,CAAC,UAAU;AAAA,IACpB,SAAS,CAAC,cAAc;AAAA,EAC1B;AACA,SAAO,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA;AAC3C;;;ACzBO,SAAS,sBAA8B;AAC5C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBT;;;ACrBO,SAAS,kBAAkB,MAAuB;AACvD,SAAO,yCAAyC,KAAK,IAAI;AAC3D;AAQO,SAAS,mBAAmB,aAA6B;AAC9D,MAAI,CAAC,kBAAkB,WAAW,GAAG;AACnC,UAAM,IAAI;AAAA,MACR,oCAAoC,WAAW;AAAA,IAEjD;AAAA,EACF;AACA,SAAO,WAAW,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuB/B;;;ARZO,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAGpD,YAAqB,WAAmB;AACtC,UAAM,kCAAkC;AADrB;AAAA,EAErB;AAAA,EAFqB;AAAA,EAFH,OAAO;AAAA,EAChB,OAAO;AAIlB;AAMA,eAAsB,aAAa,MAAwD;AACzF,QAAM,EAAE,WAAW,aAAa,QAAQ,IAAI;AAI5C,MAAI;AACF,UAAM,MAAM,WAAW,EAAE,WAAW,MAAM,CAAC;AAAA,EAC7C,SAAS,KAAK;AACZ,QAAI,qBAAqB,GAAG,KAAK,IAAI,SAAS,UAAU;AACtD,YAAM,IAAI,2BAA2B,SAAS;AAAA,IAChD;AACA,UAAM;AAAA,EACR;AAEA,QAAM,QAA+D;AAAA,IACnE,EAAE,MAAM,gBAAgB,SAAS,kBAAkB,OAAO,EAAE;AAAA,IAC5D,EAAE,MAAM,iBAAiB,SAAS,mBAAmB,WAAW,EAAE;AAAA,IAClE,EAAE,MAAM,gBAAgB,SAAS,kBAAkB,EAAE,YAAY,CAAC,EAAE;AAAA,IACpE,EAAE,MAAM,aAAa,SAAS,aAAa,EAAE,aAAa,QAAQ,CAAC,EAAE;AAAA,IACrE,EAAE,MAAM,cAAc,SAAS,gBAAgB,EAAE;AAAA,IACjD,EAAE,MAAM,gBAAgB,SAAS,iBAAiB,EAAE;AAAA,IACpD,EAAE,MAAM,iBAAiB,SAAS,mBAAmB,EAAE;AAAA,IACvD,EAAE,MAAM,gBAAgB,SAAS,oBAAoB,EAAE;AAAA,EACzD;AAEA,aAAW,EAAE,MAAM,QAAQ,KAAK,OAAO;AACrC,UAAM,WAAW,KAAK,WAAW,IAAI;AACrC,UAAM,SAAS,QAAQ,QAAQ;AAC/B,QAAI,WAAW,WAAW;AACxB,YAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,IACzC;AACA,UAAM,UAAU,UAAU,SAAS,MAAM;AAAA,EAC3C;AAEA,SAAO,EAAE,OAAO,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AAC3C;AAEA,SAAS,qBAAqB,KAA4C;AACxE,SAAO,eAAe,SAAS,OAAQ,IAA8B,SAAS;AAChF;;;AHnEA,SAAS,aAAa,MAAkC;AACtD,QAAM,EAAE,QAAQ,YAAY,IAAI,UAAU;AAAA,IACxC,MAAM,CAAC,GAAG,IAAI;AAAA,IACd,kBAAkB;AAAA,IAClB,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,SAAS,EAAE,MAAM,SAAS;AAAA,MAC1B,MAAM,EAAE,MAAM,WAAW,OAAO,IAAI;AAAA,IACtC;AAAA,EACF,CAAC;AACD,SAAO;AAAA,IACL,WAAW,YAAY,CAAC;AAAA,IACxB,kBAAkB,YAAY,MAAM,CAAC;AAAA,IACrC,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO,SAAS;AAAA,EACxB;AACF;AAEA,SAAS,YAAkB;AACzB,UAAQ,OAAO;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASF;AACF;AAEA,eAAe,KAAK,MAA0C;AAC5D,MAAI;AACJ,MAAI;AACF,aAAS,aAAa,IAAI;AAAA,EAC5B,SAAS,KAAK;AACZ,YAAQ,OAAO,MAAM,GAAI,IAAc,OAAO;AAAA;AAAA,CAAM;AACpD,cAAU;AACV,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,MAAM;AACf,cAAU;AACV,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,cAAc,QAAW;AAClC,YAAQ,OAAO,MAAM;AAAA;AAAA,CAA+C;AACpE,cAAU;AACV,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,iBAAiB,SAAS,GAAG;AACtC,YAAQ,OAAO;AAAA,MACb,sCAAsC,OAAO,iBAAiB,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA,IACzE;AACA,cAAU;AACV,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,QAAQ,QAAQ,IAAI,GAAG,OAAO,SAAS;AACzD,QAAM,cAAc,SAAS,SAAS;AAEtC,MAAI,CAAC,kBAAkB,WAAW,GAAG;AACnC,YAAQ,OAAO;AAAA,MACb,qCAAqC,WAAW;AAAA;AAAA;AAAA,IAGlD;AACA,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB;AAEtB,MAAI;AACJ,MAAI,OAAO,YAAY,QAAW;AAChC,UAAM,MAAM,OAAO,QAAQ,KAAK;AAChC,QAAI,CAAC,iBAAiB,GAAG,GAAG;AAC1B,MAAAC,QAAO,6BAA6B,OAAO,OAAO,GAAG;AACrD,aAAO;AAAA,IACT;AACA,aAAS;AAAA,EACX,OAAO;AACL,UAAM,SAAS,MAAM,cAAc;AACnC,QAAI,eAAe,QAAQ;AACzB,aAAO;AAAA,IACT;AACA,aAAS,OAAO;AAAA,EAClB;AAEA,QAAM,UAAU,oBAAoB,MAAM;AAE1C,MAAI;AACF,UAAM,aAAa,EAAE,WAAW,aAAa,QAAQ,CAAC;AAAA,EACxD,SAAS,KAAK;AACZ,QAAI,eAAe,4BAA4B;AAC7C,MAAAA,QAAO,oCAAoC,OAAO,SAAS,EAAE;AAC7D,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AAEA;AAAA,IACE,WAAW,WAAW,cAAc,MAAM;AAAA;AAAA;AAAA,OAGhC,OAAO,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM5B;AAEA,SAAO;AACT;AAEA,KAAK,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,EAC5B,KAAK,CAAC,SAAS;AACd,UAAQ,KAAK,IAAI;AACnB,CAAC,EACA,MAAM,CAAC,QAAiB;AAIvB,UAAQ,OAAO,MAAM,GAAG,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC5E,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["cancel","cancel"]}
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/licenses.ts","../src/prompts.ts","../src/scaffold/index.ts","../src/scaffold/env-example.ts","../src/scaffold/gitignore.ts","../src/scaffold/package-json.ts","../src/scaffold/readme.ts","../src/scaffold/takuhon-json.ts","../src/scaffold/tsconfig-json.ts","../src/scaffold/worker-index-ts.ts","../src/scaffold/wrangler-toml.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Entry point for `npx create-takuhon` (and `pnpm create takuhon`).\n *\n * Usage:\n * create-takuhon <target-dir> [--license <spdxId>]\n *\n * The positional target directory is required; it must not already exist (we\n * refuse to overwrite). `--license` skips the interactive picker for CI /\n * automation use.\n */\n\nimport { basename, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { cancel, intro, outro } from '@clack/prompts';\n\nimport { buildContentLicense, isValidSpdxInput } from './licenses.js';\nimport { promptLicense } from './prompts.js';\nimport { TargetDirectoryExistsError, writeProject } from './scaffold/index.js';\nimport { isValidWorkerName } from './scaffold/wrangler-toml.js';\n\ninterface CliArgs {\n readonly targetArg: string | undefined;\n readonly extraPositionals: readonly string[];\n readonly license: string | undefined;\n readonly help: boolean;\n}\n\nfunction parseCliArgs(argv: readonly string[]): CliArgs {\n const { values, positionals } = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n license: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n return {\n targetArg: positionals[0],\n extraPositionals: positionals.slice(1),\n license: values.license,\n help: values.help === true,\n };\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `Usage: create-takuhon <target-dir> [--license <spdxId>]\\n` +\n `\\n` +\n `Scaffolds a Takuhon profile deployment in <target-dir>.\\n` +\n `\\n` +\n `Options:\\n` +\n ` --license <spdxId> Skip the interactive license prompt and use the\\n` +\n ` given SPDX identifier (e.g. CC-BY-4.0, MIT,\\n` +\n ` Proprietary). Useful for CI / automation.\\n` +\n ` -h, --help Show this help.\\n`,\n );\n}\n\nasync function main(argv: readonly string[]): Promise<number> {\n let parsed: CliArgs;\n try {\n parsed = parseCliArgs(argv);\n } catch (err) {\n process.stderr.write(`${(err as Error).message}\\n\\n`);\n printHelp();\n return 2;\n }\n\n if (parsed.help) {\n printHelp();\n return 0;\n }\n\n if (parsed.targetArg === undefined) {\n process.stderr.write(`Error: missing target directory argument.\\n\\n`);\n printHelp();\n return 2;\n }\n\n if (parsed.extraPositionals.length > 0) {\n process.stderr.write(\n `Error: unexpected extra arguments: ${parsed.extraPositionals.join(' ')}\\n\\n`,\n );\n printHelp();\n return 2;\n }\n\n const targetDir = resolve(process.cwd(), parsed.targetArg);\n const projectName = basename(targetDir);\n\n if (!isValidWorkerName(projectName)) {\n process.stderr.write(\n `Error: target directory basename \"${projectName}\" is not a valid Cloudflare Worker name.\\n` +\n `Names must be lowercase, start and end with a letter or digit, and contain only ` +\n `letters, digits, and hyphens (max 63 chars).\\n`,\n );\n return 2;\n }\n\n intro('create-takuhon');\n\n let spdxId: string;\n if (parsed.license !== undefined) {\n const raw = parsed.license.trim();\n if (!isValidSpdxInput(raw)) {\n cancel(`Invalid --license value: \"${parsed.license}\"`);\n return 2;\n }\n spdxId = raw;\n } else {\n const result = await promptLicense();\n if ('cancelled' in result) {\n return 130;\n }\n spdxId = result.spdxId;\n }\n\n const license = buildContentLicense(spdxId);\n\n try {\n await writeProject({ targetDir, projectName, license });\n } catch (err) {\n if (err instanceof TargetDirectoryExistsError) {\n cancel(`Target directory already exists: ${parsed.targetArg}`);\n return 1;\n }\n throw err;\n }\n\n outro(\n `Created ${projectName} (license: ${spdxId}).\\n` +\n `\\n` +\n `Next steps:\\n` +\n ` cd ${parsed.targetArg}\\n` +\n ` # 1. Edit takuhon.json with your profile data\\n` +\n ` # 2. Provision Cloudflare KV: wrangler kv namespace create TAKUHON_KV\\n` +\n ` # 3. Set admin token: openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN\\n` +\n ` pnpm install\\n` +\n ` pnpm dev`,\n );\n\n return 0;\n}\n\nvoid main(process.argv.slice(2))\n .then((code) => {\n process.exit(code);\n })\n .catch((err: unknown) => {\n // Render only the message — full stack traces can leak host-machine\n // absolute paths and monorepo-internal layout into a published\n // binary's stderr output.\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n","/**\n * Content-license metadata and helpers for the `create-takuhon` scaffolding\n * flow.\n *\n * The four entries in `LICENSE_OPTIONS` are the curated choices the\n * interactive picker offers. \"Custom\" is handled separately by the prompt\n * flow as a free-form SPDX text input. `buildContentLicense` shapes the\n * chosen identifier into the `meta.contentLicense` fragment that lands in\n * the generated `takuhon.json`. The Takuhon profile JSON Schema accepts any\n * SPDX expression in `meta.contentLicense.spdxId`; unknown identifiers fall\n * through with `spdxId` only, and downstream UI rendering is best-effort.\n */\n\n/** A single row in the interactive license selector. */\nexport interface LicenseOption {\n /** Short label shown as the selectable label in the prompt. */\n readonly label: string;\n /** Longer hint shown alongside the label in the prompt UI. */\n readonly hint: string;\n /** SPDX identifier written to `takuhon.json` `meta.contentLicense.spdxId`. */\n readonly spdxId: string;\n /** Canonical license URL. Omitted for `Proprietary`. */\n readonly url?: string;\n}\n\n/**\n * The four curated license choices shown by the interactive picker. \"Custom\"\n * is not in this list because it triggers a free-form text prompt rather than\n * mapping to a fixed SPDX identifier here.\n */\nexport const LICENSE_OPTIONS: readonly LicenseOption[] = [\n {\n label: 'CC BY 4.0',\n hint: 'Allow reuse with attribution',\n spdxId: 'CC-BY-4.0',\n url: 'https://creativecommons.org/licenses/by/4.0/',\n },\n {\n label: 'CC BY-NC 4.0',\n hint: 'Non-commercial reuse with attribution',\n spdxId: 'CC-BY-NC-4.0',\n url: 'https://creativecommons.org/licenses/by-nc/4.0/',\n },\n {\n label: 'CC0',\n hint: 'Public domain',\n spdxId: 'CC0-1.0',\n url: 'https://creativecommons.org/publicdomain/zero/1.0/',\n },\n {\n label: 'Proprietary',\n hint: 'All rights reserved',\n spdxId: 'Proprietary',\n },\n];\n\n/**\n * Canonical URL lookup for SPDX identifiers we recognise but don't list in\n * the interactive picker. Used by `buildContentLicense` so that a user who\n * passes `--license CC-BY-SA-4.0` (or selects it via `Custom`) still gets a\n * usable `url` field in the generated `takuhon.json`.\n *\n * URLs for non-CC SPDX identifiers point at the canonical SPDX license page\n * (`spdx.org/licenses/<id>.html`). Creative Commons identifiers use the\n * `creativecommons.org` deed URL because that is the page humans expect to\n * land on (the SPDX page is metadata-only).\n */\nconst KNOWN_URL_BY_SPDX: Readonly<Record<string, string>> = {\n 'CC0-1.0': 'https://creativecommons.org/publicdomain/zero/1.0/',\n 'CC-BY-4.0': 'https://creativecommons.org/licenses/by/4.0/',\n 'CC-BY-SA-4.0': 'https://creativecommons.org/licenses/by-sa/4.0/',\n 'CC-BY-ND-4.0': 'https://creativecommons.org/licenses/by-nd/4.0/',\n 'CC-BY-NC-4.0': 'https://creativecommons.org/licenses/by-nc/4.0/',\n 'CC-BY-NC-SA-4.0': 'https://creativecommons.org/licenses/by-nc-sa/4.0/',\n 'CC-BY-NC-ND-4.0': 'https://creativecommons.org/licenses/by-nc-nd/4.0/',\n MIT: 'https://spdx.org/licenses/MIT.html',\n};\n\n/** Shape written to `takuhon.json` under `meta.contentLicense`. */\nexport interface ContentLicenseFragment {\n readonly spdxId: string;\n readonly url?: string;\n readonly rights?: string;\n}\n\n/**\n * Translate a chosen SPDX identifier into the `meta.contentLicense` fragment\n * for `takuhon.json`.\n *\n * - `Proprietary` is given a `rights` sentinel and no `url`.\n * - Known SPDX identifiers get a canonical `url`.\n * - Anything else is written as `{ spdxId }` only — the schema accepts\n * arbitrary SPDX expressions, and UI rendering is best-effort.\n */\nexport function buildContentLicense(spdxId: string): ContentLicenseFragment {\n if (spdxId === 'Proprietary') {\n return {\n spdxId: 'Proprietary',\n rights: 'All rights reserved. Contact owner for usage permission.',\n };\n }\n const url = KNOWN_URL_BY_SPDX[spdxId];\n if (url !== undefined) {\n return { spdxId, url };\n }\n return { spdxId };\n}\n\n/**\n * Light syntactic validation for a user-entered SPDX expression (the\n * `Custom` prompt branch and the `--license` flag).\n *\n * We accept any non-empty string composed of the characters that appear in\n * canonical SPDX identifiers and boolean expressions (alphanumerics, `.`,\n * `-`, `+`, parentheses, and literal spaces). Tabs and newlines are rejected\n * because real SPDX expressions never contain them. Full SPDX expression\n * parsing (`MIT OR (Apache-2.0 AND CC-BY-4.0)`) is intentionally out of\n * scope — we trust the user and let the JSON Schema's downstream validation\n * flag obvious problems.\n */\nexport function isValidSpdxInput(input: string): boolean {\n const trimmed = input.trim();\n if (trimmed === '') return false;\n return /^[A-Za-z0-9.\\-+() ]+$/.test(trimmed);\n}\n","/**\n * Interactive prompt flow for `create-takuhon`, built on `@clack/prompts`.\n *\n * A single-choice picker offers the curated `LICENSE_OPTIONS` plus a\n * `Custom` row that opens a free-form SPDX text input. Cancellation\n * (Ctrl+C or pressing Esc on a clack prompt) returns a sentinel result\n * instead of throwing so the caller can exit cleanly with a specific status\n * code.\n */\n\nimport { cancel, isCancel, select, text } from '@clack/prompts';\n\nimport { LICENSE_OPTIONS, isValidSpdxInput } from './licenses.js';\n\n/** Returned by {@link promptLicense} on successful completion. */\nexport interface PromptLicenseResult {\n /** SPDX identifier to write into `meta.contentLicense.spdxId`. */\n readonly spdxId: string;\n}\n\n/** Returned when the user cancels (Ctrl+C / Esc) instead of completing. */\nexport interface PromptCancelled {\n readonly cancelled: true;\n}\n\nconst CUSTOM_SENTINEL = '__custom__';\n\n/**\n * Run the license picker. Resolves to either the selected SPDX identifier or\n * a cancellation sentinel; never throws on user cancel.\n */\nexport async function promptLicense(): Promise<PromptLicenseResult | PromptCancelled> {\n const choice = await select<string>({\n message: 'Choose a license for your profile content:',\n options: [\n ...LICENSE_OPTIONS.map((opt) => ({\n value: opt.spdxId,\n label: opt.label,\n hint: opt.hint,\n })),\n { value: CUSTOM_SENTINEL, label: 'Custom', hint: 'enter SPDX identifier' },\n ],\n });\n\n if (isCancel(choice)) {\n cancel('Aborted.');\n return { cancelled: true };\n }\n\n if (choice !== CUSTOM_SENTINEL) {\n return { spdxId: choice };\n }\n\n const customRaw = await text({\n message: 'Enter the SPDX identifier:',\n placeholder: 'e.g. MIT, Apache-2.0, CC-BY-SA-4.0',\n validate(value): string | undefined {\n if (!isValidSpdxInput(value)) {\n return 'Must be a non-empty SPDX-like expression (alphanumerics, ., -, +, parentheses, spaces).';\n }\n return undefined;\n },\n });\n\n if (isCancel(customRaw)) {\n cancel('Aborted.');\n return { cancelled: true };\n }\n\n return { spdxId: customRaw.trim() };\n}\n","/**\n * Top-level scaffolding orchestrator for `create-takuhon`.\n *\n * `writeProject` is the single entry point used by `init.ts`. It creates the\n * target directory (must not already exist), then writes the eight files\n * that make up the scaffold: `takuhon.json`, `wrangler.toml`, `package.json`,\n * `README.md`, `.gitignore`, `.env.example`, `tsconfig.json`, and\n * `src/index.ts` (the Cloudflare Worker entry composed via\n * `createTakuhonWorker` from `@takuhon/cloudflare`).\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\nimport { renderEnvExample } from './env-example.js';\nimport { renderGitignore } from './gitignore.js';\nimport { renderPackageJson } from './package-json.js';\nimport { renderReadme } from './readme.js';\nimport { renderTakuhonJson } from './takuhon-json.js';\nimport { renderTsconfigJson } from './tsconfig-json.js';\nimport { renderWorkerIndexTs } from './worker-index-ts.js';\nimport { renderWranglerToml } from './wrangler-toml.js';\n\nexport interface WriteProjectOptions {\n /** Absolute path of the directory to create. Must not exist. */\n readonly targetDir: string;\n /** Used for the npm `name`, Worker name, and README header. */\n readonly projectName: string;\n /** Chosen content license (already mapped via `buildContentLicense`). */\n readonly license: ContentLicenseFragment;\n}\n\nexport interface WriteProjectResult {\n /** Relative paths of files that were written (in the order written). */\n readonly files: readonly string[];\n}\n\n/**\n * Error thrown when the target directory already exists. The caller is\n * expected to render a friendly message that references the user-supplied\n * path; the `code` is stable for tests. The absolute `targetDir` is exposed\n * as a field rather than embedded in the message so that bubbling the error\n * up through generic logging does not leak filesystem layout.\n */\nexport class TargetDirectoryExistsError extends Error {\n override readonly name = 'TargetDirectoryExistsError';\n readonly code = 'TARGET_EXISTS' as const;\n constructor(readonly targetDir: string) {\n super('Target directory already exists.');\n }\n}\n\n/**\n * Create the project directory and write the scaffolded files. Order is\n * deterministic so callers (and tests) can rely on it.\n */\nexport async function writeProject(opts: WriteProjectOptions): Promise<WriteProjectResult> {\n const { targetDir, projectName, license } = opts;\n\n // mkdir with recursive: false fails if the directory exists; we surface a\n // typed error so the CLI entry can render a friendly message.\n try {\n await mkdir(targetDir, { recursive: false });\n } catch (err) {\n if (isNodeErrnoException(err) && err.code === 'EEXIST') {\n throw new TargetDirectoryExistsError(targetDir);\n }\n throw err;\n }\n\n const files: { readonly path: string; readonly content: string }[] = [\n { path: 'takuhon.json', content: renderTakuhonJson(license) },\n { path: 'wrangler.toml', content: renderWranglerToml(projectName) },\n { path: 'package.json', content: renderPackageJson({ projectName }) },\n { path: 'README.md', content: renderReadme({ projectName, license }) },\n { path: '.gitignore', content: renderGitignore() },\n { path: '.env.example', content: renderEnvExample() },\n { path: 'tsconfig.json', content: renderTsconfigJson() },\n { path: 'src/index.ts', content: renderWorkerIndexTs() },\n ];\n\n for (const { path, content } of files) {\n const fullPath = join(targetDir, path);\n const parent = dirname(fullPath);\n if (parent !== targetDir) {\n await mkdir(parent, { recursive: true });\n }\n await writeFile(fullPath, content, 'utf8');\n }\n\n return { files: files.map((f) => f.path) };\n}\n\nfunction isNodeErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * Generator for the `.env.example` file in a freshly scaffolded project.\n *\n * The Wrangler-managed secrets (admin bearer token) are *not* read from `.env`\n * at deploy time — `wrangler secret put` is the source of truth. The\n * `.env.example` is therefore documentation: it lists the variables a developer\n * may want locally (e.g. for scripting or for the wrangler CLI itself) and\n * links each to its production provisioning path.\n */\n\nexport function renderEnvExample(): string {\n return `# ----------------------------------------------------------------\n# Local development variables for this Takuhon deployment.\n#\n# Production secrets (admin token) are provisioned via:\n# wrangler secret put TAKUHON_ADMIN_TOKEN\n# This file is a hint for local tooling, not a runtime source.\n# ----------------------------------------------------------------\n\n# Admin bearer token for /api/admin/* endpoints.\n# Generate with: openssl rand -base64 32\n# Then provision with:\n# echo \"$TAKUHON_ADMIN_TOKEN\" | wrangler secret put TAKUHON_ADMIN_TOKEN\nTAKUHON_ADMIN_TOKEN=\n\n# Comma-separated Origin allowlist for browser-originating admin requests.\n# Set in wrangler.toml [vars] for production. Local example:\n# TAKUHON_ADMIN_ORIGIN=https://admin.example.com,https://localhost:8787\nTAKUHON_ADMIN_ORIGIN=\n\n# Cloudflare account id (look up via \\`wrangler whoami\\`). Only needed if you\n# script Cloudflare API calls outside Wrangler itself.\nCLOUDFLARE_ACCOUNT_ID=\n`;\n}\n","/**\n * Generator for the scaffolded project's `.gitignore`.\n *\n * Covers Node, Wrangler local cache, and environment files. Kept conservative\n * — no opinions about lockfile preferences (the user picks their package\n * manager) or editor folders (per-editor patterns belong in a global ignore).\n */\n\nexport function renderGitignore(): string {\n return `# Dependencies\nnode_modules/\n\n# Wrangler local cache (do not commit; contains bundled output and tmp files)\n.wrangler/\n\n# Environment variables (commit \\`.env.example\\` instead)\n.env\n.env.local\n.env.*.local\n\n# Logs\n*.log\nnpm-debug.log*\npnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# macOS\n.DS_Store\n`;\n}\n","/**\n * Generator for the scaffolded project's `package.json`.\n *\n * Pinned dependency versions are caret ranges matched to the current\n * minor of the published `@takuhon/*` packages. Under 0.x semver, a caret\n * does not span minor versions, so the range must move forward with each\n * `@takuhon/core` minor release to keep scaffolded projects on the\n * matching schema generation. A guard test (`scaffold.test.ts`) asserts these\n * pins track the published minor, so a missed bump fails CI rather than\n * silently shipping a stale range. `hono` is included as a direct dependency for\n * projects that extend the Worker with their own Hono routes; the generated\n * `src/index.ts` reaches Takuhon's handlers through `@takuhon/cloudflare`\n * rather than importing `hono` directly. `wrangler` is a devDependency since\n * it's the deploy / dev-server tool.\n */\n\nexport interface PackageJsonOptions {\n /** Used for the npm `name` field. */\n readonly projectName: string;\n}\n\nexport function buildPackageJson(opts: PackageJsonOptions): Record<string, unknown> {\n return {\n name: opts.projectName,\n version: '0.0.0',\n private: true,\n type: 'module',\n description: 'Takuhon profile deployment.',\n scripts: {\n dev: 'wrangler dev',\n deploy: 'wrangler deploy',\n },\n dependencies: {\n '@takuhon/api': '^0.8.0',\n '@takuhon/cloudflare': '^0.8.0',\n '@takuhon/core': '^0.8.0',\n hono: '^4.0.0',\n },\n devDependencies: {\n wrangler: '^4.0.0',\n },\n engines: {\n node: '>=22.0.0',\n },\n };\n}\n\nexport function renderPackageJson(opts: PackageJsonOptions): string {\n return `${JSON.stringify(buildPackageJson(opts), null, 2)}\\n`;\n}\n","/**\n * Generator for the README.md placed in a freshly scaffolded project.\n *\n * Aims to give a first-time user enough to (1) fill in their profile data,\n * (2) provision Cloudflare KV + admin secret, and (3) `pnpm dev` / `pnpm\n * deploy` once the upstream `@takuhon/*` packages are reachable. Cross-\n * references the published `@takuhon/cloudflare` adapter README as the\n * next-step reference for the route map.\n */\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\nexport interface ReadmeOptions {\n readonly projectName: string;\n readonly license: ContentLicenseFragment;\n}\n\n/**\n * Human-readable license attribution line for the README footer. Uses the\n * canonical URL when available; falls back to plain SPDX (or\n * \"All rights reserved\" for Proprietary).\n */\nfunction formatLicenseLine(license: ContentLicenseFragment): string {\n if (license.spdxId === 'Proprietary') {\n return '**Proprietary** — all rights reserved.';\n }\n if (license.url !== undefined) {\n return `[\\`${license.spdxId}\\`](${license.url})`;\n }\n return `\\`${license.spdxId}\\``;\n}\n\nexport function renderReadme(opts: ReadmeOptions): string {\n const { projectName, license } = opts;\n const licenseLine = formatLicenseLine(license);\n return `# ${projectName}\n\nA [Takuhon](https://github.com/takuhon-dev/takuhon) profile deployment, running on Cloudflare Workers.\n\n> **Status**: pre-deploy. Edit \\`takuhon.json\\`, provision Cloudflare KV, then \\`pnpm deploy\\`.\n\n> **Heads-up — Takuhon is in a pre-publish phase.**\n>\n> The \\`@takuhon/api\\`, \\`@takuhon/core\\`, and \\`@takuhon/cloudflare\\` packages\n> referenced in \\`package.json\\` are not yet on the npm registry.\n> \\`pnpm install\\` will fail in this directory until they ship. See *Develop*\n> below for the workspace-link recipe in the meantime.\n\n## What is Takuhon?\n\nTakuhon lets you own your profile as a portable JSON document and publish it as a mobile-first profile page plus a public API (JSON-LD for AI agents and search engines included).\n\n## Setup\n\n1. **Edit your profile.** Open \\`takuhon.json\\` and replace the sample fields (\\`profile.displayName\\`, \\`links\\`, \\`careers\\`, \\`projects\\`, \\`skills\\`) with your own.\n\n2. **Create the Cloudflare KV namespaces** and copy the returned ids into \\`wrangler.toml\\`:\n\n \\`\\`\\`sh\n wrangler kv namespace create TAKUHON_KV\n wrangler kv namespace create TAKUHON_KV --preview\n \\`\\`\\`\n\n3. **Provision the admin token** as a Wrangler secret (used by \\`/api/admin/*\\`):\n\n \\`\\`\\`sh\n openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN\n \\`\\`\\`\n\n Leaving the secret unset disables admin writes entirely (every \\`PUT\\` / \\`DELETE\\` returns 401).\n\n## Develop\n\nThe Worker entry at \\`src/index.ts\\` composes the takuhon adapter via\n\\`createTakuhonWorker\\` and serves your \\`takuhon.json\\` as the fallback when\nKV has no stored profile yet. Until the \\`@takuhon/*\\` packages are on npm,\nclone the upstream repo and link them into this directory (substitute\nyour own absolute path for \\`<takuhon-repo-path>\\`):\n\n\\`\\`\\`sh\ngit clone https://github.com/takuhon-dev/takuhon <takuhon-repo-path>\n( cd <takuhon-repo-path> && pnpm install && pnpm build )\npnpm link <takuhon-repo-path>/packages/api <takuhon-repo-path>/packages/core <takuhon-repo-path>/adapters/cloudflare\n\\`\\`\\`\n\nThen:\n\n\\`\\`\\`sh\npnpm dev # runs \\`wrangler dev\\` locally\n\\`\\`\\`\n\nThe full route map (public + admin) is documented in the [\\`@takuhon/cloudflare\\` README](https://github.com/takuhon-dev/takuhon/tree/main/adapters/cloudflare#readme).\n\n## Deploy\n\n\\`\\`\\`sh\npnpm deploy # runs \\`wrangler deploy\\`\n\\`\\`\\`\n\n## License\n\nProfile content (\\`takuhon.json\\`) is licensed under ${licenseLine}\n\nThe deployment code is your own; pick a license appropriate for it.\n`;\n}\n","/**\n * Generator for the `takuhon.json` file written into a freshly scaffolded\n * project.\n *\n * The template is a copy of `examples/minimal-profile/takuhon.json` from the\n * monorepo. It is inlined here as a TypeScript constant rather than imported\n * from `examples/` so the published `@takuhon/cli` npm package does not need\n * to ship the examples directory.\n *\n * Only `meta.contentLicense` is rewritten per the user's choice; everything\n * else is the canonical minimal profile (one career, one project, three\n * skills, `en` locale only) that downstream `@takuhon/core` validation\n * already accepts (see `packages/core/src/__tests__/examples-fixtures.test.ts`).\n */\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\n/**\n * Build the `takuhon.json` payload as a deterministic, schema-valid object.\n * `meta.contentLicense` is filled from the supplied fragment so each\n * generated project carries the user's chosen SPDX identifier (and `url` /\n * `rights` where applicable).\n */\nexport function buildTakuhonJson(license: ContentLicenseFragment): unknown {\n return {\n schemaVersion: '0.4.0',\n profile: {\n displayName: {\n en: 'Sam Lee',\n },\n },\n links: [\n {\n id: 'github',\n type: 'github',\n url: 'https://example.com/github/sam-lee',\n featured: true,\n },\n ],\n careers: [\n {\n id: 'first-job',\n organization: {\n en: 'Example Co.',\n },\n role: {\n en: 'Junior Software Engineer',\n },\n startDate: '2026-04',\n endDate: null,\n isCurrent: true,\n },\n ],\n projects: [\n {\n id: 'personal-homepage',\n title: {\n en: 'Personal homepage',\n },\n },\n ],\n skills: [\n { id: 'html', label: 'HTML' },\n { id: 'css', label: 'CSS' },\n { id: 'javascript', label: 'JavaScript' },\n ],\n contact: {},\n settings: {\n defaultLocale: 'en',\n availableLocales: ['en'],\n },\n meta: {\n contentLicense: { ...license },\n },\n };\n}\n\n/** Serialise to a UTF-8 string with trailing newline (POSIX-friendly). */\nexport function renderTakuhonJson(license: ContentLicenseFragment): string {\n return `${JSON.stringify(buildTakuhonJson(license), null, 2)}\\n`;\n}\n","/**\n * Generator for the scaffolded project's `tsconfig.json`.\n *\n * Minimal configuration that makes the generated `src/index.ts` typecheck\n * with `tsc --noEmit` in a freshly scaffolded directory. The settings\n * mirror the upstream takuhon monorepo defaults (ES2022 target, ESNext\n * modules, strict mode, `noUncheckedIndexedAccess`) and explicitly enable\n * the two flags the Worker entry depends on:\n *\n * - `resolveJsonModule` — so `import takuhonJson from '../takuhon.json'`\n * resolves at type-check time.\n * - `moduleResolution: \"Bundler\"` — so the Wrangler / esbuild-style\n * resolution (no need for explicit file extensions) is the source of\n * truth that matches what `wrangler dev` actually runs.\n *\n * `noEmit: true` keeps `tsc` purely a type-checker; Wrangler's bundler\n * produces the deployable artifact.\n */\n\nexport function renderTsconfigJson(): string {\n const config = {\n compilerOptions: {\n target: 'ES2022',\n lib: ['ES2022'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n resolveJsonModule: true,\n isolatedModules: true,\n verbatimModuleSyntax: true,\n strict: true,\n noUncheckedIndexedAccess: true,\n noImplicitOverride: true,\n noFallthroughCasesInSwitch: true,\n skipLibCheck: true,\n noEmit: true,\n },\n include: ['src/**/*'],\n exclude: ['node_modules'],\n };\n return `${JSON.stringify(config, null, 2)}\\n`;\n}\n","/**\n * Generator for `src/index.ts` — the Cloudflare Worker entry file written\n * into a freshly scaffolded project.\n *\n * The generated file uses `createTakuhonWorker()` from `@takuhon/cloudflare`\n * so the scaffolded project does not need to know the internal wiring\n * (Hono router, KV-backed storage, edge cache purger, console audit logger).\n * The user's own `takuhon.json` is loaded via an ES module JSON import,\n * validated once at module load, and the resulting `Takuhon` value is\n * served as the fallback when KV has no stored profile yet.\n *\n * `wrangler.toml`'s `main` field already points at `src/index.ts`, and\n * `nodejs_compat` is enabled, so the file works under `wrangler dev` /\n * `wrangler deploy` without further configuration.\n */\n\nexport function renderWorkerIndexTs(): string {\n return `import { createTakuhonWorker } from '@takuhon/cloudflare';\nimport { validate } from '@takuhon/core';\n\n// Use the project's own takuhon.json as the fallback served when KV has no\n// stored profile yet. Validated once at module load so a malformed profile\n// fails fast rather than at first request.\nimport takuhonJson from '../takuhon.json' with { type: 'json' };\n\nconst fallback = validate(takuhonJson);\nif (!fallback.ok) {\n throw new Error(\n 'Project takuhon.json failed validation: ' +\n fallback.errors.map((e) => \\`\\${e.pointer}: \\${e.message}\\`).join('; '),\n );\n}\n\nconst fallbackTakuhon = fallback.data;\n\nexport default createTakuhonWorker({\n fallback: () => fallbackTakuhon,\n});\n`;\n}\n","/**\n * Generator for `wrangler.toml` in a freshly scaffolded project.\n *\n * The layout mirrors the reference Cloudflare adapter shipped in this\n * monorepo: a single KV namespace `TAKUHON_KV`, placeholder ids the user\n * fills in after running `wrangler kv namespace create`, and the\n * `TAKUHON_ADMIN_ORIGIN` var defaulting to \"\" (disabled). The admin bearer\n * token is provisioned as a Wrangler secret and is therefore not in this file.\n */\n\n/**\n * Validate a Cloudflare Worker name.\n *\n * Cloudflare itself accepts mixed-case DNS labels (1–63 chars, must not\n * start or end with a hyphen). This validator additionally enforces a\n * lowercase convention chosen for the scaffolder so that the resulting\n * `workers.dev` subdomain is predictable and matches the npm `name` field.\n */\nexport function isValidWorkerName(name: string): boolean {\n return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name);\n}\n\n/**\n * Render `wrangler.toml` for the given project name.\n *\n * Throws if `projectName` is not a valid Cloudflare Worker name. Callers are\n * responsible for sanitising/validating earlier in the prompt flow.\n */\nexport function renderWranglerToml(projectName: string): string {\n if (!isValidWorkerName(projectName)) {\n throw new Error(\n `Invalid Cloudflare Worker name: \"${projectName}\". Names must be lowercase, ` +\n `start with a letter or digit, and contain only letters, digits, and hyphens (max 63 chars).`,\n );\n }\n return `name = \"${projectName}\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2026-05-01\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n# Replace the placeholder ids after running:\n# wrangler kv namespace create TAKUHON_KV\n# wrangler kv namespace create TAKUHON_KV --preview\n[[kv_namespaces]]\nbinding = \"TAKUHON_KV\"\nid = \"REPLACE_WITH_PRODUCTION_NAMESPACE_ID\"\npreview_id = \"REPLACE_WITH_PREVIEW_NAMESPACE_ID\"\n\n# Admin Origin allowlist (comma-separated). Empty value disables the check.\n# Example for production: TAKUHON_ADMIN_ORIGIN = \"https://admin.example.com\"\n#\n# The admin bearer token MUST be provisioned as a Wrangler secret, never in\n# this file:\n# wrangler secret put TAKUHON_ADMIN_TOKEN\n# Use 32+ bytes of entropy, e.g. \\`openssl rand -base64 32\\`.\n[vars]\nTAKUHON_ADMIN_ORIGIN = \"\"\n`;\n}\n"],"mappings":"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","names":["cancel","cancel"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takuhon/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "create-takuhon scaffolding plus dev/validate/sync/export/migrate/restore commands",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Takuhon contributors",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@clack/prompts": "^0.11.0",
|
|
51
|
-
"@takuhon/core": "0.
|
|
51
|
+
"@takuhon/core": "0.8.1"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"typecheck": "tsc",
|