cajonzero 0.1.2 → 0.1.3

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/index.js CHANGED
@@ -11,7 +11,7 @@ import 'unstorage/drivers/memory';
11
11
 
12
12
  // src/version.ts
13
13
  var NAME = "cajonzero";
14
- var VERSION = "0.1.1";
14
+ var VERSION = "0.1.3";
15
15
 
16
16
  // src/core/config.ts
17
17
  var ConfigError = class extends Error {
@@ -213,6 +213,7 @@ function parseEntry(value, index) {
213
213
  const name = optionalString2(record.name, `maps[${index}].name`);
214
214
  const author = optionalString2(record.author, `maps[${index}].author`);
215
215
  const version = optionalString2(record.version, `maps[${index}].version`);
216
+ const etag = optionalString2(record.etag, `maps[${index}].etag`);
216
217
  const cover = optionalString2(record.cover, `maps[${index}].cover`);
217
218
  return {
218
219
  slug,
@@ -221,6 +222,7 @@ function parseEntry(value, index) {
221
222
  ...name !== void 0 ? { name } : {},
222
223
  ...author !== void 0 ? { author } : {},
223
224
  ...version !== void 0 ? { version } : {},
225
+ ...etag !== void 0 ? { etag } : {},
224
226
  ...cover !== void 0 ? { cover } : {},
225
227
  ...assetRoot !== void 0 ? { assetRoot: normalizeRootUrl(assetRoot) } : {}
226
228
  };
@@ -16155,16 +16157,125 @@ function decodeBlockInfo(bytes) {
16155
16157
  return null;
16156
16158
  }
16157
16159
  }
16158
- async function loadBlockInfo(reader, worldId, hash = BLOCK_INFO_HASH) {
16160
+ async function readBlockInfoBytes(reader, worldId, hash = BLOCK_INFO_HASH) {
16159
16161
  try {
16160
16162
  const bytes = await reader.read(`worlds/${worldId}/data/blobs/${hash}`);
16161
16163
  if (bytes === null || bytes.length === 0) return null;
16162
- return decodeBlockInfo(bytes);
16164
+ return bytes;
16163
16165
  } catch {
16164
16166
  return null;
16165
16167
  }
16166
16168
  }
16167
16169
 
16170
+ // src/core/maps/seed-pack.ts
16171
+ var SEED_PACK_VERSION = 1;
16172
+ var SEED_PACK_FORMAT = "cajonzero-seed";
16173
+ var SEED_PACK_FILES = ["seed.json.gz", "seed.json"];
16174
+ function isGzip(bytes) {
16175
+ return bytes.length >= 2 && bytes[0] === 31 && bytes[1] === 139;
16176
+ }
16177
+ async function gunzipBytes(bytes) {
16178
+ if (typeof DecompressionStream === "undefined") {
16179
+ throw new Error("this runtime does not support gzip seed packs");
16180
+ }
16181
+ const buffer = bytes.buffer.slice(
16182
+ bytes.byteOffset,
16183
+ bytes.byteOffset + bytes.byteLength
16184
+ );
16185
+ const stream = new Blob([buffer]).stream().pipeThrough(new DecompressionStream("gzip"));
16186
+ return new Uint8Array(await new Response(stream).arrayBuffer());
16187
+ }
16188
+ async function decodeSeedPackAuto(bytes) {
16189
+ return decodeSeedPack(isGzip(bytes) ? await gunzipBytes(bytes) : bytes);
16190
+ }
16191
+ async function loadSeedPackFromAssets(assets, slug, worldId) {
16192
+ if (assets === void 0) return null;
16193
+ for (const file of SEED_PACK_FILES) {
16194
+ const suffix = file.slice("seed".length);
16195
+ const path = `/seed/${encodeURIComponent(slug)}${suffix}`;
16196
+ try {
16197
+ const response = await assets.fetch(new Request(`https://cajonzero.internal${path}`));
16198
+ if (!response.ok) continue;
16199
+ const bytes = new Uint8Array(await response.arrayBuffer());
16200
+ const seed = await decodeSeedPackAuto(bytes);
16201
+ if (worldId !== void 0 && worldId.length > 0 && seed.mapId !== worldId) continue;
16202
+ return seed;
16203
+ } catch {
16204
+ }
16205
+ }
16206
+ return null;
16207
+ }
16208
+ function base64ToBytes(text) {
16209
+ const binary = atob(text);
16210
+ const out = new Uint8Array(binary.length);
16211
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
16212
+ return out;
16213
+ }
16214
+ function isRecord2(value) {
16215
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16216
+ }
16217
+ function decodeSeedPack(bytes) {
16218
+ let parsed;
16219
+ try {
16220
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
16221
+ } catch {
16222
+ throw new Error("seed pack is not valid JSON");
16223
+ }
16224
+ if (!isRecord2(parsed) || parsed["format"] !== SEED_PACK_FORMAT) {
16225
+ throw new Error("not a cajonzero seed pack");
16226
+ }
16227
+ if (parsed["version"] !== SEED_PACK_VERSION) {
16228
+ throw new Error(
16229
+ `unsupported seed pack version ${String(parsed["version"])} (expected ${SEED_PACK_VERSION})`
16230
+ );
16231
+ }
16232
+ if (typeof parsed["mapId"] !== "string" || parsed["mapId"].length === 0) {
16233
+ throw new Error("seed pack is missing mapId");
16234
+ }
16235
+ if (!isRecord2(parsed["seed"])) {
16236
+ throw new Error("seed pack is missing the seed payload");
16237
+ }
16238
+ const rawSeed = parsed["seed"];
16239
+ if (rawSeed["version"] !== WORLD_SEED_VERSION) {
16240
+ throw new Error(
16241
+ `seed pack was built for world-seed v${String(rawSeed["version"])} (this runtime expects v${WORLD_SEED_VERSION}); re-run cj0 maps pack`
16242
+ );
16243
+ }
16244
+ const rawVoxels = rawSeed["voxels"];
16245
+ let voxels = null;
16246
+ if (rawVoxels !== null) {
16247
+ if (!isRecord2(rawVoxels)) throw new Error("seed pack voxels are malformed");
16248
+ const blobs = rawVoxels["chunkBlobs"];
16249
+ if (!Array.isArray(blobs)) throw new Error("seed pack voxels are malformed");
16250
+ const rawShape = rawVoxels["shape"];
16251
+ const shape = isRecord2(rawShape) ? {
16252
+ x: Number(rawShape["x"]) || 0,
16253
+ y: Number(rawShape["y"]) || 0,
16254
+ z: Number(rawShape["z"]) || 0
16255
+ } : { x: 0, y: 0, z: 0 };
16256
+ const hashes = rawVoxels["chunkHashes"];
16257
+ voxels = {
16258
+ shape,
16259
+ chunkCount: Number(rawVoxels["chunkCount"]) || blobs.length,
16260
+ chunkBlobs: blobs.map(
16261
+ (blob) => typeof blob === "string" ? base64ToBytes(blob) : new Uint8Array(0)
16262
+ ),
16263
+ chunkHashes: Array.isArray(hashes) ? hashes.map((hash) => typeof hash === "string" ? hash : "") : []
16264
+ };
16265
+ }
16266
+ const { voxels: _ignored, ...rest } = rawSeed;
16267
+ const seed = {
16268
+ ...rest,
16269
+ mapId: parsed["mapId"],
16270
+ voxels
16271
+ };
16272
+ if (typeof parsed["blockInfo"] === "string" && parsed["blockInfo"].length > 0) {
16273
+ const blockInfo = decodeBlockInfo(base64ToBytes(parsed["blockInfo"]));
16274
+ if (blockInfo !== null) seed.blockInfo = blockInfo;
16275
+ }
16276
+ return seed;
16277
+ }
16278
+
16168
16279
  // src/core/maps/http.ts
16169
16280
  var MapSourceError = class extends Error {
16170
16281
  code;
@@ -16267,14 +16378,14 @@ var HttpMapSource = class {
16267
16378
  return new Uint8Array(buffer);
16268
16379
  }
16269
16380
  };
16270
- function isRecord2(value) {
16381
+ function isRecord3(value) {
16271
16382
  return typeof value === "object" && value !== null && !Array.isArray(value);
16272
16383
  }
16273
16384
  async function resolveWorldId(source) {
16274
16385
  const bundle = await source.readJson("bundle.json");
16275
16386
  if (bundle === null) fail3("MAP_NO_WORLD", "bundle.json is missing or not JSON");
16276
16387
  const members = Array.isArray(bundle.members) ? bundle.members : [];
16277
- const records = members.filter(isRecord2);
16388
+ const records = members.filter(isRecord3);
16278
16389
  const main = records.find((member) => member.mapRole === "main") ?? records[0];
16279
16390
  const candidate = (main !== void 0 && typeof main.id === "string" ? main.id : void 0) ?? (typeof bundle.mainProjectId === "string" ? bundle.mainProjectId : void 0);
16280
16391
  if (candidate === void 0) {
@@ -16283,6 +16394,13 @@ async function resolveWorldId(source) {
16283
16394
  return assertWorldId(candidate);
16284
16395
  }
16285
16396
  async function loadWorldSeedFromHttp(entry, options = {}) {
16397
+ if (options.preferSeedPack !== false) {
16398
+ const packed = await loadSeedPackFromHttp(entry, options);
16399
+ if (packed !== null) return packed;
16400
+ }
16401
+ return (await loadWorldSeedGraphFromHttp(entry, options)).seed;
16402
+ }
16403
+ async function loadWorldSeedGraphFromHttp(entry, options = {}) {
16286
16404
  const source = new HttpMapSource(entry.root, options);
16287
16405
  const worldId = entry.worldId ?? await resolveWorldId(source);
16288
16406
  const manifest = await source.readJson(
@@ -16291,13 +16409,35 @@ async function loadWorldSeedFromHttp(entry, options = {}) {
16291
16409
  if (manifest === null) {
16292
16410
  fail3("MAP_NO_WORLD", `worlds/${worldId}/manifest.json is missing or not JSON`);
16293
16411
  }
16294
- return resolveWorldSeed(
16412
+ const seed = await resolveWorldSeed(
16295
16413
  { worldId, manifest, reader: source },
16296
16414
  { concurrency: options.concurrency ?? DEFAULT_CONCURRENCY }
16297
- ).then(async (seed) => {
16298
- const blockInfo = await loadBlockInfo(source, worldId);
16299
- return blockInfo === null ? seed : { ...seed, blockInfo };
16300
- });
16415
+ );
16416
+ const blockInfoBytes = await readBlockInfoBytes(source, worldId) ?? void 0;
16417
+ const blockInfo = blockInfoBytes === void 0 ? null : decodeBlockInfo(blockInfoBytes);
16418
+ return {
16419
+ seed: blockInfo === null ? seed : { ...seed, blockInfo },
16420
+ ...blockInfoBytes !== void 0 ? { blockInfoBytes } : {}
16421
+ };
16422
+ }
16423
+ async function loadSeedPackFromHttp(entry, options) {
16424
+ const source = new HttpMapSource(entry.root, options);
16425
+ for (const file of SEED_PACK_FILES) {
16426
+ let bytes;
16427
+ try {
16428
+ bytes = await source.read(file);
16429
+ } catch {
16430
+ continue;
16431
+ }
16432
+ if (bytes === null) continue;
16433
+ try {
16434
+ const seed = await decodeSeedPackAuto(bytes);
16435
+ if (entry.worldId !== void 0 && seed.mapId !== entry.worldId) continue;
16436
+ return seed;
16437
+ } catch {
16438
+ }
16439
+ }
16440
+ return null;
16301
16441
  }
16302
16442
 
16303
16443
  // src/core/net/framing.ts
@@ -24521,6 +24661,8 @@ var Room = class {
24521
24661
  createPair;
24522
24662
  logger;
24523
24663
  secret;
24664
+ /** Worker static-assets binding, used to read seed packs bundled at deploy. */
24665
+ assets;
24524
24666
  engine;
24525
24667
  script;
24526
24668
  explicitScriptHost;
@@ -24583,6 +24725,10 @@ var Room = class {
24583
24725
  pendingEngineMap;
24584
24726
  /** Identity of the map the current engine was seeded from. */
24585
24727
  seededMap;
24728
+ /** Non-fatal seed warnings (missing blobs, skipped entities, …). */
24729
+ seedWarnings = [];
24730
+ /** Last map-load failure; cleared on a successful seed. */
24731
+ mapLoadError;
24586
24732
  heartbeatBeats = 0;
24587
24733
  ticksRun = 0;
24588
24734
  lastTickError;
@@ -24606,6 +24752,7 @@ var Room = class {
24606
24752
  this.createPair = options.createPair ?? defaultCreatePair;
24607
24753
  this.logger = options.logger ?? defaultLogger;
24608
24754
  this.secret = options.secret ?? resolveSessionSecret(env);
24755
+ this.assets = env.ASSETS;
24609
24756
  this.engine = options.engine ?? getRoomEngineFactory()?.() ?? createNullEngine();
24610
24757
  this.explicitScriptHost = options.scriptHost !== void 0;
24611
24758
  this.script = options.scriptHost ?? this.engine;
@@ -24700,7 +24847,9 @@ var Room = class {
24700
24847
  ok: true,
24701
24848
  slug: this.mapBootstrap?.slug ?? null,
24702
24849
  seeded: this.worldReady,
24703
- entities: this.engine.getReplica().entities.length
24850
+ entities: this.engine.getReplica().entities.length,
24851
+ mapLoadError: this.mapLoadError ?? null,
24852
+ warnings: this.seedWarnings
24704
24853
  }),
24705
24854
  { headers: { "content-type": "application/json", "cache-control": "no-store" } }
24706
24855
  );
@@ -24731,6 +24880,8 @@ var Room = class {
24731
24880
  secretPackets: this.secretPackets,
24732
24881
  clockPings: this.clockPings,
24733
24882
  inboundIds: Object.fromEntries(this.inboundIds),
24883
+ mapLoadError: this.mapLoadError ?? null,
24884
+ mapWarnings: this.seedWarnings,
24734
24885
  voxel: this.engine.voxelGeometry?.() ?? null,
24735
24886
  sampleEntity: (() => {
24736
24887
  const entity = this.engine.getReplica().entities[0];
@@ -24828,21 +24979,32 @@ var Room = class {
24828
24979
  this.engine = engine;
24829
24980
  if (!this.explicitScriptHost) this.script = engine;
24830
24981
  this.seededMap = { slug: bootstrap.slug, root: bootstrap.root, worldId: seed.mapId };
24982
+ this.seedWarnings = seed.warnings;
24983
+ this.mapLoadError = void 0;
24984
+ for (const warning of seed.warnings) {
24985
+ this.logger.log(`cajonzero: map "${bootstrap.slug}" warning: ${warning}`);
24986
+ }
24831
24987
  this.applyPendingEngineSnapshot();
24832
24988
  this.worldReady = true;
24833
24989
  this.logger.log(`cajonzero: seeded room "${bootstrap.slug}" from "${seed.mapId}"`);
24834
24990
  } catch (error) {
24991
+ this.mapLoadError = describeError2(error);
24835
24992
  this.logger.error(
24836
- `cajonzero: map load failed for "${bootstrap.slug}": ${describeError2(error)}`
24993
+ `cajonzero: map load failed for "${bootstrap.slug}": ${this.mapLoadError}`
24837
24994
  );
24838
24995
  }
24839
24996
  }
24840
24997
  loadSeed(bootstrap) {
24841
- return loadWorldSeedFromHttp(bootstrap, {
24842
- ...bootstrap.maxDownloadBytes !== void 0 ? { maxDownloadBytes: bootstrap.maxDownloadBytes } : {},
24843
- ...bootstrap.concurrency !== void 0 ? { concurrency: bootstrap.concurrency } : {},
24844
- ...bootstrap.requestTimeoutMs !== void 0 ? { requestTimeoutMs: bootstrap.requestTimeoutMs } : {}
24845
- });
24998
+ return loadSeedPackFromAssets(this.assets, bootstrap.slug, bootstrap.worldId).then(
24999
+ (packed) => {
25000
+ if (packed !== null) return packed;
25001
+ return loadWorldSeedFromHttp(bootstrap, {
25002
+ ...bootstrap.maxDownloadBytes !== void 0 ? { maxDownloadBytes: bootstrap.maxDownloadBytes } : {},
25003
+ ...bootstrap.concurrency !== void 0 ? { concurrency: bootstrap.concurrency } : {},
25004
+ ...bootstrap.requestTimeoutMs !== void 0 ? { requestTimeoutMs: bootstrap.requestTimeoutMs } : {}
25005
+ });
25006
+ }
25007
+ );
24846
25008
  }
24847
25009
  /**
24848
25010
  * Run the full connect path and return a structured outcome (no `Response`,
@@ -26658,7 +26820,7 @@ function createPlayEngine(options = {}) {
26658
26820
  const spawnOnClientInput = options.spawnOnClientInput ?? true;
26659
26821
  const scriptHost = options.scriptHost;
26660
26822
  const physicsOverride = options.physics ? buildPhysicsOverride(options.physics) : null;
26661
- const playerConfig = isRecord3(options.worldSeed?.player) ? options.worldSeed.player : null;
26823
+ const playerConfig = isRecord4(options.worldSeed?.player) ? options.worldSeed.player : null;
26662
26824
  let world = options.world ? NetStateSchema.clone(options.world) : options.worldSeed ? buildInitialWorld(options.worldSeed) : null;
26663
26825
  let currentTick = 0;
26664
26826
  let suspended = false;
@@ -26977,7 +27139,7 @@ function buildPhysicsOverride(override) {
26977
27139
  }
26978
27140
  return params;
26979
27141
  }
26980
- function isRecord3(value) {
27142
+ function isRecord4(value) {
26981
27143
  return typeof value === "object" && value !== null && !Array.isArray(value);
26982
27144
  }
26983
27145
  function finite(value) {
@@ -27035,7 +27197,7 @@ function spawnPlayerBody(id, config) {
27035
27197
  body.friction = PLAYER_FRICTION;
27036
27198
  body.restitution = PLAYER_RESTITUTION;
27037
27199
  body.mass = PLAYER_MASS;
27038
- const spawn = isRecord3(config?.initialPosition) ? config.initialPosition : null;
27200
+ const spawn = isRecord4(config?.initialPosition) ? config.initialPosition : null;
27039
27201
  body.px = finite(spawn?.x) ?? 0;
27040
27202
  body.py = finite(spawn?.y) ?? 0;
27041
27203
  body.pz = finite(spawn?.z) ?? 0;
@@ -27054,12 +27216,12 @@ function projectEnvironment(raw) {
27054
27216
  return environment;
27055
27217
  }
27056
27218
  function copyEnvironmentFields(target, source) {
27057
- if (!isRecord3(target)) return;
27219
+ if (!isRecord4(target)) return;
27058
27220
  for (const key of Object.keys(target)) {
27059
27221
  const value = source[key];
27060
27222
  if (value === void 0) continue;
27061
27223
  const current = target[key];
27062
- if (isRecord3(current) && isRecord3(value)) {
27224
+ if (isRecord4(current) && isRecord4(value)) {
27063
27225
  copyEnvironmentFields(current, value);
27064
27226
  continue;
27065
27227
  }
@@ -131,6 +131,10 @@ interface R2Bucket {
131
131
  delete(key: string): Promise<void>;
132
132
  list(options?: unknown): Promise<unknown>;
133
133
  }
134
+ /** Structural stand-in for workerd's static-assets `Fetcher` binding. */
135
+ interface AssetsBinding {
136
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
137
+ }
134
138
  /**
135
139
  * Runtime bindings + secrets injected by the Cloudflare Workers runtime.
136
140
  *
@@ -142,6 +146,11 @@ interface Env {
142
146
  readonly CAJONZERO_KV: KVNamespace;
143
147
  /** Durable Object namespace for per-room actors (`Room`). */
144
148
  readonly ROOMS: DurableObjectNamespace;
149
+ /**
150
+ * Static-assets binding (`assets.binding`). Optional: older deployments
151
+ * without it still work, they just cannot read the worker-bundled seed packs.
152
+ */
153
+ readonly ASSETS?: AssetsBinding;
145
154
  /** Optional R2 bucket for large map assets; absent in minimal deployments. */
146
155
  readonly CAJONZERO_R2?: R2Bucket;
147
156
  /** Admin bearer token guarding the management/ban API. Secret. */
@@ -533,6 +542,8 @@ interface MapEntry {
533
542
  readonly name?: string;
534
543
  readonly author?: string;
535
544
  readonly version?: string;
545
+ /** Optional content revision (e.g. `v1.0.4`); invalidates precomputed seeds. */
546
+ readonly etag?: string;
536
547
  /** Cover image URL for the lobby. */
537
548
  readonly cover?: string;
538
549
  /** Optional CDN root for browser assets; defaults to {@link root}. */
@@ -5032,6 +5043,8 @@ declare class Room {
5032
5043
  private readonly createPair;
5033
5044
  private readonly logger;
5034
5045
  private readonly secret;
5046
+ /** Worker static-assets binding, used to read seed packs bundled at deploy. */
5047
+ private readonly assets;
5035
5048
  private engine;
5036
5049
  private script;
5037
5050
  private readonly explicitScriptHost;
@@ -5094,6 +5107,10 @@ declare class Room {
5094
5107
  private pendingEngineMap;
5095
5108
  /** Identity of the map the current engine was seeded from. */
5096
5109
  private seededMap;
5110
+ /** Non-fatal seed warnings (missing blobs, skipped entities, …). */
5111
+ private seedWarnings;
5112
+ /** Last map-load failure; cleared on a successful seed. */
5113
+ private mapLoadError;
5097
5114
  private heartbeatBeats;
5098
5115
  private ticksRun;
5099
5116
  private lastTickError;
@@ -147,7 +147,7 @@ declare function toPackageName(folder: string): string;
147
147
  declare function createProject(folder: string, options?: CreateProjectOptions): ScaffoldResult;
148
148
 
149
149
  /** Where one setup value is written. */
150
- type SetupKind = "var" | "secret";
150
+ type SetupKind = "var" | "secret" | "kv";
151
151
  /** One configurable value. */
152
152
  interface SetupField {
153
153
  readonly name: string;
@@ -171,6 +171,8 @@ interface SetupResult {
171
171
  readonly vars: Record<string, string>;
172
172
  /** Secrets written to `.dev.vars`. */
173
173
  readonly secrets: Record<string, string>;
174
+ /** KV namespace id written to `wrangler.jsonc` (when provided). */
175
+ readonly kvId?: string;
174
176
  }
175
177
  interface SetupOptions {
176
178
  readonly cwd?: string;
@@ -189,6 +191,19 @@ declare function suggestedValue(field: SetupField): string | undefined;
189
191
  declare function parseDevVars(text: string): Record<string, string>;
190
192
  /** Serialize `.dev.vars` (stable order, trailing newline). */
191
193
  declare function serializeDevVars(values: Record<string, string>): string;
194
+ /** Strip `//` / block comments and trailing commas so a JSONC object can be parsed. */
195
+ declare function parseJsoncObject(text: string): Record<string, unknown>;
196
+ /**
197
+ * Read the KV namespace id bound to `binding` from a wrangler config.
198
+ * Returns `undefined` when the binding is absent or the id is the placeholder.
199
+ */
200
+ declare function readWranglerKvNamespaceId(raw: string, binding?: string): string | undefined;
201
+ /**
202
+ * Set the `id` of the `kv_namespaces` entry bound to `binding`, preserving
203
+ * comments and unrelated JSONC formatting. Adds the entry (or the whole
204
+ * `kv_namespaces` array) when missing.
205
+ */
206
+ declare function setWranglerKvNamespaceId(raw: string, binding: string, id: string): string;
192
207
  /**
193
208
  * Merge `vars` into the `wrangler.jsonc` `"vars"` object.
194
209
  *
@@ -204,5 +219,54 @@ declare function readSetupValues(cwd: string, configPath: string, devVarsPath: s
204
219
  */
205
220
  declare function applySetup(options: SetupOptions): SetupResult;
206
221
 
207
- 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 };
208
- export type { CajonzeroPackageInfo, CreateProjectOptions, ScaffoldErrorCode, ScaffoldFile, ScaffoldResult, ScaffoldTemplateOptions, SetupField, SetupKind, SetupOptions, SetupResult };
222
+ /** Marker written inside the target dir; excluded from the copy and the hash. */
223
+ declare const ASSETS_STAMP_FILE = ".cajonzero-assets.json";
224
+ /**
225
+ * Subdirectory of `assets.directory` that holds `cj0 worker build-seed` packs.
226
+ * `worker prepare` preserves it when re-syncing the packaged UI.
227
+ */
228
+ declare const ASSETS_SEED_DIR = "seed";
229
+ /** Options for {@link prepareWorkerAssets}. */
230
+ interface PrepareWorkerAssetsOptions {
231
+ /** Project directory (default: `process.cwd()`). */
232
+ readonly cwd?: string;
233
+ /** Wrangler config, relative to `cwd` (default: `wrangler.jsonc`). */
234
+ readonly configPath?: string;
235
+ /**
236
+ * Target directory, relative to the config's directory. Overrides
237
+ * `assets.directory` from the config.
238
+ */
239
+ readonly assetsDir?: string;
240
+ /** Source tree override (tests). Defaults to the installed package's `dist/ui`. */
241
+ readonly sourceDir?: string;
242
+ /** Copy even when the fingerprint already matches. */
243
+ readonly force?: boolean;
244
+ }
245
+ /** Result of a successful preparation. */
246
+ interface PrepareWorkerAssetsResult {
247
+ /** Absolute source directory (the packaged UI). */
248
+ readonly sourceDir: string;
249
+ /** Absolute target directory (the project's `assets.directory`). */
250
+ readonly targetDir: string;
251
+ /** Content fingerprint of the source tree. */
252
+ readonly fingerprint: string;
253
+ /** `false` when the target was already up to date. */
254
+ readonly copied: boolean;
255
+ /** Number of files in the source tree. */
256
+ readonly files: number;
257
+ /** Total uncompressed bytes in the source tree. */
258
+ readonly bytes: number;
259
+ }
260
+ /** Throw when `configPath` is missing or has no `assets.directory`. */
261
+ declare function readWranglerAssetsDirectory(configPath: string): string | undefined;
262
+ /**
263
+ * Materialise cajonzero's packaged UI into the project's static-assets
264
+ * directory. Idempotent: a matching fingerprint is a no-op.
265
+ *
266
+ * @throws when the packaged UI is missing, the config has no `assets.directory`
267
+ * and no override was given, or the target would overlap the project/source.
268
+ */
269
+ declare function prepareWorkerAssets(options?: PrepareWorkerAssetsOptions): Promise<PrepareWorkerAssetsResult>;
270
+
271
+ export { ASSETS_SEED_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, prepareWorkerAssets, readCajonzeroPackage, readSetupValues, readWranglerAssetsDirectory, readWranglerKvNamespaceId, serializeDevVars, setWranglerKvNamespaceId, setWranglerVars, suggestedValue, toPackageName };
272
+ export type { CajonzeroPackageInfo, CreateProjectOptions, PrepareWorkerAssetsOptions, PrepareWorkerAssetsResult, ScaffoldErrorCode, ScaffoldFile, ScaffoldResult, ScaffoldTemplateOptions, SetupField, SetupKind, SetupOptions, SetupResult };