cajonzero 0.1.2 → 0.2.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.
package/dist/scaffold.js CHANGED
@@ -1,7 +1,8 @@
1
- import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
2
- import path from 'path';
1
+ import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync, renameSync, rmSync, cpSync, readdirSync } from 'fs';
2
+ import path3 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { randomBytes } from 'crypto';
4
+ import { randomBytes, createHash } from 'crypto';
5
+ import { createRequire } from 'module';
5
6
 
6
7
  // src/scaffold/templates.ts
7
8
  var SCAFFOLD_ENTRY_PATH = "src/index.ts";
@@ -71,7 +72,7 @@ var SCAFFOLD_CONFIG_CONTENT = `${JSON.stringify(
71
72
  `;
72
73
  var SCAFFOLD_MAPS_CONTENT = `${JSON.stringify({ version: 1, maps: [] }, null, 2)}
73
74
  `;
74
- var SHIP_UI_SCRIPT = `node -e "const fs=require('node:fs'),p=require('node:path');fs.cpSync(p.join(p.dirname(require.resolve('cajonzero/package.json')),'dist','ui'),p.join('dist','ui'),{recursive:true,force:true})"`;
75
+ var SCAFFOLD_PUBLIC_KEEP_PATH = "public/.gitkeep";
75
76
  function packageJson(options) {
76
77
  const manifest = {
77
78
  name: options.packageName,
@@ -79,12 +80,12 @@ function packageJson(options) {
79
80
  private: true,
80
81
  type: "module",
81
82
  scripts: {
82
- dev: `${SHIP_UI_SCRIPT} && wrangler dev`,
83
- build: `${SHIP_UI_SCRIPT} && wrangler deploy --dry-run`,
84
- deploy: `${SHIP_UI_SCRIPT} && wrangler deploy`,
85
- // `prepare` also runs after `npm install`, so a plain install leaves a
86
- // ready-to-deploy project.
87
- prepare: SHIP_UI_SCRIPT
83
+ dev: "wrangler dev",
84
+ // `cj0 deploy` syncs the packaged UI, uploads the private map-script
85
+ // packs, then runs `wrangler deploy` — one command, no build chain.
86
+ deploy: "cj0 deploy",
87
+ sync: "cj0 sync",
88
+ maps: "cj0 list"
88
89
  },
89
90
  dependencies: {
90
91
  cajonzero: options.cajonzeroRange,
@@ -109,10 +110,17 @@ function wranglerConfig(options) {
109
110
  ` "compatibility_date": ${JSON.stringify(options.compatibilityDate)},`,
110
111
  " // Deliberately NO `nodejs_compat`: cajonzero's worker path is Node-free.",
111
112
  ' "compatibility_flags": [],',
112
- " // Static UI (play lobby + owner admin console) served by the worker.",
113
- " // cajonzero ships the built assets into this directory.",
113
+ " // QuickJS is a build-time-compiled WASM module; workerd forbids runtime",
114
+ " // WebAssembly codegen. Keep this rule when editing the config.",
115
+ ' "rules": [',
116
+ ' { "type": "CompiledWasm", "globs": ["**/*.wasm"], "fallthrough": true }',
117
+ " ],",
118
+ " // Static UI + imported maps (play lobby + owner admin console).",
119
+ " // `cj0 sync` writes the packaged UI here; `cj0 add` writes public/maps/.",
114
120
  ' "assets": {',
115
- ' "directory": "dist/ui",',
121
+ ' "directory": "public",',
122
+ " // Binding so the room can read imported maps + seed packs at runtime.",
123
+ ' "binding": "ASSETS",',
116
124
  " // History routing: deep links (/play/x, /admin/bans) boot index.html.",
117
125
  ' "not_found_handling": "single-page-application",',
118
126
  ' "run_worker_first": ["/api/*", "/runtime/*"]',
@@ -163,17 +171,18 @@ function readme(options) {
163
171
  "",
164
172
  "```bash",
165
173
  "npm install",
166
- "npx cj0 setup # \u4EA4\u4E92\u5F0F\u914D\u7F6E\u5FC5\u9700/\u53EF\u9009\u7684 ENV",
174
+ "npx cj0 setup # \u4EA4\u4E92\u5F0F\u914D\u7F6E ENV + KV namespace",
167
175
  "npm run dev # http://localhost:8787",
168
176
  "```",
169
177
  "",
170
178
  "## \u73AF\u5883\u53D8\u91CF",
171
179
  "",
172
180
  "`cj0 setup` \u628A\u975E\u5BC6\u94A5\u5199\u5165 `wrangler.jsonc` \u7684 `vars`\uFF0C\u628A\u5BC6\u94A5\u5199\u5165 `.dev.vars`\uFF08\u672C\u5730\uFF09\u3002",
173
- "\u90E8\u7F72\u65F6\u5BC6\u94A5\u8981\u5355\u72EC\u8BBE\u7F6E\uFF1A",
181
+ "KV namespace \u4E5F\u7531 `cj0 setup` \u914D\u7F6E\uFF1A\u56DE\u8F66\u4F1A\u81EA\u52A8\u8C03\u7528 wrangler \u521B\u5EFA\u5E76\u5199\u56DE id\uFF08\u9700\u5148\u767B\u5F55\uFF09\uFF0C",
182
+ "\u4E5F\u53EF\u4EE5\u7C98\u8D34\u5DF2\u6709 id\u3002\u79BB\u7EBF\u65F6\u624B\u52A8\u8FD0\u884C\uFF1A",
174
183
  "",
175
184
  "```bash",
176
- "npx wrangler kv namespace create CAJONZERO_KV # id \u586B\u8FDB wrangler.jsonc",
185
+ "npx wrangler kv namespace create <name> --update-config --binding CAJONZERO_KV",
177
186
  "npx wrangler secret put CAJONZERO_SESSION_SECRET",
178
187
  "npx wrangler secret put BOX_PLAY_ADMIN_TOKEN",
179
188
  "```",
@@ -197,24 +206,25 @@ function readme(options) {
197
206
  "",
198
207
  "## \u5730\u56FE",
199
208
  "",
200
- "\u5730\u56FE\u4E0D\u968F worker \u4E0A\u4F20\u3002\u628A\u89E3\u5305\u540E\u7684 `.boxplay` \u76EE\u5F55\u4F20\u5230 OSS/S3/CDN\uFF08\u9700\u5141\u8BB8 CORS\uFF09\uFF0C",
201
- "\u518D\u5728 `cajonzero/maps.json` \u767B\u8BB0\uFF1A",
209
+ "\u5730\u56FE\u5728\u672C\u5730\u5BFC\u5165\uFF0C\u968F worker \u4E00\u6B21\u90E8\u7F72\uFF08\u9759\u6001\u8D44\u6E90 `public/`\uFF09\uFF1A",
202
210
  "",
203
- "```json",
204
- "{",
205
- ' "version": 1,',
206
- ' "maps": [',
207
- " {",
208
- ' "slug": "omega",',
209
- ' "root": "https://cdn.example.com/cajonzero/omega/",',
210
- ' "worldId": "project-omega",',
211
- ' "name": "Omega City Reloaded"',
212
- " }",
213
- " ]",
214
- "}",
211
+ "```bash",
212
+ "npx cj0 add ./race.boxplay # \u89E3\u5305\u5230 public/maps/race/ \u5E76\u751F\u6210 seed",
213
+ "npx cj0 update race ./race-v2.boxplay # \u8986\u76D6\u5DF2\u6709 slug",
214
+ "npx cj0 list # \u67E5\u770B catalog + \u672C\u5730\u5BFC\u5165\u72B6\u6001",
215
+ "npx cj0 remove race # \u5220\u9664\u5730\u56FE\u3001\u79C1\u6709\u811A\u672C\u4E0E catalog \u6761\u76EE",
215
216
  "```",
216
217
  "",
217
- "`root` \u5FC5\u987B\u4EE5 `/` \u7ED3\u5C3E\uFF1B\u66F4\u65B0\u5730\u56FE\u540E\u5237\u65B0 CDN \u7F13\u5B58\u6216\u6362\u8DEF\u5F84\u3002",
218
+ "`add/update` \u4F1A\uFF1A",
219
+ "",
220
+ "1. \u628A\u516C\u5F00\u5730\u56FE\u8D44\u6E90\u89E3\u5305\u5230 `public/maps/<slug>/`\uFF1B",
221
+ "2. \u5728\u672C\u5730\u89E3\u6790\u4E16\u754C\u56FE\u5E76\u5199\u5165 `public/maps/<slug>/seed.json.gz`\uFF1B",
222
+ "3. \u628A**\u670D\u52A1\u7AEF\u811A\u672C**\u5199\u5230 `cajonzero/private/<slug>.json.gz`\uFF0C\u4E0D\u8FDB\u5165 `public/`\uFF1B",
223
+ '4. \u5728 `cajonzero/maps.json` \u5199\u5165 `"root": "/maps/<slug>/"`\uFF0C\u5E76\u628A\u670D\u52A1\u7AEF\u811A\u672C\u6587\u4EF6\u8BB0\u5F55\u4E3A',
224
+ ' `"scriptsFile": "cajonzero/private/<slug>.json.gz"`\uFF08\u8BE5\u76EE\u5F55\u4F1A\u63D0\u4EA4\uFF0CCF \u6784\u5EFA\u65F6 `cj0 deploy` \u53EF\u4E0A\u4F20\uFF09\u3002',
225
+ "",
226
+ "\u9700\u8981\u72EC\u7ACB CDN \u65F6\uFF0C\u624B\u52A8\u628A\u8BE5\u6761\u76EE\u7684 `root` \u6539\u6210 `https://cdn.example.com/.../` \u5373\u53EF\uFF1B",
227
+ "`cj0 update` \u9ED8\u8BA4\u4FDD\u7559\u4F60\u6539\u8FC7\u7684 `root`\uFF08`--reset-root` \u53EF\u6062\u590D\uFF09\u3002",
218
228
  "",
219
229
  "## \u90E8\u7F72",
220
230
  "",
@@ -222,6 +232,9 @@ function readme(options) {
222
232
  "npm run deploy",
223
233
  "```",
224
234
  "",
235
+ "`cj0 deploy` \u4F1A\u540C\u6B65\u6253\u5305\u7684 UI \u5230 `public/`\u3001\u4E0A\u4F20\u79C1\u6709\u811A\u672C\uFF0C\u7136\u540E\u6267\u884C",
236
+ "`wrangler deploy`\u3002\u9996\u6B21\u90E8\u7F72\u524D\u5148\u8FD0\u884C `npx cj0 setup`\u3002",
237
+ "",
225
238
  "\u5927\u5385\u5728 `/`\uFF0C\u7BA1\u7406\u540E\u53F0\u5728 `/admin`\u3002",
226
239
  ""
227
240
  ].join("\n");
@@ -236,6 +249,7 @@ function buildScaffoldFiles(options) {
236
249
  content: SCAFFOLD_CONFIG_CONTENT.replace("<project-name>", options.packageName)
237
250
  },
238
251
  { path: "cajonzero/maps.json", content: SCAFFOLD_MAPS_CONTENT },
252
+ { path: SCAFFOLD_PUBLIC_KEEP_PATH, content: "" },
239
253
  { path: ".gitignore", content: gitignore() },
240
254
  { path: "README.md", content: readme(options) }
241
255
  ];
@@ -264,7 +278,7 @@ function readCajonzeroPackage(manifestPath) {
264
278
  return { version, wranglerRange, unstorageRange };
265
279
  }
266
280
  function toPackageName(folder) {
267
- const base = path.basename(folder);
281
+ const base = path3.basename(folder);
268
282
  const slug = base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-._]+/, "").replace(/[-._]+$/, "");
269
283
  return slug.length > 0 ? slug : "cajonzero-app";
270
284
  }
@@ -279,7 +293,7 @@ function createProject(folder, options = {}) {
279
293
  throw new ScaffoldError("INVALID_TARGET", "create requires a target folder");
280
294
  }
281
295
  const cwd = options.cwd ?? process.cwd();
282
- const target = path.resolve(cwd, name);
296
+ const target = path3.resolve(cwd, name);
283
297
  if (existsSync(target) && !statSync(target).isDirectory()) {
284
298
  throw new ScaffoldError("INVALID_TARGET", `target exists and is not a directory: ${target}`);
285
299
  }
@@ -302,8 +316,8 @@ function createProject(folder, options = {}) {
302
316
  });
303
317
  mkdirSync(target, { recursive: true });
304
318
  for (const file of files) {
305
- const fullPath = path.join(target, file.path);
306
- mkdirSync(path.dirname(fullPath), { recursive: true });
319
+ const fullPath = path3.join(target, file.path);
320
+ mkdirSync(path3.dirname(fullPath), { recursive: true });
307
321
  writeFileSync(fullPath, file.content, "utf8");
308
322
  }
309
323
  return { target, files: files.map((file) => file.path) };
@@ -388,6 +402,13 @@ var SETUP_FIELDS = [
388
402
  required: true,
389
403
  hint: "\u7559\u7A7A\u81EA\u52A8\u751F\u6210\uFF1B\u672C\u5730\u5199\u5165 .dev.vars"
390
404
  },
405
+ {
406
+ name: "CAJONZERO_KV_ID",
407
+ label: "KV namespace id",
408
+ kind: "kv",
409
+ required: true,
410
+ hint: "\u56DE\u8F66\u81EA\u52A8\u521B\u5EFA\uFF08\u9700 wrangler \u767B\u5F55\uFF09\u6216\u7C98\u8D34\u5DF2\u6709 id"
411
+ },
391
412
  {
392
413
  name: "CAJONZERO_ENV",
393
414
  label: "\u90E8\u7F72\u540D\u79F0",
@@ -435,10 +456,11 @@ function serializeDevVars(values) {
435
456
  return `${lines.join("\n")}
436
457
  `;
437
458
  }
438
- function findVarsBlock(raw) {
439
- const match = /"vars"\s*:\s*\{/.exec(raw);
459
+ function findKeyBlock(raw, key, openChar) {
460
+ const closeChar = openChar === "{" ? "}" : "]";
461
+ const match = new RegExp(`"${key}"\\s*:\\s*\\${openChar}`).exec(raw);
440
462
  if (match === null) return null;
441
- const open = raw.indexOf("{", match.index);
463
+ const open = raw.indexOf(openChar, match.index);
442
464
  let depth = 0;
443
465
  let inString = false;
444
466
  let escaped = false;
@@ -451,8 +473,8 @@ function findVarsBlock(raw) {
451
473
  continue;
452
474
  }
453
475
  if (ch === '"') inString = true;
454
- else if (ch === "{") depth += 1;
455
- else if (ch === "}") {
476
+ else if (ch === openChar) depth += 1;
477
+ else if (ch === closeChar) {
456
478
  depth -= 1;
457
479
  if (depth === 0) {
458
480
  const lineStart = raw.lastIndexOf("\n", match.index) + 1;
@@ -463,7 +485,10 @@ function findVarsBlock(raw) {
463
485
  }
464
486
  return null;
465
487
  }
466
- function parseJsoncObject(text) {
488
+ function findVarsBlock(raw) {
489
+ return findKeyBlock(raw, "vars", "{");
490
+ }
491
+ function stripJsoncComments(text) {
467
492
  let out = "";
468
493
  let inString = false;
469
494
  let escaped = false;
@@ -494,13 +519,95 @@ function parseJsoncObject(text) {
494
519
  }
495
520
  out += ch;
496
521
  }
497
- out = out.replace(/,(\s*[}\]])/g, "$1");
522
+ return out;
523
+ }
524
+ function parseJsonc(text) {
498
525
  try {
499
- const parsed = JSON.parse(out);
500
- return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
526
+ const clean = text.replace(/^\uFEFF/, "");
527
+ return JSON.parse(stripJsoncComments(clean).replace(/,(\s*[}\]])/g, "$1"));
501
528
  } catch {
502
- return {};
529
+ return void 0;
530
+ }
531
+ }
532
+ function parseJsoncObject(text) {
533
+ const parsed = parseJsonc(text);
534
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
535
+ }
536
+ function parseJsoncArray(text) {
537
+ const parsed = parseJsonc(text);
538
+ return Array.isArray(parsed) ? parsed : [];
539
+ }
540
+ function escapeRegExp(text) {
541
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
542
+ }
543
+ function findBindingObject(raw, block, binding) {
544
+ const bindingPattern = new RegExp(`"binding"\\s*:\\s*"${escapeRegExp(binding)}"`);
545
+ const stack = [];
546
+ let inString = false;
547
+ let escaped = false;
548
+ for (let i = block.open; i <= block.close; i += 1) {
549
+ const ch = raw[i];
550
+ if (inString) {
551
+ if (escaped) escaped = false;
552
+ else if (ch === "\\") escaped = true;
553
+ else if (ch === '"') inString = false;
554
+ continue;
555
+ }
556
+ if (ch === '"') inString = true;
557
+ else if (ch === "{") stack.push(i);
558
+ else if (ch === "}") {
559
+ const start = stack.pop();
560
+ if (start !== void 0 && bindingPattern.test(raw.slice(start, i + 1))) {
561
+ const lineStart = raw.lastIndexOf("\n", start) + 1;
562
+ const indent = /^\s*/.exec(raw.slice(lineStart, start))?.[0] ?? " ";
563
+ return { start, end: i + 1, indent };
564
+ }
565
+ }
566
+ }
567
+ return null;
568
+ }
569
+ function readWranglerKvNamespaceId(raw, binding = "CAJONZERO_KV") {
570
+ const block = findKeyBlock(raw, "kv_namespaces", "[");
571
+ if (block === null) return void 0;
572
+ const entries = parseJsoncArray(raw.slice(block.open, block.close + 1));
573
+ for (const entry of entries) {
574
+ if (typeof entry !== "object" || entry === null) continue;
575
+ const record = entry;
576
+ if (record["binding"] !== binding) continue;
577
+ const id = record["id"];
578
+ return typeof id === "string" && id.length > 0 ? id : void 0;
503
579
  }
580
+ return void 0;
581
+ }
582
+ function setWranglerKvNamespaceId(raw, binding, id) {
583
+ const block = findKeyBlock(raw, "kv_namespaces", "[");
584
+ if (block !== null) {
585
+ const entry = findBindingObject(raw, block, binding);
586
+ if (entry !== null) {
587
+ const entryText = raw.slice(entry.start, entry.end);
588
+ const nextEntry = /"id"\s*:/.test(entryText) ? entryText.replace(/("id"\s*:\s*)"(?:[^"\\]|\\.)*"/, `$1${JSON.stringify(id)}`) : entryText.replace(
589
+ /("binding"\s*:\s*"(?:[^"\\]|\\.)*")/,
590
+ `$1,
591
+ ${entry.indent}"id": ${JSON.stringify(id)}`
592
+ );
593
+ return raw.slice(0, entry.start) + nextEntry + raw.slice(entry.end);
594
+ }
595
+ }
596
+ const existing = block === null ? [] : parseJsoncArray(raw.slice(block.open, block.close + 1));
597
+ const filtered = existing.filter(
598
+ (entry) => !(typeof entry === "object" && entry !== null && entry["binding"] === binding)
599
+ );
600
+ const indent = block?.indent ?? " ";
601
+ const arrayText = JSON.stringify([...filtered, { binding, id }], null, 2).split("\n").map((line, index) => index === 0 ? line : `${indent}${line}`).join("\n");
602
+ if (block !== null) {
603
+ return raw.slice(0, block.open) + arrayText + raw.slice(block.close + 1);
604
+ }
605
+ const close = raw.lastIndexOf("}");
606
+ if (close < 0) throw new Error("wrangler config is not a JSON object");
607
+ const body = raw.slice(0, close).trimEnd();
608
+ const separator = body.endsWith("{") || body.endsWith(",") ? "\n" : ",\n";
609
+ return `${body}${separator}${indent}"kv_namespaces": ${arrayText}
610
+ ${raw.slice(close)}`;
504
611
  }
505
612
  function setWranglerVars(raw, vars) {
506
613
  const managed = new Set(SETUP_FIELDS.filter((f) => f.kind === "var").map((f) => f.name));
@@ -543,26 +650,33 @@ function readSetupValues(cwd, configPath, devVarsPath) {
543
650
  if (typeof value === "string") out[key] = value;
544
651
  }
545
652
  }
653
+ const kvId = readWranglerKvNamespaceId(raw);
654
+ if (kvId !== void 0 && kvId !== SCAFFOLD_KV_PLACEHOLDER_ID) {
655
+ out["CAJONZERO_KV_ID"] = kvId;
656
+ }
546
657
  }
547
658
  return out;
548
659
  }
549
660
  function applySetup(options) {
550
661
  const cwd = options.cwd ?? process.cwd();
551
- const configPath = path.resolve(cwd, options.configPath ?? "wrangler.jsonc");
552
- const devVarsPath = path.join(path.dirname(configPath), ".dev.vars");
662
+ const configPath = path3.resolve(cwd, options.configPath ?? "wrangler.jsonc");
663
+ const devVarsPath = path3.join(path3.dirname(configPath), ".dev.vars");
553
664
  if (!existsSync(configPath)) {
554
- throw new Error(`\u627E\u4E0D\u5230 ${path.basename(configPath)}\uFF0C\u8BF7\u5728 cj0 create \u751F\u6210\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C`);
665
+ throw new Error(`\u627E\u4E0D\u5230 ${path3.basename(configPath)}\uFF0C\u8BF7\u5728 cj0 create \u751F\u6210\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C`);
555
666
  }
556
667
  const vars = {};
557
668
  const secrets = {};
669
+ let kvId;
558
670
  for (const field of SETUP_FIELDS) {
559
671
  const value = options.values[field.name]?.trim();
560
672
  if (value === void 0 || value.length === 0) continue;
561
673
  if (field.kind === "secret") secrets[field.name] = value;
674
+ else if (field.kind === "kv") kvId = value;
562
675
  else vars[field.name] = value;
563
676
  }
564
677
  const raw = readFileSync(configPath, "utf8");
565
- const next = setWranglerVars(raw, vars);
678
+ let next = setWranglerVars(raw, vars);
679
+ if (kvId !== void 0) next = setWranglerKvNamespaceId(next, "CAJONZERO_KV", kvId);
566
680
  if (next !== raw) writeFileSync(configPath, next, "utf8");
567
681
  const existingDev = existsSync(devVarsPath) ? parseDevVars(readFileSync(devVarsPath, "utf8")) : {};
568
682
  const managedSecrets = new Set(
@@ -574,9 +688,178 @@ function applySetup(options) {
574
688
  }
575
689
  Object.assign(mergedDev, secrets);
576
690
  writeFileSync(devVarsPath, serializeDevVars(mergedDev), "utf8");
577
- return { configPath, devVarsPath, vars, secrets };
691
+ return { configPath, devVarsPath, vars, secrets, ...kvId !== void 0 ? { kvId } : {} };
692
+ }
693
+ var ASSETS_STAMP_FILE = ".cajonzero-assets.json";
694
+ var ASSETS_MAPS_DIR = "maps";
695
+ var ASSETS_KEEP_FILE = ".gitkeep";
696
+ var DEFAULT_CONFIG = "wrangler.jsonc";
697
+ function resolvePackageRoot() {
698
+ try {
699
+ return path3.dirname(createRequire(import.meta.url).resolve("cajonzero/package.json"));
700
+ } catch {
701
+ return path3.dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
702
+ }
703
+ }
704
+ function readPackageVersion(root) {
705
+ try {
706
+ const parsed = JSON.parse(readFileSync(path3.join(root, "package.json"), "utf8"));
707
+ return typeof parsed.version === "string" ? parsed.version : void 0;
708
+ } catch {
709
+ return void 0;
710
+ }
711
+ }
712
+ function readWranglerAssetsDirectory(configPath) {
713
+ if (!existsSync(configPath)) {
714
+ throw new Error(`\u627E\u4E0D\u5230 ${path3.basename(configPath)}\uFF0C\u8BF7\u5728 cj0 create \u751F\u6210\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C`);
715
+ }
716
+ const ext = path3.extname(configPath).toLowerCase();
717
+ if (ext !== ".json" && ext !== ".jsonc") {
718
+ throw new Error(
719
+ `\u4E0D\u652F\u6301\u8BFB\u53D6 ${path3.basename(configPath)}\uFF08\u53EA\u652F\u6301 JSON/JSONC wrangler \u914D\u7F6E\uFF1B\u7528 --assets \u6307\u5B9A\u76EE\u5F55\uFF09`
720
+ );
721
+ }
722
+ const parsed = parseJsoncObject(readFileSync(configPath, "utf8"));
723
+ const assets = parsed["assets"];
724
+ if (typeof assets === "object" && assets !== null && !Array.isArray(assets)) {
725
+ const directory = assets["directory"];
726
+ if (typeof directory === "string" && directory.length > 0) return directory;
727
+ }
728
+ return void 0;
729
+ }
730
+ function fingerprintTree(dir) {
731
+ const entries = [];
732
+ const walk = (relative) => {
733
+ const absolute = relative.length === 0 ? dir : path3.join(dir, relative);
734
+ for (const entry of readdirSync(absolute, { withFileTypes: true })) {
735
+ if (entry.name === ASSETS_STAMP_FILE) continue;
736
+ const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;
737
+ if (entry.isDirectory()) walk(child);
738
+ else if (entry.isFile()) entries.push(child);
739
+ }
740
+ };
741
+ walk("");
742
+ entries.sort();
743
+ const hash = createHash("sha256");
744
+ let bytes = 0;
745
+ for (const relative of entries) {
746
+ const content = readFileSync(path3.join(dir, relative));
747
+ bytes += content.length;
748
+ hash.update(relative);
749
+ hash.update("\0");
750
+ hash.update(content);
751
+ hash.update("\0");
752
+ }
753
+ return { files: entries.length, bytes, hash: hash.digest("hex") };
754
+ }
755
+ function readStamp(targetDir) {
756
+ try {
757
+ const parsed = JSON.parse(
758
+ readFileSync(path3.join(targetDir, ASSETS_STAMP_FILE), "utf8")
759
+ );
760
+ if (parsed.version !== 1 || typeof parsed.fingerprint !== "string") return void 0;
761
+ return { version: 1, fingerprint: parsed.fingerprint, packageVersion: parsed.packageVersion };
762
+ } catch {
763
+ return void 0;
764
+ }
765
+ }
766
+ function assertSafeTarget(targetDir, sourceDir, configDir) {
767
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path3.sep}`);
768
+ if (targetDir === path3.parse(targetDir).root) {
769
+ throw new Error(`\u62D2\u7EDD\u628A\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u4F5C\u4E3A assets \u76EE\u5F55\uFF1A${targetDir}`);
770
+ }
771
+ if (contains(targetDir, configDir) || contains(targetDir, sourceDir) || contains(sourceDir, targetDir)) {
772
+ throw new Error(`assets \u76EE\u5F55\u4E0D\u80FD\u5305\u542B\u9879\u76EE\u914D\u7F6E/cajonzero \u8D44\u6E90\u76EE\u5F55\uFF1A${targetDir}`);
773
+ }
774
+ }
775
+ async function syncPublicAssets(options = {}) {
776
+ const cwd = options.cwd ?? process.cwd();
777
+ const configPath = path3.resolve(cwd, options.configPath ?? DEFAULT_CONFIG);
778
+ const configDir = path3.dirname(configPath);
779
+ const packageRoot = resolvePackageRoot();
780
+ const sourceDir = path3.resolve(options.sourceDir ?? path3.join(packageRoot, "dist", "ui"));
781
+ if (!existsSync(sourceDir)) {
782
+ throw new Error(
783
+ `cajonzero \u7684 UI \u8D44\u6E90\u7F3A\u5931\uFF1A${sourceDir}\uFF08\u8BF7\u91CD\u65B0\u5B89\u88C5 cajonzero\uFF0C\u6216\u5728\u6E90\u7801\u4ED3\u5E93\u5148 npm run build\uFF09`
784
+ );
785
+ }
786
+ const configured = options.assetsDir ?? readWranglerAssetsDirectory(configPath);
787
+ if (configured === void 0) {
788
+ throw new Error(
789
+ `${path3.basename(configPath)} \u672A\u914D\u7F6E assets.directory\uFF1B\u7528 --assets <dir> \u6307\u5B9A\u8F93\u51FA\u76EE\u5F55`
790
+ );
791
+ }
792
+ const targetDir = path3.resolve(configDir, configured);
793
+ assertSafeTarget(targetDir, sourceDir, configDir);
794
+ const source = fingerprintTree(sourceDir);
795
+ const stamp = readStamp(targetDir);
796
+ if (!options.force && stamp?.fingerprint === source.hash && existsSync(targetDir)) {
797
+ return {
798
+ sourceDir,
799
+ targetDir,
800
+ fingerprint: source.hash,
801
+ copied: false,
802
+ files: source.files,
803
+ bytes: source.bytes
804
+ };
805
+ }
806
+ const mapsDir = path3.join(targetDir, ASSETS_MAPS_DIR);
807
+ const mapsBackup = path3.join(configDir, `.cajonzero-maps-backup-${process.pid}`);
808
+ const keepFile = path3.join(targetDir, ASSETS_KEEP_FILE);
809
+ const keepBackup = path3.join(configDir, `.cajonzero-keep-backup-${process.pid}`);
810
+ if (!existsSync(mapsDir) && existsSync(mapsBackup)) {
811
+ mkdirSync(path3.dirname(mapsDir), { recursive: true });
812
+ renameSync(mapsBackup, mapsDir);
813
+ }
814
+ let hasMaps = false;
815
+ if (existsSync(mapsDir)) {
816
+ rmSync(mapsBackup, { recursive: true, force: true });
817
+ renameSync(mapsDir, mapsBackup);
818
+ hasMaps = true;
819
+ }
820
+ let hasKeep = false;
821
+ if (existsSync(keepFile)) {
822
+ rmSync(keepBackup, { force: true });
823
+ renameSync(keepFile, keepBackup);
824
+ hasKeep = true;
825
+ }
826
+ try {
827
+ rmSync(targetDir, { recursive: true, force: true });
828
+ mkdirSync(targetDir, { recursive: true });
829
+ cpSync(sourceDir, targetDir, {
830
+ recursive: true,
831
+ force: true,
832
+ filter: (from) => path3.basename(from) !== ASSETS_STAMP_FILE
833
+ });
834
+ } finally {
835
+ if (hasMaps && existsSync(mapsBackup)) {
836
+ mkdirSync(targetDir, { recursive: true });
837
+ rmSync(mapsDir, { recursive: true, force: true });
838
+ renameSync(mapsBackup, mapsDir);
839
+ }
840
+ if (hasKeep && existsSync(keepBackup)) {
841
+ mkdirSync(targetDir, { recursive: true });
842
+ rmSync(keepFile, { force: true });
843
+ renameSync(keepBackup, keepFile);
844
+ }
845
+ }
846
+ const next = {
847
+ version: 1,
848
+ fingerprint: source.hash,
849
+ packageVersion: readPackageVersion(packageRoot)
850
+ };
851
+ writeFileSync(path3.join(targetDir, ASSETS_STAMP_FILE), `${JSON.stringify(next, null, 2)}
852
+ `, "utf8");
853
+ return {
854
+ sourceDir,
855
+ targetDir,
856
+ fingerprint: source.hash,
857
+ copied: true,
858
+ files: source.files,
859
+ bytes: source.bytes
860
+ };
578
861
  }
579
862
 
580
- export { SCAFFOLD_COMPATIBILITY_DATE, SCAFFOLD_CONFIG_CONTENT, SCAFFOLD_ENTRY_CONTENT, SCAFFOLD_ENTRY_PATH, SCAFFOLD_KV_PLACEHOLDER_ID, SCAFFOLD_MAPS_CONTENT, SETUP_FIELDS, ScaffoldError, applySetup, buildScaffoldFiles, createProject, generateAdminToken, generateSecret, parseDevVars, readCajonzeroPackage, readSetupValues, serializeDevVars, setWranglerVars, suggestedValue, toPackageName };
863
+ export { ASSETS_KEEP_FILE, ASSETS_MAPS_DIR, ASSETS_STAMP_FILE, SCAFFOLD_COMPATIBILITY_DATE, SCAFFOLD_CONFIG_CONTENT, SCAFFOLD_ENTRY_CONTENT, SCAFFOLD_ENTRY_PATH, SCAFFOLD_KV_PLACEHOLDER_ID, SCAFFOLD_MAPS_CONTENT, SETUP_FIELDS, ScaffoldError, applySetup, buildScaffoldFiles, createProject, generateAdminToken, generateSecret, parseDevVars, parseJsoncObject, readCajonzeroPackage, readSetupValues, readWranglerAssetsDirectory, readWranglerKvNamespaceId, serializeDevVars, setWranglerKvNamespaceId, setWranglerVars, suggestedValue, syncPublicAssets, toPackageName };
581
864
  //# sourceMappingURL=scaffold.js.map
582
865
  //# sourceMappingURL=scaffold.js.map
package/dist/serve.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { ExecutionContext } from 'hono';
2
- import { R as Room, D as DurableObjectNamespace, b as Env } from './providers-BX7e4pNW.js';
2
+ import { R as Room, D as DurableObjectNamespace, b as Env } from './providers-Bu0-iMVs.js';
3
3
  import { StorageSource, EnvProvider, SettingDefaults } from './core.js';
4
4
  import 'unstorage';
5
+ import './catalog-BRO-mQoP.js';
5
6
  import 'gl-matrix';
6
7
  import 'zod';
7
8