tileflow 0.0.0-bootstrap.0 → 0.1.0-alpha.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.
Files changed (3) hide show
  1. package/README.md +153 -62
  2. package/dist/index.js +2126 -358
  3. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -3,20 +3,20 @@
3
3
  // src/index.ts
4
4
  import { serve } from "@hono/node-server";
5
5
  import { Command } from "commander";
6
- import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
6
+ import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
7
7
  import { existsSync, readFileSync, realpathSync } from "fs";
8
- import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
8
+ import { readFile as readFile7, writeFile as writeFile2 } from "fs/promises";
9
9
  import { hostname } from "os";
10
- import { dirname as dirname9, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
10
+ import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
11
11
  import { createInterface } from "readline/promises";
12
- import pc2 from "picocolors";
12
+ import pc3 from "picocolors";
13
13
  import {
14
+ auditTileflowMapThemeValues,
14
15
  getTileflowStyleFontFaces,
15
- parseTileflowMap as parseTileflowMap2,
16
+ parseResolvedTileflowMap as parseResolvedTileflowMap3,
16
17
  tileflowMapIdSchema as tileflowMapIdSchema2
17
18
  } from "@tileflow/core";
18
19
  import {
19
- collectTileflowMapBuildLineage,
20
20
  createTileflowMapBuildManifest
21
21
  } from "@tileflow/core/build";
22
22
  import {
@@ -27,6 +27,7 @@ import {
27
27
  createTileflowArtifactSession as createTileflowArtifactSession3,
28
28
  createTileflowBuildProvenance,
29
29
  createTileflowStyles,
30
+ prepareTileflowLocalTilesets,
30
31
  writeTileflowBuildArtifacts
31
32
  } from "@tileflow/dev/artifacts";
32
33
  import {
@@ -35,7 +36,7 @@ import {
35
36
  defaultTileflowConfigPath,
36
37
  defaultTileflowManifestPath,
37
38
  getTileflowMapNames as getTileflowMapNames3,
38
- loadTileflowConfigWithInputs as loadTileflowConfigWithInputs2,
39
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3,
39
40
  TileflowValidationError as TileflowValidationError4
40
41
  } from "@tileflow/dev/config";
41
42
  import {
@@ -50,9 +51,10 @@ import {
50
51
  createTileflowDevRequestHandler
51
52
  } from "@tileflow/dev/server";
52
53
  import {
53
- createTileflowCommandFailureDocument as createTileflowCommandFailureDocument2,
54
- createTileflowCommandSummary,
55
- serializeTileflowCommandDocument as serializeTileflowCommandDocument2,
54
+ createTileflowCommandFailureDocument as createTileflowCommandFailureDocument4,
55
+ createTileflowCommandSummary as createTileflowCommandSummary2,
56
+ createTileflowStructuredDiagnostics as createTileflowStructuredDiagnostics2,
57
+ serializeTileflowCommandDocument as serializeTileflowCommandDocument4,
56
58
  TileflowStyleValidationError as TileflowStyleValidationError2
57
59
  } from "@tileflow/dev/validation";
58
60
 
@@ -265,6 +267,405 @@ function compareCodeUnits(left, right) {
265
267
  return left < right ? -1 : left > right ? 1 : 0;
266
268
  }
267
269
 
270
+ // src/ai-commands.ts
271
+ import { dirname as dirname2 } from "path";
272
+ import {
273
+ createStyleResult,
274
+ diffTileflowMaps,
275
+ parseResolvedTileflowMap,
276
+ tileflowAuthoringManifestSchemaVersion
277
+ } from "@tileflow/core";
278
+ import { loadTileflowConfigWithInputs } from "@tileflow/dev/config";
279
+ import { prepareTileflowCatalogIcons } from "@tileflow/dev/icons";
280
+ import { sanitizeTileflowInspectableValue } from "@tileflow/dev/inspect";
281
+ import {
282
+ createTileflowCommandFailureDocument,
283
+ createTileflowCommandSummary,
284
+ createTileflowStructuredDiagnostics,
285
+ sanitizeDiagnosticSecrets,
286
+ serializeTileflowCommandDocument
287
+ } from "@tileflow/dev/validation";
288
+
289
+ // src/config-execution.ts
290
+ function hideTileflowConfigSecrets() {
291
+ const hadApiKey = Object.hasOwn(process.env, "TILEFLOW_API_KEY");
292
+ const apiKey = process.env.TILEFLOW_API_KEY;
293
+ const argv = [...process.argv];
294
+ let restored = false;
295
+ delete process.env.TILEFLOW_API_KEY;
296
+ replaceProcessArgv(stripApiKeyArguments(argv));
297
+ return {
298
+ restore() {
299
+ if (restored) return;
300
+ restored = true;
301
+ if (hadApiKey) {
302
+ process.env.TILEFLOW_API_KEY = apiKey;
303
+ } else {
304
+ delete process.env.TILEFLOW_API_KEY;
305
+ }
306
+ replaceProcessArgv(argv);
307
+ }
308
+ };
309
+ }
310
+ async function withTileflowConfigSecretsHidden(operation) {
311
+ const scope = hideTileflowConfigSecrets();
312
+ try {
313
+ return await operation();
314
+ } finally {
315
+ scope.restore();
316
+ }
317
+ }
318
+ function stripApiKeyArguments(argv) {
319
+ const sanitized = [];
320
+ for (let index = 0; index < argv.length; index += 1) {
321
+ const argument = argv[index];
322
+ if (argument === "--api-key") {
323
+ index += 1;
324
+ continue;
325
+ }
326
+ if (argument.startsWith("--api-key=")) continue;
327
+ sanitized.push(argument);
328
+ }
329
+ return sanitized;
330
+ }
331
+ function replaceProcessArgv(argv) {
332
+ process.argv.splice(0, process.argv.length, ...argv);
333
+ }
334
+
335
+ // src/ai-commands.ts
336
+ function registerAiCommands(program2, dependencies) {
337
+ program2.command("explain").description("Compile one map/theme and explain semantic compiler decisions").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--map <name>", "compile one exact configured map").option("--theme <name>", "compile one exact concrete theme; defaults to the map default").option("--inspection", "include physical-layer provenance in the compilation report").option("--api-base-url <url>", "Tileflow API base URL", dependencies.defaultApiUrl).option("--json", "print deterministic schema-version-1 JSON").action(
338
+ async (options) => runExplain(options, dependencies.createStyleResult ?? createStyleResult)
339
+ );
340
+ program2.command("semantic-diff").description("Compare resolved semantic maps from two configs or one workspace").option("-c, --config <path>", "workspace config path for the --from/--to shortcut").option("--from <map>", "before map ID in --config workspace mode").option("--to <map>", "after map ID in --config workspace mode").option("--from-config <path>", "config path used as the before state").option("--to-config <path>", "config path used as the after state").option("--from-map <map>", "before map ID; required only when --from-config is multi-map").option("--to-map <map>", "after map ID; required only when --to-config is multi-map").option("--json", "print deterministic schema-version-1 JSON").action(
341
+ async (options) => runSemanticDiff(options, dependencies.defaultConfigPath)
342
+ );
343
+ }
344
+ async function runExplain(options, compileStyle) {
345
+ let selection;
346
+ try {
347
+ const output = await withTileflowConfigSecretsHidden(async () => {
348
+ const loaded = await loadTileflowConfigWithInputs(options.config);
349
+ const mapName = selectMap(loaded, options.map, "EXPLAIN");
350
+ const themeName = selectTheme(loaded, mapName, options.theme);
351
+ selection = { map: mapName, theme: themeName };
352
+ const prepared = await prepareTileflowCatalogIcons(loaded.project, {
353
+ assetBaseUrl: "/tileflow",
354
+ baseDirectory: dirname2(loaded.configFile),
355
+ cwd: process.cwd()
356
+ });
357
+ const authoredMap = loaded.authoringMaps[mapName] ?? loaded.project.maps[mapName];
358
+ const metadata = loaded.project.mapMetadata?.[mapName];
359
+ const result = compileStyle(authoredMap, {
360
+ apiBaseUrl: options.apiBaseUrl,
361
+ inspection: options.inspection,
362
+ map: metadata ? {
363
+ id: metadata.id,
364
+ lineage: metadata.lineage.map(({ id }) => id),
365
+ version: metadata.version
366
+ } : void 0,
367
+ preparedAssets: prepared.mapAssets[mapName],
368
+ theme: themeName
369
+ });
370
+ const structuredDiagnostics = result.diagnostics.length === 0 ? [] : createTileflowStructuredDiagnostics({ diagnostics: result.diagnostics }, process.cwd(), {
371
+ code: "COMPILATION_FAILED",
372
+ phase: "compilation"
373
+ });
374
+ const compilation = {
375
+ diagnostics: structuredDiagnostics.map(toSafeCompilationDiagnostic),
376
+ ok: result.ok,
377
+ report: result.report
378
+ };
379
+ return { compilation, result, structuredDiagnostics };
380
+ });
381
+ if (!selection) throw new Error("Explain selection was not resolved.");
382
+ if (!output.result.ok) {
383
+ const failure = createTileflowCommandFailureDocument(
384
+ "explain",
385
+ { diagnostics: output.structuredDiagnostics },
386
+ process.cwd(),
387
+ { code: "COMPILATION_FAILED", phase: "compilation" }
388
+ );
389
+ const document2 = {
390
+ ...failure,
391
+ authoringManifestSchemaVersion: tileflowAuthoringManifestSchemaVersion,
392
+ selection,
393
+ compilation: output.compilation
394
+ };
395
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument(document2));
396
+ else printHumanExplainFailure(document2);
397
+ process.exitCode = 1;
398
+ return;
399
+ }
400
+ const summary = createTileflowCommandSummary({
401
+ code: "COMPILATION_EXPLAINED",
402
+ command: "explain",
403
+ message: `Compiled and explained ${selection.map}/${selection.theme}.`,
404
+ ok: true,
405
+ path: "",
406
+ phase: "compilation",
407
+ severity: "info",
408
+ suggestion: "Use the report targets and planner decisions to make semantic authoring edits."
409
+ });
410
+ const document = {
411
+ ...summary,
412
+ authoringManifestSchemaVersion: tileflowAuthoringManifestSchemaVersion,
413
+ selection,
414
+ compilation: output.compilation,
415
+ diagnostics: output.structuredDiagnostics
416
+ };
417
+ if (options.json) process.stdout.write(serializeTileflowCommandDocument(document));
418
+ else printHumanExplain(document);
419
+ } catch (error) {
420
+ const failure = createTileflowCommandFailureDocument("explain", error, process.cwd(), {
421
+ code: "EXPLAIN_FAILED",
422
+ phase: "compilation"
423
+ });
424
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure));
425
+ else printHumanCommandFailure(failure);
426
+ process.exitCode = 1;
427
+ }
428
+ }
429
+ async function runSemanticDiff(options, defaultConfigPath2) {
430
+ try {
431
+ const difference = await withTileflowConfigSecretsHidden(async () => {
432
+ const plan = createSemanticDiffPlan(options, defaultConfigPath2);
433
+ if (plan.mode === "configs") {
434
+ const fromLoaded = await loadTileflowConfigWithInputs(plan.fromConfig);
435
+ const fromName2 = selectConfigMap(fromLoaded, plan.fromMap, "from");
436
+ const toLoaded = await loadTileflowConfigWithInputs(plan.toConfig);
437
+ const toName2 = selectConfigMap(toLoaded, plan.toMap, "to");
438
+ return sanitizeSemanticDiff(
439
+ diffTileflowMaps(
440
+ fromLoaded.authoringMaps[fromName2] ?? fromLoaded.project.maps[fromName2],
441
+ toLoaded.authoringMaps[toName2] ?? toLoaded.project.maps[toName2]
442
+ ),
443
+ process.cwd()
444
+ );
445
+ }
446
+ const loaded = await loadTileflowConfigWithInputs(plan.config);
447
+ const fromName = selectExactMap(loaded, plan.from, "SEMANTIC_DIFF_FROM", "from");
448
+ const toName = selectExactMap(loaded, plan.to, "SEMANTIC_DIFF_TO", "to");
449
+ const difference2 = diffTileflowMaps(
450
+ loaded.authoringMaps[fromName] ?? loaded.project.maps[fromName],
451
+ loaded.authoringMaps[toName] ?? loaded.project.maps[toName]
452
+ );
453
+ return sanitizeSemanticDiff(difference2, process.cwd());
454
+ });
455
+ const summary = createTileflowCommandSummary({
456
+ code: "SEMANTIC_DIFF_READY",
457
+ command: "semantic-diff",
458
+ message: `Compared ${difference.from.id} to ${difference.to.id} (${difference.summary.total} semantic changes).`,
459
+ ok: true,
460
+ path: "",
461
+ phase: "semantic-diff",
462
+ severity: "info",
463
+ suggestion: difference.summary.total === 0 ? "The resolved semantic designs are equivalent." : "Apply changes by JSON Pointer through the public semantic module API."
464
+ });
465
+ const document = { ...summary, diff: difference, diagnostics: [] };
466
+ if (options.json) process.stdout.write(serializeTileflowCommandDocument(document));
467
+ else printHumanSemanticDiff(difference);
468
+ } catch (error) {
469
+ const failure = createTileflowCommandFailureDocument("semantic-diff", error, process.cwd(), {
470
+ code: "SEMANTIC_DIFF_FAILED",
471
+ phase: "semantic-diff"
472
+ });
473
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure));
474
+ else printHumanCommandFailure(failure);
475
+ process.exitCode = 1;
476
+ }
477
+ }
478
+ function createSemanticDiffPlan(options, defaultConfigPath2) {
479
+ const usesConfigPair = options.fromConfig !== void 0 || options.toConfig !== void 0;
480
+ if (usesConfigPair) {
481
+ const conflictingWorkspaceOption = [
482
+ ["config", options.config],
483
+ ["from", options.from],
484
+ ["to", options.to]
485
+ ].find((entry) => entry[1] !== void 0);
486
+ if (conflictingWorkspaceOption) {
487
+ throw commandSelectionError(
488
+ "SEMANTIC_DIFF_MODE_CONFLICT",
489
+ `--${conflictingWorkspaceOption[0]} cannot be combined with --from-config/--to-config; use either paired configs or the --config --from --to workspace shortcut.`,
490
+ conflictingWorkspaceOption[0]
491
+ );
492
+ }
493
+ if (!options.fromConfig) {
494
+ throw commandSelectionError(
495
+ "SEMANTIC_DIFF_FROM_CONFIG_REQUIRED",
496
+ "Paired-config mode requires --from-config <path>.",
497
+ "from-config"
498
+ );
499
+ }
500
+ if (!options.toConfig) {
501
+ throw commandSelectionError(
502
+ "SEMANTIC_DIFF_TO_CONFIG_REQUIRED",
503
+ "Paired-config mode requires --to-config <path>.",
504
+ "to-config"
505
+ );
506
+ }
507
+ return {
508
+ fromConfig: options.fromConfig,
509
+ ...options.fromMap === void 0 ? {} : { fromMap: options.fromMap },
510
+ mode: "configs",
511
+ toConfig: options.toConfig,
512
+ ...options.toMap === void 0 ? {} : { toMap: options.toMap }
513
+ };
514
+ }
515
+ const configSelector = options.fromMap === void 0 ? options.toMap : options.fromMap;
516
+ if (configSelector !== void 0) {
517
+ const path = options.fromMap === void 0 ? "to-map" : "from-map";
518
+ throw commandSelectionError(
519
+ "SEMANTIC_DIFF_MODE_CONFLICT",
520
+ `--${path} is valid only with --from-config and --to-config.`,
521
+ path
522
+ );
523
+ }
524
+ if (!options.from || !options.to) {
525
+ throw commandSelectionError(
526
+ "SEMANTIC_DIFF_SELECTION_REQUIRED",
527
+ "Workspace mode requires both --from <map> and --to <map>.",
528
+ !options.from ? "from" : "to"
529
+ );
530
+ }
531
+ return {
532
+ config: options.config ?? defaultConfigPath2,
533
+ from: options.from,
534
+ mode: "workspace",
535
+ to: options.to
536
+ };
537
+ }
538
+ function selectMap(loaded, requested, code) {
539
+ const mapNames = Object.keys(loaded.project.maps).sort(compareCodeUnits2);
540
+ if (requested) return selectExactMap(loaded, requested, `${code}_MAP`, "map");
541
+ if (mapNames.length === 1) return mapNames[0];
542
+ throw commandSelectionError(
543
+ `${code}_MAP_REQUIRED`,
544
+ `This config defines ${mapNames.length} maps; select one with --map. Available maps: ${mapNames.join(", ")}.`,
545
+ "map"
546
+ );
547
+ }
548
+ function selectConfigMap(loaded, requested, side) {
549
+ const mapNames = Object.keys(loaded.project.maps).sort(compareCodeUnits2);
550
+ const sideCode = side.toUpperCase();
551
+ const path = `${side}-map`;
552
+ if (mapNames.length === 1) {
553
+ if (requested === void 0) return mapNames[0];
554
+ throw commandSelectionError(
555
+ `SEMANTIC_DIFF_${sideCode}_MAP_UNNECESSARY`,
556
+ `--${path} is not valid because --${side}-config defines exactly one map (${mapNames[0]}).`,
557
+ path
558
+ );
559
+ }
560
+ if (requested === void 0) {
561
+ throw commandSelectionError(
562
+ `SEMANTIC_DIFF_${sideCode}_MAP_REQUIRED`,
563
+ `--${side}-config defines ${mapNames.length} maps; select one with --${path}. Available maps: ${mapNames.join(", ")}.`,
564
+ path
565
+ );
566
+ }
567
+ return selectExactMap(loaded, requested, `SEMANTIC_DIFF_${sideCode}_MAP`, path);
568
+ }
569
+ function selectExactMap(loaded, requested, code, path) {
570
+ if (Object.hasOwn(loaded.project.maps, requested)) return requested;
571
+ const mapNames = Object.keys(loaded.project.maps).sort(compareCodeUnits2);
572
+ throw commandSelectionError(
573
+ `${code}_NOT_FOUND`,
574
+ `Unknown Tileflow map "${requested}". Available maps: ${mapNames.join(", ") || "(none)"}.`,
575
+ path
576
+ );
577
+ }
578
+ function selectTheme(loaded, mapName, requested) {
579
+ const map = parseResolvedTileflowMap(loaded.project.maps[mapName]);
580
+ const themeNames = Object.keys(map.themes).sort(compareCodeUnits2);
581
+ const themeName = requested ?? map.defaultTheme;
582
+ if (Object.hasOwn(map.themes, themeName)) return themeName;
583
+ throw commandSelectionError(
584
+ "EXPLAIN_THEME_NOT_FOUND",
585
+ `Unknown theme "${themeName}" for map "${mapName}". Available themes: ${themeNames.join(", ")}.`,
586
+ "theme"
587
+ );
588
+ }
589
+ function commandSelectionError(code, message, path) {
590
+ return Object.assign(new Error(message), {
591
+ code,
592
+ issues: [{ code, message, path, phase: "command-validation" }],
593
+ phase: "command-validation"
594
+ });
595
+ }
596
+ function sanitizeSemanticDiff(difference, cwd) {
597
+ return {
598
+ ...difference,
599
+ changes: difference.changes.map((change) => ({
600
+ ...change,
601
+ path: sanitizeDiagnosticSecrets(change.path),
602
+ ..."before" in change ? {
603
+ before: sanitizeTileflowInspectableValue(
604
+ change.before,
605
+ cwd
606
+ )
607
+ } : {},
608
+ ..."after" in change ? {
609
+ after: sanitizeTileflowInspectableValue(change.after, cwd)
610
+ } : {}
611
+ }))
612
+ };
613
+ }
614
+ function toSafeCompilationDiagnostic(diagnostic) {
615
+ return {
616
+ code: diagnostic.code,
617
+ ...diagnostic.domain ? { domain: diagnostic.domain } : {},
618
+ message: diagnostic.message,
619
+ path: diagnostic.path,
620
+ phase: diagnostic.phase,
621
+ severity: diagnostic.severity === "info" ? "warning" : diagnostic.severity,
622
+ ...diagnostic.suggestion ? { suggestion: diagnostic.suggestion } : {},
623
+ ...diagnostic.target ? { target: diagnostic.target } : {}
624
+ };
625
+ }
626
+ function printHumanExplain(document) {
627
+ console.log(document.message);
628
+ const emitted = document.compilation.report.domains.filter(({ status }) => status === "emitted");
629
+ const suppressed = document.compilation.report.domains.length - emitted.length;
630
+ console.log(`Domains: ${emitted.length} emitted, ${suppressed} suppressed.`);
631
+ console.log(`Semantic targets: ${document.compilation.report.targets.length}.`);
632
+ for (const decision of document.compilation.report.planner) {
633
+ console.log(`Planner ${decision.stage}: ${decision.inputCount} -> ${decision.outputCount}.`);
634
+ }
635
+ console.log(
636
+ document.compilation.diagnostics.length === 0 ? "Diagnostics: none." : `Diagnostics: ${document.compilation.diagnostics.length}.`
637
+ );
638
+ console.log("Run tileflow explain --json for the complete machine-readable report.");
639
+ }
640
+ function printHumanExplainFailure(document) {
641
+ console.error(`${document.message} [${document.code}; ${document.phase}]`);
642
+ for (const diagnostic of document.compilation.diagnostics) {
643
+ console.error(`- ${diagnostic.path || "(root)"}: ${diagnostic.message}`);
644
+ }
645
+ console.error(`Suggestion: ${document.suggestion}`);
646
+ }
647
+ function printHumanSemanticDiff(difference) {
648
+ console.log(
649
+ `Compared ${difference.from.id}@${difference.from.version} to ${difference.to.id}@${difference.to.version}.`
650
+ );
651
+ console.log(
652
+ `Changes: ${difference.summary.total} (${difference.summary.add} add, ${difference.summary.remove} remove, ${difference.summary.change} change).`
653
+ );
654
+ for (const change of difference.changes) {
655
+ console.log(`${change.kind.padEnd(6)} ${change.path || "/"}`);
656
+ }
657
+ }
658
+ function printHumanCommandFailure(document) {
659
+ console.error(`${document.message} [${document.code}; ${document.phase}]`);
660
+ for (const diagnostic of document.diagnostics.slice(1)) {
661
+ console.error(`- ${diagnostic.path || "(root)"}: ${diagnostic.message}`);
662
+ }
663
+ console.error(`Suggestion: ${document.suggestion}`);
664
+ }
665
+ function compareCodeUnits2(left, right) {
666
+ return left < right ? -1 : left > right ? 1 : 0;
667
+ }
668
+
268
669
  // src/capture-command.ts
269
670
  import { spawn } from "child_process";
270
671
  import { isAbsolute, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
@@ -277,7 +678,7 @@ import {
277
678
  TileflowVisualReviewError
278
679
  } from "@tileflow/capture";
279
680
  import {
280
- compareCodeUnits as compareCodeUnits2,
681
+ compareCodeUnits as compareCodeUnits3,
281
682
  normalizeTileflowCaptureScene,
282
683
  tileflowCaptureSceneNameSchema,
283
684
  tileflowCaptureSceneSchema
@@ -293,12 +694,12 @@ import { TileflowStyleValidationError } from "@tileflow/dev/validation";
293
694
  // src/capture-output.ts
294
695
  import { randomUUID } from "crypto";
295
696
  import { lstat, mkdir as mkdir2, open as open2, readFile as readFile2, rename as rename2, unlink as unlink2 } from "fs/promises";
296
- import { basename, dirname as dirname2, relative, resolve as resolve2, sep } from "path";
697
+ import { basename, dirname as dirname3, relative, resolve as resolve2, sep } from "path";
297
698
  var maximumAtomicArtifactBytes = 256 * 1024 * 1024;
298
699
  async function writeCapturePair(input) {
299
700
  const outputPath = resolve2(input.outputPath);
300
701
  const receiptPath = resolve2(input.receiptPath);
301
- const boundaryPath = resolve2(input.boundaryPath ?? dirname2(outputPath));
702
+ const boundaryPath = resolve2(input.boundaryPath ?? dirname3(outputPath));
302
703
  if (outputPath === receiptPath) {
303
704
  throw new Error("Capture PNG and receipt paths must be different.");
304
705
  }
@@ -325,7 +726,7 @@ async function writeAtomicFileSet(input) {
325
726
  throw new Error(`${input.label} paths must be unique and include at least one file.`);
326
727
  }
327
728
  for (const path of targets) {
328
- await assertNoSymlinkComponents(dirname2(path), boundaryPath);
729
+ await assertNoSymlinkComponents(dirname3(path), boundaryPath);
329
730
  await assertNotSymlink(path);
330
731
  }
331
732
  const existing = /* @__PURE__ */ new Map();
@@ -345,12 +746,12 @@ async function writeAtomicFileSet(input) {
345
746
  );
346
747
  }
347
748
  await Promise.all(
348
- [...new Set(files.map((file) => dirname2(file.path)))].map(
749
+ [...new Set(files.map((file) => dirname3(file.path)))].map(
349
750
  (path) => mkdir2(path, { recursive: true })
350
751
  )
351
752
  );
352
753
  for (const path of targets) {
353
- await assertNoSymlinkComponents(dirname2(path), boundaryPath);
754
+ await assertNoSymlinkComponents(dirname3(path), boundaryPath);
354
755
  await assertNotSymlink(path);
355
756
  }
356
757
  const transaction = `${process.pid}.${randomUUID()}`;
@@ -378,7 +779,7 @@ async function writeAtomicFileSet(input) {
378
779
  stateByPath.get(file.path).installed = true;
379
780
  }
380
781
  await Promise.all(
381
- [...new Set(targets.map((path) => dirname2(path)))].map((path) => syncDirectory2(path))
782
+ [...new Set(targets.map((path) => dirname3(path)))].map((path) => syncDirectory2(path))
382
783
  );
383
784
  const cleanup = await Promise.allSettled(states.map((state) => removeOptional(state.backup)));
384
785
  cleanup.forEach((result, index) => {
@@ -415,7 +816,7 @@ async function writeAtomicFile(input) {
415
816
  const boundaryPath = resolve2(input.boundaryPath);
416
817
  const path = resolve2(input.path);
417
818
  const source = typeof input.source === "string" ? new TextEncoder().encode(input.source) : new Uint8Array(input.source);
418
- await assertNoSymlinkComponents(dirname2(path), boundaryPath);
819
+ await assertNoSymlinkComponents(dirname3(path), boundaryPath);
419
820
  await assertNotSymlink(path);
420
821
  const existing = await readOptional(path);
421
822
  if (existing !== void 0 && Buffer.from(existing).equals(Buffer.from(source))) return false;
@@ -424,14 +825,14 @@ async function writeAtomicFile(input) {
424
825
  `${input.label} already exists with different contents. Use --force to replace it.`
425
826
  );
426
827
  }
427
- await mkdir2(dirname2(path), { recursive: true });
428
- await assertNoSymlinkComponents(dirname2(path), boundaryPath);
828
+ await mkdir2(dirname3(path), { recursive: true });
829
+ await assertNoSymlinkComponents(dirname3(path), boundaryPath);
429
830
  await assertNotSymlink(path);
430
831
  const temporary = temporarySibling(path, `${process.pid}.${randomUUID()}`, "new");
431
832
  try {
432
833
  await writeSyncedExclusive(temporary, source);
433
834
  await rename2(temporary, path);
434
- await syncDirectory2(dirname2(path));
835
+ await syncDirectory2(dirname3(path));
435
836
  return true;
436
837
  } finally {
437
838
  await removeOptional(temporary);
@@ -466,7 +867,7 @@ async function assertBoundaryOrNearestExistingAncestorIsNotSymlink(path) {
466
867
  throw new Error("Capture paths cannot contain symbolic links.");
467
868
  }
468
869
  if (info) return;
469
- const parent = dirname2(current);
870
+ const parent = dirname3(current);
470
871
  if (parent === current) return;
471
872
  current = parent;
472
873
  }
@@ -501,7 +902,7 @@ async function syncDirectory2(path) {
501
902
  }
502
903
  }
503
904
  function temporarySibling(path, transaction, role) {
504
- return resolve2(dirname2(path), `.${basename(path)}.${transaction}.${role}.tmp`);
905
+ return resolve2(dirname3(path), `.${basename(path)}.${transaction}.${role}.tmp`);
505
906
  }
506
907
  async function readOptional(path) {
507
908
  const info = await lstatOptional(path);
@@ -532,52 +933,6 @@ function isMissingFile2(error) {
532
933
  return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
533
934
  }
534
935
 
535
- // src/config-execution.ts
536
- function hideTileflowConfigSecrets() {
537
- const hadApiKey = Object.hasOwn(process.env, "TILEFLOW_API_KEY");
538
- const apiKey = process.env.TILEFLOW_API_KEY;
539
- const argv = [...process.argv];
540
- let restored = false;
541
- delete process.env.TILEFLOW_API_KEY;
542
- replaceProcessArgv(stripApiKeyArguments(argv));
543
- return {
544
- restore() {
545
- if (restored) return;
546
- restored = true;
547
- if (hadApiKey) {
548
- process.env.TILEFLOW_API_KEY = apiKey;
549
- } else {
550
- delete process.env.TILEFLOW_API_KEY;
551
- }
552
- replaceProcessArgv(argv);
553
- }
554
- };
555
- }
556
- async function withTileflowConfigSecretsHidden(operation) {
557
- const scope = hideTileflowConfigSecrets();
558
- try {
559
- return await operation();
560
- } finally {
561
- scope.restore();
562
- }
563
- }
564
- function stripApiKeyArguments(argv) {
565
- const sanitized = [];
566
- for (let index = 0; index < argv.length; index += 1) {
567
- const argument = argv[index];
568
- if (argument === "--api-key") {
569
- index += 1;
570
- continue;
571
- }
572
- if (argument.startsWith("--api-key=")) continue;
573
- sanitized.push(argument);
574
- }
575
- return sanitized;
576
- }
577
- function replaceProcessArgv(argv) {
578
- process.argv.splice(0, process.argv.length, ...argv);
579
- }
580
-
581
936
  // src/capture-command.ts
582
937
  function registerCaptureCommands(program2, dependencies) {
583
938
  const setup = program2.command("setup").description("Provision local Tileflow tooling");
@@ -642,7 +997,7 @@ function createTileflowCaptureJson(captures, cwd) {
642
997
  return {
643
998
  schemaVersion: 1,
644
999
  command: "capture",
645
- captures: [...captures].sort((left, right) => compareCodeUnits2(left.capture.scene, right.capture.scene)).map(({ capture, definition, outputPath, receiptPath }) => ({
1000
+ captures: [...captures].sort((left, right) => compareCodeUnits3(left.capture.scene, right.capture.scene)).map(({ capture, definition, outputPath, receiptPath }) => ({
646
1001
  scene: capture.scene,
647
1002
  map: capture.map,
648
1003
  theme: capture.theme,
@@ -657,7 +1012,7 @@ function createTileflowCaptureJson(captures, cwd) {
657
1012
  dpr: capture.dpr,
658
1013
  renderer: capture.renderer,
659
1014
  networkDependent: capture.networkDependent,
660
- warnings: [...capture.warnings].sort(compareCodeUnits2)
1015
+ warnings: [...capture.warnings].sort(compareCodeUnits3)
661
1016
  }))
662
1017
  };
663
1018
  }
@@ -898,7 +1253,13 @@ async function runCaptureWatchCommand(positionalScenes, options) {
898
1253
  emit({ event: "recovered", generation: state.generation });
899
1254
  invalidSinceLastReady = false;
900
1255
  }
901
- const task = captureGeneration(state.generation, state.artifacts, activeCapture.signal);
1256
+ const acquisition = artifactSession.acquireArtifacts(state.generation);
1257
+ if (!acquisition) return;
1258
+ const task = captureGeneration(
1259
+ state.generation,
1260
+ acquisition.artifacts,
1261
+ activeCapture.signal
1262
+ ).finally(() => acquisition.release());
902
1263
  activeTasks.add(task);
903
1264
  void task.finally(() => activeTasks.delete(task));
904
1265
  };
@@ -1185,8 +1546,8 @@ function runGit(cwd, args) {
1185
1546
  // src/config-inspect-command.ts
1186
1547
  import { inspectTileflowConfig } from "@tileflow/dev/inspect";
1187
1548
  import {
1188
- createTileflowCommandFailureDocument,
1189
- serializeTileflowCommandDocument
1549
+ createTileflowCommandFailureDocument as createTileflowCommandFailureDocument2,
1550
+ serializeTileflowCommandDocument as serializeTileflowCommandDocument2
1190
1551
  } from "@tileflow/dev/validation";
1191
1552
  function registerConfigInspectCommand(inspect, dependencies) {
1192
1553
  inspect.description("Inspect resolved maps, lineage, and merge provenance").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--map <name>", "inspect one exact configured map").option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
@@ -1195,17 +1556,17 @@ function registerConfigInspectCommand(inspect, dependencies) {
1195
1556
  () => inspectTileflowConfig({ config: options.config, cwd: process.cwd(), map: options.map })
1196
1557
  );
1197
1558
  if (options.json) {
1198
- process.stdout.write(serializeTileflowCommandDocument(document));
1559
+ process.stdout.write(serializeTileflowCommandDocument2(document));
1199
1560
  return;
1200
1561
  }
1201
1562
  printHumanConfigInspection(document);
1202
1563
  } catch (error) {
1203
- const failure = createTileflowCommandFailureDocument("inspect", error, process.cwd(), {
1564
+ const failure = createTileflowCommandFailureDocument2("inspect", error, process.cwd(), {
1204
1565
  code: "INSPECTION_FAILED",
1205
1566
  phase: "config-inspection"
1206
1567
  });
1207
1568
  if (options.json) {
1208
- process.stderr.write(serializeTileflowCommandDocument(failure));
1569
+ process.stderr.write(serializeTileflowCommandDocument2(failure));
1209
1570
  } else {
1210
1571
  console.error(`${failure.message} [${failure.code}; ${failure.phase}]`);
1211
1572
  for (const diagnostic of failure.diagnostics.slice(1)) {
@@ -1378,13 +1739,13 @@ function tileflowDevOrigin(host, port) {
1378
1739
  }
1379
1740
 
1380
1741
  // src/feature-inspect-command.ts
1381
- import { dirname as dirname3 } from "path";
1742
+ import { dirname as dirname4 } from "path";
1382
1743
  import {
1383
1744
  getFirstTileflowMapName,
1384
1745
  loadValidTileflowConfigWithInputs,
1385
1746
  TileflowValidationError as TileflowValidationError2
1386
1747
  } from "@tileflow/dev/config";
1387
- import { prepareTileflowCatalogIcons } from "@tileflow/dev/icons";
1748
+ import { prepareTileflowCatalogIcons as prepareTileflowCatalogIcons2 } from "@tileflow/dev/icons";
1388
1749
  import { inspectTileflowFeatures } from "@tileflow/dev/inspect";
1389
1750
  function registerFeatureInspectCommand(inspect, dependencies) {
1390
1751
  inspect.command("features").description("Inspect bounded vector features near a map camera").requiredOption("--center <longitude,latitude>", "camera center").requiredOption("--zoom <number>", "camera zoom from 0 to 24").requiredOption("--layers <names>", "comma-separated source-layer names").option(
@@ -1419,9 +1780,9 @@ async function runFeatureInspect(options) {
1419
1780
  );
1420
1781
  const project = loaded.project;
1421
1782
  const mapName = options.map ?? getFirstTileflowMapName(project);
1422
- const prepared = await prepareTileflowCatalogIcons(project, {
1783
+ const prepared = await prepareTileflowCatalogIcons2(project, {
1423
1784
  assetBaseUrl: "/tileflow",
1424
- baseDirectory: dirname3(loaded.configFile),
1785
+ baseDirectory: dirname4(loaded.configFile),
1425
1786
  cwd: process.cwd()
1426
1787
  });
1427
1788
  const inspection = await inspectTileflowFeatures(project, mapName, {
@@ -1514,7 +1875,7 @@ var publicHttpUrlSchema = z2.url().max(2048).refine((value) => {
1514
1875
  var hostedStyleDeploymentResponseSchema = z2.object({
1515
1876
  changed: z2.boolean().optional(),
1516
1877
  deploymentId: safeIdentifierSchema.optional(),
1517
- mapId: safeIdentifierSchema,
1878
+ mapId: z2.string().regex(/^map_[A-Za-z0-9_-]{16}$/u),
1518
1879
  themes: z2.record(
1519
1880
  safeIdentifierSchema,
1520
1881
  z2.object({
@@ -1522,8 +1883,7 @@ var hostedStyleDeploymentResponseSchema = z2.object({
1522
1883
  styleUrl: publicHttpUrlSchema
1523
1884
  }).strict()
1524
1885
  ).refine((themes) => Object.keys(themes).length > 0),
1525
- version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
1526
- worldPromotionId: safeIdentifierSchema.optional()
1886
+ version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional()
1527
1887
  }).strict();
1528
1888
  var hostedIconPackageResponseSchema = z2.object({
1529
1889
  changed: z2.boolean().optional(),
@@ -1548,8 +1908,8 @@ var hostedStatusStyleSchema = z2.object({
1548
1908
  size: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
1549
1909
  uploaded: z2.iso.datetime({ offset: true })
1550
1910
  });
1551
- var hostedProjectStatusSchema = z2.object({
1552
- projectId: safeIdentifierSchema,
1911
+ var hostedMapStatusSchema = z2.object({
1912
+ mapId: z2.string().regex(/^map_[A-Za-z0-9_-]{16}$/u),
1553
1913
  styles: z2.array(hostedStatusStyleSchema).max(1e4)
1554
1914
  });
1555
1915
  async function readBoundedResponseText(response, label) {
@@ -1685,7 +2045,7 @@ async function validateApiKey(apiUrl, apiKey, options = {}) {
1685
2045
  return { error: `API key validation failed (${response.status}).`, ok: false };
1686
2046
  }
1687
2047
  const body = asRecord(response.body);
1688
- if (!response.json || typeof body.apiKeyId !== "string" || body.credentialType !== "project_api_key" || !isProjectIdentity(body.organization) || !isProjectIdentity(body.project) || typeof body.projectId !== "string" || body.projectId !== body.project.id || !Array.isArray(body.scopes)) {
2048
+ if (!response.json || typeof body.apiKeyId !== "string" || body.credentialType !== "project_api_key" || typeof body.mapId !== "string" || !/^map_[A-Za-z0-9_-]{16}$/u.test(body.mapId) || !isProjectIdentity(body.organization) || !isProjectIdentity(body.project) || typeof body.projectId !== "string" || body.projectId !== body.project.id || !Array.isArray(body.scopes)) {
1689
2049
  return { error: "API key validation returned an invalid response.", ok: false };
1690
2050
  }
1691
2051
  return {
@@ -1693,6 +2053,7 @@ async function validateApiKey(apiUrl, apiKey, options = {}) {
1693
2053
  value: {
1694
2054
  apiKeyId: body.apiKeyId,
1695
2055
  credentialType: body.credentialType,
2056
+ mapId: body.mapId,
1696
2057
  organization: body.organization,
1697
2058
  project: body.project,
1698
2059
  projectId: body.projectId,
@@ -1700,6 +2061,33 @@ async function validateApiKey(apiUrl, apiKey, options = {}) {
1700
2061
  }
1701
2062
  };
1702
2063
  }
2064
+ async function requestMapCapability(session, target, scopes, options = {}) {
2065
+ let response;
2066
+ try {
2067
+ response = await requestHostedJson(
2068
+ session.apiOrigin,
2069
+ "/v1/cli/map-capabilities",
2070
+ {
2071
+ ...jsonRequest("POST", { ...target, scopes }),
2072
+ headers: {
2073
+ ...authorizationHeaders(session.accountSession),
2074
+ "Content-Type": "application/json"
2075
+ }
2076
+ },
2077
+ options
2078
+ );
2079
+ } catch (error) {
2080
+ return { error: safeTransportError(error, "Map capability request failed."), ok: false };
2081
+ }
2082
+ if (!response.ok) {
2083
+ return { error: `Map capability request failed (${response.status}).`, ok: false };
2084
+ }
2085
+ const body = asRecord(response.body);
2086
+ if (!response.json || typeof body.capability !== "string" || !body.capability.startsWith("tf_cap_") || body.capability.length > 8192 || typeof body.mapId !== "string" || !/^map_[A-Za-z0-9_-]{16}$/u.test(body.mapId) || body.mapId !== target.mapId || !validIsoDate(body.expiresAt) || !Array.isArray(body.scopes) || body.scopes.join("\0") !== [...scopes].sort().join("\0")) {
2087
+ return { error: "Map capability response was invalid.", ok: false };
2088
+ }
2089
+ return { capability: body.capability, mapId: body.mapId, ok: true };
2090
+ }
1703
2091
  async function requestProjectCapability(session, project, scopes, options = {}) {
1704
2092
  let response;
1705
2093
  try {
@@ -1730,6 +2118,53 @@ async function requestProjectCapability(session, project, scopes, options = {})
1730
2118
  }
1731
2119
  return { capability: body.capability, ok: true };
1732
2120
  }
2121
+ async function requestTeamCapability(session, team, scopes, options = {}) {
2122
+ let response;
2123
+ try {
2124
+ response = await requestHostedJson(
2125
+ session.apiOrigin,
2126
+ "/v1/cli/team-capabilities",
2127
+ {
2128
+ ...jsonRequest("POST", { scopes, team }),
2129
+ headers: {
2130
+ ...authorizationHeaders(session.accountSession),
2131
+ "Content-Type": "application/json"
2132
+ }
2133
+ },
2134
+ options
2135
+ );
2136
+ } catch (error) {
2137
+ return { error: safeTransportError(error, "Team capability request failed."), ok: false };
2138
+ }
2139
+ if (!response.ok) {
2140
+ return { error: `Team capability request failed (${response.status}).`, ok: false };
2141
+ }
2142
+ const body = asRecord(response.body);
2143
+ if (!response.json || body.schemaVersion !== 1 || typeof body.capability !== "string" || !body.capability.startsWith("tf_cap_") || body.capability.length > 8192 || body.reference !== team || !isProjectIdentity(body.team) || `@${body.team.slug}` !== team || !validIsoDate(body.expiresAt) || !Array.isArray(body.scopes) || body.scopes.join("\0") !== [...scopes].sort().join("\0")) {
2144
+ return { error: "Team capability response was invalid.", ok: false };
2145
+ }
2146
+ return { capability: body.capability, ok: true, team: body.team };
2147
+ }
2148
+ async function listAccountTeams(session, options = {}) {
2149
+ const response = await requestHostedJson(
2150
+ session.apiOrigin,
2151
+ "/v1/cli/teams",
2152
+ { headers: authorizationHeaders(session.accountSession) },
2153
+ options
2154
+ );
2155
+ const body = asRecord(response.body);
2156
+ if (!response.ok) return { error: `Team discovery failed (${response.status}).`, ok: false };
2157
+ if (!response.json || body.schemaVersion !== 1 || !Array.isArray(body.teams) || !body.teams.every(isProjectIdentity)) {
2158
+ return { error: "Team discovery returned an invalid response.", ok: false };
2159
+ }
2160
+ const teams = [...body.teams].sort(
2161
+ (left, right) => left.slug < right.slug ? -1 : left.slug > right.slug ? 1 : 0
2162
+ );
2163
+ if (new Set(teams.map(({ id }) => id)).size !== teams.length || teams.some((team, index) => index > 0 && teams[index - 1].slug >= team.slug)) {
2164
+ return { error: "Team discovery returned unstable ordering.", ok: false };
2165
+ }
2166
+ return { ok: true, teams };
2167
+ }
1733
2168
  async function publishHostedStyle(api, body, responseLabel, options = {}) {
1734
2169
  const response = await requestHostedJson(
1735
2170
  api.apiUrl,
@@ -1816,7 +2251,7 @@ async function uploadHostedFontBundle(api, bundle, options = {}) {
1816
2251
  }
1817
2252
  return { ok: true, value };
1818
2253
  }
1819
- async function fetchHostedProjectStatus(api, options = {}) {
2254
+ async function fetchHostedMapStatus(api, options = {}) {
1820
2255
  const response = await requestHostedJson(
1821
2256
  api.apiUrl,
1822
2257
  "/v1/status",
@@ -1824,7 +2259,7 @@ async function fetchHostedProjectStatus(api, options = {}) {
1824
2259
  options
1825
2260
  );
1826
2261
  if (!response.ok) throw new Error(`Status failed: ${response.status}.`);
1827
- return parseResponse(response, hostedProjectStatusSchema, "Status response");
2262
+ return parseResponse(response, hostedMapStatusSchema, "Status response");
1828
2263
  }
1829
2264
  async function revokeHostedAccountSession(apiUrl, accountSession, options = {}) {
1830
2265
  try {
@@ -1981,20 +2416,54 @@ function safeTransportError(error, fallback) {
1981
2416
  }
1982
2417
 
1983
2418
  // src/hosted-preflight.ts
1984
- import { compareCodeUnits as compareCodeUnits3, tileflowHostedAlphaCompatibility } from "@tileflow/core";
1985
- function inspectTileflowHostedCompatibility(project, styles) {
1986
- const issues = [];
1987
- const mapNames = Object.keys(project.maps).sort(compareCodeUnits3);
1988
- if (mapNames.length > tileflowHostedAlphaCompatibility.maxMapsPerDeploy) {
1989
- issues.push({
1990
- message: `Hosted alpha accepts at most ${tileflowHostedAlphaCompatibility.maxMapsPerDeploy} maps per deploy.`,
1991
- path: "maps"
1992
- });
1993
- }
1994
- for (const mapName of [...mapNames].sort(compareCodeUnits3)) {
1995
- const themes = styles[mapName] ?? {};
1996
- for (const themeName of Object.keys(themes).sort(compareCodeUnits3)) {
1997
- const data = themes[themeName]?.metadata?.["tileflow:data"];
2419
+ import {
2420
+ compareCodeUnits as compareCodeUnits4,
2421
+ tileflowHostedAlphaCompatibility
2422
+ } from "@tileflow/core";
2423
+ function prepareTileflowHostedThemeFamily(mapName, map, styles) {
2424
+ const prepared = Object.fromEntries(
2425
+ Object.entries(styles).sort(([left], [right]) => compareCodeUnits4(left, right)).map(([themeName, style]) => [themeName, cloneStyle(style)])
2426
+ );
2427
+ const teamSources = Object.fromEntries(
2428
+ Object.entries(map.sources ?? {}).sort(([left], [right]) => compareCodeUnits4(left, right)).map(([sourceId, source]) => {
2429
+ for (const [themeName, style] of Object.entries(prepared)) {
2430
+ const definition = style.sources[sourceId];
2431
+ if (!definition) {
2432
+ throw Object.assign(
2433
+ new Error(`Hosted theme ${themeName} is missing logical source ${sourceId}.`),
2434
+ {
2435
+ code: "TF_HOSTED_SOURCE_MISSING",
2436
+ path: `maps.${mapName}.themes.${themeName}.sources.${sourceId}`
2437
+ }
2438
+ );
2439
+ }
2440
+ definition.url = `tileflow://hosted-sources/${sourceId}`;
2441
+ delete definition.tiles;
2442
+ }
2443
+ return [
2444
+ sourceId,
2445
+ Object.freeze({
2446
+ tileset: source.tileset,
2447
+ type: source.type
2448
+ })
2449
+ ];
2450
+ })
2451
+ );
2452
+ return { styles: prepared, teamSources };
2453
+ }
2454
+ function inspectTileflowHostedCompatibility(project, styles) {
2455
+ const issues = [];
2456
+ const mapNames = Object.keys(project.maps).sort(compareCodeUnits4);
2457
+ if (mapNames.length > tileflowHostedAlphaCompatibility.maxMapsPerDeploy) {
2458
+ issues.push({
2459
+ message: `Hosted alpha accepts at most ${tileflowHostedAlphaCompatibility.maxMapsPerDeploy} maps per deploy.`,
2460
+ path: "maps"
2461
+ });
2462
+ }
2463
+ for (const mapName of [...mapNames].sort(compareCodeUnits4)) {
2464
+ const themes = styles[mapName] ?? {};
2465
+ for (const themeName of Object.keys(themes).sort(compareCodeUnits4)) {
2466
+ const data = themes[themeName]?.metadata?.["tileflow:data"];
1998
2467
  if (!data || typeof data !== "object" || Array.isArray(data) || data.kind !== "tileflow-world") {
1999
2468
  issues.push({
2000
2469
  map: mapName,
@@ -2006,9 +2475,12 @@ function inspectTileflowHostedCompatibility(project, styles) {
2006
2475
  }
2007
2476
  return issues;
2008
2477
  }
2478
+ function cloneStyle(style) {
2479
+ return JSON.parse(JSON.stringify(style));
2480
+ }
2009
2481
 
2010
2482
  // src/icon-diff-command.ts
2011
- import { dirname as dirname5, resolve as resolve4 } from "path";
2483
+ import { dirname as dirname6, resolve as resolve4 } from "path";
2012
2484
  import { z as z4 } from "zod";
2013
2485
  import {
2014
2486
  diffTileflowIconPackageManifests,
@@ -2022,17 +2494,17 @@ import {
2022
2494
  import {
2023
2495
  assertValidTileflowConfig,
2024
2496
  getTileflowMapNames,
2025
- loadTileflowConfigWithInputs
2497
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs2
2026
2498
  } from "@tileflow/dev/config";
2027
2499
  import { compileTileflowIconPackages } from "@tileflow/dev/icons";
2028
2500
 
2029
2501
  // src/icon-diff-report.ts
2030
2502
  import { randomUUID as randomUUID2 } from "crypto";
2031
2503
  import { mkdir as mkdir3, readFile as readFile3, rename as rename3, unlink as unlink3, writeFile } from "fs/promises";
2032
- import { basename as basename2, dirname as dirname4, join } from "path";
2504
+ import { basename as basename2, dirname as dirname5, join } from "path";
2033
2505
  import { z as z3 } from "zod";
2034
2506
  import {
2035
- compareCodeUnits as compareCodeUnits4,
2507
+ compareCodeUnits as compareCodeUnits5,
2036
2508
  sha256Hex,
2037
2509
  tileflowIconIdSchema,
2038
2510
  tileflowIconPackageLimits as tileflowIconPackageLimits2
@@ -2153,9 +2625,9 @@ async function readBoundedResponse(response, exactMaximum) {
2153
2625
  }
2154
2626
  function renderReport(document, before, after, mapName) {
2155
2627
  const changeGroups = [
2156
- { id: "added", label: "Added", names: [...document.icons.added].sort(compareCodeUnits4) },
2157
- { id: "modified", label: "Modified", names: [...document.icons.modified].sort(compareCodeUnits4) },
2158
- { id: "removed", label: "Removed", names: [...document.icons.removed].sort(compareCodeUnits4) }
2628
+ { id: "added", label: "Added", names: [...document.icons.added].sort(compareCodeUnits5) },
2629
+ { id: "modified", label: "Modified", names: [...document.icons.modified].sort(compareCodeUnits5) },
2630
+ { id: "removed", label: "Removed", names: [...document.icons.removed].sort(compareCodeUnits5) }
2159
2631
  ];
2160
2632
  const changedCount = changeGroups.reduce((total, group) => total + group.names.length, 0);
2161
2633
  const beforeIconCount = document.icons.unchangedCount + document.icons.modified.length + document.icons.removed.length;
@@ -2500,9 +2972,9 @@ async function writeAtomicReport(path, html, force) {
2500
2972
  } catch (error) {
2501
2973
  if (!isMissingFile3(error)) throw error;
2502
2974
  }
2503
- await mkdir3(dirname4(path), { recursive: true });
2975
+ await mkdir3(dirname5(path), { recursive: true });
2504
2976
  const temporaryPath = join(
2505
- dirname4(path),
2977
+ dirname5(path),
2506
2978
  `.${basename2(path)}.${process.pid}.${randomUUID2()}.tmp`
2507
2979
  );
2508
2980
  try {
@@ -2583,7 +3055,7 @@ var tileflowIconDiffDocumentSchema = z4.object({
2583
3055
  hasChanges: z4.boolean()
2584
3056
  }).strict();
2585
3057
  function registerIconDiffCommand(icons, dependencies) {
2586
- icons.command("diff").description("Preview a local icon package against an active hosted revision").requiredOption("--against <environment>", "named map environment to compare").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--project <target>", "technical destination @organization/project").option("--json", "print deterministic schema-version-1 JSON").option("--report <path>", "write a self-contained HTML visual report").option("--open", "open an explicitly requested report").option("--force", "replace a different explicitly requested report").action(async (options) => {
3058
+ icons.command("diff").description("Preview a local icon package against an active hosted revision").requiredOption("--against <environment>", "named map environment to compare").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).requiredOption("--map-id <id>", "managed Map destination").option("--json", "print deterministic schema-version-1 JSON").option("--report <path>", "write a self-contained HTML visual report").option("--open", "open an explicitly requested report").option("--force", "replace a different explicitly requested report").action(async (options) => {
2587
3059
  try {
2588
3060
  await runIconDiff(options, dependencies);
2589
3061
  } catch (error) {
@@ -2603,12 +3075,8 @@ async function runIconDiff(options, dependencies) {
2603
3075
  throw new Error("--force requires --report <path>");
2604
3076
  }
2605
3077
  const environment = environmentSchema.parse(options.against);
2606
- const api = await dependencies.resolveApi(options);
2607
- if (!api) {
2608
- throw new Error("Missing Tileflow API key. Run tileflow login or set TILEFLOW_API_KEY.");
2609
- }
2610
3078
  const loaded = await withTileflowConfigSecretsHidden(
2611
- () => loadTileflowConfigWithInputs(options.config)
3079
+ () => loadTileflowConfigWithInputs2(options.config)
2612
3080
  );
2613
3081
  const project = loaded.project;
2614
3082
  assertValidTileflowConfig(project);
@@ -2618,9 +3086,13 @@ async function runIconDiff(options, dependencies) {
2618
3086
  `Unknown map environment "${environment}". Available maps: ${mapNames.join(", ") || "(none)"}`
2619
3087
  );
2620
3088
  }
3089
+ const api = await dependencies.resolveApi(options);
3090
+ if (!api) {
3091
+ throw new Error("Missing Tileflow API key. Run tileflow login or set TILEFLOW_API_KEY.");
3092
+ }
2621
3093
  const selectedProject = { ...project, maps: { [environment]: project.maps[environment] } };
2622
3094
  const compiled = await compileTileflowIconPackages(selectedProject, {
2623
- baseDirectory: dirname5(loaded.configFile),
3095
+ baseDirectory: dirname6(loaded.configFile),
2624
3096
  cwd: process.cwd(),
2625
3097
  target: "hosted"
2626
3098
  });
@@ -2763,8 +3235,8 @@ function formatSignedBytes(bytes) {
2763
3235
  }
2764
3236
 
2765
3237
  // src/icon-list-command.ts
2766
- import { dirname as dirname6 } from "path";
2767
- import { compareCodeUnits as compareCodeUnits5 } from "@tileflow/core";
3238
+ import { dirname as dirname7 } from "path";
3239
+ import { compareCodeUnits as compareCodeUnits6 } from "@tileflow/core";
2768
3240
  import {
2769
3241
  getTileflowMapNames as getTileflowMapNames2,
2770
3242
  loadValidTileflowConfigWithInputs as loadValidTileflowConfigWithInputs2,
@@ -2795,7 +3267,7 @@ function createTileflowIconListJson(inspection) {
2795
3267
  return {
2796
3268
  schemaVersion: 2,
2797
3269
  pathBase: "cwd",
2798
- maps: [...inspection.maps].sort((left, right) => compareCodeUnits5(left.name, right.name)).map((map) => createMapJson(map, inspection.catalogs))
3270
+ maps: [...inspection.maps].sort((left, right) => compareCodeUnits6(left.name, right.name)).map((map) => createMapJson(map, inspection.catalogs))
2799
3271
  };
2800
3272
  }
2801
3273
  function serializeTileflowIconListJson(value) {
@@ -2807,14 +3279,14 @@ async function runIconList(options) {
2807
3279
  () => loadValidTileflowConfigWithInputs2(options.config)
2808
3280
  );
2809
3281
  const project = loaded.project;
2810
- const mapIds = getTileflowMapNames2(project).sort(compareCodeUnits5);
3282
+ const mapIds = getTileflowMapNames2(project).sort(compareCodeUnits6);
2811
3283
  if (options.map && !Object.hasOwn(project.maps, options.map)) {
2812
3284
  throw new Error(
2813
3285
  `Unknown map "${options.map}". Available maps: ${mapIds.join(", ") || "(none)"}`
2814
3286
  );
2815
3287
  }
2816
3288
  const inspection = await inspectTileflowIconCatalogs(project, {
2817
- baseDirectory: dirname6(loaded.configFile),
3289
+ baseDirectory: dirname7(loaded.configFile),
2818
3290
  cwd: process.cwd(),
2819
3291
  ...options.map ? { mapNames: [options.map] } : {}
2820
3292
  });
@@ -2844,7 +3316,7 @@ function createMapJson(map, catalogs) {
2844
3316
  winner: replacement.winner
2845
3317
  })),
2846
3318
  packageHash: mapIcons.packageHash,
2847
- sources: [...catalog.icons].sort((left, right) => compareCodeUnits5(left.id, right.id)).map((icon) => ({
3319
+ sources: [...catalog.icons].sort((left, right) => compareCodeUnits6(left.id, right.id)).map((icon) => ({
2848
3320
  id: icon.id,
2849
3321
  path: icon.source.path,
2850
3322
  format: icon.source.format,
@@ -2874,6 +3346,83 @@ function printIssues(heading, issues) {
2874
3346
  );
2875
3347
  }
2876
3348
 
3349
+ // src/language-command.ts
3350
+ import { readFile as readFile4 } from "fs/promises";
3351
+ import { fileURLToPath } from "url";
3352
+ import {
3353
+ serializeCanonicalJson,
3354
+ tileflowAuthoringManifest
3355
+ } from "@tileflow/core";
3356
+ import {
3357
+ createTileflowCommandFailureDocument as createTileflowCommandFailureDocument3,
3358
+ serializeTileflowCommandDocument as serializeTileflowCommandDocument3
3359
+ } from "@tileflow/dev/validation";
3360
+ function registerLanguageCommand(program2, dependencies = {}) {
3361
+ const language = program2.command("language").description("Inspect the complete machine-readable Tileflow semantic language");
3362
+ language.command("manifest").description("Print the semantic compiler authoring manifest").option("--json", "print deterministic JSON").action(
3363
+ async (options) => runLanguageCommand(
3364
+ "language-manifest",
3365
+ options,
3366
+ () => printManifest((dependencies.loadManifest ?? (() => tileflowAuthoringManifest))(), options)
3367
+ )
3368
+ );
3369
+ language.command("schema").description("Print the generated authoring and resolved JSON Schema").option("--json", "print deterministic JSON").action(
3370
+ async (options) => runLanguageCommand(
3371
+ "language-schema",
3372
+ options,
3373
+ async () => printSchema(await (dependencies.loadSchema ?? loadPackagedSchema)(), options)
3374
+ )
3375
+ );
3376
+ }
3377
+ async function runLanguageCommand(command2, options, operation) {
3378
+ try {
3379
+ await operation();
3380
+ } catch (error) {
3381
+ const failure = createTileflowCommandFailureDocument3(command2, error, process.cwd(), {
3382
+ code: command2 === "language-manifest" ? "LANGUAGE_MANIFEST_FAILED" : "LANGUAGE_SCHEMA_FAILED",
3383
+ phase: "language-discovery"
3384
+ });
3385
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument3(failure));
3386
+ else {
3387
+ console.error(`${failure.message} [${failure.code}; ${failure.phase}]`);
3388
+ console.error(`Suggestion: ${failure.suggestion}`);
3389
+ }
3390
+ process.exitCode = 1;
3391
+ }
3392
+ }
3393
+ function printManifest(manifest, options) {
3394
+ if (options.json) {
3395
+ process.stdout.write(`${serializeCanonicalJson(manifest)}
3396
+ `);
3397
+ return;
3398
+ }
3399
+ console.log(
3400
+ `${manifest.language} (${manifest.compiler.name} v${manifest.compiler.version}); ${manifest.domains.length} domains, ${manifest.expressions.builders.length} expression builders.`
3401
+ );
3402
+ console.log("Run tileflow language manifest --json for the complete machine-readable document.");
3403
+ }
3404
+ function printSchema(schema, options) {
3405
+ if (options.json) {
3406
+ process.stdout.write(`${serializeCanonicalJson(schema)}
3407
+ `);
3408
+ return;
3409
+ }
3410
+ console.log(
3411
+ `Tileflow config reference schema ${String(schema.schemaVersion)}: ${String(schema.$id)}.`
3412
+ );
3413
+ console.log("Run tileflow language schema --json for the complete machine-readable document.");
3414
+ }
3415
+ async function loadPackagedSchema() {
3416
+ const schemaUrl = import.meta.resolve("@tileflow/core/config-reference.json");
3417
+ const parsed = JSON.parse(await readFile4(fileURLToPath(schemaUrl), "utf8"));
3418
+ if (!isRecord(parsed))
3419
+ throw new Error("Packaged Tileflow config reference is not a JSON object.");
3420
+ return parsed;
3421
+ }
3422
+ function isRecord(value) {
3423
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
3424
+ }
3425
+
2877
3426
  // src/open-external.ts
2878
3427
  import { spawn as spawnChild } from "child_process";
2879
3428
  import { platform as hostPlatform } from "os";
@@ -3050,147 +3599,1233 @@ async function fetchAccountProjects(session, includeArchived) {
3050
3599
  if (!Array.isArray(body.items) || !body.items.every(isAccountProjectTarget)) {
3051
3600
  return { error: "Destination discovery returned an invalid response.", ok: false, status: 502 };
3052
3601
  }
3053
- items.push(...body.items);
3054
- cursor = typeof body.nextCursor === "string" ? body.nextCursor : null;
3055
- if (!cursor) return validateTargetOrder(items);
3056
- }
3057
- return { error: "Destination discovery exceeded its safe page limit.", ok: false, status: 502 };
3602
+ items.push(...body.items);
3603
+ cursor = typeof body.nextCursor === "string" ? body.nextCursor : null;
3604
+ if (!cursor) return validateTargetOrder(items);
3605
+ }
3606
+ return { error: "Destination discovery exceeded its safe page limit.", ok: false, status: 502 };
3607
+ }
3608
+ async function resolveAccountProjectTarget(session, requestedProject) {
3609
+ const result = await fetchAccountProjects(session, false);
3610
+ if (!result.ok) return { error: result.error, kind: "remote", status: result.status };
3611
+ if (requestedProject) {
3612
+ const parsed = parseProjectReference(requestedProject);
3613
+ if (!parsed) return { kind: "invalid", targets: result.items };
3614
+ const target = result.items.find((candidate) => candidate.reference === requestedProject);
3615
+ return target ? { kind: "selected", target } : { kind: "missing", targets: result.items };
3616
+ }
3617
+ if (result.items.length === 1) return { kind: "selected", target: result.items[0] };
3618
+ return result.items.length ? { kind: "ambiguous", targets: result.items } : { kind: "missing", targets: [] };
3619
+ }
3620
+ async function requireCommandSession(options, dependencies) {
3621
+ let config;
3622
+ try {
3623
+ config = await dependencies.loadAuthConfig();
3624
+ } catch (error) {
3625
+ emitLocalFailure(options.json, "auth_state_unavailable", safeMessage(error));
3626
+ return null;
3627
+ }
3628
+ const resolved = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
3629
+ if (resolved.kind !== "selected") {
3630
+ emitLocalFailure(
3631
+ options.json,
3632
+ `account_session_${resolved.kind}`,
3633
+ resolved.kind === "expired" ? "The Tileflow account session has expired. Run tileflow login." : "Not logged in. Run tileflow login."
3634
+ );
3635
+ return null;
3636
+ }
3637
+ return resolved.session;
3638
+ }
3639
+ async function accountRequest(session, path, init) {
3640
+ let response;
3641
+ try {
3642
+ response = await requestHostedJson(session.apiOrigin, path, {
3643
+ ...init,
3644
+ headers: { ...init.headers, Authorization: `Bearer ${session.accountSession}` }
3645
+ });
3646
+ } catch (error) {
3647
+ return {
3648
+ body: null,
3649
+ error: error instanceof Error && /safe size limit|timed out/u.test(error.message) ? error.message : "Hosted request failed.",
3650
+ ok: false,
3651
+ status: 502
3652
+ };
3653
+ }
3654
+ if (response.ok && !response.json) {
3655
+ return {
3656
+ body: null,
3657
+ error: "Hosted response returned invalid JSON.",
3658
+ ok: false,
3659
+ status: 502
3660
+ };
3661
+ }
3662
+ return response.ok ? { body: response.body, ok: true, status: response.status } : {
3663
+ body: response.body,
3664
+ error: `Request failed (${response.status}).`,
3665
+ ok: false,
3666
+ status: response.status
3667
+ };
3668
+ }
3669
+ function validateTargetOrder(items) {
3670
+ const references = items.map((item) => item.reference);
3671
+ if (new Set(references).size !== references.length || references.some((reference, index) => index > 0 && references[index - 1] >= reference)) {
3672
+ return {
3673
+ error: "Destination discovery returned unstable ordering.",
3674
+ ok: false,
3675
+ status: 502
3676
+ };
3677
+ }
3678
+ return { items, ok: true };
3679
+ }
3680
+ function uniqueOrganizations(items) {
3681
+ return [...new Map(items.map((item) => [item.organization.id, item.organization])).values()];
3682
+ }
3683
+ function parseOrganizationReference(value) {
3684
+ if (!value) return null;
3685
+ const match = /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(value);
3686
+ return match?.[1] ?? null;
3687
+ }
3688
+ function emitAmbiguousOrganizations(json, organizations, slug, name) {
3689
+ const references = organizations.map((organization) => `@${organization.slug}`);
3690
+ const retry = `tileflow projects create ${slug} --name ${JSON.stringify(name.trim())} --organization ${references[0]}`;
3691
+ return emitLocalFailure(
3692
+ json,
3693
+ "organization_ambiguous",
3694
+ `More than one organization is available: ${references.join(", ")}. Retry with: ${retry}`,
3695
+ { options: references, retry }
3696
+ );
3697
+ }
3698
+ function emitRemoteFailure(json, result) {
3699
+ return emitLocalFailure(json, `http_${result.status}`, result.error);
3700
+ }
3701
+ function emitLocalFailure(json, code, message, details = {}) {
3702
+ if (json) {
3703
+ console.error(message);
3704
+ emitJson({ schemaVersion: 1, ok: false, error: { code, ...details } });
3705
+ } else {
3706
+ console.error(`${pc.red("\u2715")} ${message}`);
3707
+ }
3708
+ process.exitCode = 1;
3709
+ }
3710
+ function emitJson(value) {
3711
+ process.stdout.write(`${JSON.stringify(value)}
3712
+ `);
3713
+ }
3714
+ function asRecord2(value) {
3715
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3716
+ }
3717
+ function isAccountProjectTarget(value) {
3718
+ const target = asRecord2(value);
3719
+ return isIdentity(target.organization) && isProjectItem(target.project) && typeof target.reference === "string" && target.reference === `@${target.organization.slug}/${target.project.slug}`;
3720
+ }
3721
+ function isIdentity(value) {
3722
+ const identity = asRecord2(value);
3723
+ return typeof identity.id === "string" && typeof identity.name === "string" && typeof identity.slug === "string";
3724
+ }
3725
+ function isProjectItem(value) {
3726
+ const item = asRecord2(value);
3727
+ return isIdentity(value) && (item.archivedAt === null || typeof item.archivedAt === "string") && typeof item.createdAt === "string" && typeof item.updatedAt === "string";
3728
+ }
3729
+ function validSlug(value) {
3730
+ return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value);
3731
+ }
3732
+ function validName(value) {
3733
+ const normalized = value.trim();
3734
+ return normalized.length > 0 && normalized.length <= 200 && !/[\p{Cc}]/u.test(normalized);
3735
+ }
3736
+ function safeMessage(error) {
3737
+ return error instanceof Error && error.message.length <= 300 ? error.message : "Tileflow auth state is unavailable.";
3738
+ }
3739
+
3740
+ // src/tileset-command.ts
3741
+ import pc2 from "picocolors";
3742
+ import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
3743
+ import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
3744
+ import { z as z6 } from "zod";
3745
+
3746
+ // src/tileset-upload.ts
3747
+ import { createHash } from "crypto";
3748
+ import { open as open3, stat } from "fs/promises";
3749
+ import { z as z5 } from "zod";
3750
+ import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
3751
+ var teamTilesetPartSize = 16777216;
3752
+ var maximumTeamTilesetBytes = 4e9;
3753
+ var authorizationBatchSize = 16;
3754
+ var uploadConcurrency = 4;
3755
+ var maximumPartAttempts = 4;
3756
+ var maximumControlAttempts = 4;
3757
+ var maximumInitiatingWaitMs = 2 * 60 * 1e3;
3758
+ var partTransferTimeoutMs = 10 * 60 * 1e3;
3759
+ var authorizationClockSkewMs = 3e4;
3760
+ var minimumPartAuthorizationLifetimeMs = partTransferTimeoutMs + authorizationClockSkewMs;
3761
+ var TeamTilesetUploadError = class extends Error {
3762
+ code;
3763
+ details;
3764
+ constructor(code, message, details = {}) {
3765
+ super(message);
3766
+ this.name = "TeamTilesetUploadError";
3767
+ this.code = code;
3768
+ this.details = details;
3769
+ }
3770
+ };
3771
+ function createTeamTilesetPartLayout(byteCount) {
3772
+ if (!Number.isSafeInteger(byteCount) || byteCount < 1 || byteCount > maximumTeamTilesetBytes) {
3773
+ throw new TeamTilesetUploadError(
3774
+ "TF_TILESET_VERSION_TOO_LARGE",
3775
+ "PMTiles archive exceeds the 4,000,000,000-byte platform maximum.",
3776
+ {
3777
+ actualBytes: Number.isSafeInteger(byteCount) && byteCount >= 0 ? byteCount : 0,
3778
+ limitBytes: maximumTeamTilesetBytes
3779
+ }
3780
+ );
3781
+ }
3782
+ return Object.freeze(
3783
+ Array.from({ length: Math.ceil(byteCount / teamTilesetPartSize) }, (_, index) => {
3784
+ const position = index * teamTilesetPartSize;
3785
+ return Object.freeze({
3786
+ byteCount: Math.min(teamTilesetPartSize, byteCount - position),
3787
+ partNumber: index + 1,
3788
+ position
3789
+ });
3790
+ })
3791
+ );
3792
+ }
3793
+ async function inspectNodeTeamTilesetArchive(path) {
3794
+ let value;
3795
+ try {
3796
+ value = await stat(path, { bigint: true });
3797
+ } catch {
3798
+ throw new TeamTilesetUploadError(
3799
+ "TF_TILESET_ARCHIVE_INVALID",
3800
+ "PMTiles archive is unavailable."
3801
+ );
3802
+ }
3803
+ if (!value.isFile()) {
3804
+ throw new TeamTilesetUploadError(
3805
+ "TF_TILESET_ARCHIVE_INVALID",
3806
+ "PMTiles archive must be a file."
3807
+ );
3808
+ }
3809
+ return checkedIdentity(value.dev, value.ino, value.mtimeNs, value.size);
3810
+ }
3811
+ async function openNodeTeamTilesetArchive(path, expectedIdentity) {
3812
+ const handle = await open3(path, "r");
3813
+ const identity = async () => {
3814
+ let pathStat;
3815
+ let handleStat;
3816
+ try {
3817
+ [pathStat, handleStat] = await Promise.all([
3818
+ stat(path, { bigint: true }),
3819
+ handle.stat({ bigint: true })
3820
+ ]);
3821
+ } catch {
3822
+ throw localFileChanged();
3823
+ }
3824
+ if (!pathStat.isFile() || !handleStat.isFile()) throw localFileChanged();
3825
+ const pathIdentity = checkedIdentity(
3826
+ pathStat.dev,
3827
+ pathStat.ino,
3828
+ pathStat.mtimeNs,
3829
+ pathStat.size
3830
+ );
3831
+ const handleIdentity = checkedIdentity(
3832
+ handleStat.dev,
3833
+ handleStat.ino,
3834
+ handleStat.mtimeNs,
3835
+ handleStat.size
3836
+ );
3837
+ assertFileIdentity(pathIdentity, handleIdentity);
3838
+ return pathIdentity;
3839
+ };
3840
+ try {
3841
+ assertFileIdentity(expectedIdentity, await identity());
3842
+ } catch (error) {
3843
+ await handle.close();
3844
+ throw error;
3845
+ }
3846
+ return {
3847
+ close: () => handle.close(),
3848
+ identity,
3849
+ async read(position, byteCount) {
3850
+ const bytes = Buffer.allocUnsafe(byteCount);
3851
+ let offset = 0;
3852
+ while (offset < byteCount) {
3853
+ const result = await handle.read(bytes, offset, byteCount - offset, position + offset);
3854
+ if (result.bytesRead === 0) throw localFileChanged();
3855
+ offset += result.bytesRead;
3856
+ }
3857
+ return bytes;
3858
+ }
3859
+ };
3860
+ }
3861
+ async function prepareTeamTilesetUpload(file) {
3862
+ const identity = await file.identity();
3863
+ const layout = createTeamTilesetPartLayout(identity.size);
3864
+ const parts = [];
3865
+ for (const part of layout) {
3866
+ const bytes = await file.read(part.position, part.byteCount);
3867
+ if (bytes.byteLength !== part.byteCount) throw localFileChanged();
3868
+ parts.push(
3869
+ Object.freeze({
3870
+ byteCount: part.byteCount,
3871
+ md5Base64: createHash("md5").update(bytes).digest("base64"),
3872
+ partNumber: part.partNumber,
3873
+ sha256Base64: createHash("sha256").update(bytes).digest("base64")
3874
+ })
3875
+ );
3876
+ }
3877
+ assertFileIdentity(identity, await file.identity());
3878
+ const contentIdentity = {
3879
+ algorithm: "sha256-fixed-parts-v1",
3880
+ byteCount: identity.size,
3881
+ partSize: teamTilesetPartSize,
3882
+ parts: parts.map(({ byteCount, partNumber, sha256Base64 }) => ({
3883
+ byteCount,
3884
+ partNumber,
3885
+ sha256Base64
3886
+ }))
3887
+ };
3888
+ return Object.freeze({
3889
+ byteCount: identity.size,
3890
+ contentHash: createHash("sha256").update(serializeCanonicalJson2(contentIdentity)).digest("hex"),
3891
+ contentHashAlgorithm: "sha256-fixed-parts-v1",
3892
+ identity,
3893
+ partSize: teamTilesetPartSize,
3894
+ parts: Object.freeze(parts),
3895
+ schemaVersion: 1
3896
+ });
3897
+ }
3898
+ function createTeamTilesetUploadIdempotencyKey(tilesetId, upload) {
3899
+ if (!/^tls_[A-Za-z0-9_-]{16,76}$/u.test(tilesetId)) {
3900
+ throw new TeamTilesetUploadError(
3901
+ "TF_TILESET_NOT_FOUND",
3902
+ "Tileset registration returned an invalid ID."
3903
+ );
3904
+ }
3905
+ return createHash("sha256").update(
3906
+ serializeCanonicalJson2({
3907
+ byteCount: upload.byteCount,
3908
+ contentHash: upload.contentHash,
3909
+ contentHashAlgorithm: upload.contentHashAlgorithm,
3910
+ tilesetId
3911
+ })
3912
+ ).digest("hex");
3913
+ }
3914
+ async function publishPreparedTeamTilesetUpload(input) {
3915
+ assertFileIdentity(input.upload.identity, await input.file.identity());
3916
+ const beginInput = {
3917
+ byteCount: input.upload.byteCount,
3918
+ contentHash: input.upload.contentHash,
3919
+ contentHashAlgorithm: input.upload.contentHashAlgorithm,
3920
+ idempotencyKey: createTeamTilesetUploadIdempotencyKey(input.tilesetId, input.upload),
3921
+ parts: input.upload.parts,
3922
+ partSize: input.upload.partSize,
3923
+ schemaVersion: 1
3924
+ };
3925
+ const begun = await input.transport.begin(beginInput);
3926
+ assertSessionMatches(begun, input.upload);
3927
+ const changed = begun.state !== "published";
3928
+ if (begun.state === "published") return publicationResult(begun, input.upload, changed);
3929
+ let current = await input.transport.status(begun.uploadId);
3930
+ assertSessionMatches(current, input.upload, true);
3931
+ assertUsableSession(current);
3932
+ let initiatingWaitMs = 0;
3933
+ for (let attempt = 1; current.state === "initiating"; attempt += 1) {
3934
+ const delay = Math.min(
3935
+ 2e3,
3936
+ 250 * 2 ** Math.min(attempt - 1, 3),
3937
+ maximumInitiatingWaitMs - initiatingWaitMs
3938
+ );
3939
+ if (delay <= 0) {
3940
+ throw new TeamTilesetUploadError(
3941
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
3942
+ "Tileset upload initialization did not converge within two minutes."
3943
+ );
3944
+ }
3945
+ await (input.sleep ?? defaultSleep)(delay);
3946
+ initiatingWaitMs += delay;
3947
+ current = await input.transport.status(current.uploadId);
3948
+ assertSessionMatches(current, input.upload, true);
3949
+ assertUsableSession(current);
3950
+ }
3951
+ if (current.state === "uploading") {
3952
+ const uploading = current;
3953
+ const missingParts = input.upload.parts.filter(
3954
+ (part) => !uploading.parts.find((candidate) => candidate.partNumber === part.partNumber)?.uploaded
3955
+ );
3956
+ await uploadMissingParts({ ...input, missingParts, uploadId: uploading.uploadId });
3957
+ assertFileIdentity(input.upload.identity, await input.file.identity());
3958
+ current = await input.transport.complete(uploading.uploadId);
3959
+ assertSessionMatches(current, input.upload);
3960
+ assertUsableSession(current);
3961
+ }
3962
+ while (current.state !== "published") {
3963
+ if (!["publishing", "ready_to_publish", "validating"].includes(current.state)) {
3964
+ throw new TeamTilesetUploadError(
3965
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
3966
+ "Tileset upload cannot continue from its current state."
3967
+ );
3968
+ }
3969
+ input.onProgress?.({
3970
+ completedParts: input.upload.parts.length,
3971
+ phase: "validating",
3972
+ totalParts: input.upload.parts.length
3973
+ });
3974
+ await (input.sleep ?? defaultSleep)(2e3);
3975
+ current = await input.transport.status(current.uploadId);
3976
+ assertSessionMatches(current, input.upload, true);
3977
+ assertUsableSession(current);
3978
+ }
3979
+ return publicationResult(current, input.upload, changed);
3980
+ }
3981
+ function createHostedTeamTilesetUploadTransport(input) {
3982
+ const basePath = `/v1/tilesets/${encodeURIComponent(input.tilesetId)}/uploads`;
3983
+ const control = async (path, init) => {
3984
+ for (let attempt = 1; attempt <= maximumControlAttempts; attempt += 1) {
3985
+ let response;
3986
+ try {
3987
+ response = await requestHostedJson(
3988
+ input.apiOrigin,
3989
+ path,
3990
+ {
3991
+ ...init,
3992
+ headers: {
3993
+ Authorization: `Bearer ${input.credential}`,
3994
+ ...init.body ? { "Content-Type": "application/json" } : {},
3995
+ ...init.headers
3996
+ }
3997
+ },
3998
+ { fetch: input.fetch }
3999
+ );
4000
+ } catch {
4001
+ if (attempt === maximumControlAttempts) {
4002
+ throw new TeamTilesetUploadError(
4003
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
4004
+ "Tileset upload control request did not complete."
4005
+ );
4006
+ }
4007
+ }
4008
+ if (response?.ok) return response.body;
4009
+ if (response && !isRetryableStatus(response.status)) throw responseError(response);
4010
+ if (attempt === maximumControlAttempts) {
4011
+ if (response) throw responseError(response);
4012
+ break;
4013
+ }
4014
+ await (input.sleep ?? defaultSleep)(Math.min(2e3, 250 * 2 ** (attempt - 1)));
4015
+ }
4016
+ throw new TeamTilesetUploadError(
4017
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
4018
+ "Tileset upload control request did not complete."
4019
+ );
4020
+ };
4021
+ return {
4022
+ async authorize(uploadId, partNumbers) {
4023
+ const body = await control(
4024
+ `${basePath}/${encodeURIComponent(uploadId)}/part-authorizations`,
4025
+ {
4026
+ body: JSON.stringify({ partNumbers, schemaVersion: 1 }),
4027
+ method: "POST"
4028
+ }
4029
+ );
4030
+ return authorizationResponseSchema.parse(body).authorizations;
4031
+ },
4032
+ async begin(body) {
4033
+ return parseUploadSession(
4034
+ await control(basePath, { body: JSON.stringify(body), method: "POST" })
4035
+ );
4036
+ },
4037
+ async complete(uploadId) {
4038
+ return parseUploadSession(
4039
+ await control(`${basePath}/${encodeURIComponent(uploadId)}/complete`, {
4040
+ body: JSON.stringify({ schemaVersion: 1 }),
4041
+ method: "POST"
4042
+ })
4043
+ );
4044
+ },
4045
+ async putPart(authorization, bytes) {
4046
+ let response;
4047
+ try {
4048
+ const body = new Uint8Array(
4049
+ bytes.buffer,
4050
+ bytes.byteOffset,
4051
+ bytes.byteLength
4052
+ );
4053
+ response = await (input.fetch ?? globalThis.fetch)(authorization.url, {
4054
+ body,
4055
+ headers: authorization.headers,
4056
+ method: "PUT",
4057
+ redirect: "error",
4058
+ signal: AbortSignal.timeout(partTransferTimeoutMs)
4059
+ });
4060
+ } catch {
4061
+ throw new Error("Direct part transfer failed.");
4062
+ }
4063
+ const etag = response.headers.get("etag");
4064
+ await response.body?.cancel().catch(() => void 0);
4065
+ return { etag, status: response.status };
4066
+ },
4067
+ async recordPart(uploadId, body) {
4068
+ partReceiptResponseSchema.parse(
4069
+ await control(`${basePath}/${encodeURIComponent(uploadId)}/parts`, {
4070
+ body: JSON.stringify(body),
4071
+ method: "POST"
4072
+ })
4073
+ );
4074
+ },
4075
+ async status(uploadId) {
4076
+ return parseUploadSession(
4077
+ await control(`${basePath}/${encodeURIComponent(uploadId)}`, { method: "GET" })
4078
+ );
4079
+ }
4080
+ };
4081
+ }
4082
+ async function uploadMissingParts(input) {
4083
+ let completedParts = input.upload.parts.length - input.missingParts.length;
4084
+ for (let offset = 0; offset < input.missingParts.length; offset += authorizationBatchSize) {
4085
+ const authorizationParts = input.missingParts.slice(offset, offset + authorizationBatchSize);
4086
+ const initial = indexAuthorizations(
4087
+ await input.transport.authorize(
4088
+ input.uploadId,
4089
+ authorizationParts.map(({ partNumber }) => partNumber)
4090
+ ),
4091
+ authorizationParts
4092
+ );
4093
+ for (let groupOffset = 0; groupOffset < authorizationParts.length; groupOffset += uploadConcurrency) {
4094
+ assertFileIdentity(input.upload.identity, await input.file.identity());
4095
+ const group = authorizationParts.slice(groupOffset, groupOffset + uploadConcurrency);
4096
+ const settled = await Promise.allSettled(
4097
+ group.map(async (part) => {
4098
+ const position = (part.partNumber - 1) * teamTilesetPartSize;
4099
+ const bytes = await input.file.read(position, part.byteCount);
4100
+ if (bytes.byteLength !== part.byteCount) throw localFileChanged();
4101
+ await uploadPartWithRetry({
4102
+ ...input,
4103
+ initialAuthorization: initial.get(part.partNumber),
4104
+ part,
4105
+ bytes
4106
+ });
4107
+ })
4108
+ );
4109
+ const failed = settled.find(
4110
+ (result) => result.status === "rejected"
4111
+ );
4112
+ if (failed) throw failed.reason;
4113
+ completedParts += group.length;
4114
+ input.onProgress?.({
4115
+ completedParts,
4116
+ phase: "uploading",
4117
+ totalParts: input.upload.parts.length
4118
+ });
4119
+ }
4120
+ }
4121
+ }
4122
+ async function uploadPartWithRetry(input) {
4123
+ let authorization = input.initialAuthorization;
4124
+ for (let attempt = 1; attempt <= maximumPartAttempts; attempt += 1) {
4125
+ if (!authorizationCoversTransfer(authorization, input.now?.() ?? Date.now())) {
4126
+ const refreshed2 = await input.transport.authorize(input.uploadId, [input.part.partNumber]);
4127
+ authorization = indexAuthorizations(refreshed2, [input.part]).get(input.part.partNumber);
4128
+ if (!authorizationCoversTransfer(authorization, input.now?.() ?? Date.now())) {
4129
+ throw invalidRemoteResponse();
4130
+ }
4131
+ }
4132
+ assertAuthorizationMatches(authorization, input.part);
4133
+ let result;
4134
+ try {
4135
+ result = await input.transport.putPart(authorization, input.bytes);
4136
+ } catch {
4137
+ result = { etag: null, status: 503 };
4138
+ }
4139
+ if (result.status >= 200 && result.status < 300) {
4140
+ const etag = normalizePartEtag(result.etag, input.part.md5Base64);
4141
+ await input.transport.recordPart(input.uploadId, {
4142
+ etag,
4143
+ partNumber: input.part.partNumber
4144
+ });
4145
+ return;
4146
+ }
4147
+ if ((result.status === 401 || result.status === 403) && !authorizationCoversTransfer(authorization, input.now?.() ?? Date.now()) && attempt < maximumPartAttempts) {
4148
+ const refreshed2 = await input.transport.authorize(input.uploadId, [input.part.partNumber]);
4149
+ authorization = indexAuthorizations(refreshed2, [input.part]).get(input.part.partNumber);
4150
+ continue;
4151
+ }
4152
+ if (!isRetryableStatus(result.status)) {
4153
+ throw new TeamTilesetUploadError(
4154
+ result.status === 401 || result.status === 403 ? "TF_TILESET_UPLOAD_AUTHORIZATION_FAILED" : "TF_TILESET_UPLOAD_PART_INVALID",
4155
+ "Direct part transfer was rejected."
4156
+ );
4157
+ }
4158
+ if (attempt === maximumPartAttempts) break;
4159
+ await (input.sleep ?? defaultSleep)(Math.min(2e3, 250 * 2 ** (attempt - 1)));
4160
+ const refreshed = await input.transport.authorize(input.uploadId, [input.part.partNumber]);
4161
+ authorization = indexAuthorizations(refreshed, [input.part]).get(input.part.partNumber);
4162
+ }
4163
+ throw new TeamTilesetUploadError(
4164
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
4165
+ "Direct part transfer did not complete after bounded retries."
4166
+ );
4167
+ }
4168
+ function authorizationCoversTransfer(authorization, now) {
4169
+ return Date.parse(authorization.expiresAt) - now >= minimumPartAuthorizationLifetimeMs;
4170
+ }
4171
+ function isRetryableStatus(status) {
4172
+ return status === 408 || status === 429 || status >= 500;
4173
+ }
4174
+ function indexAuthorizations(authorizations, parts) {
4175
+ const expected = new Map(parts.map((part) => [part.partNumber, part]));
4176
+ const indexed = /* @__PURE__ */ new Map();
4177
+ for (const authorization of authorizations) {
4178
+ const part = expected.get(authorization.partNumber);
4179
+ if (!part || indexed.has(authorization.partNumber)) {
4180
+ throw invalidRemoteResponse();
4181
+ }
4182
+ assertAuthorizationMatches(authorization, part);
4183
+ indexed.set(authorization.partNumber, authorization);
4184
+ }
4185
+ if (indexed.size !== expected.size) throw invalidRemoteResponse();
4186
+ return indexed;
4187
+ }
4188
+ function assertAuthorizationMatches(authorization, part) {
4189
+ if (authorization.partNumber !== part.partNumber || authorization.method !== "PUT" || authorization.headers["Content-Length"] !== String(part.byteCount) || authorization.headers["Content-MD5"] !== part.md5Base64) {
4190
+ throw invalidRemoteResponse();
4191
+ }
4192
+ }
4193
+ function normalizePartEtag(value, md5Base64) {
4194
+ if (!value || !/^(?:[0-9a-f]{32}|"[0-9a-f]{32}")$/u.test(value)) {
4195
+ throw new TeamTilesetUploadError(
4196
+ "TF_TILESET_UPLOAD_PART_INVALID",
4197
+ "Direct part transfer returned an invalid ETag."
4198
+ );
4199
+ }
4200
+ const normalized = value.replaceAll('"', "");
4201
+ if (normalized !== Buffer.from(md5Base64, "base64").toString("hex")) {
4202
+ throw new TeamTilesetUploadError(
4203
+ "TF_TILESET_UPLOAD_PART_INVALID",
4204
+ "Direct part transfer returned a conflicting ETag."
4205
+ );
4206
+ }
4207
+ return normalized;
4208
+ }
4209
+ function assertSessionMatches(session, upload, requireParts = false) {
4210
+ if (session.byteCount !== upload.byteCount || session.contentHash !== upload.contentHash || session.contentHashAlgorithm !== upload.contentHashAlgorithm) {
4211
+ throw invalidRemoteResponse();
4212
+ }
4213
+ if (session.state === "published") return;
4214
+ if (session.partCount !== upload.parts.length || session.partSize !== upload.partSize || requireParts && session.parts.length !== upload.parts.length || session.parts.some((part, index) => {
4215
+ const expected = upload.parts[index];
4216
+ return !expected || part.partNumber !== expected.partNumber || part.byteCount !== expected.byteCount;
4217
+ })) {
4218
+ throw invalidRemoteResponse();
4219
+ }
4220
+ }
4221
+ function assertUsableSession(session) {
4222
+ if (session.state === "published") return;
4223
+ if (session.failure) {
4224
+ throw new TeamTilesetUploadError(session.failure.code, failureMessage(session.failure.code));
4225
+ }
4226
+ if (["aborted", "aborting", "expired", "expiring", "rejected", "rejecting"].includes(session.state)) {
4227
+ throw new TeamTilesetUploadError(
4228
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
4229
+ "Tileset upload is no longer resumable."
4230
+ );
4231
+ }
4232
+ }
4233
+ function publicationResult(session, upload, changed) {
4234
+ return Object.freeze({
4235
+ byteCount: upload.byteCount,
4236
+ changed,
4237
+ contentHash: upload.contentHash,
4238
+ contentHashAlgorithm: upload.contentHashAlgorithm,
4239
+ state: "published",
4240
+ version: session.publishedVersion,
4241
+ versionId: session.versionId
4242
+ });
4243
+ }
4244
+ function assertFileIdentity(expected, actual) {
4245
+ const inodeChanged = expected.inode !== 0n && actual.inode !== 0n && (expected.inode !== actual.inode || expected.device !== actual.device);
4246
+ if (inodeChanged || expected.size !== actual.size || expected.mtimeNanoseconds !== actual.mtimeNanoseconds) {
4247
+ throw localFileChanged();
4248
+ }
4249
+ }
4250
+ function checkedIdentity(device, inode, mtimeNanoseconds, size) {
4251
+ const numericSize = Number(size);
4252
+ if (numericSize === 0) {
4253
+ throw new TeamTilesetUploadError("TF_TILESET_ARCHIVE_EMPTY", "PMTiles archive is empty.");
4254
+ }
4255
+ if (numericSize > maximumTeamTilesetBytes) {
4256
+ throw new TeamTilesetUploadError(
4257
+ "TF_TILESET_VERSION_TOO_LARGE",
4258
+ "PMTiles archive exceeds the 4,000,000,000-byte platform maximum.",
4259
+ { actualBytes: numericSize, limitBytes: maximumTeamTilesetBytes }
4260
+ );
4261
+ }
4262
+ return Object.freeze({ device, inode, mtimeNanoseconds, size: numericSize });
4263
+ }
4264
+ function localFileChanged() {
4265
+ return new TeamTilesetUploadError(
4266
+ "TF_TILESET_LOCAL_FILE_CHANGED",
4267
+ "PMTiles archive changed during publication."
4268
+ );
4269
+ }
4270
+ function invalidRemoteResponse() {
4271
+ return new TeamTilesetUploadError(
4272
+ "TF_TILESET_UPLOAD_UNAVAILABLE",
4273
+ "Tileset upload returned an invalid response."
4274
+ );
4275
+ }
4276
+ function failureMessage(code) {
4277
+ if (code === "TF_TILESET_SCHEMA_INCOMPATIBLE") {
4278
+ return "Tileset schema is incompatible with retained Map deployments.";
4279
+ }
4280
+ if (code === "TF_TILESET_UPLOAD_CHECKSUM_MISMATCH") {
4281
+ return "Tileset upload checksum validation failed.";
4282
+ }
4283
+ if (code === "TF_TILESET_UPLOAD_EXPIRED") return "Tileset upload expired.";
4284
+ return "Tileset upload failed.";
4285
+ }
4286
+ function responseError(response) {
4287
+ const body = asRecord3(response.body);
4288
+ const code = typeof body.code === "string" && /^TF_[A-Z0-9_]{1,96}$/u.test(body.code) ? body.code : "TF_TILESET_UPLOAD_UNAVAILABLE";
4289
+ const details = numericDetails(body.details);
4290
+ return new TeamTilesetUploadError(code, failureMessage(code), details);
4291
+ }
4292
+ function numericDetails(value) {
4293
+ const source = asRecord3(value);
4294
+ const details = {};
4295
+ for (const key of ["actualBytes", "limitBytes"]) {
4296
+ const candidate = source[key];
4297
+ if (Number.isSafeInteger(candidate) && candidate >= 0) {
4298
+ details[key] = candidate;
4299
+ }
4300
+ }
4301
+ return Object.freeze(details);
4302
+ }
4303
+ function parseUploadSession(value) {
4304
+ return uploadSessionSchema.parse(value);
4305
+ }
4306
+ function asRecord3(value) {
4307
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4308
+ }
4309
+ function defaultSleep(milliseconds) {
4310
+ return new Promise((resolve8) => setTimeout(resolve8, milliseconds));
4311
+ }
4312
+ var identifierSchema = z5.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
4313
+ var uploadPartStatusSchema = z5.object({
4314
+ byteCount: z5.number().int().min(1).max(teamTilesetPartSize),
4315
+ partNumber: z5.number().int().min(1).max(239),
4316
+ uploaded: z5.boolean(),
4317
+ validationState: z5.enum(["mismatch", "pending", "processing", "valid"])
4318
+ }).strict();
4319
+ var publishedVersionSchema = z5.object({
4320
+ byteCount: z5.number().int().min(1).max(maximumTeamTilesetBytes),
4321
+ contentHash: z5.string().regex(/^[0-9a-f]{64}$/u),
4322
+ contentHashAlgorithm: z5.literal("sha256-fixed-parts-v1"),
4323
+ publishedVersion: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4324
+ schemaVersion: z5.literal(1),
4325
+ state: z5.literal("published"),
4326
+ versionId: identifierSchema.regex(/^tsv_[A-Za-z0-9_-]+$/u)
4327
+ }).strict();
4328
+ var uploadControlSchema = z5.object({
4329
+ byteCount: z5.number().int().min(1).max(maximumTeamTilesetBytes),
4330
+ contentHash: z5.string().regex(/^[0-9a-f]{64}$/u),
4331
+ contentHashAlgorithm: z5.literal("sha256-fixed-parts-v1"),
4332
+ expiresAt: z5.iso.datetime({ offset: true }),
4333
+ failure: z5.object({ code: z5.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
4334
+ partCount: z5.number().int().min(1).max(239),
4335
+ parts: z5.array(uploadPartStatusSchema).max(239),
4336
+ partSize: z5.literal(teamTilesetPartSize),
4337
+ publishedVersion: z5.null(),
4338
+ schemaVersion: z5.literal(1),
4339
+ state: z5.enum([
4340
+ "aborted",
4341
+ "aborting",
4342
+ "expired",
4343
+ "expiring",
4344
+ "initiating",
4345
+ "publishing",
4346
+ "ready_to_publish",
4347
+ "rejected",
4348
+ "rejecting",
4349
+ "uploading",
4350
+ "validating"
4351
+ ]),
4352
+ uploadId: z5.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
4353
+ versionId: z5.null()
4354
+ }).strict();
4355
+ var uploadSessionSchema = z5.union([publishedVersionSchema, uploadControlSchema]);
4356
+ var authorizationSchema = z5.object({
4357
+ expiresAt: z5.iso.datetime({ offset: true }),
4358
+ headers: z5.object({
4359
+ "Content-Length": z5.string().regex(/^[1-9][0-9]{0,9}$/u),
4360
+ "Content-MD5": z5.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
4361
+ }).strict(),
4362
+ method: z5.literal("PUT"),
4363
+ partNumber: z5.number().int().min(1).max(239),
4364
+ url: z5.url().max(8192).refine((value) => {
4365
+ const url = new URL(value);
4366
+ return url.protocol === "https:" && !url.username && !url.password && !url.hash;
4367
+ })
4368
+ }).strict();
4369
+ var authorizationResponseSchema = z5.object({
4370
+ authorizations: z5.array(authorizationSchema).min(1).max(16),
4371
+ schemaVersion: z5.literal(1)
4372
+ }).strict();
4373
+ var partReceiptResponseSchema = z5.object({
4374
+ changed: z5.boolean(),
4375
+ etag: z5.string().regex(/^[0-9a-f]{32}$/u),
4376
+ partNumber: z5.number().int().min(1).max(239),
4377
+ schemaVersion: z5.literal(1)
4378
+ }).strict();
4379
+
4380
+ // src/tileset-command.ts
4381
+ function registerTilesetCommands(program2, dependencies) {
4382
+ const tileset = program2.command("tileset").description("Inspect and manage geospatial tilesets");
4383
+ tileset.command("inspect").description("Inspect one local PMTiles archive deterministically").argument("<archive>", "repository-local PMTiles archive").option("--json", "print deterministic inspection-schema-version-1 JSON").option("--no-sample", "read authoritative header and metadata without bounded MVT sampling").option(
4384
+ "--include-values <fields>",
4385
+ "include bounded observed values for comma-separated field names (requires --json)"
4386
+ ).action(
4387
+ async (archive, options) => {
4388
+ try {
4389
+ if (options.includeValues && !options.json) {
4390
+ throw inspectionOptionError("--include-values requires --json.");
4391
+ }
4392
+ const inspection = await inspectTileflowPmtiles(archive, {
4393
+ includeValues: parseIncludedValueFields(options.includeValues),
4394
+ sample: options.sample
4395
+ });
4396
+ if (options.json) {
4397
+ process.stdout.write(`${serializeCanonicalJson3(inspection)}
4398
+ `);
4399
+ return;
4400
+ }
4401
+ console.log(`${pc2.green("\u2713")} PMTiles archive is valid.`);
4402
+ console.log(` Type: ${inspection.contract.tileType}`);
4403
+ console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
4404
+ console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
4405
+ if (inspection.observation) {
4406
+ console.log(
4407
+ ` Sample: ${inspection.observation.featuresRead} features in ${inspection.observation.tilesRead} tiles`
4408
+ );
4409
+ }
4410
+ for (const warning of inspection.warnings) {
4411
+ console.log(`${pc2.yellow("!")} ${warning.message}`);
4412
+ }
4413
+ } catch (error) {
4414
+ const record = asRecord4(error);
4415
+ const code = typeof record.code === "string" ? record.code : "TF_TILESET_INSPECTION_FAILED";
4416
+ const path = typeof record.path === "string" ? record.path : "archive";
4417
+ const message = error instanceof Error ? error.message : "Tileset inspection failed.";
4418
+ if (options.json) {
4419
+ process.stderr.write(
4420
+ `${serializeCanonicalJson3({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
4421
+ `
4422
+ );
4423
+ } else {
4424
+ console.error(`${pc2.red("Error:")} ${message}`);
4425
+ }
4426
+ process.exitCode = 1;
4427
+ }
4428
+ }
4429
+ );
4430
+ tileset.command("publish").description("Register and publish one PMTiles archive to a Team").argument("<archive>", "repository-local PMTiles archive").requiredOption("--id <id>", "Team-local logical tileset ID").option("--name <name>", "display name").option("--attribution <text>", "data attribution").option("--team <team>", "target Team as @team").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Team data API key", process.env.TILEFLOW_API_KEY).option("--json", "print deterministic schema-version-2 JSON").action(
4431
+ async (archive, options) => {
4432
+ let uploadFile = null;
4433
+ try {
4434
+ if (!isTilesetId(options.id)) {
4435
+ return emitFailure(options.json, "invalid_tileset_id", "Tileset ID is invalid.");
4436
+ }
4437
+ const initialIdentity = await inspectNodeTeamTilesetArchive(archive);
4438
+ await inspectTileflowPmtiles(archive, { sample: false });
4439
+ uploadFile = await openNodeTeamTilesetArchive(archive, initialIdentity);
4440
+ const prepared = await prepareTeamTilesetUpload(uploadFile);
4441
+ const authority = await resolveTeamAuthority(options, ["tilesets:write"], dependencies);
4442
+ if (!authority) return;
4443
+ const registration = await requestHostedJson(authority.apiOrigin, "/v1/tilesets", {
4444
+ body: JSON.stringify({
4445
+ ...options.attribution ? { attribution: options.attribution } : {},
4446
+ format: "pmtiles",
4447
+ name: options.name?.trim() || options.id,
4448
+ slug: options.id
4449
+ }),
4450
+ headers: {
4451
+ Authorization: `Bearer ${authority.credential}`,
4452
+ "Content-Type": "application/json"
4453
+ },
4454
+ method: "POST"
4455
+ });
4456
+ const registered = asRecord4(registration.body);
4457
+ const resourceId = String(registered.tilesetId ?? "");
4458
+ if (!registration.ok || !/^tls_[A-Za-z0-9_-]{16,76}$/u.test(resourceId)) {
4459
+ return emitFailure(
4460
+ options.json,
4461
+ `http_${registration.status}`,
4462
+ `Tileset registration failed (${registration.status}).`
4463
+ );
4464
+ }
4465
+ let reportedParts = 0;
4466
+ let reportedValidation = false;
4467
+ const publication = await publishPreparedTeamTilesetUpload({
4468
+ file: uploadFile,
4469
+ onProgress: options.json ? void 0 : ({ completedParts, phase, totalParts }) => {
4470
+ if (phase === "validating") {
4471
+ if (!reportedValidation) {
4472
+ process.stderr.write("Validating uploaded bytes.\n");
4473
+ reportedValidation = true;
4474
+ }
4475
+ return;
4476
+ }
4477
+ if (completedParts === totalParts || completedParts - reportedParts >= 16) {
4478
+ process.stderr.write(`Uploaded ${completedParts}/${totalParts} parts.
4479
+ `);
4480
+ reportedParts = completedParts;
4481
+ }
4482
+ },
4483
+ tilesetId: resourceId,
4484
+ transport: createHostedTeamTilesetUploadTransport({
4485
+ apiOrigin: authority.apiOrigin,
4486
+ credential: authority.credential,
4487
+ tilesetId: resourceId
4488
+ }),
4489
+ upload: prepared
4490
+ });
4491
+ const document = {
4492
+ command: "tileset publish",
4493
+ publication: publication.changed ? "changed" : "unchanged",
4494
+ schemaVersion: 2,
4495
+ team: authority.team ? { id: authority.team.id, slug: authority.team.slug } : null,
4496
+ tileset: {
4497
+ byteCount: publication.byteCount,
4498
+ contentHash: publication.contentHash,
4499
+ contentHashAlgorithm: publication.contentHashAlgorithm,
4500
+ logicalId: options.id,
4501
+ state: publication.state,
4502
+ tilesetId: resourceId,
4503
+ version: { id: publication.versionId, number: publication.version }
4504
+ }
4505
+ };
4506
+ if (options.json) return emitJson2(document);
4507
+ console.log(
4508
+ `${pc2.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc2.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
4509
+ );
4510
+ } catch (error) {
4511
+ if (error instanceof TeamTilesetUploadError) {
4512
+ emitFailure(options.json, error.code, error.message, error.details);
4513
+ } else {
4514
+ emitFailure(options.json, "tileset_publish_failed", "Tileset publication failed.");
4515
+ }
4516
+ } finally {
4517
+ await uploadFile?.close().catch(() => void 0);
4518
+ }
4519
+ }
4520
+ );
4521
+ tileset.command("list").description("List tilesets owned by one Team").option("--team <team>", "target Team as @team").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Team data API key", process.env.TILEFLOW_API_KEY).option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
4522
+ try {
4523
+ const authority = await resolveTeamAuthority(options, ["tilesets:read"], dependencies);
4524
+ if (!authority) return;
4525
+ const items = await fetchAllTeamTilesets(authority);
4526
+ const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
4527
+ if (options.json) return emitJson2(document);
4528
+ if (items.length === 0) return console.log(pc2.gray("No Team tilesets."));
4529
+ for (const item of items) {
4530
+ console.log(` ${pc2.bold(item.logicalName)} ${pc2.gray(item.status)}`);
4531
+ }
4532
+ } catch {
4533
+ emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
4534
+ }
4535
+ });
4536
+ tileset.command("status").description("Show one Team tileset").argument("<id>", "Team-local logical ID or opaque resource ID").option("--team <team>", "target Team as @team").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Team data API key", process.env.TILEFLOW_API_KEY).option("--json", "print deterministic schema-version-1 JSON").action(async (id, options) => {
4537
+ try {
4538
+ if (!isTilesetId(id))
4539
+ return emitFailure(options.json, "invalid_tileset_id", "Tileset ID is invalid.");
4540
+ const authority = await resolveTeamAuthority(options, ["tilesets:read"], dependencies);
4541
+ if (!authority) return;
4542
+ const response = await authorizedTeamRequest(
4543
+ authority,
4544
+ `/v1/tilesets/${encodeURIComponent(id)}`,
4545
+ "GET"
4546
+ );
4547
+ if (!response.ok) {
4548
+ return emitFailure(
4549
+ options.json,
4550
+ `http_${response.status}`,
4551
+ `Tileset status failed (${response.status}).`
4552
+ );
4553
+ }
4554
+ const parsed = tilesetDetailResponseSchema.parse(response.body);
4555
+ const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
4556
+ if (options.json) return emitJson2(document);
4557
+ const item = parsed.tileset;
4558
+ console.log(`${pc2.bold(item.name)} ${pc2.gray(item.state)}`);
4559
+ console.log(` ID: ${item.id}`);
4560
+ console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
4561
+ } catch {
4562
+ emitFailure(options.json, "tileset_status_failed", "Tileset status failed.");
4563
+ }
4564
+ });
4565
+ tileset.command("purge").description("Permanently purge one unreferenced Team tileset").argument("<id>", "Team-local logical ID or opaque resource ID").requiredOption("--confirm <id>", "repeat the exact tileset ID").option("--team <team>", "target Team as @team").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Team data API key", process.env.TILEFLOW_API_KEY).option("--json", "print deterministic schema-version-1 JSON").action(async (id, options) => {
4566
+ try {
4567
+ if (!isTilesetId(id) || options.confirm !== id) {
4568
+ return emitFailure(
4569
+ options.json,
4570
+ "purge_confirmation_mismatch",
4571
+ "Purge confirmation must match the tileset ID."
4572
+ );
4573
+ }
4574
+ const authority = await resolveTeamAuthority(options, ["tilesets:write"], dependencies);
4575
+ if (!authority) return;
4576
+ const response = await requestHostedJson(
4577
+ authority.apiOrigin,
4578
+ `/v1/tilesets/${encodeURIComponent(id)}`,
4579
+ {
4580
+ headers: {
4581
+ Authorization: `Bearer ${authority.credential}`,
4582
+ "X-Tileflow-Confirm-Tileset-Purge": id
4583
+ },
4584
+ method: "DELETE"
4585
+ }
4586
+ );
4587
+ if (!response.ok) {
4588
+ const inUse = tilesetInUseResponseSchema.safeParse(response.body);
4589
+ if (inUse.success) {
4590
+ return emitFailure(
4591
+ options.json,
4592
+ inUse.data.code,
4593
+ "Tileset is used by retained Map deployments.",
4594
+ {
4595
+ dependencies: inUse.data.dependencies,
4596
+ nextCursor: inUse.data.nextCursor
4597
+ }
4598
+ );
4599
+ }
4600
+ return emitFailure(
4601
+ options.json,
4602
+ `http_${response.status}`,
4603
+ `Tileset purge failed (${response.status}).`
4604
+ );
4605
+ }
4606
+ const parsed = tilesetPurgeResponseSchema.parse(response.body);
4607
+ const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
4608
+ if (options.json) return emitJson2(document);
4609
+ console.log(`${pc2.green("\u2713")} Purge ${parsed.reclamation}.`);
4610
+ } catch {
4611
+ emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
4612
+ }
4613
+ });
3058
4614
  }
3059
- async function resolveAccountProjectTarget(session, requestedProject) {
3060
- const result = await fetchAccountProjects(session, false);
3061
- if (!result.ok) return { error: result.error, kind: "remote", status: result.status };
3062
- if (requestedProject) {
3063
- const parsed = parseProjectReference(requestedProject);
3064
- if (!parsed) return { kind: "invalid", targets: result.items };
3065
- const target = result.items.find((candidate) => candidate.reference === requestedProject);
3066
- return target ? { kind: "selected", target } : { kind: "missing", targets: result.items };
4615
+ async function resolveTeamAuthority(options, scopes, dependencies) {
4616
+ let apiOrigin;
4617
+ try {
4618
+ apiOrigin = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
4619
+ } catch (error) {
4620
+ emitFailure(options.json, "invalid_api_url", safeMessage2(error));
4621
+ return null;
4622
+ }
4623
+ if (options.apiKey) {
4624
+ if (!/^tf_live_[0-9a-f]{48}$/u.test(options.apiKey)) {
4625
+ emitFailure(options.json, "invalid_team_data_key", "Team data API key is invalid.");
4626
+ return null;
4627
+ }
4628
+ if (options.team) {
4629
+ emitFailure(
4630
+ options.json,
4631
+ "team_selector_with_api_key",
4632
+ "A Team data key already selects its Team; omit --team."
4633
+ );
4634
+ return null;
4635
+ }
4636
+ return { apiOrigin, credential: options.apiKey, team: null };
3067
4637
  }
3068
- if (result.items.length === 1) return { kind: "selected", target: result.items[0] };
3069
- return result.items.length ? { kind: "ambiguous", targets: result.items } : { kind: "missing", targets: [] };
3070
- }
3071
- async function requireCommandSession(options, dependencies) {
3072
4638
  let config;
3073
4639
  try {
3074
4640
  config = await dependencies.loadAuthConfig();
3075
4641
  } catch (error) {
3076
- emitLocalFailure(options.json, "auth_state_unavailable", safeMessage(error));
4642
+ emitFailure(options.json, "auth_state_unavailable", safeMessage2(error));
3077
4643
  return null;
3078
4644
  }
3079
- const resolved = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
3080
- if (resolved.kind !== "selected") {
3081
- emitLocalFailure(
4645
+ const selected = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
4646
+ if (selected.kind !== "selected") {
4647
+ emitFailure(
3082
4648
  options.json,
3083
- `account_session_${resolved.kind}`,
3084
- resolved.kind === "expired" ? "The Tileflow account session has expired. Run tileflow login." : "Not logged in. Run tileflow login."
4649
+ `account_session_${selected.kind}`,
4650
+ selected.kind === "expired" ? "Account session expired. Run tileflow login." : "Run tileflow login."
3085
4651
  );
3086
4652
  return null;
3087
4653
  }
3088
- return resolved.session;
3089
- }
3090
- async function accountRequest(session, path, init) {
3091
- let response;
3092
- try {
3093
- response = await requestHostedJson(session.apiOrigin, path, {
3094
- ...init,
3095
- headers: { ...init.headers, Authorization: `Bearer ${session.accountSession}` }
3096
- });
3097
- } catch (error) {
3098
- return {
3099
- body: null,
3100
- error: error instanceof Error && /safe size limit|timed out/u.test(error.message) ? error.message : "Hosted request failed.",
3101
- ok: false,
3102
- status: 502
3103
- };
4654
+ const discovered = await listAccountTeams(selected.session);
4655
+ if (!discovered.ok) {
4656
+ emitFailure(options.json, "team_discovery_failed", discovered.error);
4657
+ return null;
3104
4658
  }
3105
- if (response.ok && !response.json) {
3106
- return {
3107
- body: null,
3108
- error: "Hosted response returned invalid JSON.",
3109
- ok: false,
3110
- status: 502
3111
- };
4659
+ const requested = options.team ? /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(options.team) : null;
4660
+ if (options.team && !requested) {
4661
+ emitFailure(options.json, "invalid_team", "Team must use @team syntax.");
4662
+ return null;
3112
4663
  }
3113
- return response.ok ? { body: response.body, ok: true, status: response.status } : {
3114
- body: response.body,
3115
- error: `Request failed (${response.status}).`,
3116
- ok: false,
3117
- status: response.status
3118
- };
3119
- }
3120
- function validateTargetOrder(items) {
3121
- const references = items.map((item) => item.reference);
3122
- if (new Set(references).size !== references.length || references.some((reference, index) => index > 0 && references[index - 1] >= reference)) {
3123
- return {
3124
- error: "Destination discovery returned unstable ordering.",
3125
- ok: false,
3126
- status: 502
3127
- };
4664
+ const team = requested ? discovered.teams.find(({ slug }) => slug === requested[1]) : discovered.teams.length === 1 ? discovered.teams[0] : null;
4665
+ if (!team) {
4666
+ const options_ = discovered.teams.map(({ slug }) => `@${slug}`);
4667
+ emitFailure(
4668
+ options.json,
4669
+ options_.length > 1 ? "team_ambiguous" : "team_not_found",
4670
+ options_.length > 1 ? `Choose one Team with --team: ${options_.join(", ")}.` : "No matching Team is available.",
4671
+ { options: options_ }
4672
+ );
4673
+ return null;
3128
4674
  }
3129
- return { items, ok: true };
3130
- }
3131
- function uniqueOrganizations(items) {
3132
- return [...new Map(items.map((item) => [item.organization.id, item.organization])).values()];
4675
+ const capability = await requestTeamCapability(selected.session, `@${team.slug}`, scopes);
4676
+ if (!capability.ok) {
4677
+ emitFailure(options.json, "team_capability_failed", capability.error);
4678
+ return null;
4679
+ }
4680
+ return {
4681
+ apiOrigin: selected.session.apiOrigin,
4682
+ credential: capability.capability,
4683
+ team
4684
+ };
3133
4685
  }
3134
- function parseOrganizationReference(value) {
3135
- if (!value) return null;
3136
- const match = /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(value);
3137
- return match?.[1] ?? null;
4686
+ function authorizedTeamRequest(authority, path, method) {
4687
+ return requestHostedJson(authority.apiOrigin, path, {
4688
+ headers: { Authorization: `Bearer ${authority.credential}` },
4689
+ method
4690
+ });
3138
4691
  }
3139
- function emitAmbiguousOrganizations(json, organizations, slug, name) {
3140
- const references = organizations.map((organization) => `@${organization.slug}`);
3141
- const retry = `tileflow projects create ${slug} --name ${JSON.stringify(name.trim())} --organization ${references[0]}`;
3142
- return emitLocalFailure(
3143
- json,
3144
- "organization_ambiguous",
3145
- `More than one organization is available: ${references.join(", ")}. Retry with: ${retry}`,
3146
- { options: references, retry }
3147
- );
4692
+ async function fetchAllTeamTilesets(authority) {
4693
+ const items = [];
4694
+ const seenCursors = /* @__PURE__ */ new Set();
4695
+ const seenIds = /* @__PURE__ */ new Set();
4696
+ let cursor = null;
4697
+ for (let page = 0; page < 100; page += 1) {
4698
+ const query = new URLSearchParams({ limit: "100" });
4699
+ if (cursor) query.set("cursor", cursor);
4700
+ const response = await authorizedTeamRequest(
4701
+ authority,
4702
+ `/v1/tilesets?${query.toString()}`,
4703
+ "GET"
4704
+ );
4705
+ if (!response.ok) {
4706
+ throw new TeamTilesetUploadError(
4707
+ `http_${response.status}`,
4708
+ `Tileset list failed (${response.status}).`
4709
+ );
4710
+ }
4711
+ const parsed = tilesetListResponseSchema.parse(response.body);
4712
+ for (const item of parsed.tilesets) {
4713
+ if (seenIds.has(item.id)) throw new Error("Tileset list returned a duplicate resource.");
4714
+ const previous = items.at(-1);
4715
+ if (previous && (previous.logicalName > item.logicalName || previous.logicalName === item.logicalName && previous.id >= item.id)) {
4716
+ throw new Error("Tileset list returned unstable ordering.");
4717
+ }
4718
+ seenIds.add(item.id);
4719
+ items.push(item);
4720
+ }
4721
+ cursor = parsed.nextCursor ?? null;
4722
+ if (!cursor) return Object.freeze(items);
4723
+ if (seenCursors.has(cursor)) throw new Error("Tileset list repeated a cursor.");
4724
+ seenCursors.add(cursor);
4725
+ }
4726
+ throw new Error("Tileset list exceeded its safe page limit.");
3148
4727
  }
3149
- function emitRemoteFailure(json, result) {
3150
- return emitLocalFailure(json, `http_${result.status}`, result.error);
4728
+ function emitJson2(value) {
4729
+ process.stdout.write(`${serializeCanonicalJson3(value)}
4730
+ `);
3151
4731
  }
3152
- function emitLocalFailure(json, code, message, details = {}) {
4732
+ function emitFailure(json, code, message, context = {}) {
3153
4733
  if (json) {
3154
- console.error(message);
3155
- emitJson({ schemaVersion: 1, ok: false, error: { code, ...details } });
4734
+ process.stderr.write(
4735
+ `${serializeCanonicalJson3({ error: { code, ...context, message }, ok: false, schemaVersion: 1 })}
4736
+ `
4737
+ );
3156
4738
  } else {
3157
- console.error(`${pc.red("\u2715")} ${message}`);
4739
+ console.error(`${pc2.red("Error:")} ${message}`);
3158
4740
  }
3159
4741
  process.exitCode = 1;
3160
4742
  }
3161
- function emitJson(value) {
3162
- process.stdout.write(`${JSON.stringify(value)}
3163
- `);
3164
- }
3165
- function asRecord2(value) {
3166
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3167
- }
3168
- function isAccountProjectTarget(value) {
3169
- const target = asRecord2(value);
3170
- return isIdentity(target.organization) && isProjectItem(target.project) && typeof target.reference === "string" && target.reference === `@${target.organization.slug}/${target.project.slug}`;
3171
- }
3172
- function isIdentity(value) {
3173
- const identity = asRecord2(value);
3174
- return typeof identity.id === "string" && typeof identity.name === "string" && typeof identity.slug === "string";
4743
+ function isTilesetId(value) {
4744
+ return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value) && !["terrain", "world"].includes(value);
3175
4745
  }
3176
- function isProjectItem(value) {
3177
- const item = asRecord2(value);
3178
- return isIdentity(value) && (item.archivedAt === null || typeof item.archivedAt === "string") && typeof item.createdAt === "string" && typeof item.updatedAt === "string";
4746
+ function parseIncludedValueFields(value) {
4747
+ if (value === void 0) return void 0;
4748
+ const fields = value.split(",").map((field) => field.trim());
4749
+ if (fields.some((field) => !field)) {
4750
+ throw inspectionOptionError("--include-values requires comma-separated field names.");
4751
+ }
4752
+ return fields;
3179
4753
  }
3180
- function validSlug(value) {
3181
- return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value);
4754
+ function inspectionOptionError(message) {
4755
+ return Object.assign(new Error(message), {
4756
+ code: "TF_TILESET_INSPECTION_OPTIONS_INVALID",
4757
+ path: "includeValues"
4758
+ });
3182
4759
  }
3183
- function validName(value) {
3184
- const normalized = value.trim();
3185
- return normalized.length > 0 && normalized.length <= 200 && !/[\p{Cc}]/u.test(normalized);
4760
+ function safeMessage2(error) {
4761
+ return error instanceof Error ? error.message : "Tileset command failed.";
3186
4762
  }
3187
- function safeMessage(error) {
3188
- return error instanceof Error && error.message.length <= 300 ? error.message : "Tileflow auth state is unavailable.";
4763
+ function asRecord4(value) {
4764
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3189
4765
  }
4766
+ var safeIdentifierSchema2 = z6.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
4767
+ var physicalTilesetIdSchema = z6.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
4768
+ var cursorSchema = z6.string().min(1).max(2048).nullable();
4769
+ var tilesetInventoryItemSchema = z6.object({
4770
+ currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
4771
+ hasCurrent: z6.boolean(),
4772
+ id: physicalTilesetIdSchema,
4773
+ logicalName: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
4774
+ name: z6.string().min(1).max(200),
4775
+ retainedBytes: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
4776
+ retainedDeploymentCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
4777
+ status: z6.enum(["active", "purging", "retired"]),
4778
+ updatedAt: z6.iso.datetime({ offset: true }),
4779
+ usedByMapCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
4780
+ }).strict();
4781
+ var tilesetListResponseSchema = z6.object({
4782
+ nextCursor: cursorSchema.optional(),
4783
+ schemaVersion: z6.literal(1),
4784
+ tilesets: z6.array(tilesetInventoryItemSchema).max(100)
4785
+ }).strict();
4786
+ var tilesetDependencySchema = z6.object({
4787
+ active: z6.boolean(),
4788
+ deploymentId: safeIdentifierSchema2,
4789
+ deploymentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4790
+ environment: z6.string().min(1).max(64),
4791
+ mapId: safeIdentifierSchema2,
4792
+ sourceId: safeIdentifierSchema2
4793
+ }).strict();
4794
+ var tilesetDetailResponseSchema = z6.object({
4795
+ schemaVersion: z6.literal(1),
4796
+ tileset: z6.object({
4797
+ attribution: z6.string().max(4096).nullable(),
4798
+ currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
4799
+ dependencies: z6.array(tilesetDependencySchema).max(25),
4800
+ dependencyNextCursor: cursorSchema,
4801
+ id: physicalTilesetIdSchema,
4802
+ name: z6.string().min(1).max(200),
4803
+ slug: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
4804
+ state: z6.enum(["active", "purging", "purged"])
4805
+ }).strict()
4806
+ }).strict();
4807
+ var purgeDependencySchema = z6.object({
4808
+ deploymentId: safeIdentifierSchema2,
4809
+ mapId: safeIdentifierSchema2,
4810
+ sourceId: safeIdentifierSchema2
4811
+ }).strict();
4812
+ var tilesetInUseResponseSchema = z6.object({
4813
+ code: z6.literal("TF_TILESET_IN_USE"),
4814
+ dependencies: z6.array(purgeDependencySchema).max(32),
4815
+ error: z6.literal("Tileset is used by retained Map deployments"),
4816
+ nextCursor: cursorSchema
4817
+ }).strict();
4818
+ var tilesetPurgeResponseSchema = z6.object({
4819
+ deliveryFence: z6.enum(["complete", "pending"]),
4820
+ pendingVersions: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
4821
+ reclamation: z6.enum(["complete", "pending"]),
4822
+ state: z6.enum(["purged", "purging"]),
4823
+ tilesetId: physicalTilesetIdSchema
4824
+ }).strict();
3190
4825
 
3191
4826
  // src/visual-command.ts
3192
- import { lstat as lstat3, readFile as readFile5 } from "fs/promises";
3193
- import { dirname as dirname8, isAbsolute as isAbsolute3, parse as parse2, relative as relative4, resolve as resolve6, sep as sep4 } from "path";
4827
+ import { lstat as lstat3, readFile as readFile6 } from "fs/promises";
4828
+ import { dirname as dirname9, isAbsolute as isAbsolute3, parse as parse2, relative as relative4, resolve as resolve6, sep as sep4 } from "path";
3194
4829
  import {
3195
4830
  analyzeTileflowCaptureReference,
3196
4831
  compareTileflowCaptureToBaseline,
@@ -3204,12 +4839,12 @@ import {
3204
4839
  tileflowVisualArtifactLimits,
3205
4840
  validateTileflowVisualReferencePng
3206
4841
  } from "@tileflow/capture";
3207
- import { compareCodeUnits as compareCodeUnits7 } from "@tileflow/core";
4842
+ import { compareCodeUnits as compareCodeUnits8 } from "@tileflow/core";
3208
4843
 
3209
4844
  // src/visual-compare-command.ts
3210
- import { createHash as createHash2 } from "crypto";
3211
- import { lstat as lstat2, readFile as readFile4, realpath } from "fs/promises";
3212
- import { basename as basename3, dirname as dirname7, extname, isAbsolute as isAbsolute2, parse, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
4845
+ import { createHash as createHash3 } from "crypto";
4846
+ import { lstat as lstat2, readFile as readFile5, realpath } from "fs/promises";
4847
+ import { basename as basename3, dirname as dirname8, extname, isAbsolute as isAbsolute2, parse, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
3213
4848
  import {
3214
4849
  compareTileflowCapturesForReview,
3215
4850
  createTileflowCaptureSession as createTileflowCaptureSession2,
@@ -3218,9 +4853,9 @@ import {
3218
4853
  tileflowSyntheticAssetOrigin as tileflowSyntheticAssetOrigin2
3219
4854
  } from "@tileflow/capture";
3220
4855
  import {
3221
- compareCodeUnits as compareCodeUnits6,
4856
+ compareCodeUnits as compareCodeUnits7,
3222
4857
  normalizeTileflowCaptureScene as normalizeTileflowCaptureScene2,
3223
- parseTileflowMap,
4858
+ parseResolvedTileflowMap as parseResolvedTileflowMap2,
3224
4859
  resolveThemeSelection,
3225
4860
  tileflowCaptureSceneLimits
3226
4861
  } from "@tileflow/core";
@@ -3231,7 +4866,7 @@ import {
3231
4866
  } from "@tileflow/dev/artifacts";
3232
4867
 
3233
4868
  // src/visual-compare-report.ts
3234
- import { createHash } from "crypto";
4869
+ import { createHash as createHash2 } from "crypto";
3235
4870
  var tileflowVisualCompareReportSchemaVersion = 1;
3236
4871
  var tileflowVisualCompareReportLimits = Object.freeze({
3237
4872
  maximumEmbeddedPngBytes: 64 * 1024 * 1024,
@@ -3286,7 +4921,7 @@ function renderTileflowVisualCompareReport(input) {
3286
4921
  };
3287
4922
  const rowHtml = rows.map((row, index) => renderRow(row, index)).join("\n");
3288
4923
  const script = reportScript();
3289
- const scriptHash = createHash("sha256").update(script).digest("base64");
4924
+ const scriptHash = createHash2("sha256").update(script).digest("base64");
3290
4925
  return `<!doctype html>
3291
4926
  <html lang="en">
3292
4927
  <head>
@@ -3650,7 +5285,7 @@ async function prepareVisualCompareOutput(plan, leftArtifacts, rightArtifacts, c
3650
5285
  documentPath: relativePath(cwd, plan.output.documentPath),
3651
5286
  reportPath: relativePath(cwd, plan.output.reportPath)
3652
5287
  },
3653
- warnings: [...warnings].sort(compareCodeUnits6)
5288
+ warnings: [...warnings].sort(compareCodeUnits7)
3654
5289
  };
3655
5290
  const report = renderTileflowVisualCompareReport({
3656
5291
  generatedBy: "Tileflow CLI",
@@ -3938,7 +5573,7 @@ async function runVisualCompareWatch(plan, openReport) {
3938
5573
  }
3939
5574
  }
3940
5575
  function resolveVisualCompareSide(artifacts, config, requestedMap, requestedTheme, role) {
3941
- const availableMaps = Object.keys(artifacts.project.maps).sort(compareCodeUnits6);
5576
+ const availableMaps = Object.keys(artifacts.project.maps).sort(compareCodeUnits7);
3942
5577
  const map = requestedMap ?? (availableMaps.length === 1 ? availableMaps[0] : void 0);
3943
5578
  if (!map) {
3944
5579
  throw new Error(
@@ -3951,7 +5586,7 @@ function resolveVisualCompareSide(artifacts, config, requestedMap, requestedThem
3951
5586
  `Unknown ${role} map "${map}". Available maps: ${availableMaps.join(", ") || "(none)"}.`
3952
5587
  );
3953
5588
  }
3954
- const resolvedMap = parseTileflowMap(candidate);
5589
+ const resolvedMap = parseResolvedTileflowMap2(candidate);
3955
5590
  const theme = resolveThemeSelection(resolvedMap, requestedTheme).name;
3956
5591
  if (!artifacts.styles[map]?.[theme]) {
3957
5592
  throw new Error(`The ${role} map theme "${map}/${theme}" was not compiled.`);
@@ -4007,7 +5642,7 @@ async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zoo
4007
5642
  if (extname(reportPath).toLowerCase() !== ".html") {
4008
5643
  throw new Error("--report must name an .html file.");
4009
5644
  }
4010
- const reportDirectory = dirname7(reportPath);
5645
+ const reportDirectory = dirname8(reportPath);
4011
5646
  if (reportDirectory === cwd || reportDirectory === parse(reportPath).root) {
4012
5647
  throw new Error("Visual compare output must use a dedicated subdirectory.");
4013
5648
  }
@@ -4147,7 +5782,7 @@ function createVisualCompareInventory(assetDirectory, assets) {
4147
5782
  throw new Error("Visual compare produced an invalid managed asset path.");
4148
5783
  }
4149
5784
  return { path: relativeAssetPath, sha256: sha256Source(source) };
4150
- }).sort((left, right) => compareCodeUnits6(left.path, right.path))
5785
+ }).sort((left, right) => compareCodeUnits7(left.path, right.path))
4151
5786
  };
4152
5787
  }
4153
5788
  function serializeVisualCompareInventory(inventory) {
@@ -4155,7 +5790,7 @@ function serializeVisualCompareInventory(inventory) {
4155
5790
  `;
4156
5791
  }
4157
5792
  async function readVisualCompareInventory(assetDirectory, inventoryPath, boundary) {
4158
- await assertNoSymlinkComponents(dirname7(inventoryPath), boundary);
5793
+ await assertNoSymlinkComponents(dirname8(inventoryPath), boundary);
4159
5794
  let info;
4160
5795
  try {
4161
5796
  info = await lstat2(inventoryPath);
@@ -4171,7 +5806,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
4171
5806
  }
4172
5807
  let parsed;
4173
5808
  try {
4174
- parsed = JSON.parse(await readFile4(inventoryPath, "utf8"));
5809
+ parsed = JSON.parse(await readFile5(inventoryPath, "utf8"));
4175
5810
  } catch (error) {
4176
5811
  throw new Error("Visual compare inventory is not valid bounded JSON.", { cause: error });
4177
5812
  }
@@ -4179,7 +5814,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
4179
5814
  const assets = [];
4180
5815
  for (const asset of inventory.assets) {
4181
5816
  const path = resolve5(assetDirectory, asset.path);
4182
- await assertNoSymlinkComponents(dirname7(path), boundary);
5817
+ await assertNoSymlinkComponents(dirname8(path), boundary);
4183
5818
  let assetInfo;
4184
5819
  try {
4185
5820
  assetInfo = await lstat2(path);
@@ -4193,7 +5828,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
4193
5828
  if (assetInfo.size > tileflowVisualCompareReportLimits.maximumEmbeddedPngBytes) {
4194
5829
  throw new Error("A managed visual compare asset exceeds its bounded byte limit.");
4195
5830
  }
4196
- const source = await readFile4(path);
5831
+ const source = await readFile5(path);
4197
5832
  if (sha256Source(source) !== asset.sha256) {
4198
5833
  throw new Error(
4199
5834
  "A previously managed visual compare asset was modified; move or remove it explicitly."
@@ -4222,7 +5857,7 @@ function validateVisualCompareInventory(input) {
4222
5857
  return { path: asset.path, sha256: asset.sha256 };
4223
5858
  });
4224
5859
  const paths = assets.map((asset) => asset.path);
4225
- const sortedPaths = [...paths].sort(compareCodeUnits6);
5860
+ const sortedPaths = [...paths].sort(compareCodeUnits7);
4226
5861
  if (new Set(paths).size !== paths.length || paths.some((path, index) => path !== sortedPaths[index])) {
4227
5862
  throw new Error("Visual compare inventory asset paths must be unique and sorted.");
4228
5863
  }
@@ -4242,12 +5877,12 @@ function isVisualCompareOwnedAssetName(value) {
4242
5877
  return Number.isInteger(row) && row >= 1 && row <= tileflowVisualCompareLimits.maximumRows;
4243
5878
  }
4244
5879
  function hasExactKeys(record, keys) {
4245
- const actual = Object.keys(record).sort(compareCodeUnits6);
4246
- const expected = [...keys].sort(compareCodeUnits6);
5880
+ const actual = Object.keys(record).sort(compareCodeUnits7);
5881
+ const expected = [...keys].sort(compareCodeUnits7);
4247
5882
  return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
4248
5883
  }
4249
5884
  function sha256Source(source) {
4250
- return createHash2("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
5885
+ return createHash3("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
4251
5886
  }
4252
5887
  async function canonicalPathIdentity(path) {
4253
5888
  const absolute = resolve5(path);
@@ -4260,7 +5895,7 @@ async function canonicalPathIdentity(path) {
4260
5895
  return process.platform === "win32" ? canonical.toLowerCase() : canonical;
4261
5896
  } catch (error) {
4262
5897
  if (!isMissingPathComponent(error)) throw error;
4263
- const parent = dirname7(current);
5898
+ const parent = dirname8(current);
4264
5899
  if (parent === current) {
4265
5900
  const canonical = absolute.normalize("NFC");
4266
5901
  return process.platform === "win32" ? canonical.toLowerCase() : canonical;
@@ -4417,7 +6052,7 @@ function createTileflowVisualDiffJson(comparisons, cwd) {
4417
6052
  return {
4418
6053
  schemaVersion: 1,
4419
6054
  command: "visual.diff",
4420
- comparisons: [...comparisons].sort((left, right) => compareCodeUnits7(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
6055
+ comparisons: [...comparisons].sort((left, right) => compareCodeUnits8(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
4421
6056
  ...createTileflowVisualComparisonDocument(comparison),
4422
6057
  baselinePath: relativePath(cwd, baselinePath),
4423
6058
  actualPath: relativePath(cwd, actualPath),
@@ -4632,7 +6267,7 @@ async function runVisualUpdate(scenes, options) {
4632
6267
  renderer: capture.renderer,
4633
6268
  networkDependent: capture.networkDependent
4634
6269
  },
4635
- warnings: [...capture.warnings].sort(compareCodeUnits7)
6270
+ warnings: [...capture.warnings].sort(compareCodeUnits8)
4636
6271
  });
4637
6272
  outputFiles.push(
4638
6273
  { path: files.pngPath, source: capture.png },
@@ -4649,7 +6284,7 @@ async function runVisualUpdate(scenes, options) {
4649
6284
  const document = {
4650
6285
  schemaVersion: 1,
4651
6286
  command: "visual.update",
4652
- updates: updates.sort((left, right) => compareCodeUnits7(left.scene, right.scene))
6287
+ updates: updates.sort((left, right) => compareCodeUnits8(left.scene, right.scene))
4653
6288
  };
4654
6289
  if (options.json) {
4655
6290
  process.stdout.write(serializeTileflowVisualCommandJson(document));
@@ -4705,7 +6340,7 @@ async function readBaselineFiles(directory, scene) {
4705
6340
  return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
4706
6341
  }
4707
6342
  async function readOptionalArtifact(path, boundary, maximumBytes) {
4708
- await assertNoSymlinkComponents(dirname8(path), boundary);
6343
+ await assertNoSymlinkComponents(dirname9(path), boundary);
4709
6344
  let info;
4710
6345
  try {
4711
6346
  info = await lstat3(path);
@@ -4723,7 +6358,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
4723
6358
  throw new TileflowCaptureError2("BASELINE_INVALID", "A visual baseline artifact is too large.");
4724
6359
  }
4725
6360
  await assertNotSymlink(path);
4726
- return new Uint8Array(await readFile5(path));
6361
+ return new Uint8Array(await readFile6(path));
4727
6362
  }
4728
6363
  async function readVisualReference(path) {
4729
6364
  let info;
@@ -4747,7 +6382,7 @@ async function readVisualReference(path) {
4747
6382
  );
4748
6383
  }
4749
6384
  await assertNotSymlink(path);
4750
- return new Uint8Array(await readFile5(path));
6385
+ return new Uint8Array(await readFile6(path));
4751
6386
  }
4752
6387
  async function resolveVisualDirectory(cwd, value, kind) {
4753
6388
  const path = resolve6(cwd, value);
@@ -4810,7 +6445,9 @@ var defaultApiUrl = defaultTileflowApiUrl;
4810
6445
  var defaultAppUrl = "https://tileflow.dev";
4811
6446
  var defaultConfigPath = defaultTileflowConfigPath;
4812
6447
  var defaultManifestPath = defaultTileflowManifestPath;
4813
- program.name("tileflow").description("Beautiful maps from config.").version(packageJson.version).showHelpAfterError(pc2.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
6448
+ program.name("tileflow").description("Beautiful maps from config.").version(packageJson.version).showHelpAfterError(pc3.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
6449
+ registerLanguageCommand(program);
6450
+ registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
4814
6451
  program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
4815
6452
  const configPath = resolve7(process.cwd(), defaultConfigPath);
4816
6453
  const ignorePath = resolve7(process.cwd(), ".gitignore");
@@ -4829,9 +6466,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
4829
6466
  }
4830
6467
  logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
4831
6468
  printKeyValue("Path", pathLabel(configPath));
4832
- printKeyValue("Map", pc2.bold("madrid"));
6469
+ printKeyValue("Map", pc3.bold("madrid"));
4833
6470
  printNextSteps([
4834
- `Install ${pc2.cyan("@tileflow/core")} and ${pc2.cyan("@tileflow/maps")} in this project if needed.`,
6471
+ `Install ${pc3.cyan("@tileflow/core")} and ${pc3.cyan("@tileflow/maps")} in this project if needed.`,
4835
6472
  `Preview locally with ${command("tileflow preview")}.`,
4836
6473
  `Validate the config with ${command("tileflow validate")}.`
4837
6474
  ]);
@@ -4927,7 +6564,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
4927
6564
  ).option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
4928
6565
  if (options.target !== "local" && options.target !== "hosted") {
4929
6566
  if (options.json) {
4930
- const failure = createTileflowCommandFailureDocument2(
6567
+ const failure = createTileflowCommandFailureDocument4(
4931
6568
  "validate",
4932
6569
  {
4933
6570
  code: "INVALID_TARGET",
@@ -4944,7 +6581,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
4944
6581
  process.cwd(),
4945
6582
  { code: "INVALID_TARGET", phase: "command-validation" }
4946
6583
  );
4947
- process.stderr.write(serializeTileflowCommandDocument2(failure));
6584
+ process.stderr.write(serializeTileflowCommandDocument4(failure));
4948
6585
  process.exitCode = 1;
4949
6586
  return;
4950
6587
  }
@@ -4959,10 +6596,10 @@ program.command("validate").description("Validate a Tileflow config").option("-c
4959
6596
  try {
4960
6597
  if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
4961
6598
  const loaded = await withTileflowConfigSecretsHidden(
4962
- () => loadTileflowConfigWithInputs2(options.config)
6599
+ () => loadTileflowConfigWithInputs3(options.config)
4963
6600
  );
4964
6601
  const project = loaded.project;
4965
- const baseDirectory = dirname9(loaded.configFile);
6602
+ const baseDirectory = dirname10(loaded.configFile);
4966
6603
  failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
4967
6604
  assertValidTileflowConfig2(project);
4968
6605
  const mapNames = getTileflowMapNames3(project).sort();
@@ -4981,8 +6618,14 @@ program.command("validate").description("Validate a Tileflow config").option("-c
4981
6618
  apiBaseUrl: options.apiBaseUrl,
4982
6619
  mapAssets
4983
6620
  });
6621
+ failureDefaults = { code: "TF_LOCAL_TILESET_INVALID", phase: "local-tileset-resolution" };
6622
+ const localTilesets = await prepareTileflowLocalTilesets(project, compiledStyles, {
6623
+ assetBaseUrl: "/tileflow",
6624
+ baseDirectory,
6625
+ cwd: process.cwd()
6626
+ });
4984
6627
  failureDefaults = { code: "FONT_COMPILATION_FAILED", phase: "font-compilation" };
4985
- const { styles } = await prepareTileflowStyleFonts(project, compiledStyles, {
6628
+ const { styles } = await prepareTileflowStyleFonts(project, localTilesets.styles, {
4986
6629
  assetBaseUrl: "/tileflow",
4987
6630
  baseDirectory,
4988
6631
  cwd: process.cwd(),
@@ -5006,6 +6649,17 @@ program.command("validate").description("Validate a Tileflow config").option("-c
5006
6649
  phase: "hosted-validation"
5007
6650
  });
5008
6651
  }
6652
+ const diagnostics = createTileflowStructuredDiagnostics2(
6653
+ {
6654
+ diagnostics: mapNames.flatMap(
6655
+ (mapName) => auditTileflowMapThemeValues(project.maps[mapName]).filter(
6656
+ ({ severity }) => severity === "warning"
6657
+ )
6658
+ )
6659
+ },
6660
+ process.cwd(),
6661
+ { code: "VALIDATION_WARNING", phase: "theme-audit" }
6662
+ );
5009
6663
  const checks = [
5010
6664
  "Config schema",
5011
6665
  "Icon asset closure",
@@ -5015,7 +6669,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
5015
6669
  ...options.target === "hosted" ? ["Hosted compatibility"] : []
5016
6670
  ];
5017
6671
  if (options.json) {
5018
- const summary = createTileflowCommandSummary({
6672
+ const summary = createTileflowCommandSummary2({
5019
6673
  code: "VALIDATION_OK",
5020
6674
  command: "validate",
5021
6675
  message: `Tileflow config is valid (${plural(mapNames.length, "map")}).`,
@@ -5026,27 +6680,30 @@ program.command("validate").description("Validate a Tileflow config").option("-c
5026
6680
  suggestion: "No changes are required."
5027
6681
  });
5028
6682
  process.stdout.write(
5029
- serializeTileflowCommandDocument2({
6683
+ serializeTileflowCommandDocument4({
5030
6684
  ...summary,
5031
6685
  target: options.target,
5032
6686
  maps: mapNames,
5033
6687
  checks,
5034
- diagnostics: []
6688
+ diagnostics
5035
6689
  })
5036
6690
  );
5037
6691
  return;
5038
6692
  }
5039
6693
  logSuccess(`Config is valid (${plural(mapNames.length, "map")}).`);
5040
6694
  printChecks(checks);
6695
+ for (const diagnostic of diagnostics) {
6696
+ logWarning(`${diagnostic.path || "(root)"}: ${diagnostic.message}`);
6697
+ }
5041
6698
  } catch (error) {
5042
6699
  if (!options.json) throw error;
5043
- const failure = createTileflowCommandFailureDocument2(
6700
+ const failure = createTileflowCommandFailureDocument4(
5044
6701
  "validate",
5045
6702
  error,
5046
6703
  process.cwd(),
5047
6704
  failureDefaults
5048
6705
  );
5049
- process.stderr.write(serializeTileflowCommandDocument2(failure));
6706
+ process.stderr.write(serializeTileflowCommandDocument4(failure));
5050
6707
  process.exitCode = 1;
5051
6708
  }
5052
6709
  });
@@ -5224,7 +6881,7 @@ program.command("preview").alias("dev").description("Run a local map preview").o
5224
6881
  });
5225
6882
  }
5226
6883
  );
5227
- program.command("deploy").description("Deploy maps to Tileflow and write the frontend manifest").option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--project <target>", "technical destination @organization/project").option("--world-promotion <id>", "continue one verified Tileflow World promotion").option("--map <name>", "map to connect when a promotion config contains multiple maps").option(
6884
+ program.command("deploy").description("Deploy maps to Tileflow and write the frontend manifest").option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--map-id <id>", "managed Map destination").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
5228
6885
  "--overwrite-self-hosted-manifest",
5229
6886
  "explicitly replace an existing self-hosted manifest at --manifest"
5230
6887
  ).action(
@@ -5232,6 +6889,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5232
6889
  const source = resolveDeploySource(process.env);
5233
6890
  const resolveApi = (selectedMap) => requireApiOptions(options, {
5234
6891
  allowStoredCredential: allowsStoredDeployCredential(source),
6892
+ assertExplicitKeyMap: true,
5235
6893
  capabilityScopes: ["styles:write"],
5236
6894
  retryCommand: cliInvocation([
5237
6895
  "tileflow",
@@ -5243,21 +6901,23 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5243
6901
  "--api-url",
5244
6902
  options.apiUrl ?? defaultApiUrl,
5245
6903
  ...options.overwriteSelfHostedManifest ? ["--overwrite-self-hosted-manifest"] : [],
5246
- ...options.worldPromotion && selectedMap ? ["--world-promotion", options.worldPromotion, "--map", selectedMap] : []
6904
+ ...options.mapId ? ["--map-id", options.mapId] : [],
6905
+ ...options.map && selectedMap ? ["--map", selectedMap] : []
5247
6906
  ])
5248
6907
  });
5249
6908
  const apiUrl = normalizeApiOrigin(options.apiUrl ?? defaultApiUrl);
5250
6909
  delete process.env.TILEFLOW_API_KEY;
5251
6910
  logInfo(`Deploying ${pathLabel(options.config)}.`);
5252
6911
  const loaded = await withTileflowConfigSecretsHidden(
5253
- () => loadTileflowConfigWithInputs2(options.config)
6912
+ () => loadTileflowConfigWithInputs3(options.config)
5254
6913
  );
5255
6914
  const project = loaded.project;
5256
- const baseDirectory = dirname9(loaded.configFile);
6915
+ const baseDirectory = dirname10(loaded.configFile);
5257
6916
  assertValidTileflowConfig2(project);
5258
6917
  const configuredMapNames = getTileflowMapNames3(project);
5259
- const mapNames = selectWorldPromotionMaps(configuredMapNames, options);
6918
+ const mapNames = selectManagedDeployMaps(configuredMapNames, options);
5260
6919
  if (!mapNames) return;
6920
+ if (!validateDeployTarget(options)) return;
5261
6921
  if (!validateDeployManifestMapNames(mapNames)) return;
5262
6922
  const deploymentProject = mapNames.length === configuredMapNames.length ? project : {
5263
6923
  ...project,
@@ -5284,9 +6944,14 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5284
6944
  apiBaseUrl: apiUrl,
5285
6945
  mapAssets: preflightMapAssets
5286
6946
  });
5287
- const preflightFonts = await prepareTileflowStyleFonts(
6947
+ const preflightLocalTilesets = await prepareTileflowLocalTilesets(
5288
6948
  deploymentProject,
5289
6949
  compiledPreflightStyles,
6950
+ { assetBaseUrl: apiUrl, baseDirectory, cwd: process.cwd() }
6951
+ );
6952
+ const preflightFonts = await prepareTileflowStyleFonts(
6953
+ deploymentProject,
6954
+ preflightLocalTilesets.styles,
5290
6955
  {
5291
6956
  assetBaseUrl: `${apiUrl}/fonts/preflight`,
5292
6957
  baseDirectory,
@@ -5299,6 +6964,14 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5299
6964
  )) {
5300
6965
  return;
5301
6966
  }
6967
+ const mapsWithHostedFontBundles = Object.keys(preflightFonts.bundles).sort();
6968
+ if (mapsWithHostedFontBundles.length > 0) {
6969
+ logError("Hosted deploy does not yet support package-owned web fonts.");
6970
+ printKeyValue("Maps", mapsWithHostedFontBundles.join(", "));
6971
+ printNextSteps(["Use self-hosted delivery or an explicit public glyph provider."]);
6972
+ process.exitCode = 1;
6973
+ return;
6974
+ }
5302
6975
  let outputManifest = null;
5303
6976
  try {
5304
6977
  outputManifest = await loadExistingDeployManifest(options.manifest, {
@@ -5315,7 +6988,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5315
6988
  process.exitCode = 1;
5316
6989
  return;
5317
6990
  }
5318
- const existingManifest = options.worldPromotion ? outputManifest : null;
6991
+ const existingManifest = outputManifest;
5319
6992
  const api = await resolveApi(mapNames[0]);
5320
6993
  if (!api) return;
5321
6994
  const hostedSpriteByPackageHash = /* @__PURE__ */ new Map();
@@ -5335,7 +7008,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5335
7008
  );
5336
7009
  const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
5337
7010
  logSuccess(
5338
- `${action} icon package ${pc2.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
7011
+ `${action} icon package ${pc3.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
5339
7012
  );
5340
7013
  hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
5341
7014
  }
@@ -5348,12 +7021,21 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5348
7021
  apiBaseUrl: api.apiUrl,
5349
7022
  mapAssets: hostedMapAssets
5350
7023
  });
5351
- const hostedFonts = await prepareTileflowStyleFonts(deploymentProject, compiledHostedStyles, {
5352
- assetBaseUrl: `${api.apiUrl}/font-bundles/preflight`,
5353
- baseDirectory,
5354
- cwd: process.cwd(),
5355
- target: "hosted"
5356
- });
7024
+ const hostedLocalTilesets = await prepareTileflowLocalTilesets(
7025
+ deploymentProject,
7026
+ compiledHostedStyles,
7027
+ { assetBaseUrl: api.apiUrl, baseDirectory, cwd: process.cwd() }
7028
+ );
7029
+ const hostedFonts = await prepareTileflowStyleFonts(
7030
+ deploymentProject,
7031
+ hostedLocalTilesets.styles,
7032
+ {
7033
+ assetBaseUrl: `${api.apiUrl}/font-bundles/preflight`,
7034
+ baseDirectory,
7035
+ cwd: process.cwd(),
7036
+ target: "hosted"
7037
+ }
7038
+ );
5357
7039
  for (const mapName of mapNames) {
5358
7040
  if (hostedFonts.bundles[mapName]?.contentHash !== preflightFonts.bundles[mapName]?.contentHash) {
5359
7041
  throw new Error(`Font bundle changed after authentication for map ${mapName}.`);
@@ -5378,7 +7060,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5378
7060
  );
5379
7061
  hostedFontBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
5380
7062
  }
5381
- const styles = Object.fromEntries(
7063
+ const fontBoundStyles = Object.fromEntries(
5382
7064
  mapNames.map((mapName) => {
5383
7065
  const themeStyles = hostedFonts.styles[mapName];
5384
7066
  const bundle = hostedFonts.bundles[mapName];
@@ -5396,6 +7078,19 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5396
7078
  ];
5397
7079
  })
5398
7080
  );
7081
+ const hostedThemeFamilies = Object.fromEntries(
7082
+ mapNames.map((mapName) => [
7083
+ mapName,
7084
+ prepareTileflowHostedThemeFamily(
7085
+ mapName,
7086
+ deploymentProject.maps[mapName],
7087
+ fontBoundStyles[mapName]
7088
+ )
7089
+ ])
7090
+ );
7091
+ const styles = Object.fromEntries(
7092
+ Object.entries(hostedThemeFamilies).map(([mapName, family]) => [mapName, family.styles])
7093
+ );
5399
7094
  const buildManifest = await createTileflowMapBuildManifest(
5400
7095
  Object.fromEntries(
5401
7096
  mapNames.map((mapName) => {
@@ -5413,7 +7108,9 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5413
7108
  })),
5414
7109
  ...hostedFonts.bundles[mapName]?.files ?? []
5415
7110
  ],
5416
- lineage: deploymentProject.mapMetadata?.[mapName]?.lineage ?? collectTileflowMapBuildLineage(map),
7111
+ lineage: deploymentProject.mapMetadata?.[mapName]?.lineage ?? [
7112
+ { id: map.id, mapVersion: map.version }
7113
+ ],
5417
7114
  map,
5418
7115
  sourceAssets: {
5419
7116
  fonts: hostedFonts.sourceIdentities[mapName] ?? [],
@@ -5431,11 +7128,11 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5431
7128
  const iconPackage = iconBinding ? packagesByHash.get(iconBinding.packageHash) : void 0;
5432
7129
  const fontBundle = hostedFonts.bundles[mapName];
5433
7130
  return {
5434
- allowedOrigins: deploymentProject.maps[mapName]?.delivery?.hosted?.allowedOrigins,
5435
7131
  iconBinding,
5436
7132
  iconPackage,
5437
7133
  fontBundle,
5438
7134
  mapName,
7135
+ teamSources: hostedThemeFamilies[mapName].teamSources,
5439
7136
  buildManifest: {
5440
7137
  maps: { [mapName]: buildManifest.maps[mapName] },
5441
7138
  ...buildManifest.provenance ? { provenance: buildManifest.provenance } : {},
@@ -5447,30 +7144,32 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5447
7144
  const deployedMaps = {};
5448
7145
  for (const deployment of deployments) {
5449
7146
  const {
5450
- allowedOrigins,
5451
7147
  buildManifest: buildManifest2,
5452
7148
  fontBundle,
5453
7149
  iconBinding,
5454
7150
  iconPackage,
5455
7151
  mapName,
7152
+ teamSources,
5456
7153
  styles: themeStyles
5457
7154
  } = deployment;
5458
7155
  const themeNames = Object.keys(themeStyles).sort();
5459
7156
  logInfo(
5460
- `Deploying compiled map ${pc2.bold(mapName)} (${plural(themeNames.length, "theme")}).`
7157
+ `Deploying compiled map ${pc3.bold(mapName)} (${plural(themeNames.length, "theme")}).`
5461
7158
  );
5462
7159
  const response = await publishHostedStyle(
5463
7160
  api,
5464
7161
  {
5465
7162
  artifact: {
5466
7163
  buildManifest: buildManifest2,
7164
+ kind: "tileflow-map-deployment",
5467
7165
  mapId: mapName,
5468
- schemaVersion: 3,
5469
- styles: themeStyles
7166
+ schemaVersion: 1,
7167
+ styles: themeStyles,
7168
+ teamSources
5470
7169
  },
5471
7170
  environment: mapName,
5472
- ...options.worldPromotion ? { usageMode: "session", worldPromotionId: options.worldPromotion } : {},
5473
- ...allowedOrigins ? { policy: { allowedOrigins } } : {},
7171
+ managedMapId: options.mapId,
7172
+ usageMode: "session",
5474
7173
  ...iconBinding && iconPackage ? {
5475
7174
  iconPackage: {
5476
7175
  contentHash: iconPackage.contentHash,
@@ -5493,8 +7192,9 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5493
7192
  return;
5494
7193
  }
5495
7194
  const body = response.value;
5496
- if (options.worldPromotion && body.worldPromotionId !== options.worldPromotion) {
5497
- logError(`Deploy response did not confirm the World promotion for ${mapName}.`);
7195
+ const expectedMapId = options.mapId ?? api.mapId;
7196
+ if (expectedMapId && body.mapId !== expectedMapId) {
7197
+ logError(`Deploy response did not confirm the requested Map for ${mapName}.`);
5498
7198
  process.exitCode = 1;
5499
7199
  return;
5500
7200
  }
@@ -5504,7 +7204,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5504
7204
  process.exitCode = 1;
5505
7205
  return;
5506
7206
  }
5507
- const resolvedMap = parseTileflowMap2(deploymentProject.maps[mapName]);
7207
+ const resolvedMap = parseResolvedTileflowMap3(deploymentProject.maps[mapName]);
5508
7208
  const mapBuild = buildManifest2.maps[mapName];
5509
7209
  deployedMaps[mapName] = {
5510
7210
  defaultTheme: resolvedMap.defaultTheme,
@@ -5528,13 +7228,14 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5528
7228
  })
5529
7229
  ),
5530
7230
  ...resolvedMap.view ? { view: resolvedMap.view } : {},
5531
- ...options.worldPromotion ? { usageMode: "session", worldGeneration: "v1" } : {}
7231
+ usageMode: "session",
7232
+ worldGeneration: "v1"
5532
7233
  };
5533
7234
  const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
5534
7235
  if (body.changed === false) {
5535
- logSuccess(`Unchanged ${pc2.bold(mapName)}${versionLabel}.`);
7236
+ logSuccess(`Unchanged ${pc3.bold(mapName)}${versionLabel}.`);
5536
7237
  } else {
5537
- logSuccess(`Published ${pc2.bold(mapName)}${versionLabel}.`);
7238
+ logSuccess(`Published ${pc3.bold(mapName)}${versionLabel}.`);
5538
7239
  }
5539
7240
  }
5540
7241
  const manifestPath = await writeDeployManifest(options.manifest, {
@@ -5545,10 +7246,21 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
5545
7246
  logSuccess("Deployed Tileflow maps.");
5546
7247
  printKeyValue("Manifest", pathLabel(manifestPath));
5547
7248
  printDeployedMaps(deployedMaps);
5548
- printNextSteps([`Check hosted state with ${command("tileflow status")}.`]);
7249
+ const deployedMapId = Object.values(deployedMaps)[0]?.mapId;
7250
+ if (deployedMapId) {
7251
+ printNextSteps([
7252
+ `Check hosted state with ${command(`tileflow status --map-id ${deployedMapId}`)}.`
7253
+ ]);
7254
+ }
5549
7255
  }
5550
7256
  );
5551
- program.command("status").description("Show deployed compiled styles").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--project <target>", "technical destination @organization/project").option("--json", "print raw JSON").action(async (options) => {
7257
+ program.command("status").description("Show deployed compiled styles").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--map-id <id>", "managed Map destination").option("--json", "print raw JSON").action(async (options) => {
7258
+ if (!options.mapId || !/^map_[A-Za-z0-9_-]{16}$/u.test(options.mapId)) {
7259
+ if (options.json) console.error("Status requires a valid --map-id.");
7260
+ else logError("Status requires a valid --map-id.");
7261
+ process.exitCode = 1;
7262
+ return;
7263
+ }
5552
7264
  let api;
5553
7265
  try {
5554
7266
  api = await requireApiOptions(options, {
@@ -5576,7 +7288,7 @@ program.command("status").description("Show deployed compiled styles").option("-
5576
7288
  }
5577
7289
  let status;
5578
7290
  try {
5579
- status = await fetchHostedProjectStatus(api);
7291
+ status = await fetchHostedMapStatus(api);
5580
7292
  } catch (error) {
5581
7293
  const message = safeStatusError(error);
5582
7294
  if (options.json) console.error(message);
@@ -5584,11 +7296,17 @@ program.command("status").description("Show deployed compiled styles").option("-
5584
7296
  process.exitCode = 1;
5585
7297
  return;
5586
7298
  }
7299
+ if (status.mapId !== options.mapId) {
7300
+ if (options.json) console.error("Status response belongs to another Map.");
7301
+ else logError("Status response belongs to another Map.");
7302
+ process.exitCode = 1;
7303
+ return;
7304
+ }
5587
7305
  if (options.json) {
5588
7306
  console.log(JSON.stringify(status, null, 2));
5589
7307
  return;
5590
7308
  }
5591
- printProjectStatus(status, api.apiUrl);
7309
+ printMapStatus(status, api.apiUrl);
5592
7310
  });
5593
7311
  var iconsCommand = program.command("icons").description("Inspect managed Tileflow icons");
5594
7312
  registerIconListCommand(iconsCommand, { defaultConfigPath });
@@ -5599,6 +7317,7 @@ registerIconDiffCommand(iconsCommand, {
5599
7317
  resolveApi: (options) => {
5600
7318
  const source = resolveDeploySource(process.env);
5601
7319
  return requireApiOptions(options, {
7320
+ assertExplicitKeyMap: true,
5602
7321
  allowStoredCredential: allowsStoredDeployCredential(source),
5603
7322
  capabilityScopes: ["status:read"],
5604
7323
  retryCommand: cliInvocation([
@@ -5608,6 +7327,8 @@ registerIconDiffCommand(iconsCommand, {
5608
7327
  "--against",
5609
7328
  options.against,
5610
7329
  ...options.config ? ["--config", options.config] : [],
7330
+ "--map-id",
7331
+ options.mapId,
5611
7332
  "--api-url",
5612
7333
  options.apiUrl ?? defaultApiUrl
5613
7334
  ]),
@@ -5618,6 +7339,7 @@ registerIconDiffCommand(iconsCommand, {
5618
7339
  var inspectCommand = program.command("inspect").description("Inspect map data for authoring");
5619
7340
  registerConfigInspectCommand(inspectCommand, { defaultConfigPath });
5620
7341
  registerFeatureInspectCommand(inspectCommand, { defaultConfigPath });
7342
+ registerAiCommands(program, { defaultApiUrl, defaultConfigPath });
5621
7343
  registerCaptureCommands(program, { defaultConfigPath });
5622
7344
  registerVisualCommands(program, {
5623
7345
  defaultConfigPath,
@@ -5857,7 +7579,7 @@ async function loginWithDeviceFlow(options) {
5857
7579
  codeChallenge,
5858
7580
  deviceName: hostname()
5859
7581
  });
5860
- printKeyValue("Code", pc2.bold(authorization.userCode));
7582
+ printKeyValue("Code", pc3.bold(authorization.userCode));
5861
7583
  printKeyValue("URL", link(authorization.verificationUriComplete));
5862
7584
  if (options.noBrowser) {
5863
7585
  logMuted("Open the URL above to continue.");
@@ -5867,7 +7589,7 @@ async function loginWithDeviceFlow(options) {
5867
7589
  output: process.stdout
5868
7590
  });
5869
7591
  try {
5870
- await readline.question(`${pc2.cyan("?")} Press Enter to open Tileflow Dashboard...`);
7592
+ await readline.question(`${pc3.cyan("?")} Press Enter to open Tileflow Dashboard...`);
5871
7593
  openBrowser(authorization.verificationUriComplete);
5872
7594
  } finally {
5873
7595
  readline.close();
@@ -5901,7 +7623,7 @@ async function loginWithDeviceFlow(options) {
5901
7623
  printKeyValue("Dashboard", link(options.appUrl));
5902
7624
  }
5903
7625
  function createPkceChallenge(verifier) {
5904
- return createHash3("sha256").update(verifier).digest("base64url");
7626
+ return createHash4("sha256").update(verifier).digest("base64url");
5905
7627
  }
5906
7628
  function randomBase64Url(byteLength) {
5907
7629
  return randomBytes2(byteLength).toString("base64url");
@@ -5920,7 +7642,7 @@ function openBrowser(url, errorsToStderr = false) {
5920
7642
  async function writeDeployManifest(manifestPath, manifest) {
5921
7643
  const outputPath = resolve7(process.cwd(), manifestPath);
5922
7644
  await writeAtomicFile({
5923
- boundaryPath: dirname9(outputPath),
7645
+ boundaryPath: dirname10(outputPath),
5924
7646
  force: true,
5925
7647
  label: "Deploy manifest",
5926
7648
  managed: true,
@@ -5938,36 +7660,46 @@ function createCompiledMapAssets(compiled, resolveSprite) {
5938
7660
  ])
5939
7661
  );
5940
7662
  }
5941
- function selectWorldPromotionMaps(mapNames, options) {
5942
- if (options.map && !options.worldPromotion) {
5943
- logError("--map is reserved for continuing a verified World promotion.");
5944
- process.exitCode = 1;
5945
- return null;
5946
- }
5947
- if (!options.worldPromotion) return mapNames;
5948
- if (!/^wpr_[A-Za-z0-9_-]{8,80}$/u.test(options.worldPromotion)) {
5949
- logError("World promotion reference is invalid.");
7663
+ function selectManagedDeployMaps(mapNames, options) {
7664
+ if (options.map && !options.mapId) {
7665
+ logError("--map requires --map-id.");
5950
7666
  process.exitCode = 1;
5951
7667
  return null;
5952
7668
  }
5953
7669
  if (options.map) {
5954
7670
  if (!mapNames.includes(options.map)) {
5955
- logError(`Unknown Tileflow map for World promotion: ${options.map}.`);
7671
+ logError(`Unknown Tileflow map: ${options.map}.`);
5956
7672
  printKeyValue("Maps", mapNames.join(", "));
5957
7673
  process.exitCode = 1;
5958
7674
  return null;
5959
7675
  }
5960
7676
  return [options.map];
5961
7677
  }
7678
+ if (!options.mapId) return mapNames;
5962
7679
  if (mapNames.length === 1) return [mapNames[0]];
5963
- logError("World promotion requires an explicit map because this config contains multiple maps.");
7680
+ logError(
7681
+ "This managed target requires an explicit map because the config contains multiple maps."
7682
+ );
5964
7683
  printKeyValue("Maps", mapNames.join(", "));
5965
7684
  printNextSteps([
5966
- `Retry with ${command(`tileflow deploy --world-promotion ${options.worldPromotion} --map ${mapNames[0]}`)}.`
7685
+ `Retry with ${command(`tileflow deploy --map-id ${options.mapId} --map ${mapNames[0]}`)}.`
5967
7686
  ]);
5968
7687
  process.exitCode = 1;
5969
7688
  return null;
5970
7689
  }
7690
+ function validateDeployTarget(options) {
7691
+ if (!options.mapId) {
7692
+ logError("Managed deploy requires --map-id.");
7693
+ process.exitCode = 1;
7694
+ return false;
7695
+ }
7696
+ if (options.mapId && !/^map_[A-Za-z0-9_-]{16}$/u.test(options.mapId)) {
7697
+ logError("Managed Map ID is invalid.");
7698
+ process.exitCode = 1;
7699
+ return false;
7700
+ }
7701
+ return true;
7702
+ }
5971
7703
  function validateDeployManifestMapNames(mapNames) {
5972
7704
  for (const mapName of mapNames) {
5973
7705
  if (!tileflowMapIdSchema2.safeParse(mapName).success) {
@@ -5981,7 +7713,7 @@ function validateDeployManifestMapNames(mapNames) {
5981
7713
  async function loadExistingDeployManifest(manifestPath, options) {
5982
7714
  let source;
5983
7715
  try {
5984
- source = await readFile6(resolve7(process.cwd(), manifestPath), "utf8");
7716
+ source = await readFile7(resolve7(process.cwd(), manifestPath), "utf8");
5985
7717
  } catch (error) {
5986
7718
  if (error.code === "ENOENT") return null;
5987
7719
  throw new Error("Existing Tileflow manifest could not be read.");
@@ -6014,13 +7746,32 @@ async function loadExistingDeployManifest(manifestPath, options) {
6014
7746
  }
6015
7747
  async function requireApiOptions(options, behavior = {}) {
6016
7748
  const apiUrl = normalizeApiOrigin(options.apiUrl ?? defaultApiUrl);
7749
+ const requestedMapId = options.mapId;
6017
7750
  const requested = options.project ? parseProjectReference(options.project) : null;
7751
+ if (requestedMapId && !/^map_[A-Za-z0-9_-]{16}$/u.test(requestedMapId)) {
7752
+ if (!behavior.silent) logError("Managed Map ID is invalid.");
7753
+ process.exitCode = 1;
7754
+ return null;
7755
+ }
6018
7756
  if (options.project && !requested) {
6019
7757
  if (!behavior.silent) logError("Managed destination must use @organization/project syntax.");
6020
7758
  process.exitCode = 1;
6021
7759
  return null;
6022
7760
  }
6023
7761
  if (options.apiKey) {
7762
+ if (requestedMapId && behavior.assertExplicitKeyMap) {
7763
+ const profile = await validateApiKey(apiUrl, options.apiKey);
7764
+ if (!profile.ok) {
7765
+ if (!behavior.silent) logError(profile.error);
7766
+ process.exitCode = 1;
7767
+ return null;
7768
+ }
7769
+ if (profile.value.mapId !== requestedMapId) {
7770
+ if (!behavior.silent) logError("This API key belongs to another Map.");
7771
+ process.exitCode = 1;
7772
+ return null;
7773
+ }
7774
+ }
6024
7775
  if (requested) {
6025
7776
  const profile = await validateApiKey(apiUrl, options.apiKey);
6026
7777
  if (!profile.ok) {
@@ -6028,7 +7779,7 @@ async function requireApiOptions(options, behavior = {}) {
6028
7779
  process.exitCode = 1;
6029
7780
  return null;
6030
7781
  }
6031
- if (projectReference(profile.value) !== options.project) {
7782
+ if (requested && projectReference(profile.value) !== options.project) {
6032
7783
  if (!behavior.silent) {
6033
7784
  logError(
6034
7785
  `This project key belongs to ${projectReference(profile.value)}, not ${options.project}.`
@@ -6038,14 +7789,18 @@ async function requireApiOptions(options, behavior = {}) {
6038
7789
  return null;
6039
7790
  }
6040
7791
  }
6041
- return { apiKey: options.apiKey, apiUrl };
7792
+ return {
7793
+ apiKey: options.apiKey,
7794
+ apiUrl,
7795
+ ...requestedMapId ? { mapId: requestedMapId } : {}
7796
+ };
6042
7797
  }
6043
7798
  const allowStoredCredential = behavior.allowStoredCredential !== false && allowsStoredDeployCredential(resolveDeploySource(process.env));
6044
7799
  if (!allowStoredCredential) {
6045
7800
  if (!behavior.silent) {
6046
- logError("CI requires an explicit application-scoped Tileflow API key.");
7801
+ logError("CI requires an explicit Map-scoped Tileflow API key.");
6047
7802
  printNextSteps([
6048
- `Set ${pc2.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
7803
+ `Set ${pc3.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
6049
7804
  "The saved personal account session is never used in CI."
6050
7805
  ]);
6051
7806
  }
@@ -6071,6 +7826,19 @@ async function requireApiOptions(options, behavior = {}) {
6071
7826
  process.exitCode = 1;
6072
7827
  return null;
6073
7828
  }
7829
+ if (requestedMapId) {
7830
+ const capability2 = await requestMapCapability(
7831
+ resolvedSession.session,
7832
+ { mapId: requestedMapId },
7833
+ behavior.capabilityScopes ?? ["status:read"]
7834
+ );
7835
+ if (!capability2.ok) {
7836
+ if (!behavior.silent) logError(capability2.error);
7837
+ process.exitCode = 1;
7838
+ return null;
7839
+ }
7840
+ return { apiKey: capability2.capability, apiUrl, mapId: capability2.mapId };
7841
+ }
6074
7842
  const target = await resolveAccountProjectTarget(resolvedSession.session, options.project);
6075
7843
  if (target.kind === "remote") {
6076
7844
  if (!behavior.silent) logError(target.error);
@@ -6138,17 +7906,17 @@ function printCliError(error) {
6138
7906
  }
6139
7907
  const message = error instanceof Error ? error.message : "Command failed.";
6140
7908
  if (message.includes("Cannot find module '@tileflow/core'")) {
6141
- logError(`Cannot load ${pc2.cyan("@tileflow/core")} from this project.`);
7909
+ logError(`Cannot load ${pc3.cyan("@tileflow/core")} from this project.`);
6142
7910
  printNextSteps([
6143
- `Install ${pc2.cyan("@tileflow/core")} in this project.`,
7911
+ `Install ${pc3.cyan("@tileflow/core")} in this project.`,
6144
7912
  `Run the command again from the project root.`
6145
7913
  ]);
6146
7914
  return;
6147
7915
  }
6148
7916
  if (message.includes("Cannot find module '@tileflow/maps'")) {
6149
- logError(`Cannot load ${pc2.cyan("@tileflow/maps")} from this project.`);
7917
+ logError(`Cannot load ${pc3.cyan("@tileflow/maps")} from this project.`);
6150
7918
  printNextSteps([
6151
- `Install ${pc2.cyan("@tileflow/maps")} in this project.`,
7919
+ `Install ${pc3.cyan("@tileflow/maps")} in this project.`,
6152
7920
  `Run the command again from the project root.`
6153
7921
  ]);
6154
7922
  return;
@@ -6159,51 +7927,51 @@ function printCliError(error) {
6159
7927
  }
6160
7928
  }
6161
7929
  function logSuccess(message) {
6162
- console.log(`${pc2.green("\u2713")} ${message}`);
7930
+ console.log(`${pc3.green("\u2713")} ${message}`);
6163
7931
  }
6164
7932
  function logError(message) {
6165
- console.log(`${pc2.red("\u2715")} ${message}`);
7933
+ console.log(`${pc3.red("\u2715")} ${message}`);
6166
7934
  }
6167
7935
  function logWarning(message) {
6168
- console.log(`${pc2.yellow("!")} ${message}`);
7936
+ console.log(`${pc3.yellow("!")} ${message}`);
6169
7937
  }
6170
7938
  function logInfo(message) {
6171
- console.log(`${pc2.cyan("i")} ${message}`);
7939
+ console.log(`${pc3.cyan("i")} ${message}`);
6172
7940
  }
6173
7941
  function logMuted(message) {
6174
- console.log(pc2.gray(message));
7942
+ console.log(pc3.gray(message));
6175
7943
  }
6176
7944
  function printTitle(title, subtitle) {
6177
- console.log(pc2.bold(title));
7945
+ console.log(pc3.bold(title));
6178
7946
  if (subtitle) {
6179
- console.log(pc2.gray(subtitle));
7947
+ console.log(pc3.gray(subtitle));
6180
7948
  }
6181
7949
  }
6182
7950
  function printKeyValue(label, value) {
6183
- console.log(`${pc2.gray(`${label}:`.padEnd(13))} ${value}`);
7951
+ console.log(`${pc3.gray(`${label}:`.padEnd(13))} ${value}`);
6184
7952
  }
6185
7953
  function printChecks(items) {
6186
7954
  for (const item of items) {
6187
- console.log(` ${pc2.green("\u2713")} ${item}`);
7955
+ console.log(` ${pc3.green("\u2713")} ${item}`);
6188
7956
  }
6189
7957
  }
6190
7958
  function printNextSteps(steps) {
6191
7959
  if (steps.length === 0) return;
6192
7960
  console.log(`
6193
- ${pc2.bold("Next steps")}`);
7961
+ ${pc3.bold("Next steps")}`);
6194
7962
  for (const step of steps) {
6195
- console.log(` ${pc2.cyan("\u2022")} ${step}`);
7963
+ console.log(` ${pc3.cyan("\u2022")} ${step}`);
6196
7964
  }
6197
7965
  }
6198
7966
  function printValidationErrors(messages) {
6199
7967
  logError("Tileflow config has errors.");
6200
7968
  for (const message of messages) {
6201
- const path = message.path ? pc2.cyan(message.path) : pc2.cyan("(root)");
6202
- console.log(` ${pc2.red("\u2715")} ${path} ${pc2.gray("-")} ${message.message}`);
7969
+ const path = message.path ? pc3.cyan(message.path) : pc3.cyan("(root)");
7970
+ console.log(` ${pc3.red("\u2715")} ${path} ${pc3.gray("-")} ${message.message}`);
6203
7971
  }
6204
7972
  }
6205
7973
  function printAccountDetails(session) {
6206
- printKeyValue("Account", pc2.bold(session.account.name));
7974
+ printKeyValue("Account", pc3.bold(session.account.name));
6207
7975
  printKeyValue("Email", session.account.email);
6208
7976
  printKeyValue("API", link(session.apiOrigin));
6209
7977
  printKeyValue("Expires", formatDate(session.expiresAt));
@@ -6232,11 +8000,11 @@ function printDeployedMaps(maps) {
6232
8000
  const entries = Object.entries(maps);
6233
8001
  if (entries.length === 0) return;
6234
8002
  console.log(`
6235
- ${pc2.bold("Maps")}`);
8003
+ ${pc3.bold("Maps")}`);
6236
8004
  for (const [name, map] of entries) {
6237
8005
  for (const [themeName, theme] of Object.entries(map.themes)) {
6238
8006
  const label = `${name}/${themeName}`;
6239
- console.log(` ${pc2.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
8007
+ console.log(` ${pc3.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
6240
8008
  }
6241
8009
  }
6242
8010
  }
@@ -6254,7 +8022,7 @@ function plural(count, singular, pluralForm = `${singular}s`) {
6254
8022
  return `${count} ${count === 1 ? singular : pluralForm}`;
6255
8023
  }
6256
8024
  function command(value) {
6257
- return pc2.cyan(value);
8025
+ return pc3.cyan(value);
6258
8026
  }
6259
8027
  function cliInvocation(parts) {
6260
8028
  return parts.map(quoteCliArgument).join(" ");
@@ -6263,10 +8031,10 @@ function quoteCliArgument(value) {
6263
8031
  return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
6264
8032
  }
6265
8033
  function link(value) {
6266
- return pc2.cyan(value);
8034
+ return pc3.cyan(value);
6267
8035
  }
6268
8036
  function pathLabel(value) {
6269
- return pc2.cyan(value);
8037
+ return pc3.cyan(value);
6270
8038
  }
6271
8039
  function starterConfig() {
6272
8040
  return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
@@ -6295,7 +8063,7 @@ export default defineMap({
6295
8063
  systemThemes: { light: "light", dark: "dark" },
6296
8064
  modules: {
6297
8065
  labels: labels({ roads: "major" }),
6298
- poi: poi({ enabled: true, density: 3, icons: true })
8066
+ poi: poi({ density: 3, icons: true })
6299
8067
  },
6300
8068
  view: {
6301
8069
  center: [-3.7038, 40.4168],
@@ -6310,17 +8078,17 @@ function printHostedCompatibilityIssues(issues) {
6310
8078
  process.exitCode = 1;
6311
8079
  return false;
6312
8080
  }
6313
- function printProjectStatus(status, apiUrl) {
8081
+ function printMapStatus(status, apiUrl) {
6314
8082
  printTitle("Tileflow status");
6315
- printKeyValue("Application", pc2.bold(status.projectId));
8083
+ printKeyValue("Map", pc3.bold(status.mapId));
6316
8084
  console.log(`
6317
- ${pc2.bold("Styles")}`);
8085
+ ${pc3.bold("Styles")}`);
6318
8086
  if (status.styles.length === 0) {
6319
8087
  logMuted(" No styles deployed.");
6320
8088
  }
6321
8089
  for (const style of status.styles) {
6322
8090
  console.log(
6323
- ` ${pc2.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes3(style.size).padStart(10)} ${pc2.gray(formatDate(style.uploaded))}`
8091
+ ` ${pc3.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes3(style.size).padStart(10)} ${pc3.gray(formatDate(style.uploaded))}`
6324
8092
  );
6325
8093
  }
6326
8094
  }