@takuhon/cli 0.10.0 → 0.11.0

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.
@@ -0,0 +1,404 @@
1
+ // src/scaffold/wrangler-toml.ts
2
+ function isValidWorkerName(name) {
3
+ return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name);
4
+ }
5
+ var ADMIN_DIST_DIRNAME = "admin-dist";
6
+ function renderWranglerToml(projectName) {
7
+ if (!isValidWorkerName(projectName)) {
8
+ throw new Error(
9
+ `Invalid Cloudflare Worker name: "${projectName}". Names must be lowercase, start with a letter or digit, and contain only letters, digits, and hyphens (max 63 chars).`
10
+ );
11
+ }
12
+ return `name = "${projectName}"
13
+ main = "src/index.ts"
14
+ compatibility_date = "2026-05-01"
15
+ compatibility_flags = ["nodejs_compat"]
16
+
17
+ # Admin SPA bundle. The React admin UI is committed in ./${ADMIN_DIST_DIRNAME}
18
+ # (copied here when this project was created with create-takuhon) and served at
19
+ # /admin.
20
+ #
21
+ # run_worker_first = true makes the Worker mediate every request: the bundle's
22
+ # files sit at the assets root, so without this they would shadow the public "/"
23
+ # route. The Worker serves the bundle only for /admin/* (attaching the strict
24
+ # admin CSP) and handles everything else itself.
25
+ #
26
+ # SECURITY-CRITICAL: do not set run_worker_first to false or remove it. The
27
+ # Worker is the only thing that attaches the strict admin CSP (plus HSTS /
28
+ # no-store) to the admin assets; serving them directly would expose the admin UI
29
+ # with no policy. To deploy without the form UI, remove this whole [assets]
30
+ # block \u2014 the Worker then falls back to a minimal inline editor.
31
+ [assets]
32
+ directory = "${ADMIN_DIST_DIRNAME}"
33
+ binding = "ASSETS"
34
+ run_worker_first = true
35
+ not_found_handling = "single-page-application"
36
+
37
+ # Replace the placeholder ids after running:
38
+ # wrangler kv namespace create TAKUHON_KV
39
+ # wrangler kv namespace create TAKUHON_KV --preview
40
+ [[kv_namespaces]]
41
+ binding = "TAKUHON_KV"
42
+ id = "REPLACE_WITH_PRODUCTION_NAMESPACE_ID"
43
+ preview_id = "REPLACE_WITH_PREVIEW_NAMESPACE_ID"
44
+
45
+ # Admin Origin allowlist (comma-separated). Empty value disables the check.
46
+ # Example for production: TAKUHON_ADMIN_ORIGIN = "https://admin.example.com"
47
+ #
48
+ # The admin bearer token MUST be provisioned as a Wrangler secret, never in
49
+ # this file:
50
+ # wrangler secret put TAKUHON_ADMIN_TOKEN
51
+ # Use 32+ bytes of entropy, e.g. \`openssl rand -base64 32\`.
52
+ [vars]
53
+ TAKUHON_ADMIN_ORIGIN = ""
54
+ `;
55
+ }
56
+
57
+ // src/scaffold/index.ts
58
+ import { cp, mkdir, writeFile } from "fs/promises";
59
+ import { dirname, join } from "path";
60
+ import { fileURLToPath } from "url";
61
+
62
+ // src/scaffold/env-example.ts
63
+ function renderEnvExample() {
64
+ return `# ----------------------------------------------------------------
65
+ # Local development variables for this Takuhon deployment.
66
+ #
67
+ # Production secrets (admin token) are provisioned via:
68
+ # wrangler secret put TAKUHON_ADMIN_TOKEN
69
+ # This file is a hint for local tooling, not a runtime source.
70
+ # ----------------------------------------------------------------
71
+
72
+ # Admin bearer token for /api/admin/* endpoints.
73
+ # Generate with: openssl rand -base64 32
74
+ # Then provision with:
75
+ # echo "$TAKUHON_ADMIN_TOKEN" | wrangler secret put TAKUHON_ADMIN_TOKEN
76
+ TAKUHON_ADMIN_TOKEN=
77
+
78
+ # Comma-separated Origin allowlist for browser-originating admin requests.
79
+ # Set in wrangler.toml [vars] for production. Local example:
80
+ # TAKUHON_ADMIN_ORIGIN=https://admin.example.com,https://localhost:8787
81
+ TAKUHON_ADMIN_ORIGIN=
82
+
83
+ # Cloudflare account id (look up via \`wrangler whoami\`). Only needed if you
84
+ # script Cloudflare API calls outside Wrangler itself.
85
+ CLOUDFLARE_ACCOUNT_ID=
86
+ `;
87
+ }
88
+
89
+ // src/scaffold/gitignore.ts
90
+ function renderGitignore() {
91
+ return `# Dependencies
92
+ node_modules/
93
+
94
+ # Wrangler local cache (do not commit; contains bundled output and tmp files)
95
+ .wrangler/
96
+
97
+ # Environment variables (commit \`.env.example\` instead)
98
+ .env
99
+ .env.local
100
+ .env.*.local
101
+
102
+ # Logs
103
+ *.log
104
+ npm-debug.log*
105
+ pnpm-debug.log*
106
+ yarn-debug.log*
107
+ yarn-error.log*
108
+
109
+ # macOS
110
+ .DS_Store
111
+ `;
112
+ }
113
+
114
+ // src/scaffold/package-json.ts
115
+ function buildPackageJson(opts) {
116
+ return {
117
+ name: opts.projectName,
118
+ version: "0.0.0",
119
+ private: true,
120
+ type: "module",
121
+ description: "Takuhon profile deployment.",
122
+ scripts: {
123
+ dev: "wrangler dev",
124
+ deploy: "wrangler deploy"
125
+ },
126
+ dependencies: {
127
+ "@takuhon/api": "^0.11.0",
128
+ "@takuhon/cloudflare": "^0.11.0",
129
+ "@takuhon/core": "^0.11.0",
130
+ hono: "^4.0.0"
131
+ },
132
+ devDependencies: {
133
+ wrangler: "^4.0.0"
134
+ },
135
+ engines: {
136
+ node: ">=22.0.0"
137
+ }
138
+ };
139
+ }
140
+ function renderPackageJson(opts) {
141
+ return `${JSON.stringify(buildPackageJson(opts), null, 2)}
142
+ `;
143
+ }
144
+
145
+ // src/scaffold/readme.ts
146
+ function formatLicenseLine(license) {
147
+ if (license.spdxId === "Proprietary") {
148
+ return "**Proprietary** \u2014 all rights reserved.";
149
+ }
150
+ if (license.url !== void 0) {
151
+ return `[\`${license.spdxId}\`](${license.url})`;
152
+ }
153
+ return `\`${license.spdxId}\``;
154
+ }
155
+ function renderReadme(opts) {
156
+ const { projectName, license } = opts;
157
+ const licenseLine = formatLicenseLine(license);
158
+ return `# ${projectName}
159
+
160
+ A [Takuhon](https://github.com/takuhon-dev/takuhon) profile deployment, running on Cloudflare Workers.
161
+
162
+ > **Status**: pre-deploy. Edit \`takuhon.json\`, provision Cloudflare KV, then \`pnpm deploy\`.
163
+
164
+ ## What is Takuhon?
165
+
166
+ Takuhon 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).
167
+
168
+ ## Setup
169
+
170
+ 1. **Install dependencies.**
171
+
172
+ \`\`\`sh
173
+ pnpm install
174
+ \`\`\`
175
+
176
+ 2. **Edit your profile.** Open \`takuhon.json\` and replace the sample fields (\`profile.displayName\`, \`links\`, \`careers\`, \`projects\`, \`skills\`) with your own.
177
+
178
+ 3. **Create the Cloudflare KV namespaces** and copy the returned ids into \`wrangler.toml\`:
179
+
180
+ \`\`\`sh
181
+ npx wrangler kv namespace create TAKUHON_KV
182
+ npx wrangler kv namespace create TAKUHON_KV --preview
183
+ \`\`\`
184
+
185
+ 4. **Provision the admin token** as a Wrangler secret (used by \`/api/admin/*\`):
186
+
187
+ \`\`\`sh
188
+ openssl rand -base64 32 | npx wrangler secret put TAKUHON_ADMIN_TOKEN
189
+ \`\`\`
190
+
191
+ Leaving the secret unset disables admin writes entirely (every \`PUT\` / \`DELETE\` returns 401).
192
+
193
+ ## Develop
194
+
195
+ The Worker entry at \`src/index.ts\` composes the takuhon adapter via
196
+ \`createTakuhonWorker\` and serves your \`takuhon.json\` as the fallback when
197
+ KV has no stored profile yet.
198
+
199
+ \`\`\`sh
200
+ pnpm dev # runs \`wrangler dev\` locally
201
+ \`\`\`
202
+
203
+ The full route map (public + admin) is documented in the [\`@takuhon/cloudflare\` README](https://github.com/takuhon-dev/takuhon/tree/main/adapters/cloudflare#readme).
204
+
205
+ ## Admin
206
+
207
+ The admin form UI is served at \`/admin\` under a strict Content-Security-Policy. Its compiled bundle is committed in \`admin-dist/\` and bound as Workers Assets in \`wrangler.toml\`. Sign in with the admin token you set as the \`TAKUHON_ADMIN_TOKEN\` Wrangler secret.
208
+
209
+ The bundle is a snapshot taken when this project was created. To pick up a newer admin UI, re-run \`create-takuhon\` into a fresh directory and copy its \`admin-dist/\` over. To deploy without the form UI, remove the \`[assets]\` block from \`wrangler.toml\`; the Worker then falls back to a minimal inline editor.
210
+
211
+ ## Deploy
212
+
213
+ \`\`\`sh
214
+ pnpm deploy # runs \`wrangler deploy\`
215
+ \`\`\`
216
+
217
+ ## License
218
+
219
+ Profile content (\`takuhon.json\`) is licensed under ${licenseLine}
220
+
221
+ The deployment code is your own; pick a license appropriate for it.
222
+ `;
223
+ }
224
+
225
+ // src/scaffold/takuhon-json.ts
226
+ function buildTakuhonJson(license) {
227
+ return {
228
+ schemaVersion: "0.4.0",
229
+ profile: {
230
+ displayName: {
231
+ en: "Sam Lee"
232
+ }
233
+ },
234
+ links: [
235
+ {
236
+ id: "github",
237
+ type: "github",
238
+ url: "https://example.com/github/sam-lee",
239
+ featured: true
240
+ }
241
+ ],
242
+ careers: [
243
+ {
244
+ id: "first-job",
245
+ organization: {
246
+ en: "Example Co."
247
+ },
248
+ role: {
249
+ en: "Junior Software Engineer"
250
+ },
251
+ startDate: "2026-04",
252
+ endDate: null,
253
+ isCurrent: true
254
+ }
255
+ ],
256
+ projects: [
257
+ {
258
+ id: "personal-homepage",
259
+ title: {
260
+ en: "Personal homepage"
261
+ }
262
+ }
263
+ ],
264
+ skills: [
265
+ { id: "html", label: "HTML" },
266
+ { id: "css", label: "CSS" },
267
+ { id: "javascript", label: "JavaScript" }
268
+ ],
269
+ contact: {},
270
+ settings: {
271
+ defaultLocale: "en",
272
+ availableLocales: ["en"]
273
+ },
274
+ meta: {
275
+ contentLicense: { ...license }
276
+ }
277
+ };
278
+ }
279
+ function renderTakuhonJson(license) {
280
+ return `${JSON.stringify(buildTakuhonJson(license), null, 2)}
281
+ `;
282
+ }
283
+
284
+ // src/scaffold/tsconfig-json.ts
285
+ function renderTsconfigJson() {
286
+ const config = {
287
+ compilerOptions: {
288
+ target: "ES2022",
289
+ lib: ["ES2022"],
290
+ module: "ESNext",
291
+ moduleResolution: "Bundler",
292
+ esModuleInterop: true,
293
+ resolveJsonModule: true,
294
+ isolatedModules: true,
295
+ verbatimModuleSyntax: true,
296
+ strict: true,
297
+ noUncheckedIndexedAccess: true,
298
+ noImplicitOverride: true,
299
+ noFallthroughCasesInSwitch: true,
300
+ skipLibCheck: true,
301
+ noEmit: true
302
+ },
303
+ include: ["src/**/*"],
304
+ exclude: ["node_modules"]
305
+ };
306
+ return `${JSON.stringify(config, null, 2)}
307
+ `;
308
+ }
309
+
310
+ // src/scaffold/worker-index-ts.ts
311
+ function renderWorkerIndexTs() {
312
+ return `import { createTakuhonWorker } from '@takuhon/cloudflare';
313
+ import { validate } from '@takuhon/core';
314
+
315
+ // Use the project's own takuhon.json as the fallback served when KV has no
316
+ // stored profile yet. Validated once at module load so a malformed profile
317
+ // fails fast rather than at first request.
318
+ import takuhonJson from '../takuhon.json' with { type: 'json' };
319
+
320
+ const fallback = validate(takuhonJson);
321
+ if (!fallback.ok) {
322
+ throw new Error(
323
+ 'Project takuhon.json failed validation: ' +
324
+ fallback.errors.map((e) => \`\${e.pointer}: \${e.message}\`).join('; '),
325
+ );
326
+ }
327
+
328
+ const fallbackTakuhon = fallback.data;
329
+
330
+ export default createTakuhonWorker({
331
+ fallback: () => fallbackTakuhon,
332
+ });
333
+ `;
334
+ }
335
+
336
+ // src/scaffold/index.ts
337
+ var TargetDirectoryExistsError = class extends Error {
338
+ constructor(targetDir) {
339
+ super("Target directory already exists.");
340
+ this.targetDir = targetDir;
341
+ }
342
+ targetDir;
343
+ name = "TargetDirectoryExistsError";
344
+ code = "TARGET_EXISTS";
345
+ };
346
+ async function writeProject(opts) {
347
+ const { targetDir, projectName, license } = opts;
348
+ try {
349
+ await mkdir(targetDir, { recursive: false });
350
+ } catch (err) {
351
+ if (isNodeErrnoException(err) && err.code === "EEXIST") {
352
+ throw new TargetDirectoryExistsError(targetDir);
353
+ }
354
+ throw err;
355
+ }
356
+ const files = [
357
+ { path: "takuhon.json", content: renderTakuhonJson(license) },
358
+ { path: "wrangler.toml", content: renderWranglerToml(projectName) },
359
+ { path: "package.json", content: renderPackageJson({ projectName }) },
360
+ { path: "README.md", content: renderReadme({ projectName, license }) },
361
+ { path: ".gitignore", content: renderGitignore() },
362
+ { path: ".env.example", content: renderEnvExample() },
363
+ { path: "tsconfig.json", content: renderTsconfigJson() },
364
+ { path: "src/index.ts", content: renderWorkerIndexTs() }
365
+ ];
366
+ for (const { path, content } of files) {
367
+ const fullPath = join(targetDir, path);
368
+ const parent = dirname(fullPath);
369
+ if (parent !== targetDir) {
370
+ await mkdir(parent, { recursive: true });
371
+ }
372
+ await writeFile(fullPath, content, "utf8");
373
+ }
374
+ return { files: files.map((f) => f.path) };
375
+ }
376
+ function isNodeErrnoException(err) {
377
+ return err instanceof Error && typeof err.code === "string";
378
+ }
379
+ function resolveAdminBundleDir() {
380
+ return fileURLToPath(new URL("../admin-bundle", import.meta.url));
381
+ }
382
+ async function copyAdminBundle(opts) {
383
+ const bundleDir = opts.bundleDir ?? resolveAdminBundleDir();
384
+ const dest = join(opts.targetDir, ADMIN_DIST_DIRNAME);
385
+ try {
386
+ await cp(bundleDir, dest, { recursive: true });
387
+ } catch (err) {
388
+ const code = isNodeErrnoException(err) ? ` (${err.code})` : "";
389
+ throw new Error(
390
+ `Failed to copy the admin UI bundle from @takuhon/cli${code}. Reinstall or upgrade @takuhon/cli and try again.`,
391
+ { cause: err }
392
+ );
393
+ }
394
+ return { dest };
395
+ }
396
+
397
+ export {
398
+ isValidWorkerName,
399
+ TargetDirectoryExistsError,
400
+ writeProject,
401
+ resolveAdminBundleDir,
402
+ copyAdminBundle
403
+ };
404
+ //# sourceMappingURL=chunk-JI2AH5Y3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scaffold/wrangler-toml.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"],"sourcesContent":["/**\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 * In-project directory the admin SPA bundle is copied into and the scaffolded\n * `wrangler.toml` binds as `ASSETS`. Shared so the copy destination and the\n * `[assets] directory` value cannot drift apart. Defined in this leaf module\n * (which `scaffold/index.ts` already imports) to avoid an import cycle.\n */\nexport const ADMIN_DIST_DIRNAME = 'admin-dist';\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# Admin SPA bundle. The React admin UI is committed in ./${ADMIN_DIST_DIRNAME}\n# (copied here when this project was created with create-takuhon) and served at\n# /admin.\n#\n# run_worker_first = true makes the Worker mediate every request: the bundle's\n# files sit at the assets root, so without this they would shadow the public \"/\"\n# route. The Worker serves the bundle only for /admin/* (attaching the strict\n# admin CSP) and handles everything else itself.\n#\n# SECURITY-CRITICAL: do not set run_worker_first to false or remove it. The\n# Worker is the only thing that attaches the strict admin CSP (plus HSTS /\n# no-store) to the admin assets; serving them directly would expose the admin UI\n# with no policy. To deploy without the form UI, remove this whole [assets]\n# block — the Worker then falls back to a minimal inline editor.\n[assets]\ndirectory = \"${ADMIN_DIST_DIRNAME}\"\nbinding = \"ASSETS\"\nrun_worker_first = true\nnot_found_handling = \"single-page-application\"\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","/**\n * Top-level scaffolding orchestrator for `create-takuhon`.\n *\n * `writeProject` creates the target directory (must not already exist), then\n * writes the eight files that make up the scaffold: `takuhon.json`,\n * `wrangler.toml`, `package.json`, `README.md`, `.gitignore`, `.env.example`,\n * `tsconfig.json`, and `src/index.ts` (the Cloudflare Worker entry composed via\n * `createTakuhonWorker` from `@takuhon/cloudflare`).\n *\n * `copyAdminBundle` then copies the bundled admin SPA into the project's\n * `admin-dist/` so the Worker can serve the form UI at `/admin`. `init.ts`\n * calls both in sequence.\n */\n\nimport { cp, mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\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 { ADMIN_DIST_DIRNAME, 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/**\n * Resolve the admin SPA bundle that ships inside this package. The bundle is\n * copied here from `apps/admin/dist` at build time (see\n * `scripts/copy-admin-bundle.mjs`) and listed in the package's `files`, so it\n * is present in the published `@takuhon/cli` tarball. Resolved relative to the\n * compiled module (`dist/init.js` or `dist/index.js`), whose sibling is\n * `admin-bundle/`.\n */\nexport function resolveAdminBundleDir(): string {\n return fileURLToPath(new URL('../admin-bundle', import.meta.url));\n}\n\nexport interface CopyAdminBundleOptions {\n /** Project directory previously created by {@link writeProject}. */\n readonly targetDir: string;\n /**\n * Source bundle directory. Defaults to the bundle shipped in this package\n * ({@link resolveAdminBundleDir}); overridable for tests.\n */\n readonly bundleDir?: string;\n}\n\nexport interface CopyAdminBundleResult {\n /** Absolute path of the directory the bundle was copied into. */\n readonly dest: string;\n}\n\n/**\n * Copy the admin SPA bundle into the scaffolded project's\n * `{@link ADMIN_DIST_DIRNAME}` directory. The generated `wrangler.toml` binds\n * this directory as `ASSETS`, and the Cloudflare Worker serves it at `/admin`\n * under a strict CSP (falling back to an inline editor when the binding is\n * absent). Kept separate from {@link writeProject} so the static-asset copy and\n * the generated-file rendering are independently testable.\n */\nexport async function copyAdminBundle(\n opts: CopyAdminBundleOptions,\n): Promise<CopyAdminBundleResult> {\n const bundleDir = opts.bundleDir ?? resolveAdminBundleDir();\n const dest = join(opts.targetDir, ADMIN_DIST_DIRNAME);\n try {\n await cp(bundleDir, dest, { recursive: true });\n } catch (err) {\n // The bundle ships inside @takuhon/cli, so a failure here means a broken\n // or incomplete install. Surface a sanitized message — the raw error would\n // embed the absolute node_modules path of this package.\n const code = isNodeErrnoException(err) ? ` (${err.code})` : '';\n throw new Error(\n `Failed to copy the admin UI bundle from @takuhon/cli${code}. ` +\n `Reinstall or upgrade @takuhon/cli and try again.`,\n { cause: err },\n );\n }\n return { dest };\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.11.0',\n '@takuhon/cloudflare': '^0.11.0',\n '@takuhon/core': '^0.11.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) install dependencies,\n * (2) fill in their profile data, (3) provision Cloudflare KV + admin\n * secret, and (4) `pnpm dev` / `pnpm deploy`. Cross-references the published\n * `@takuhon/cloudflare` adapter README as the next-step reference for the\n * 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## 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. **Install dependencies.**\n\n \\`\\`\\`sh\n pnpm install\n \\`\\`\\`\n\n2. **Edit your profile.** Open \\`takuhon.json\\` and replace the sample fields (\\`profile.displayName\\`, \\`links\\`, \\`careers\\`, \\`projects\\`, \\`skills\\`) with your own.\n\n3. **Create the Cloudflare KV namespaces** and copy the returned ids into \\`wrangler.toml\\`:\n\n \\`\\`\\`sh\n npx wrangler kv namespace create TAKUHON_KV\n npx wrangler kv namespace create TAKUHON_KV --preview\n \\`\\`\\`\n\n4. **Provision the admin token** as a Wrangler secret (used by \\`/api/admin/*\\`):\n\n \\`\\`\\`sh\n openssl rand -base64 32 | npx 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.\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## Admin\n\nThe admin form UI is served at \\`/admin\\` under a strict Content-Security-Policy. Its compiled bundle is committed in \\`admin-dist/\\` and bound as Workers Assets in \\`wrangler.toml\\`. Sign in with the admin token you set as the \\`TAKUHON_ADMIN_TOKEN\\` Wrangler secret.\n\nThe bundle is a snapshot taken when this project was created. To pick up a newer admin UI, re-run \\`create-takuhon\\` into a fresh directory and copy its \\`admin-dist/\\` over. To deploy without the form UI, remove the \\`[assets]\\` block from \\`wrangler.toml\\`; the Worker then falls back to a minimal inline editor.\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"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  * `@takuhon/cli` entry point — the `takuhon` command.
4
4
  *
5
5
  * Exposes `--version` / `--help`, the local profile commands (`validate`,
6
- * `migrate`, `restore`, `export`, `import`, `build`, `dev`), `sync` (push a
7
- * local profile to a deployment), and a pointer to `create-takuhon` for
8
- * scaffolding.
6
+ * `migrate`, `restore`, `export`, `import`, `build`, `dev`, `admin`), `sync`
7
+ * (push a local profile to a deployment), and a pointer to `create-takuhon`
8
+ * for scaffolding.
9
9
  *
10
10
  * `main` is pure (returns an exit code, never calls `process.exit`); the only
11
11
  * place that exits the process is {@link run}, invoked either when this module