tileflow 0.1.0-alpha.0 → 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,54 +7,59 @@ import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
7
7
  import { existsSync, readFileSync, realpathSync } from "fs";
8
8
  import { readFile as readFile7, writeFile as writeFile2 } from "fs/promises";
9
9
  import { hostname } from "os";
10
- import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
10
+ import { dirname as dirname12, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve8, sep as sep5 } from "path";
11
11
  import { createInterface } from "readline/promises";
12
- import pc3 from "picocolors";
12
+ import pc6 from "picocolors";
13
+ import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
13
14
  import {
14
- auditTileflowMapThemeValues,
15
- getTileflowStyleFontFaces,
16
- parseResolvedTileflowMap as parseResolvedTileflowMap3,
15
+ auditTileflowMapThemeValues as auditTileflowMapThemeValues2,
16
+ getTileflowStyleFontFaces as getTileflowStyleFontFaces2,
17
+ parseResolvedTileflowMap as parseResolvedTileflowMap5,
17
18
  tileflowMapIdSchema as tileflowMapIdSchema2
18
19
  } from "@tileflow/core";
19
20
  import {
20
- createTileflowMapBuildManifest
21
+ createTileflowMapBuildManifest as createTileflowMapBuildManifest2
21
22
  } from "@tileflow/core/build";
22
23
  import {
23
- parseTileflowRuntimeManifest
24
+ parseTileflowRuntimeManifest as parseTileflowRuntimeManifest2
24
25
  } from "@tileflow/core/manifest";
26
+ import { resolveTileflowRenderer as resolveTileflowRenderer2 } from "@tileflow/core/native-profile";
25
27
  import {
26
28
  createTileflowArtifactDiagnostics as createTileflowArtifactDiagnostics3,
27
29
  createTileflowArtifactSession as createTileflowArtifactSession3,
28
- createTileflowBuildProvenance,
29
- createTileflowStyles,
30
- prepareTileflowLocalTilesets,
30
+ createTileflowBuildProvenance as createTileflowBuildProvenance2,
31
+ createTileflowStyles as createTileflowStyles2,
32
+ prepareTileflowLocalTilesets as prepareTileflowLocalTilesets2,
31
33
  writeTileflowBuildArtifacts
32
34
  } from "@tileflow/dev/artifacts";
33
35
  import {
34
- assertValidTileflowConfig as assertValidTileflowConfig2,
36
+ assertValidTileflowConfig as assertValidTileflowConfig3,
35
37
  defaultTileflowApiUrl,
36
38
  defaultTileflowConfigPath,
37
39
  defaultTileflowManifestPath,
38
- getTileflowMapNames as getTileflowMapNames3,
39
- loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3,
40
- TileflowValidationError as TileflowValidationError4
40
+ getTileflowMapNames as getTileflowMapNames5,
41
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs4,
42
+ TileflowValidationError as TileflowValidationError5
41
43
  } from "@tileflow/dev/config";
42
44
  import {
43
45
  bindTileflowStyleFontBundle,
44
- prepareTileflowStyleFonts,
46
+ prepareTileflowStyleFonts as prepareTileflowStyleFonts2,
45
47
  TileflowFontCompilationError
46
48
  } from "@tileflow/dev/fonts";
47
- import { compileTileflowIconPackages as compileTileflowIconPackages2, TileflowIconCompilationError as TileflowIconCompilationError3 } from "@tileflow/dev/icons";
49
+ import {
50
+ compileTileflowIconPackages as compileTileflowIconPackages3,
51
+ TileflowIconCompilationError as TileflowIconCompilationError3
52
+ } from "@tileflow/dev/icons";
48
53
  import { resolveTileflowPreview } from "@tileflow/dev/preview";
49
54
  import {
50
55
  createTileflowComparisonRequestHandler,
51
- createTileflowDevRequestHandler
56
+ createTileflowDevRequestHandler as createTileflowDevRequestHandler2
52
57
  } from "@tileflow/dev/server";
53
58
  import {
54
- createTileflowCommandFailureDocument as createTileflowCommandFailureDocument4,
55
- createTileflowCommandSummary as createTileflowCommandSummary2,
56
- createTileflowStructuredDiagnostics as createTileflowStructuredDiagnostics2,
57
- serializeTileflowCommandDocument as serializeTileflowCommandDocument4,
59
+ createTileflowCommandFailureDocument as createTileflowCommandFailureDocument5,
60
+ createTileflowCommandSummary as createTileflowCommandSummary3,
61
+ createTileflowStructuredDiagnostics as createTileflowStructuredDiagnostics3,
62
+ serializeTileflowCommandDocument as serializeTileflowCommandDocument5,
58
63
  TileflowStyleValidationError as TileflowStyleValidationError2
59
64
  } from "@tileflow/dev/validation";
60
65
 
@@ -64,6 +69,7 @@ import { chmod, mkdir, open, readFile, rename, unlink } from "fs/promises";
64
69
  import { homedir } from "os";
65
70
  import { dirname, resolve } from "path";
66
71
  import { z } from "zod";
72
+ var rejectedAccountSessionMessage = "The saved Tileflow account session is no longer valid. Run tileflow login to authorize this machine again.";
67
73
  var maximumAuthFileBytes = 1024 * 1024;
68
74
  var maximumOrigins = 16;
69
75
  var accountSchema = z.object({
@@ -380,14 +386,14 @@ async function runExplain(options, compileStyle) {
380
386
  });
381
387
  if (!selection) throw new Error("Explain selection was not resolved.");
382
388
  if (!output.result.ok) {
383
- const failure = createTileflowCommandFailureDocument(
389
+ const failure2 = createTileflowCommandFailureDocument(
384
390
  "explain",
385
391
  { diagnostics: output.structuredDiagnostics },
386
392
  process.cwd(),
387
393
  { code: "COMPILATION_FAILED", phase: "compilation" }
388
394
  );
389
395
  const document2 = {
390
- ...failure,
396
+ ...failure2,
391
397
  authoringManifestSchemaVersion: tileflowAuthoringManifestSchemaVersion,
392
398
  selection,
393
399
  compilation: output.compilation
@@ -417,12 +423,12 @@ async function runExplain(options, compileStyle) {
417
423
  if (options.json) process.stdout.write(serializeTileflowCommandDocument(document));
418
424
  else printHumanExplain(document);
419
425
  } catch (error) {
420
- const failure = createTileflowCommandFailureDocument("explain", error, process.cwd(), {
426
+ const failure2 = createTileflowCommandFailureDocument("explain", error, process.cwd(), {
421
427
  code: "EXPLAIN_FAILED",
422
428
  phase: "compilation"
423
429
  });
424
- if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure));
425
- else printHumanCommandFailure(failure);
430
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure2));
431
+ else printHumanCommandFailure(failure2);
426
432
  process.exitCode = 1;
427
433
  }
428
434
  }
@@ -466,12 +472,12 @@ async function runSemanticDiff(options, defaultConfigPath2) {
466
472
  if (options.json) process.stdout.write(serializeTileflowCommandDocument(document));
467
473
  else printHumanSemanticDiff(difference);
468
474
  } catch (error) {
469
- const failure = createTileflowCommandFailureDocument("semantic-diff", error, process.cwd(), {
475
+ const failure2 = createTileflowCommandFailureDocument("semantic-diff", error, process.cwd(), {
470
476
  code: "SEMANTIC_DIFF_FAILED",
471
477
  phase: "semantic-diff"
472
478
  });
473
- if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure));
474
- else printHumanCommandFailure(failure);
479
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure2));
480
+ else printHumanCommandFailure(failure2);
475
481
  process.exitCode = 1;
476
482
  }
477
483
  }
@@ -1210,14 +1216,14 @@ async function runCaptureWatchCommand(positionalScenes, options) {
1210
1216
  } catch (error) {
1211
1217
  if (signal.aborted || stopped) return;
1212
1218
  unresolvedFailure = true;
1213
- const failure = createTileflowCaptureFailureJson(error, cwd);
1219
+ const failure2 = createTileflowCaptureFailureJson(error, cwd);
1214
1220
  emit({
1215
1221
  event: "failed",
1216
1222
  generation,
1217
- code: failure.code,
1218
- phase: failure.phase,
1219
- diagnostics: failure.diagnostics,
1220
- ...failure.resources ? { resources: failure.resources } : {}
1223
+ code: failure2.code,
1224
+ phase: failure2.phase,
1225
+ diagnostics: failure2.diagnostics,
1226
+ ...failure2.resources ? { resources: failure2.resources } : {}
1221
1227
  });
1222
1228
  }
1223
1229
  };
@@ -1543,6 +1549,11 @@ function runGit(cwd, args) {
1543
1549
  });
1544
1550
  }
1545
1551
 
1552
+ // src/cli-version.ts
1553
+ function isRootCliVersionRequest(arguments_) {
1554
+ return arguments_.length === 1 && (arguments_[0] === "--version" || arguments_[0] === "-V");
1555
+ }
1556
+
1546
1557
  // src/config-inspect-command.ts
1547
1558
  import { inspectTileflowConfig } from "@tileflow/dev/inspect";
1548
1559
  import {
@@ -1561,18 +1572,18 @@ function registerConfigInspectCommand(inspect, dependencies) {
1561
1572
  }
1562
1573
  printHumanConfigInspection(document);
1563
1574
  } catch (error) {
1564
- const failure = createTileflowCommandFailureDocument2("inspect", error, process.cwd(), {
1575
+ const failure2 = createTileflowCommandFailureDocument2("inspect", error, process.cwd(), {
1565
1576
  code: "INSPECTION_FAILED",
1566
1577
  phase: "config-inspection"
1567
1578
  });
1568
1579
  if (options.json) {
1569
- process.stderr.write(serializeTileflowCommandDocument2(failure));
1580
+ process.stderr.write(serializeTileflowCommandDocument2(failure2));
1570
1581
  } else {
1571
- console.error(`${failure.message} [${failure.code}; ${failure.phase}]`);
1572
- for (const diagnostic of failure.diagnostics.slice(1)) {
1582
+ console.error(`${failure2.message} [${failure2.code}; ${failure2.phase}]`);
1583
+ for (const diagnostic of failure2.diagnostics.slice(1)) {
1573
1584
  console.error(`- ${diagnostic.path || "(root)"}: ${diagnostic.message}`);
1574
1585
  }
1575
- console.error(`Suggestion: ${failure.suggestion}`);
1586
+ console.error(`Suggestion: ${failure2.suggestion}`);
1576
1587
  }
1577
1588
  process.exitCode = 1;
1578
1589
  }
@@ -1588,6 +1599,350 @@ function printHumanConfigInspection(inspection) {
1588
1599
  console.log("Run tileflow inspect --json for the complete machine-readable result.");
1589
1600
  }
1590
1601
 
1602
+ // src/coordinates-command.ts
1603
+ import { lstat as lstat2, open as open3 } from "fs/promises";
1604
+ import { CoordinatesSetupError } from "@tileflow/coordinates-runtime";
1605
+ import {
1606
+ CoordinatesContractError,
1607
+ coordinatesFailureSchema,
1608
+ coordinatesLimits,
1609
+ parseCoordinatesJsonRequest,
1610
+ parseCoordinatesRequest
1611
+ } from "@tileflow/coordinates/contract";
1612
+ var CoordinatesInputError = class extends Error {
1613
+ constructor(reason) {
1614
+ super("Coordinates input could not be read.");
1615
+ this.reason = reason;
1616
+ }
1617
+ reason;
1618
+ };
1619
+ function registerCoordinatesCommands(program2, dependencies) {
1620
+ const setup = program2.commands.find((command2) => command2.name() === "setup") ?? program2.command("setup").description("Provision local Tileflow tooling");
1621
+ configureCommand(
1622
+ setup.command("coordinates").description("Provision an offline Coordinates execution release").option("--source <url-or-path>", "distribution manifest URL or offline path").option("--archive <path>", "offline archive path alias").option("--cache-dir <path>", "Coordinates cache directory").option("--release <release-id>", "required Coordinates release identity").option("--development", "allow a development distribution").option("--json", "print one JSON receipt")
1623
+ ).action(async (options, command2) => {
1624
+ if (options.help) {
1625
+ printHelp(command2, options.json, () => printSetupFailure());
1626
+ return;
1627
+ }
1628
+ if (command2.args.length > 0 || options.source && options.archive || options.archive && isRemoteLocation(options.archive)) {
1629
+ printSetupFailure(new CoordinatesSetupError("COORDINATES_SETUP_INVALID_REQUEST"));
1630
+ return;
1631
+ }
1632
+ const controller = (dependencies.createSignalController ?? installSignalAbortController)();
1633
+ try {
1634
+ const receipt = await dependencies.setupCoordinates({
1635
+ source: options.archive ?? options.source,
1636
+ cacheDirectory: options.cacheDir,
1637
+ requiredReleaseId: options.release,
1638
+ allowDevelopment: options.development,
1639
+ signal: controller.signal
1640
+ });
1641
+ printDocument(receipt);
1642
+ } catch (error) {
1643
+ printSetupFailure(error);
1644
+ } finally {
1645
+ controller.close();
1646
+ }
1647
+ });
1648
+ const coordinates = program2.command("coordinates").description("Inspect and execute Coordinates requests using a provisioned local release");
1649
+ configureCommand(coordinates).option("--json", "print one JSON response");
1650
+ coordinates.action((options, command2) => {
1651
+ if (options.help) {
1652
+ printHelp(
1653
+ command2,
1654
+ options.json,
1655
+ () => printFailure(invalidRequest(null, "INVALID_VALUE", { path: ["command"] }))
1656
+ );
1657
+ return;
1658
+ }
1659
+ const reason = command2.args.length === 0 ? "INVALID_VALUE" : "UNKNOWN_FIELD";
1660
+ printFailure(invalidRequest(null, reason, { path: ["command"] }));
1661
+ });
1662
+ registerOperation(
1663
+ coordinates,
1664
+ "search",
1665
+ dependencies,
1666
+ (options) => parseCoordinatesRequest("search", { query: options.query })
1667
+ );
1668
+ registerOperation(
1669
+ coordinates,
1670
+ "describe",
1671
+ dependencies,
1672
+ (options) => parseCoordinatesRequest("describe", { id: options.id })
1673
+ );
1674
+ registerOperation(
1675
+ coordinates,
1676
+ "operations",
1677
+ dependencies,
1678
+ (options) => parseCoordinatesRequest("operations", { from: options.from, to: options.to })
1679
+ );
1680
+ registerOperation(
1681
+ coordinates,
1682
+ "transform",
1683
+ dependencies,
1684
+ (options) => parseTransformRequest(options)
1685
+ );
1686
+ }
1687
+ function registerOperation(coordinates, name, dependencies, parse3) {
1688
+ const command2 = configureCommand(
1689
+ coordinates.command(name).description(`Run the local Coordinates ${name} operation`).option("--request <json>", "complete JSON request").option("--input <path-or-dash>", "complete JSON request file, or - for standard input").option("--runtime-dir <path>", "prepared Coordinates runtime directory").option("--release <release-id>", "required Coordinates release identity").option("--development", "allow a development runtime").option("--json", "print one JSON response")
1690
+ );
1691
+ if (name === "search") command2.option("--query <text>", "search query");
1692
+ if (name === "describe") command2.option("--id <crs-id>", "CRS identifier");
1693
+ if (name === "operations" || name === "transform") {
1694
+ command2.option("--from <crs-id>", "source CRS identifier").option("--to <crs-id>", "target CRS identifier");
1695
+ }
1696
+ if (name === "transform") command2.option("--positions <json>", "JSON coordinate positions");
1697
+ command2.action(async (options, invoked) => {
1698
+ const parent = invoked.parent?.opts();
1699
+ const wantsHelp = options.help ?? parent?.help;
1700
+ const wantsJson = options.json ?? parent?.json;
1701
+ if (wantsHelp) {
1702
+ printHelp(
1703
+ invoked,
1704
+ wantsJson,
1705
+ () => printFailure(invalidRequest(name, "INVALID_VALUE", { path: ["command"] }))
1706
+ );
1707
+ return;
1708
+ }
1709
+ if (invoked.args.length > 0) {
1710
+ printFailure(invalidRequest(name, "UNKNOWN_FIELD", { path: ["options"] }));
1711
+ return;
1712
+ }
1713
+ const controller = (dependencies.createSignalController ?? installSignalAbortController)();
1714
+ let local;
1715
+ try {
1716
+ const request = await resolveRequest(
1717
+ name,
1718
+ options,
1719
+ parse3,
1720
+ dependencies.readInput,
1721
+ controller.signal
1722
+ );
1723
+ local = await dependencies.createLocalCoordinates({
1724
+ directory: options.runtimeDir,
1725
+ requiredReleaseId: options.release,
1726
+ allowDevelopment: options.development,
1727
+ signal: controller.signal
1728
+ });
1729
+ const response = await local[name](request, { signal: controller.signal });
1730
+ await local.close();
1731
+ local = void 0;
1732
+ printDocument(response);
1733
+ } catch (error) {
1734
+ printFailure(error, name);
1735
+ } finally {
1736
+ try {
1737
+ await local?.close();
1738
+ } catch {
1739
+ } finally {
1740
+ controller.close();
1741
+ }
1742
+ }
1743
+ });
1744
+ }
1745
+ function configureCommand(command2) {
1746
+ return command2.allowUnknownOption(true).allowExcessArguments(true).helpOption(false).option("-h, --help", "display help");
1747
+ }
1748
+ async function resolveRequest(command2, options, parse3, readInput = readCoordinatesInput, signal) {
1749
+ const rawRequest = options.request !== void 0 || options.input !== void 0;
1750
+ if (options.request !== void 0 && options.input !== void 0 || rawRequest && hasConvenienceOptions(command2, options)) {
1751
+ throw invalidRequest(command2, "INVALID_VALUE", { path: ["request"] });
1752
+ }
1753
+ if (options.request !== void 0) return parseCoordinatesJsonRequest(command2, options.request);
1754
+ if (options.input !== void 0) {
1755
+ try {
1756
+ const input = await readInput(options.input, signal ?? new AbortController().signal);
1757
+ if (signal?.aborted) throw cancelledRequest(command2);
1758
+ return parseCoordinatesJsonRequest(command2, input);
1759
+ } catch (error) {
1760
+ if (signal?.aborted) throw cancelledRequest(command2);
1761
+ if (error instanceof CoordinatesInputError) throw invalidRequest(command2, error.reason);
1762
+ throw error;
1763
+ }
1764
+ }
1765
+ return parse3(options);
1766
+ }
1767
+ function hasConvenienceOptions(command2, options) {
1768
+ if (command2 === "search") return options.query !== void 0;
1769
+ if (command2 === "describe") return options.id !== void 0;
1770
+ if (command2 === "operations") {
1771
+ const pair = options;
1772
+ return pair.from !== void 0 || pair.to !== void 0;
1773
+ }
1774
+ const transform = options;
1775
+ return transform.from !== void 0 || transform.to !== void 0 || transform.positions !== void 0;
1776
+ }
1777
+ function parseTransformRequest(options) {
1778
+ let positions;
1779
+ try {
1780
+ positions = options.positions === void 0 ? void 0 : JSON.parse(options.positions);
1781
+ } catch {
1782
+ throw invalidRequest("transform", "INVALID_JSON");
1783
+ }
1784
+ return parseCoordinatesRequest("transform", { from: options.from, to: options.to, positions });
1785
+ }
1786
+ async function readCoordinatesInput(input, signal) {
1787
+ if (input === "-") return readStandardInput(signal);
1788
+ assertNotAborted(signal);
1789
+ let info;
1790
+ try {
1791
+ info = await lstat2(input);
1792
+ } catch {
1793
+ throw new CoordinatesInputError("INVALID_VALUE");
1794
+ }
1795
+ if (!info.isFile() || info.isSymbolicLink()) throw new CoordinatesInputError("INVALID_VALUE");
1796
+ if (info.size > coordinatesLimits.maximumRequestBytes) {
1797
+ throw new CoordinatesInputError("REQUEST_TOO_LARGE");
1798
+ }
1799
+ const handle = await open3(input, "r");
1800
+ try {
1801
+ let length = 0;
1802
+ const bytes = Buffer.alloc(coordinatesLimits.maximumRequestBytes + 1);
1803
+ while (length < bytes.length) {
1804
+ assertNotAborted(signal);
1805
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
1806
+ if (bytesRead === 0) break;
1807
+ length += bytesRead;
1808
+ }
1809
+ assertNotAborted(signal);
1810
+ if (length > coordinatesLimits.maximumRequestBytes) {
1811
+ throw new CoordinatesInputError("REQUEST_TOO_LARGE");
1812
+ }
1813
+ return decodeInput(bytes.subarray(0, length));
1814
+ } finally {
1815
+ await handle.close();
1816
+ }
1817
+ }
1818
+ async function readStandardInput(signal) {
1819
+ const chunks = [];
1820
+ let length = 0;
1821
+ return new Promise((resolve9, reject) => {
1822
+ const finish = (result) => {
1823
+ process.stdin.removeListener("data", onData);
1824
+ process.stdin.removeListener("end", onEnd);
1825
+ process.stdin.removeListener("error", onError);
1826
+ signal.removeEventListener("abort", onAbort);
1827
+ result();
1828
+ };
1829
+ const onAbort = () => {
1830
+ process.stdin.pause();
1831
+ finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
1832
+ };
1833
+ const onData = (chunk) => {
1834
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1835
+ length += bytes.length;
1836
+ if (length > coordinatesLimits.maximumRequestBytes) {
1837
+ process.stdin.pause();
1838
+ finish(() => reject(new CoordinatesInputError("REQUEST_TOO_LARGE")));
1839
+ return;
1840
+ }
1841
+ chunks.push(bytes);
1842
+ };
1843
+ const onEnd = () => finish(() => resolve9(decodeInput(Buffer.concat(chunks))));
1844
+ const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
1845
+ if (signal.aborted) {
1846
+ onAbort();
1847
+ return;
1848
+ }
1849
+ process.stdin.on("data", onData);
1850
+ process.stdin.once("end", onEnd);
1851
+ process.stdin.once("error", onError);
1852
+ signal.addEventListener("abort", onAbort, { once: true });
1853
+ process.stdin.resume();
1854
+ });
1855
+ }
1856
+ function invalidRequest(command2, reason, details = {}) {
1857
+ return new CoordinatesContractError({
1858
+ schemaVersion: 1,
1859
+ ok: false,
1860
+ command: command2,
1861
+ releaseId: null,
1862
+ provenance: null,
1863
+ warnings: [],
1864
+ usage: null,
1865
+ error: { code: "COORDINATES_INVALID_REQUEST", reason, details, phase: "input" }
1866
+ });
1867
+ }
1868
+ function cancelledRequest(command2) {
1869
+ return new CoordinatesContractError({
1870
+ schemaVersion: 1,
1871
+ ok: false,
1872
+ command: command2,
1873
+ releaseId: null,
1874
+ provenance: null,
1875
+ warnings: [],
1876
+ usage: null,
1877
+ error: { code: "COORDINATES_CANCELLED", reason: "CANCELLED", details: {}, phase: "input" }
1878
+ });
1879
+ }
1880
+ function printDocument(document) {
1881
+ process.stdout.write(`${JSON.stringify(document)}
1882
+ `);
1883
+ }
1884
+ function printFailure(error, command2) {
1885
+ const document = serializeCoordinatesFailure(error, command2) ?? (command2 ? invalidRequest(command2, "INVALID_VALUE").toJSON() : setupFailureDocument());
1886
+ process.stderr.write(`${JSON.stringify(document)}
1887
+ `);
1888
+ process.exitCode = 1;
1889
+ }
1890
+ function printSetupFailure(error) {
1891
+ process.stderr.write(
1892
+ `${JSON.stringify(serializeSetupFailure(error) ?? setupFailureDocument())}
1893
+ `
1894
+ );
1895
+ process.exitCode = 1;
1896
+ }
1897
+ function serializeCoordinatesFailure(error, command2) {
1898
+ if (!(error instanceof CoordinatesContractError)) return void 0;
1899
+ const parsed = coordinatesFailureSchema.safeParse(error.toJSON());
1900
+ if (!parsed.success) return void 0;
1901
+ const failure2 = parsed.data;
1902
+ return new CoordinatesContractError({
1903
+ ...failure2,
1904
+ command: failure2.command ?? command2 ?? null
1905
+ }).toJSON();
1906
+ }
1907
+ function serializeSetupFailure(error) {
1908
+ if (!(error instanceof CoordinatesSetupError)) return void 0;
1909
+ const codes = /* @__PURE__ */ new Set([
1910
+ "COORDINATES_SETUP_INVALID_REQUEST",
1911
+ "COORDINATES_SETUP_RELEASE_UNAVAILABLE",
1912
+ "COORDINATES_SETUP_MANIFEST_INVALID",
1913
+ "COORDINATES_SETUP_ARTIFACT_UNAVAILABLE",
1914
+ "COORDINATES_SETUP_INTEGRITY_FAILED",
1915
+ "COORDINATES_SETUP_INSTALLATION_FAILED",
1916
+ "COORDINATES_SETUP_CANCELLED",
1917
+ "COORDINATES_SETUP_TIMEOUT"
1918
+ ]);
1919
+ if (!codes.has(error.code)) return void 0;
1920
+ return new CoordinatesSetupError(error.code, error.releaseId, error.assetId).toJSON();
1921
+ }
1922
+ function setupFailureDocument() {
1923
+ return new CoordinatesSetupError("COORDINATES_SETUP_INVALID_REQUEST").toJSON();
1924
+ }
1925
+ function printHelp(command2, json, printJson) {
1926
+ if (json) {
1927
+ printJson();
1928
+ return;
1929
+ }
1930
+ command2.outputHelp();
1931
+ }
1932
+ function isRemoteLocation(input) {
1933
+ return /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(input);
1934
+ }
1935
+ function assertNotAborted(signal) {
1936
+ if (signal.aborted) throw new CoordinatesInputError("INVALID_VALUE");
1937
+ }
1938
+ function decodeInput(bytes) {
1939
+ try {
1940
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1941
+ } catch {
1942
+ throw new CoordinatesInputError("INVALID_VALUE");
1943
+ }
1944
+ }
1945
+
1591
1946
  // src/deploy-source.ts
1592
1947
  var overrideFields = [
1593
1948
  ["TILEFLOW_DEPLOY_REPOSITORY", "repository", 255],
@@ -1868,6 +2223,7 @@ import { tileflowIconPackageLimits } from "@tileflow/core";
1868
2223
  var maximumHostedResponseBytes = 1024 * 1024;
1869
2224
  var safeTextSchema = z2.string().min(1).max(300).refine((value) => !/[\p{Cc}]/u.test(value));
1870
2225
  var safeIdentifierSchema = z2.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u);
2226
+ var safeStorageKeySchema = z2.string().min(1).max(512).regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/u).refine((value) => value.split("/").every((segment) => segment !== "." && segment !== ".."));
1871
2227
  var publicHttpUrlSchema = z2.url().max(2048).refine((value) => {
1872
2228
  const url = new URL(value);
1873
2229
  return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && !url.hash;
@@ -1903,7 +2259,7 @@ var hostedFontBundleResponseSchema = z2.object({
1903
2259
  });
1904
2260
  var hostedStatusStyleSchema = z2.object({
1905
2261
  environment: safeTextSchema,
1906
- key: safeIdentifierSchema,
2262
+ key: safeStorageKeySchema,
1907
2263
  mapId: safeIdentifierSchema,
1908
2264
  size: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
1909
2265
  uploaded: z2.iso.datetime({ offset: true })
@@ -1952,6 +2308,10 @@ async function readBoundedResponseText(response, label) {
1952
2308
  // src/hosted-client.ts
1953
2309
  var defaultHostedRequestTimeoutMs = 3e4;
1954
2310
  var maximumHostedRequestTimeoutMs = 6e4;
2311
+ function apiProfileCanTargetMap(profile, mapId) {
2312
+ if (!/^map_[A-Za-z0-9_-]{16}$/u.test(mapId)) return false;
2313
+ return profile.credentialType === "project_api_key" ? profile.mapId === mapId : profile.mapAccess?.mode === "all" || profile.mapAccess?.mode === "selected" && profile.mapAccess.ids.includes(mapId);
2314
+ }
1955
2315
  async function requestHostedJson(apiUrl, path, init = {}, options = {}) {
1956
2316
  return withHostedResponse(apiUrl, path, init, options, async (response) => {
1957
2317
  const source = await readBoundedResponseText(response, "Hosted API");
@@ -1990,7 +2350,7 @@ async function startDeviceAuthorization(appUrl, body, options = {}) {
1990
2350
  async function pollDeviceToken(appUrl, authorization, input, options = {}) {
1991
2351
  const expiresAt = Date.now() + authorization.expiresIn * 1e3;
1992
2352
  const intervalMs = Math.max(authorization.interval, 1) * 1e3;
1993
- const wait = input.sleep ?? ((milliseconds) => new Promise((resolve8) => setTimeout(resolve8, milliseconds)));
2353
+ const wait = input.sleep ?? ((milliseconds) => new Promise((resolve9) => setTimeout(resolve9, milliseconds)));
1994
2354
  while (Date.now() < expiresAt) {
1995
2355
  await wait(intervalMs);
1996
2356
  const response = await requestHostedJson(
@@ -2018,7 +2378,10 @@ async function validateAccountSession(session, options = {}) {
2018
2378
  options
2019
2379
  );
2020
2380
  if (!response.ok) {
2021
- return { error: `Account session validation failed (${response.status}).`, ok: false };
2381
+ return {
2382
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Account session validation failed (${response.status}).`,
2383
+ ok: false
2384
+ };
2022
2385
  }
2023
2386
  const body = asRecord(response.body);
2024
2387
  const account = asRecord(body.account);
@@ -2045,6 +2408,28 @@ async function validateApiKey(apiUrl, apiKey, options = {}) {
2045
2408
  return { error: `API key validation failed (${response.status}).`, ok: false };
2046
2409
  }
2047
2410
  const body = asRecord(response.body);
2411
+ if (response.json && body.credentialType === "team_api_key") {
2412
+ const mapAccess = asRecord(body.mapAccess);
2413
+ const validMaps = body.mapAccess === null || mapAccess.mode === "all" && Object.keys(mapAccess).length === 1 || mapAccess.mode === "selected" && Object.keys(mapAccess).length === 2 && Array.isArray(mapAccess.ids) && mapAccess.ids.length > 0 && mapAccess.ids.length <= 1e3 && mapAccess.ids.every(
2414
+ (id) => typeof id === "string" && /^map_[A-Za-z0-9_-]{16}$/u.test(id)
2415
+ ) && new Set(mapAccess.ids).size === mapAccess.ids.length;
2416
+ if (typeof body.apiKeyId !== "string" || !isProjectIdentity(body.organization) || body.project !== null || body.projectId !== null || body.mapId !== null || !validMaps || !Array.isArray(body.scopes) || !body.scopes.every((scope) => typeof scope === "string")) {
2417
+ return { error: "API key validation returned an invalid response.", ok: false };
2418
+ }
2419
+ return {
2420
+ ok: true,
2421
+ value: {
2422
+ apiKeyId: body.apiKeyId,
2423
+ credentialType: "team_api_key",
2424
+ organization: body.organization,
2425
+ mapId: null,
2426
+ project: null,
2427
+ projectId: null,
2428
+ scopes: body.scopes,
2429
+ mapAccess: body.mapAccess === null ? null : mapAccess.mode === "all" ? { mode: "all" } : { mode: "selected", ids: mapAccess.ids }
2430
+ }
2431
+ };
2432
+ }
2048
2433
  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)) {
2049
2434
  return { error: "API key validation returned an invalid response.", ok: false };
2050
2435
  }
@@ -2110,7 +2495,10 @@ async function requestProjectCapability(session, project, scopes, options = {})
2110
2495
  };
2111
2496
  }
2112
2497
  if (!response.ok) {
2113
- return { error: `Project capability request failed (${response.status}).`, ok: false };
2498
+ return {
2499
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Project capability request failed (${response.status}).`,
2500
+ ok: false
2501
+ };
2114
2502
  }
2115
2503
  const body = asRecord(response.body);
2116
2504
  if (!response.json || typeof body.capability !== "string" || !body.capability.startsWith("tf_cap_") || body.capability.length > 8192 || body.reference !== project || !validIsoDate(body.expiresAt) || !Array.isArray(body.scopes) || body.scopes.join("\0") !== [...scopes].sort().join("\0")) {
@@ -2171,7 +2559,7 @@ async function publishHostedStyle(api, body, responseLabel, options = {}) {
2171
2559
  "/v1/styles",
2172
2560
  {
2173
2561
  ...jsonRequest("POST", body),
2174
- headers: { ...authorizationHeaders(api.apiKey), "Content-Type": "application/json" }
2562
+ headers: { ...authorizationHeaders(api.apiKey, api.mapId), "Content-Type": "application/json" }
2175
2563
  },
2176
2564
  options
2177
2565
  );
@@ -2199,7 +2587,7 @@ async function uploadHostedIconPackage(api, iconPackage, options = {}) {
2199
2587
  const response = await requestHostedJson(
2200
2588
  api.apiUrl,
2201
2589
  `/v1/icon-packages/${encodeURIComponent(iconPackage.contentHash)}`,
2202
- { body: formData, headers: authorizationHeaders(api.apiKey), method: "PUT" },
2590
+ { body: formData, headers: authorizationHeaders(api.apiKey, api.mapId), method: "PUT" },
2203
2591
  options
2204
2592
  );
2205
2593
  if (!response.ok) return { ok: false, status: response.status };
@@ -2236,7 +2624,7 @@ async function uploadHostedFontBundle(api, bundle, options = {}) {
2236
2624
  const response = await requestHostedJson(
2237
2625
  api.apiUrl,
2238
2626
  `/v1/font-bundles/${encodeURIComponent(bundle.contentHash)}`,
2239
- { body: formData, headers: authorizationHeaders(api.apiKey), method: "PUT" },
2627
+ { body: formData, headers: authorizationHeaders(api.apiKey, api.mapId), method: "PUT" },
2240
2628
  options
2241
2629
  );
2242
2630
  if (!response.ok) return { ok: false, status: response.status };
@@ -2255,7 +2643,7 @@ async function fetchHostedMapStatus(api, options = {}) {
2255
2643
  const response = await requestHostedJson(
2256
2644
  api.apiUrl,
2257
2645
  "/v1/status",
2258
- { headers: authorizationHeaders(api.apiKey) },
2646
+ { headers: authorizationHeaders(api.apiKey, api.mapId) },
2259
2647
  options
2260
2648
  );
2261
2649
  if (!response.ok) throw new Error(`Status failed: ${response.status}.`);
@@ -2292,7 +2680,7 @@ async function withHostedResponse(apiUrl, path, init, options, consume) {
2292
2680
  }
2293
2681
  function raceWithAbort(operation, signal) {
2294
2682
  if (signal.aborted) return Promise.reject(signal.reason);
2295
- return new Promise((resolve8, reject) => {
2683
+ return new Promise((resolve9, reject) => {
2296
2684
  const cleanup = () => signal.removeEventListener("abort", onAbort);
2297
2685
  const onAbort = () => {
2298
2686
  cleanup();
@@ -2302,7 +2690,7 @@ function raceWithAbort(operation, signal) {
2302
2690
  operation.then(
2303
2691
  (value) => {
2304
2692
  cleanup();
2305
- resolve8(value);
2693
+ resolve9(value);
2306
2694
  },
2307
2695
  (error) => {
2308
2696
  cleanup();
@@ -2352,11 +2740,16 @@ function jsonRequest(method, body) {
2352
2740
  method
2353
2741
  };
2354
2742
  }
2355
- function authorizationHeaders(token) {
2743
+ function authorizationHeaders(token, mapId) {
2356
2744
  if (!token || token.length > 8192 || /[\p{Cc}]/u.test(token)) {
2357
2745
  throw new TypeError("Hosted authorization credential is invalid.");
2358
2746
  }
2359
- return { Authorization: `Bearer ${token}` };
2747
+ if (mapId !== void 0 && !/^map_[A-Za-z0-9_-]{16}$/u.test(mapId))
2748
+ throw new TypeError("Hosted Map ID is invalid.");
2749
+ return {
2750
+ Authorization: `Bearer ${token}`,
2751
+ ...mapId === void 0 ? {} : { "X-Tileflow-Map-Id": mapId }
2752
+ };
2360
2753
  }
2361
2754
  function parseResponse(response, schema, label) {
2362
2755
  if (!response.json) throw new Error(`${label} returned invalid JSON.`);
@@ -2415,6 +2808,33 @@ function safeTransportError(error, fallback) {
2415
2808
  return fallback;
2416
2809
  }
2417
2810
 
2811
+ // src/hosted-native-deploy.ts
2812
+ import { dirname as dirname5 } from "path";
2813
+ import {
2814
+ getTileflowStyleFontFaces,
2815
+ parseResolvedTileflowMap as parseResolvedTileflowMap2,
2816
+ serializeCanonicalJson
2817
+ } from "@tileflow/core";
2818
+ import {
2819
+ createTileflowMapBuildManifest,
2820
+ parseTileflowRendererDeploymentArtifact,
2821
+ tileflowRendererDeploymentResponseSchema
2822
+ } from "@tileflow/core/build";
2823
+ import { parseTileflowRuntimeManifest } from "@tileflow/core/manifest";
2824
+ import {
2825
+ createTileflowBuildProvenance,
2826
+ createTileflowStyles,
2827
+ prepareTileflowHostedNativeDeployment,
2828
+ prepareTileflowLocalTilesets
2829
+ } from "@tileflow/dev/artifacts";
2830
+ import {
2831
+ assertValidTileflowConfig,
2832
+ getTileflowMapNames,
2833
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs2
2834
+ } from "@tileflow/dev/config";
2835
+ import { prepareTileflowStyleFonts } from "@tileflow/dev/fonts";
2836
+ import { compileTileflowIconPackages } from "@tileflow/dev/icons";
2837
+
2418
2838
  // src/hosted-preflight.ts
2419
2839
  import {
2420
2840
  compareCodeUnits as compareCodeUnits4,
@@ -2479,44 +2899,312 @@ function cloneStyle(style) {
2479
2899
  return JSON.parse(JSON.stringify(style));
2480
2900
  }
2481
2901
 
2902
+ // src/hosted-native-deploy.ts
2903
+ function failure(message) {
2904
+ return Object.assign(new Error(message), { code: "TF_NATIVE_DEPLOYMENT_INVALID" });
2905
+ }
2906
+ function requireNativeTarget(options) {
2907
+ if (!options.mapId || !/^map_[A-Za-z0-9_-]{16}$/u.test(options.mapId))
2908
+ throw failure("--with-native requires one valid --map-id.");
2909
+ const apiOrigin = normalizeApiOrigin(options.apiUrl);
2910
+ if (!apiOrigin.startsWith("https://"))
2911
+ throw failure("Hosted Native deployment requires an HTTPS API origin.");
2912
+ return { apiOrigin, mapId: options.mapId };
2913
+ }
2914
+ async function prepareNativeHostedDeploy(options) {
2915
+ const { apiOrigin } = requireNativeTarget(options);
2916
+ const loaded = await withTileflowConfigSecretsHidden(
2917
+ () => loadTileflowConfigWithInputs2(options.config)
2918
+ );
2919
+ assertValidTileflowConfig(loaded.project);
2920
+ const names = getTileflowMapNames(loaded.project);
2921
+ const mapName = options.map ?? (names.length === 1 ? names[0] : void 0);
2922
+ if (!mapName || !names.includes(mapName))
2923
+ throw failure("Select one configured map with --map for this Native deployment.");
2924
+ const map = loaded.project.maps[mapName];
2925
+ const project = { ...loaded.project, maps: { [mapName]: map } };
2926
+ const baseDirectory = dirname5(loaded.configFile);
2927
+ const compiledIcons = await compileTileflowIconPackages(project, {
2928
+ baseDirectory,
2929
+ cwd: process.cwd(),
2930
+ target: "hosted",
2931
+ icons: {
2932
+ ...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
2933
+ ...options.offline ? { offline: true } : {}
2934
+ }
2935
+ });
2936
+ const binding = compiledIcons.bindings.find((entry) => entry.mapName === mapName);
2937
+ const iconPackage = binding ? compiledIcons.packages.find((entry) => entry.contentHash === binding.packageHash) : void 0;
2938
+ const sprite = binding ? `${apiOrigin}/sprites/preflight/${binding.packageHash}/sprite` : void 0;
2939
+ const mapAssets = Object.fromEntries(
2940
+ compiledIcons.bindings.map((entry) => [
2941
+ entry.mapName,
2942
+ {
2943
+ icons: {
2944
+ ids: entry.iconIds,
2945
+ sprite: `${apiOrigin}/sprites/preflight/${entry.packageHash}/sprite`
2946
+ }
2947
+ }
2948
+ ])
2949
+ );
2950
+ const styles = createTileflowStyles(project, { apiBaseUrl: apiOrigin, mapAssets });
2951
+ const local = await prepareTileflowLocalTilesets(project, styles, {
2952
+ assetBaseUrl: apiOrigin,
2953
+ baseDirectory,
2954
+ cwd: process.cwd()
2955
+ });
2956
+ try {
2957
+ const fonts = await prepareTileflowStyleFonts(project, local.styles, {
2958
+ assetBaseUrl: `${apiOrigin}/fonts/preflight`,
2959
+ baseDirectory,
2960
+ cwd: process.cwd(),
2961
+ target: "hosted"
2962
+ });
2963
+ if (inspectTileflowHostedCompatibility(project, fonts.styles).length)
2964
+ throw failure("Hosted renderer deployment requires compatible Tileflow World sources.");
2965
+ if (Object.keys(fonts.bundles).length)
2966
+ throw failure(
2967
+ "Hosted deploy does not yet support package-owned web fonts. Use an explicit public glyph provider."
2968
+ );
2969
+ const assets = (iconPackage?.files ?? []).map((file) => ({
2970
+ contentType: file.contentType,
2971
+ fileName: `icons/${mapName}/${file.fileName}`,
2972
+ source: file.source
2973
+ }));
2974
+ const provenance = await createTileflowBuildProvenance(process.cwd());
2975
+ const resolved = parseResolvedTileflowMap2(map);
2976
+ const inputStyles = JSON.parse(
2977
+ serializeCanonicalJson(fonts.styles[mapName])
2978
+ );
2979
+ const createArtifact = async (selectedSprite) => {
2980
+ const selected = JSON.parse(
2981
+ serializeCanonicalJson(inputStyles)
2982
+ );
2983
+ if (binding) {
2984
+ if (!sprite || !selectedSprite) throw failure("Missing confirmed sprite binding.");
2985
+ for (const style of Object.values(selected)) {
2986
+ if (style.sprite !== sprite)
2987
+ throw failure("Compiled sprite identity changed before publication.");
2988
+ style.sprite = selectedSprite;
2989
+ }
2990
+ }
2991
+ const family = prepareTileflowHostedThemeFamily(mapName, map, selected);
2992
+ const buildManifest = await createTileflowMapBuildManifest(
2993
+ {
2994
+ [mapName]: {
2995
+ assets,
2996
+ lineage: project.mapMetadata?.[mapName]?.lineage ?? [
2997
+ { id: map.id, mapVersion: map.version }
2998
+ ],
2999
+ map,
3000
+ sourceAssets: {
3001
+ fonts: fonts.sourceIdentities[mapName] ?? [],
3002
+ icons: compiledIcons.sourceIdentities[mapName] ?? [],
3003
+ ...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {}
3004
+ },
3005
+ styles: family.styles
3006
+ }
3007
+ },
3008
+ { provenance }
3009
+ );
3010
+ return prepareTileflowHostedNativeDeployment({
3011
+ mapId: mapName,
3012
+ buildManifest,
3013
+ styles: family.styles,
3014
+ teamSources: family.teamSources,
3015
+ assets,
3016
+ ...resolved.view ? { view: resolved.view } : {}
3017
+ });
3018
+ };
3019
+ const artifact = await createArtifact(sprite);
3020
+ return {
3021
+ mapName,
3022
+ artifact,
3023
+ packages: compiledIcons.packages,
3024
+ ...binding ? { iconBinding: binding } : {},
3025
+ ...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
3026
+ async retarget(sprites) {
3027
+ return binding ? createArtifact(sprites.get(binding.packageHash)) : artifact;
3028
+ },
3029
+ dispose: local.dispose
3030
+ };
3031
+ } catch (error) {
3032
+ await local.dispose();
3033
+ throw error;
3034
+ }
3035
+ }
3036
+ async function runHostedNativeDeploy(options, ports) {
3037
+ const { apiOrigin, mapId } = requireNativeTarget(options);
3038
+ const prepared = await (ports.prepare ?? prepareNativeHostedDeploy)({
3039
+ ...options,
3040
+ apiUrl: apiOrigin
3041
+ });
3042
+ try {
3043
+ const preflight = await parseTileflowRendererDeploymentArtifact(prepared.artifact);
3044
+ if (preflight.mapId !== prepared.mapName)
3045
+ throw failure("The prepared map does not match its artifact.");
3046
+ const existing = await ports.loadManifest(options.manifest, {
3047
+ overwriteSelfHosted: options.overwriteSelfHostedManifest === true
3048
+ });
3049
+ const previousOrigin = existing?.apiUrl ?? (existing ? Object.values(existing.maps)[0]?.apiUrl : void 0);
3050
+ if (previousOrigin !== void 0 && normalizeApiOrigin(previousOrigin) !== apiOrigin)
3051
+ throw failure("Existing manifest belongs to a different Tileflow API origin.");
3052
+ const api = await ports.resolveApi(prepared.mapName);
3053
+ if (!api) return null;
3054
+ if (api.apiUrl !== apiOrigin || api.mapId !== mapId || !api.apiKey || api.apiKey.length > 8192 || /[\p{Cc}]/u.test(api.apiKey))
3055
+ throw failure("Authorization did not confirm the requested API and Map.");
3056
+ const sprites = /* @__PURE__ */ new Map();
3057
+ for (const icon of prepared.packages) {
3058
+ if (sprites.has(icon.contentHash)) continue;
3059
+ let uploaded;
3060
+ try {
3061
+ uploaded = await (ports.uploadIcon ?? uploadHostedIconPackage)(api, icon, ports.request);
3062
+ } catch {
3063
+ throw failure("Native deployment icon upload failed. The local manifest was preserved.");
3064
+ }
3065
+ if (!uploaded.ok)
3066
+ throw failure("Native deployment icon upload failed. Retry the same artifact.");
3067
+ const expected = `${apiOrigin}/sprites/${uploaded.value.id}/sprite`;
3068
+ if (uploaded.value.contentHash !== icon.contentHash || uploaded.value.spriteUrl !== expected)
3069
+ throw failure("Icon publication did not confirm the owned resource and origin.");
3070
+ sprites.set(icon.contentHash, expected);
3071
+ }
3072
+ const artifact = await parseTileflowRendererDeploymentArtifact(
3073
+ await prepared.retarget(sprites)
3074
+ );
3075
+ if (artifact.mapId !== preflight.mapId || artifact.renderers.web.buildManifest.maps[artifact.mapId].mapRevisionSha256 !== preflight.renderers.web.buildManifest.maps[preflight.mapId].mapRevisionSha256)
3076
+ throw failure("Authored map identity changed after preflight.");
3077
+ const iconPackage = prepared.iconBinding ? prepared.packages.find((entry) => entry.contentHash === prepared.iconBinding.packageHash) : void 0;
3078
+ if (prepared.iconComposition && prepared.iconComposition.packageHash !== iconPackage?.contentHash)
3079
+ throw failure("Icon composition does not match the confirmed effective package.");
3080
+ let response;
3081
+ try {
3082
+ response = await requestHostedJson(
3083
+ apiOrigin,
3084
+ "/v1/styles",
3085
+ {
3086
+ method: "POST",
3087
+ headers: {
3088
+ Authorization: `Bearer ${api.apiKey}`,
3089
+ "X-Tileflow-Map-Id": mapId,
3090
+ "Content-Type": "application/json"
3091
+ },
3092
+ body: serializeCanonicalJson({
3093
+ artifact,
3094
+ environment: prepared.mapName,
3095
+ managedMapId: mapId,
3096
+ usageMode: "session",
3097
+ source: ports.source,
3098
+ ...iconPackage && prepared.iconBinding ? {
3099
+ iconPackage: {
3100
+ contentHash: iconPackage.contentHash,
3101
+ label: prepared.iconBinding.label
3102
+ }
3103
+ } : {},
3104
+ ...prepared.iconComposition ? { iconComposition: prepared.iconComposition } : {}
3105
+ })
3106
+ },
3107
+ ports.request
3108
+ );
3109
+ } catch {
3110
+ throw failure(
3111
+ "Renderer deployment response was unavailable. Retry the same artifact; the local manifest was preserved."
3112
+ );
3113
+ }
3114
+ if (!response.ok || !response.json)
3115
+ throw failure("Renderer deployment failed. The local manifest was preserved.");
3116
+ const parsed = tileflowRendererDeploymentResponseSchema.safeParse(response.body);
3117
+ if (!parsed.success)
3118
+ throw failure("Invalid renderer deployment response. The local manifest was preserved.");
3119
+ const result = parsed.data;
3120
+ const names = Object.keys(artifact.renderers.web.styles).sort();
3121
+ const expectedManifestUrl = `${apiOrigin}/maps/${mapId}/native/manifest.json`;
3122
+ if (result.mapId !== mapId || result.renderers.native.manifestUrl !== expectedManifestUrl || names.join("\0") !== Object.keys(result.renderers.web.themes).sort().join("\0") || names.join("\0") !== Object.keys(result.renderers.native.themes).sort().join("\0"))
3123
+ throw failure(
3124
+ "Renderer deployment did not confirm the requested Map and complete theme families."
3125
+ );
3126
+ for (const name of names) {
3127
+ if (result.renderers.web.themes[name].styleUrl !== `${apiOrigin}/maps/${mapId}/${name}.json` || result.renderers.native.themes[name].styleUrl !== `${apiOrigin}/maps/${mapId}/native/v${result.version}/${name}.json`)
3128
+ throw failure("Renderer deployment returned an unexpected delivery route.");
3129
+ }
3130
+ const build = artifact.renderers.web.buildManifest.maps[artifact.mapId];
3131
+ const manifest = parseTileflowRuntimeManifest({
3132
+ apiUrl: apiOrigin,
3133
+ version: 1,
3134
+ maps: {
3135
+ ...existing?.maps ?? {},
3136
+ [prepared.mapName]: {
3137
+ defaultTheme: build.defaultTheme,
3138
+ environment: prepared.mapName,
3139
+ mapId,
3140
+ ...build.systemThemes ? { systemThemes: build.systemThemes } : {},
3141
+ ...artifact.view ? { view: artifact.view } : {},
3142
+ usageMode: "session",
3143
+ worldGeneration: "v1",
3144
+ themes: Object.fromEntries(
3145
+ names.map((name) => [
3146
+ name,
3147
+ {
3148
+ colorScheme: build.themes[name].colorScheme,
3149
+ fontFaces: getTileflowStyleFontFaces(
3150
+ artifact.renderers.web.styles[name]
3151
+ ),
3152
+ revision: build.themes[name].styleSha256,
3153
+ ...result.renderers.web.themes[name]
3154
+ }
3155
+ ])
3156
+ )
3157
+ }
3158
+ }
3159
+ });
3160
+ const manifestPath = await ports.writeManifest(options.manifest, manifest);
3161
+ return Object.freeze({
3162
+ manifestPath,
3163
+ mapName: prepared.mapName,
3164
+ mapId,
3165
+ deploymentId: result.deploymentId,
3166
+ version: result.version,
3167
+ changed: result.changed,
3168
+ nativeManifestUrl: expectedManifestUrl
3169
+ });
3170
+ } finally {
3171
+ await prepared.dispose();
3172
+ }
3173
+ }
3174
+
2482
3175
  // src/icon-diff-command.ts
2483
- import { dirname as dirname6, resolve as resolve4 } from "path";
2484
- import { z as z4 } from "zod";
3176
+ import { dirname as dirname7, resolve as resolve4 } from "path";
3177
+ import { z as z3 } from "zod";
2485
3178
  import {
2486
3179
  diffTileflowIconPackageManifests,
2487
3180
  hashTileflowIconPackageManifest,
2488
3181
  tileflowIconPackageContentHashSchema,
2489
3182
  tileflowIconPackageLabelSchema,
2490
- tileflowIconPackageLimits as tileflowIconPackageLimits3,
3183
+ tileflowIconPackageLimits as tileflowIconPackageLimits2,
2491
3184
  tileflowIconPackageManifestSchema,
2492
3185
  tileflowMapIdSchema
2493
3186
  } from "@tileflow/core";
2494
3187
  import {
2495
- assertValidTileflowConfig,
2496
- getTileflowMapNames,
2497
- loadTileflowConfigWithInputs as loadTileflowConfigWithInputs2
3188
+ assertValidTileflowConfig as assertValidTileflowConfig2,
3189
+ getTileflowMapNames as getTileflowMapNames2,
3190
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3
2498
3191
  } from "@tileflow/dev/config";
2499
- import { compileTileflowIconPackages } from "@tileflow/dev/icons";
3192
+ import {
3193
+ compileTileflowIconPackages as compileTileflowIconPackages2
3194
+ } from "@tileflow/dev/icons";
2500
3195
 
2501
3196
  // src/icon-diff-report.ts
2502
3197
  import { randomUUID as randomUUID2 } from "crypto";
2503
3198
  import { mkdir as mkdir3, readFile as readFile3, rename as rename3, unlink as unlink3, writeFile } from "fs/promises";
2504
- import { basename as basename2, dirname as dirname5, join } from "path";
2505
- import { z as z3 } from "zod";
3199
+ import { basename as basename2, dirname as dirname6, join } from "path";
2506
3200
  import {
2507
3201
  compareCodeUnits as compareCodeUnits5,
2508
3202
  sha256Hex,
2509
- tileflowIconIdSchema,
2510
- tileflowIconPackageLimits as tileflowIconPackageLimits2
3203
+ tileflowIconSpriteIndexSchema
2511
3204
  } from "@tileflow/core";
2512
- var spriteEntrySchema = z3.object({
2513
- height: z3.number().int().positive().max(tileflowIconPackageLimits2.maxAtlasDimension),
2514
- pixelRatio: z3.union([z3.literal(1), z3.literal(2)]),
2515
- width: z3.number().int().positive().max(tileflowIconPackageLimits2.maxAtlasDimension),
2516
- x: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxAtlasDimension),
2517
- y: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxAtlasDimension)
2518
- }).strict();
2519
- var spriteIndexSchema = z3.record(tileflowIconIdSchema, spriteEntrySchema);
3205
+ import {
3206
+ renderTileflowIconAtlasPreview
3207
+ } from "@tileflow/dev/icons";
2520
3208
  async function writeIconDiffReport(input) {
2521
3209
  const [before, after] = await Promise.all([
2522
3210
  input.baseline?.package ? loadRemoteSprite(input.baseline.package) : null,
@@ -2554,18 +3242,20 @@ function loadCompiledSprite(iconPackage) {
2554
3242
  iconPackage.manifest.sprites.twoX
2555
3243
  );
2556
3244
  }
2557
- function reportSpriteFromFiles(files, oneX, twoX) {
3245
+ async function reportSpriteFromFiles(files, oneX, twoX) {
2558
3246
  const oneXJson = requiredFile(files, "sprite.json");
2559
3247
  const oneXPng = requiredFile(files, "sprite.png");
2560
3248
  const twoXJson = requiredFile(files, "sprite@2x.json");
2561
3249
  const twoXPng = requiredFile(files, "sprite@2x.png");
3250
+ const indexOneX = parseIndex(oneXJson, 1, oneX.width, oneX.height);
3251
+ const indexTwoX = parseIndex(twoXJson, 2, twoX.width, twoX.height);
2562
3252
  return {
2563
- indexOneX: parseIndex(oneXJson, 1, oneX.width, oneX.height),
2564
- indexTwoX: parseIndex(twoXJson, 2, twoX.width, twoX.height),
2565
- oneXDataUrl: pngDataUrl(oneXPng),
3253
+ indexOneX,
3254
+ indexTwoX,
3255
+ oneXDataUrl: pngDataUrl(await renderTileflowIconAtlasPreview(oneXPng, indexOneX)),
2566
3256
  oneXHeight: oneX.height,
2567
3257
  oneXWidth: oneX.width,
2568
- twoXDataUrl: pngDataUrl(twoXPng),
3258
+ twoXDataUrl: pngDataUrl(await renderTileflowIconAtlasPreview(twoXPng, indexTwoX)),
2569
3259
  twoXHeight: twoX.height,
2570
3260
  twoXWidth: twoX.width
2571
3261
  };
@@ -2577,7 +3267,7 @@ function parseIndex(source, pixelRatio, atlasWidth, atlasHeight) {
2577
3267
  } catch {
2578
3268
  throw new Error("Sprite report index is not valid UTF-8 JSON");
2579
3269
  }
2580
- const parsed = spriteIndexSchema.safeParse(value);
3270
+ const parsed = tileflowIconSpriteIndexSchema.safeParse(value);
2581
3271
  if (!parsed.success) {
2582
3272
  throw new Error("Sprite report index does not match the MapLibre schema");
2583
3273
  }
@@ -2972,9 +3662,9 @@ async function writeAtomicReport(path, html, force) {
2972
3662
  } catch (error) {
2973
3663
  if (!isMissingFile3(error)) throw error;
2974
3664
  }
2975
- await mkdir3(dirname5(path), { recursive: true });
3665
+ await mkdir3(dirname6(path), { recursive: true });
2976
3666
  const temporaryPath = join(
2977
- dirname5(path),
3667
+ dirname6(path),
2978
3668
  `.${basename2(path)}.${process.pid}.${randomUUID2()}.tmp`
2979
3669
  );
2980
3670
  try {
@@ -3003,59 +3693,59 @@ function isMissingFile3(error) {
3003
3693
 
3004
3694
  // src/icon-diff-command.ts
3005
3695
  var environmentSchema = tileflowMapIdSchema;
3006
- var publicHttpUrlSchema2 = z4.url().max(2048).refine((value) => {
3696
+ var publicHttpUrlSchema2 = z3.url().max(2048).refine((value) => {
3007
3697
  const url = new URL(value);
3008
3698
  return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
3009
3699
  });
3010
- var iconPackageBaselineResponseSchema = z4.object({
3011
- baseline: z4.object({
3012
- deployedAt: z4.iso.datetime({ offset: true }),
3013
- deploymentId: z4.string().min(1).max(128),
3014
- package: z4.object({
3700
+ var iconPackageBaselineResponseSchema = z3.object({
3701
+ baseline: z3.object({
3702
+ deployedAt: z3.iso.datetime({ offset: true }),
3703
+ deploymentId: z3.string().min(1).max(128),
3704
+ package: z3.object({
3015
3705
  contentHash: tileflowIconPackageContentHashSchema,
3016
3706
  label: tileflowIconPackageLabelSchema,
3017
3707
  manifest: tileflowIconPackageManifestSchema,
3018
3708
  spriteUrl: publicHttpUrlSchema2,
3019
- totalBytes: z4.number().int().positive().max(tileflowIconPackageLimits3.maxGeneratedPackageBytes)
3709
+ totalBytes: z3.number().int().positive().max(tileflowIconPackageLimits2.maxGeneratedPackageBytes)
3020
3710
  }).strict().nullable(),
3021
- version: z4.number().int().positive()
3711
+ version: z3.number().int().positive()
3022
3712
  }).strict().nullable(),
3023
3713
  environment: environmentSchema,
3024
- schemaVersion: z4.literal(1)
3714
+ schemaVersion: z3.literal(1)
3025
3715
  }).strict();
3026
- var packageSummarySchema = z4.object({
3716
+ var packageSummarySchema = z3.object({
3027
3717
  contentHash: tileflowIconPackageContentHashSchema,
3028
- iconCount: z4.number().int().nonnegative().max(tileflowIconPackageLimits3.maxIconCount),
3718
+ iconCount: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxIconCount),
3029
3719
  label: tileflowIconPackageLabelSchema,
3030
- totalBytes: z4.number().int().nonnegative().max(tileflowIconPackageLimits3.maxGeneratedPackageBytes)
3720
+ totalBytes: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxGeneratedPackageBytes)
3031
3721
  }).strict();
3032
- var tileflowIconDiffDocumentSchema = z4.object({
3033
- schemaVersion: z4.literal(1),
3722
+ var tileflowIconDiffDocumentSchema = z3.object({
3723
+ schemaVersion: z3.literal(1),
3034
3724
  environment: environmentSchema,
3035
- baseline: z4.object({
3036
- deploymentId: z4.string(),
3725
+ baseline: z3.object({
3726
+ deploymentId: z3.string(),
3037
3727
  package: packageSummarySchema.nullable(),
3038
- version: z4.number().int().positive()
3728
+ version: z3.number().int().positive()
3039
3729
  }).strict().nullable(),
3040
- proposed: z4.object({
3730
+ proposed: z3.object({
3041
3731
  package: packageSummarySchema.nullable()
3042
3732
  }).strict(),
3043
- icons: z4.object({
3044
- added: z4.array(z4.string()),
3045
- removed: z4.array(z4.string()),
3046
- modified: z4.array(z4.string()),
3047
- unchangedCount: z4.number().int().nonnegative()
3733
+ icons: z3.object({
3734
+ added: z3.array(z3.string()),
3735
+ removed: z3.array(z3.string()),
3736
+ modified: z3.array(z3.string()),
3737
+ unchangedCount: z3.number().int().nonnegative()
3048
3738
  }).strict(),
3049
- generatedBytes: z4.object({
3050
- before: z4.number().int().nonnegative(),
3051
- after: z4.number().int().nonnegative(),
3052
- delta: z4.number().int()
3739
+ generatedBytes: z3.object({
3740
+ before: z3.number().int().nonnegative(),
3741
+ after: z3.number().int().nonnegative(),
3742
+ delta: z3.number().int()
3053
3743
  }).strict(),
3054
- artifacts: z4.object({ report: z4.string().nullable() }).strict(),
3055
- hasChanges: z4.boolean()
3744
+ artifacts: z3.object({ report: z3.string().nullable() }).strict(),
3745
+ hasChanges: z3.boolean()
3056
3746
  }).strict();
3057
3747
  function registerIconDiffCommand(icons, dependencies) {
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) => {
3748
+ 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("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").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) => {
3059
3749
  try {
3060
3750
  await runIconDiff(options, dependencies);
3061
3751
  } catch (error) {
@@ -3076,11 +3766,11 @@ async function runIconDiff(options, dependencies) {
3076
3766
  }
3077
3767
  const environment = environmentSchema.parse(options.against);
3078
3768
  const loaded = await withTileflowConfigSecretsHidden(
3079
- () => loadTileflowConfigWithInputs2(options.config)
3769
+ () => loadTileflowConfigWithInputs3(options.config)
3080
3770
  );
3081
3771
  const project = loaded.project;
3082
- assertValidTileflowConfig(project);
3083
- const mapNames = getTileflowMapNames(project);
3772
+ assertValidTileflowConfig2(project);
3773
+ const mapNames = getTileflowMapNames2(project);
3084
3774
  if (!Object.hasOwn(project.maps, environment)) {
3085
3775
  throw new Error(
3086
3776
  `Unknown map environment "${environment}". Available maps: ${mapNames.join(", ") || "(none)"}`
@@ -3091,14 +3781,19 @@ async function runIconDiff(options, dependencies) {
3091
3781
  throw new Error("Missing Tileflow API key. Run tileflow login or set TILEFLOW_API_KEY.");
3092
3782
  }
3093
3783
  const selectedProject = { ...project, maps: { [environment]: project.maps[environment] } };
3094
- const compiled = await compileTileflowIconPackages(selectedProject, {
3095
- baseDirectory: dirname6(loaded.configFile),
3784
+ const icons = {
3785
+ ...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
3786
+ ...options.offline ? { offline: true } : {}
3787
+ };
3788
+ const compiled = await compileTileflowIconPackages2(selectedProject, {
3789
+ baseDirectory: dirname7(loaded.configFile),
3096
3790
  cwd: process.cwd(),
3791
+ ...Object.keys(icons).length > 0 ? { icons } : {},
3097
3792
  target: "hosted"
3098
3793
  });
3099
3794
  const binding = compiled.bindings.find((candidate) => candidate.mapName === environment);
3100
3795
  const proposedPackage = binding ? compiled.packages.find((candidate) => candidate.contentHash === binding.packageHash) ?? null : null;
3101
- const baseline = await readBaseline(api, environment);
3796
+ const baseline = await readBaseline({ ...api, mapId: options.mapId }, environment);
3102
3797
  const beforeManifest = baseline.baseline?.package?.manifest ?? null;
3103
3798
  const iconDiff = diffTileflowIconPackageManifests(
3104
3799
  beforeManifest,
@@ -3156,7 +3851,7 @@ async function readBaseline(api, environment) {
3156
3851
  const response = await requestHostedJson(
3157
3852
  api.apiUrl,
3158
3853
  `/v1/icon-packages/baseline/${encodeURIComponent(environment)}`,
3159
- { headers: { Authorization: `Bearer ${api.apiKey}` } }
3854
+ { headers: { Authorization: `Bearer ${api.apiKey}`, "X-Tileflow-Map-Id": api.mapId } }
3160
3855
  );
3161
3856
  if (!response.ok) {
3162
3857
  throw new Error(`Icon diff baseline failed: ${response.status}.`);
@@ -3235,10 +3930,10 @@ function formatSignedBytes(bytes) {
3235
3930
  }
3236
3931
 
3237
3932
  // src/icon-list-command.ts
3238
- import { dirname as dirname7 } from "path";
3933
+ import { dirname as dirname8 } from "path";
3239
3934
  import { compareCodeUnits as compareCodeUnits6 } from "@tileflow/core";
3240
3935
  import {
3241
- getTileflowMapNames as getTileflowMapNames2,
3936
+ getTileflowMapNames as getTileflowMapNames3,
3242
3937
  loadValidTileflowConfigWithInputs as loadValidTileflowConfigWithInputs2,
3243
3938
  TileflowValidationError as TileflowValidationError3
3244
3939
  } from "@tileflow/dev/config";
@@ -3247,7 +3942,7 @@ import {
3247
3942
  TileflowIconCompilationError as TileflowIconCompilationError2
3248
3943
  } from "@tileflow/dev/icons";
3249
3944
  function registerIconListCommand(icons, dependencies) {
3250
- icons.command("list").description("List each map icon directory composition as deterministic agent JSON").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--map <id>", "inspect one exact configured map").option("--json", "print deterministic schema-version-2 JSON").action(async (options) => {
3945
+ icons.command("list").description("List each map icon directory composition as deterministic agent JSON").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--map <id>", "inspect one exact configured map").option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--json", "print deterministic schema-version-3 JSON").action(async (options) => {
3251
3946
  if (!options.json) {
3252
3947
  console.error(
3253
3948
  "tileflow icons list currently requires --json. Run tileflow icons list --json."
@@ -3265,7 +3960,7 @@ function registerIconListCommand(icons, dependencies) {
3265
3960
  }
3266
3961
  function createTileflowIconListJson(inspection) {
3267
3962
  return {
3268
- schemaVersion: 2,
3963
+ schemaVersion: 3,
3269
3964
  pathBase: "cwd",
3270
3965
  maps: [...inspection.maps].sort((left, right) => compareCodeUnits6(left.name, right.name)).map((map) => createMapJson(map, inspection.catalogs))
3271
3966
  };
@@ -3279,15 +3974,20 @@ async function runIconList(options) {
3279
3974
  () => loadValidTileflowConfigWithInputs2(options.config)
3280
3975
  );
3281
3976
  const project = loaded.project;
3282
- const mapIds = getTileflowMapNames2(project).sort(compareCodeUnits6);
3977
+ const mapIds = getTileflowMapNames3(project).sort(compareCodeUnits6);
3283
3978
  if (options.map && !Object.hasOwn(project.maps, options.map)) {
3284
3979
  throw new Error(
3285
3980
  `Unknown map "${options.map}". Available maps: ${mapIds.join(", ") || "(none)"}`
3286
3981
  );
3287
3982
  }
3983
+ const icons = {
3984
+ ...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
3985
+ ...options.offline ? { offline: true } : {}
3986
+ };
3288
3987
  const inspection = await inspectTileflowIconCatalogs(project, {
3289
- baseDirectory: dirname7(loaded.configFile),
3988
+ baseDirectory: dirname8(loaded.configFile),
3290
3989
  cwd: process.cwd(),
3990
+ ...Object.keys(icons).length > 0 ? { icons } : {},
3291
3991
  ...options.map ? { mapNames: [options.map] } : {}
3292
3992
  });
3293
3993
  process.stdout.write(serializeTileflowIconListJson(createTileflowIconListJson(inspection)));
@@ -3298,7 +3998,7 @@ function createMapJson(map, catalogs) {
3298
3998
  }
3299
3999
  const mapIcons = map.icons;
3300
4000
  const catalog = catalogs.find(
3301
- (candidate) => candidate.compiledPackage.contentHash === mapIcons.packageHash && sameStrings(candidate.directories, mapIcons.directories)
4001
+ (candidate) => candidate.compiledPackage.contentHash === mapIcons.packageHash && sameContributors(candidate.contributors, mapIcons.contributors)
3302
4002
  );
3303
4003
  if (!catalog) {
3304
4004
  throw new Error(`Missing inspected icon catalog for map ${map.name}`);
@@ -3306,9 +4006,21 @@ function createMapJson(map, catalogs) {
3306
4006
  return {
3307
4007
  id: map.name,
3308
4008
  icons: {
3309
- kind: "directories",
3310
- directories: [...mapIcons.directories],
4009
+ kind: "sources",
4010
+ contributors: mapIcons.contributors.map(createContributorJson),
3311
4011
  finalIds: [...mapIcons.iconIds],
4012
+ ...catalog.icons.some((icon) => icon.appearance) ? {
4013
+ appearances: Object.fromEntries(
4014
+ catalog.icons.flatMap(
4015
+ (icon) => icon.appearance ? [[icon.id, icon.appearance]] : []
4016
+ )
4017
+ )
4018
+ } : {},
4019
+ ...catalog.icons.some((icon) => icon.layout) ? {
4020
+ layouts: Object.fromEntries(
4021
+ catalog.icons.flatMap((icon) => icon.layout ? [[icon.id, icon.layout]] : [])
4022
+ )
4023
+ } : {},
3312
4024
  insideWorkingTree: catalog.insideWorkingTree,
3313
4025
  replacements: catalog.replacements.map((replacement) => ({
3314
4026
  id: replacement.id,
@@ -3316,18 +4028,43 @@ function createMapJson(map, catalogs) {
3316
4028
  winner: replacement.winner
3317
4029
  })),
3318
4030
  packageHash: mapIcons.packageHash,
3319
- sources: [...catalog.icons].sort((left, right) => compareCodeUnits6(left.id, right.id)).map((icon) => ({
3320
- id: icon.id,
3321
- path: icon.source.path,
3322
- format: icon.source.format,
3323
- byteLength: icon.source.byteLength,
3324
- dimensions: icon.source.dimensions ? { width: icon.source.dimensions.width, height: icon.source.dimensions.height } : null
3325
- }))
4031
+ composition: mapIcons.composition,
4032
+ sources: [...catalog.icons].sort((left, right) => compareCodeUnits6(left.id, right.id)).map(
4033
+ (icon) => icon.source.kind === "icon-set" ? {
4034
+ kind: "icon-set",
4035
+ id: icon.id,
4036
+ contributor: icon.source.contributor,
4037
+ reference: icon.source.reference,
4038
+ version: icon.source.version
4039
+ } : {
4040
+ kind: "file",
4041
+ id: icon.id,
4042
+ contributor: icon.source.contributor,
4043
+ path: icon.source.path,
4044
+ format: icon.source.format,
4045
+ byteLength: icon.source.byteLength,
4046
+ dimensions: icon.source.dimensions ? { width: icon.source.dimensions.width, height: icon.source.dimensions.height } : null
4047
+ }
4048
+ )
3326
4049
  }
3327
4050
  };
3328
4051
  }
3329
- function sameStrings(left, right) {
3330
- return left.length === right.length && left.every((value, index) => value === right[index]);
4052
+ function createContributorJson(contributor) {
4053
+ return contributor.kind === "icon-set" ? {
4054
+ kind: "icon-set",
4055
+ label: contributor.label,
4056
+ iconIds: [...contributor.iconIds],
4057
+ reference: contributor.reference,
4058
+ version: contributor.version
4059
+ } : {
4060
+ kind: contributor.kind,
4061
+ label: contributor.label,
4062
+ iconIds: [...contributor.iconIds],
4063
+ insideWorkingTree: contributor.insideWorkingTree
4064
+ };
4065
+ }
4066
+ function sameContributors(left, right) {
4067
+ return left.length === right.length && left.every((contributor, index) => contributor.label === right[index]?.label);
3331
4068
  }
3332
4069
  function printIconListError(error) {
3333
4070
  if (error instanceof TileflowValidationError3) {
@@ -3346,11 +4083,905 @@ function printIssues(heading, issues) {
3346
4083
  );
3347
4084
  }
3348
4085
 
4086
+ // src/icon-lock-command.ts
4087
+ import { dirname as dirname9 } from "path";
4088
+ import pc3 from "picocolors";
4089
+ import {
4090
+ collectTileflowIconSetReferences,
4091
+ compareCodeUnits as compareCodeUnits7,
4092
+ parseResolvedTileflowMap as parseResolvedTileflowMap3,
4093
+ parseTileflowIconsLockfile,
4094
+ tileflowIconSetReferenceSchema as tileflowIconSetReferenceSchema2,
4095
+ tileflowIconsLockfileName
4096
+ } from "@tileflow/core";
4097
+ import {
4098
+ getTileflowMapNames as getTileflowMapNames4,
4099
+ loadValidTileflowConfigWithInputs as loadValidTileflowConfigWithInputs3,
4100
+ TileflowValidationError as TileflowValidationError4
4101
+ } from "@tileflow/dev/config";
4102
+ import { readTileflowIconsLockfileText, writeTileflowIconsLockfile } from "@tileflow/dev/icons";
4103
+
4104
+ // src/hosted-team.ts
4105
+ import pc from "picocolors";
4106
+ import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
4107
+ async function resolveTeamAuthority(options, scopes, dependencies) {
4108
+ let apiOrigin;
4109
+ try {
4110
+ apiOrigin = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
4111
+ } catch (error) {
4112
+ emitFailure(options.json, "invalid_api_url", safeMessage(error));
4113
+ return null;
4114
+ }
4115
+ if (options.apiKey) {
4116
+ if (!/^tf_live_[0-9a-f]{48}$/u.test(options.apiKey)) {
4117
+ emitFailure(options.json, "invalid_team_data_key", "Team data API key is invalid.");
4118
+ return null;
4119
+ }
4120
+ if (options.team) {
4121
+ emitFailure(
4122
+ options.json,
4123
+ "team_selector_with_api_key",
4124
+ "A Team data key already selects its Team; omit --team."
4125
+ );
4126
+ return null;
4127
+ }
4128
+ return { apiOrigin, credential: options.apiKey, team: null };
4129
+ }
4130
+ let config;
4131
+ try {
4132
+ config = await dependencies.loadAuthConfig();
4133
+ } catch (error) {
4134
+ emitFailure(options.json, "auth_state_unavailable", safeMessage(error));
4135
+ return null;
4136
+ }
4137
+ const selected = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
4138
+ if (selected.kind !== "selected") {
4139
+ emitFailure(
4140
+ options.json,
4141
+ `account_session_${selected.kind}`,
4142
+ selected.kind === "expired" ? "Account session expired. Run tileflow login." : "Run tileflow login."
4143
+ );
4144
+ return null;
4145
+ }
4146
+ const discovered = await listAccountTeams(selected.session);
4147
+ if (!discovered.ok) {
4148
+ emitFailure(options.json, "team_discovery_failed", discovered.error);
4149
+ return null;
4150
+ }
4151
+ const requested = options.team ? /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(options.team) : null;
4152
+ if (options.team && !requested) {
4153
+ emitFailure(options.json, "invalid_team", "Team must use @team syntax.");
4154
+ return null;
4155
+ }
4156
+ const team = requested ? discovered.teams.find(({ slug }) => slug === requested[1]) : discovered.teams.length === 1 ? discovered.teams[0] : null;
4157
+ if (!team) {
4158
+ const options_ = discovered.teams.map(({ slug }) => `@${slug}`);
4159
+ emitFailure(
4160
+ options.json,
4161
+ options_.length > 1 ? "team_ambiguous" : "team_not_found",
4162
+ options_.length > 1 ? `Choose one Team with --team: ${options_.join(", ")}.` : "No matching Team is available.",
4163
+ { options: options_ }
4164
+ );
4165
+ return null;
4166
+ }
4167
+ const capability = await requestTeamCapability(selected.session, `@${team.slug}`, scopes);
4168
+ if (!capability.ok) {
4169
+ emitFailure(options.json, "team_capability_failed", capability.error);
4170
+ return null;
4171
+ }
4172
+ return {
4173
+ apiOrigin: selected.session.apiOrigin,
4174
+ credential: capability.capability,
4175
+ team
4176
+ };
4177
+ }
4178
+ function authorizedTeamRequest(authority, path, method, init = {}) {
4179
+ return requestHostedJson(authority.apiOrigin, path, {
4180
+ ...init.body === void 0 ? {} : { body: init.body },
4181
+ headers: { Authorization: `Bearer ${authority.credential}`, ...init.headers },
4182
+ method
4183
+ });
4184
+ }
4185
+ function emitJson(value) {
4186
+ process.stdout.write(`${serializeCanonicalJson2(value)}
4187
+ `);
4188
+ }
4189
+ function emitFailure(json, code, message, context = {}) {
4190
+ if (json) {
4191
+ process.stderr.write(
4192
+ `${serializeCanonicalJson2({ error: { code, ...context, message }, ok: false, schemaVersion: 1 })}
4193
+ `
4194
+ );
4195
+ } else {
4196
+ console.error(`${pc.red("Error:")} ${message}`);
4197
+ }
4198
+ process.exitCode = 1;
4199
+ }
4200
+ function safeMessage(error) {
4201
+ return error instanceof Error ? error.message : "Team command failed.";
4202
+ }
4203
+
4204
+ // src/icon-set-command.ts
4205
+ import { isAbsolute as isAbsolute2 } from "path";
4206
+ import pc2 from "picocolors";
4207
+ import { z as z4 } from "zod";
4208
+ import {
4209
+ tileflowIconPackageLimits as tileflowIconPackageLimits3,
4210
+ tileflowIconSetPackageIdSchema,
4211
+ tileflowIconSetPinSchema,
4212
+ tileflowIconSetReferenceSchema
4213
+ } from "@tileflow/core";
4214
+ import { composeTileflowIconSources } from "@tileflow/dev/icons";
4215
+ var maximumCatalogPages = 100;
4216
+ var pageSize = 100;
4217
+ function registerIconSetCommands(program2, dependencies) {
4218
+ const iconSet = program2.command("icon-set").description("Publish and manage versioned Team Icon Sets");
4219
+ withTeamOptions(
4220
+ iconSet.command("publish").description("Compile one local directory and publish it as an immutable Team revision").argument("<directory>", "repository-local icon directory").requiredOption("--id <id>", "Team-local Icon Set slug").requiredOption("--idempotency-key <key>", "durable retry key for this exact publication").option("--name <name>", "display name").option("--description <text>", "display description")
4221
+ ).action(
4222
+ async (directory, options) => {
4223
+ try {
4224
+ if (!isIconSetSlug(options.id)) {
4225
+ return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
4226
+ }
4227
+ if (!isIdempotencyKey(options.idempotencyKey)) {
4228
+ return emitFailure(
4229
+ options.json,
4230
+ "invalid_idempotency_key",
4231
+ "Idempotency key must be 8 to 128 characters from A-Z a-z 0-9 . _ : -"
4232
+ );
4233
+ }
4234
+ const source = normalizeLocalDirectory(directory);
4235
+ if (!source) {
4236
+ return emitFailure(
4237
+ options.json,
4238
+ "invalid_icon_directory",
4239
+ "Publish one repository-relative icon directory."
4240
+ );
4241
+ }
4242
+ let iconPackage;
4243
+ try {
4244
+ const composed = await composeTileflowIconSources([source], {
4245
+ cwd: process.cwd(),
4246
+ target: "hosted"
4247
+ });
4248
+ if (!composed.package) throw new Error("The selected directory has no icons.");
4249
+ iconPackage = composed.package;
4250
+ } catch (error) {
4251
+ return emitFailure(options.json, "icon_compilation_failed", safeMessage(error));
4252
+ }
4253
+ const authority = await resolveTeamAuthority(options, ["icons:write"], dependencies);
4254
+ if (!authority) return;
4255
+ const body = new FormData();
4256
+ for (const file of iconPackage.files) {
4257
+ const bytes = new Uint8Array(file.source.byteLength);
4258
+ bytes.set(file.source);
4259
+ body.append(
4260
+ file.fileName,
4261
+ new Blob([bytes.buffer], { type: file.contentType }),
4262
+ file.fileName
4263
+ );
4264
+ }
4265
+ const response = await authorizedTeamRequest(
4266
+ authority,
4267
+ `/v1/icon-sets/${encodeURIComponent(options.id)}/versions/${encodeURIComponent(iconPackage.contentHash)}`,
4268
+ "PUT",
4269
+ { body, headers: { "Idempotency-Key": options.idempotencyKey } }
4270
+ );
4271
+ if (!response.ok) return emitHttpFailure(options.json, response.status, "publish");
4272
+ const parsed = publishResponseSchema.parse(response.body);
4273
+ if (parsed.revision.pin && parsed.revision.pin.contentHash !== iconPackage.contentHash) {
4274
+ return emitFailure(
4275
+ options.json,
4276
+ "icon_set_publish_unconfirmed",
4277
+ "Publication response did not confirm the submitted generated artifact."
4278
+ );
4279
+ }
4280
+ const metadata = {
4281
+ ...options.description === void 0 ? {} : { description: options.description },
4282
+ ...options.name === void 0 ? {} : { name: options.name }
4283
+ };
4284
+ if (Object.keys(metadata).length > 0) {
4285
+ try {
4286
+ const metadataResponse = await authorizedTeamRequest(
4287
+ authority,
4288
+ `/v1/icon-sets/${encodeURIComponent(options.id)}`,
4289
+ "PATCH",
4290
+ {
4291
+ body: JSON.stringify(metadata),
4292
+ headers: { "Content-Type": "application/json" }
4293
+ }
4294
+ );
4295
+ if (!metadataResponse.ok || !catalogDetailResponseSchema.safeParse(metadataResponse.body).success) {
4296
+ return emitFailure(
4297
+ options.json,
4298
+ "icon_set_metadata_update_failed",
4299
+ "Icon Set publication succeeded, but display metadata could not be updated; rerun the same command."
4300
+ );
4301
+ }
4302
+ } catch {
4303
+ return emitFailure(
4304
+ options.json,
4305
+ "icon_set_metadata_update_failed",
4306
+ "Icon Set publication succeeded, but display metadata could not be updated; rerun the same command."
4307
+ );
4308
+ }
4309
+ }
4310
+ const totalBytes = iconPackage.manifest.files.reduce(
4311
+ (total, file) => total + file.byteLength,
4312
+ 0
4313
+ );
4314
+ const document = {
4315
+ command: "icon-set publish",
4316
+ iconSet: {
4317
+ contentHash: iconPackage.contentHash,
4318
+ iconCount: iconPackage.manifest.iconNames.length,
4319
+ logicalId: options.id,
4320
+ packageId: parsed.revision.pin?.packageId ?? null,
4321
+ reference: parsed.revision.reference,
4322
+ totalBytes,
4323
+ version: parsed.revision.version,
4324
+ versionId: parsed.revision.id
4325
+ },
4326
+ publication: parsed.changed ? "changed" : "unchanged",
4327
+ schemaVersion: 1,
4328
+ team: describeTeam(authority)
4329
+ };
4330
+ if (options.json) return emitJson(document);
4331
+ console.log(
4332
+ `${pc2.green("\u2713")} ${parsed.changed ? "Published" : "Unchanged"} ${pc2.bold(parsed.revision.reference)} revision ${parsed.revision.version}.`
4333
+ );
4334
+ } catch (error) {
4335
+ emitFailure(options.json, "icon_set_publish_failed", boundedMessage(error, "publish"));
4336
+ }
4337
+ }
4338
+ );
4339
+ withTeamOptions(
4340
+ iconSet.command("list").description("List Icon Sets owned by one Team").option("--archived", "list archived Icon Sets instead of active ones")
4341
+ ).action(async (options) => {
4342
+ try {
4343
+ const authority = await resolveTeamAuthority(options, ["icons:read"], dependencies);
4344
+ if (!authority) return;
4345
+ const sets = await readAllPages(
4346
+ authority,
4347
+ "/v1/icon-sets",
4348
+ options.archived ? { archived: "true" } : {},
4349
+ catalogListResponseSchema,
4350
+ (page) => page.sets,
4351
+ (set) => set.id
4352
+ );
4353
+ const document = {
4354
+ command: "icon-set list",
4355
+ schemaVersion: 1,
4356
+ sets,
4357
+ team: describeTeam(authority)
4358
+ };
4359
+ if (options.json) return emitJson(document);
4360
+ if (sets.length === 0) return console.log(pc2.gray("No Team Icon Sets."));
4361
+ for (const set of sets) {
4362
+ console.log(
4363
+ ` ${pc2.bold(set.reference)} ${pc2.gray(set.archivedAt ? "archived" : `v${String(set.latestVersion ?? 0)}`)}`
4364
+ );
4365
+ }
4366
+ } catch (error) {
4367
+ emitFailure(options.json, "icon_set_list_failed", boundedMessage(error, "list"));
4368
+ }
4369
+ });
4370
+ withTeamOptions(
4371
+ iconSet.command("status").description("Show one Team Icon Set").argument("<slug>", "Team-local Icon Set slug")
4372
+ ).action(async (slug, options) => {
4373
+ await readCommand(slug, options, "icon-set status", async (authority) => {
4374
+ const response = await authorizedTeamRequest(
4375
+ authority,
4376
+ `/v1/icon-sets/${encodeURIComponent(slug)}`,
4377
+ "GET"
4378
+ );
4379
+ if (!response.ok) return { status: response.status };
4380
+ const parsed = catalogDetailResponseSchema.parse(response.body);
4381
+ return { value: { set: parsed.set } };
4382
+ });
4383
+ });
4384
+ withTeamOptions(
4385
+ iconSet.command("versions").description("List the retained revisions of one Team Icon Set").argument("<slug>", "Team-local Icon Set slug")
4386
+ ).action(async (slug, options) => {
4387
+ await readCommand(slug, options, "icon-set versions", async (authority) => {
4388
+ const versions = await readAllPages(
4389
+ authority,
4390
+ `/v1/icon-sets/${encodeURIComponent(slug)}/versions`,
4391
+ {},
4392
+ versionsResponseSchema,
4393
+ (page) => page.versions,
4394
+ (revision) => revision.id
4395
+ );
4396
+ return { value: { versions: versions.map(describeRevision) } };
4397
+ });
4398
+ });
4399
+ withTeamOptions(
4400
+ iconSet.command("uses").description("List the known hosted deployments that retain one Team Icon Set").argument("<slug>", "Team-local Icon Set slug")
4401
+ ).action(async (slug, options) => {
4402
+ await readCommand(slug, options, "icon-set uses", async (authority) => {
4403
+ const uses = await readAllPages(
4404
+ authority,
4405
+ `/v1/icon-sets/${encodeURIComponent(slug)}/uses`,
4406
+ {},
4407
+ usesResponseSchema,
4408
+ (page) => page.uses,
4409
+ (use) => `${use.deploymentId}\0${use.versionId}\0${String(use.ordinal)}`
4410
+ );
4411
+ return {
4412
+ value: {
4413
+ // Repository locks that were never deployed are unknowable to the catalog.
4414
+ coverage: "known-hosted-deployments",
4415
+ includesUndeployedRepositoryLocks: false,
4416
+ uses
4417
+ }
4418
+ };
4419
+ });
4420
+ });
4421
+ for (const [name, archived] of [
4422
+ ["archive", true],
4423
+ ["unarchive", false]
4424
+ ]) {
4425
+ withTeamOptions(
4426
+ iconSet.command(name).description(
4427
+ archived ? "Archive one Team Icon Set without removing its retained revisions" : "Return one archived Team Icon Set to the active catalog"
4428
+ ).argument("<slug>", "Team-local Icon Set slug")
4429
+ ).action(async (slug, options) => {
4430
+ try {
4431
+ if (!isIconSetSlug(slug)) {
4432
+ return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
4433
+ }
4434
+ const authority = await resolveTeamAuthority(options, ["icons:write"], dependencies);
4435
+ if (!authority) return;
4436
+ const response = await authorizedTeamRequest(
4437
+ authority,
4438
+ `/v1/icon-sets/${encodeURIComponent(slug)}`,
4439
+ "PATCH",
4440
+ {
4441
+ body: JSON.stringify({ archived }),
4442
+ headers: { "Content-Type": "application/json" }
4443
+ }
4444
+ );
4445
+ if (!response.ok) return emitHttpFailure(options.json, response.status, name);
4446
+ const parsed = catalogDetailResponseSchema.parse(response.body);
4447
+ const document = {
4448
+ command: `icon-set ${name}`,
4449
+ schemaVersion: 1,
4450
+ set: parsed.set,
4451
+ team: describeTeam(authority)
4452
+ };
4453
+ if (options.json) return emitJson(document);
4454
+ console.log(
4455
+ `${pc2.green("\u2713")} ${archived ? "Archived" : "Restored"} ${pc2.bold(parsed.set.reference)}.`
4456
+ );
4457
+ } catch (error) {
4458
+ emitFailure(options.json, `icon_set_${name}_failed`, boundedMessage(error, name));
4459
+ }
4460
+ });
4461
+ }
4462
+ withTeamOptions(
4463
+ iconSet.command("purge").description("Permanently purge one exact Team Icon Set revision").argument("<slug>", "Team-local Icon Set slug").requiredOption("--version <version>", "exact positive revision to purge").requiredOption("--idempotency-key <key>", "durable retry key for this exact purge").requiredOption("--confirm <reference>", "repeat the exact @team/set@version being purged").option(
4464
+ "--acknowledge-unknown-locks",
4465
+ "acknowledge that undeployed repository locks are not discoverable"
4466
+ )
4467
+ ).action(
4468
+ async (slug, options) => {
4469
+ try {
4470
+ if (!isIconSetSlug(slug)) {
4471
+ return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
4472
+ }
4473
+ if (!/^[1-9][0-9]*$/u.test(options.version) || !Number.isSafeInteger(+options.version)) {
4474
+ return emitFailure(
4475
+ options.json,
4476
+ "invalid_icon_set_version",
4477
+ "Purge requires one exact positive revision."
4478
+ );
4479
+ }
4480
+ if (!isIdempotencyKey(options.idempotencyKey)) {
4481
+ return emitFailure(
4482
+ options.json,
4483
+ "invalid_idempotency_key",
4484
+ "Idempotency key must be 8 to 128 characters from A-Z a-z 0-9 . _ : -"
4485
+ );
4486
+ }
4487
+ const confirmation = parsePurgeConfirmation(options.confirm);
4488
+ if (!confirmation || confirmation.slug !== slug || confirmation.version !== Number(options.version)) {
4489
+ return emitFailure(
4490
+ options.json,
4491
+ "purge_confirmation_mismatch",
4492
+ "Purge confirmation must repeat the exact @team/set@version being purged."
4493
+ );
4494
+ }
4495
+ if (options.acknowledgeUnknownLocks !== true) {
4496
+ return emitFailure(
4497
+ options.json,
4498
+ "purge_unknown_locks_unacknowledged",
4499
+ "Purge requires --acknowledge-unknown-locks; undeployed repository locks are not discoverable."
4500
+ );
4501
+ }
4502
+ const authority = await resolveTeamAuthority(options, ["icons:write"], dependencies);
4503
+ if (!authority) return;
4504
+ if (authority.team && `@${authority.team.slug}` !== confirmation.team) {
4505
+ return emitFailure(
4506
+ options.json,
4507
+ "purge_confirmation_mismatch",
4508
+ "Purge confirmation must name the selected Team."
4509
+ );
4510
+ }
4511
+ const response = await authorizedTeamRequest(
4512
+ authority,
4513
+ `/v1/icon-sets/${encodeURIComponent(slug)}/versions/${encodeURIComponent(options.version)}/purge`,
4514
+ "POST",
4515
+ {
4516
+ body: JSON.stringify({
4517
+ acknowledgeUnknownLocks: true,
4518
+ confirmation: options.confirm
4519
+ }),
4520
+ headers: {
4521
+ "Content-Type": "application/json",
4522
+ "Idempotency-Key": options.idempotencyKey
4523
+ }
4524
+ }
4525
+ );
4526
+ if (!response.ok) return emitHttpFailure(options.json, response.status, "purge");
4527
+ const parsed = purgeResponseSchema.parse(response.body);
4528
+ const document = {
4529
+ command: "icon-set purge",
4530
+ result: parsed.result,
4531
+ schemaVersion: 1,
4532
+ team: describeTeam(authority)
4533
+ };
4534
+ if (options.json) return emitJson(document);
4535
+ console.log(`${pc2.green("\u2713")} Purged ${pc2.bold(options.confirm)}.`);
4536
+ console.log(pc2.yellow(`! ${parsed.result.warning}`));
4537
+ } catch (error) {
4538
+ emitFailure(options.json, "icon_set_purge_failed", boundedMessage(error, "purge"));
4539
+ }
4540
+ }
4541
+ );
4542
+ async function readCommand(slug, options, command2, run) {
4543
+ try {
4544
+ if (!isIconSetSlug(slug)) {
4545
+ return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
4546
+ }
4547
+ const authority = await resolveTeamAuthority(options, ["icons:read"], dependencies);
4548
+ if (!authority) return;
4549
+ const result = await run(authority);
4550
+ if ("status" in result) return emitHttpFailure(options.json, result.status, command2);
4551
+ const document = {
4552
+ command: command2,
4553
+ schemaVersion: 1,
4554
+ team: describeTeam(authority),
4555
+ ...result.value
4556
+ };
4557
+ if (options.json) return emitJson(document);
4558
+ process.stdout.write(`${JSON.stringify(document, null, 2)}
4559
+ `);
4560
+ } catch (error) {
4561
+ emitFailure(
4562
+ options.json,
4563
+ `${command2.replaceAll(/[^a-z]+/gu, "_")}_failed`,
4564
+ boundedMessage(error, command2)
4565
+ );
4566
+ }
4567
+ }
4568
+ }
4569
+ function withTeamOptions(command2) {
4570
+ return command2.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");
4571
+ }
4572
+ async function readAllPages(authority, path, query, schema, select, identify) {
4573
+ const items = [];
4574
+ const seenCursors = /* @__PURE__ */ new Set();
4575
+ const seenIds = /* @__PURE__ */ new Set();
4576
+ let cursor = null;
4577
+ for (let page = 0; page < maximumCatalogPages; page += 1) {
4578
+ const parameters = new URLSearchParams({ limit: String(pageSize), ...query });
4579
+ if (cursor) parameters.set("cursor", cursor);
4580
+ const response = await authorizedTeamRequest(
4581
+ authority,
4582
+ `${path}?${parameters.toString()}`,
4583
+ "GET"
4584
+ );
4585
+ if (!response.ok) throw new IconSetTransportError(response.status);
4586
+ const parsed = schema.parse(response.body);
4587
+ for (const item of select(parsed)) {
4588
+ const id = identify(item);
4589
+ if (seenIds.has(id)) throw new Error("Icon Set list returned a duplicate row.");
4590
+ seenIds.add(id);
4591
+ items.push(item);
4592
+ }
4593
+ cursor = parsed.nextCursor ?? null;
4594
+ if (!cursor) return items;
4595
+ if (seenCursors.has(cursor)) throw new Error("Icon Set list repeated a cursor.");
4596
+ seenCursors.add(cursor);
4597
+ }
4598
+ throw new Error("Icon Set list exceeded its safe page limit.");
4599
+ }
4600
+ var IconSetTransportError = class extends Error {
4601
+ constructor(status) {
4602
+ super(`Icon Set request failed (${status}).`);
4603
+ this.status = status;
4604
+ this.name = "IconSetTransportError";
4605
+ }
4606
+ status;
4607
+ };
4608
+ function emitHttpFailure(json, status, command2) {
4609
+ emitFailure(json, `http_${status}`, `Icon Set ${command2} failed (${status}).`);
4610
+ }
4611
+ function boundedMessage(error, command2) {
4612
+ if (error instanceof IconSetTransportError) return error.message;
4613
+ if (error instanceof Error && /limit|cursor|duplicate row/u.test(error.message)) {
4614
+ return error.message;
4615
+ }
4616
+ return `Icon Set ${command2} failed.`;
4617
+ }
4618
+ function describeTeam(authority) {
4619
+ return authority.team ? { id: authority.team.id, slug: authority.team.slug } : null;
4620
+ }
4621
+ function describeRevision(revision) {
4622
+ return {
4623
+ iconCount: revision.iconCount,
4624
+ id: revision.id,
4625
+ packageId: revision.packageId,
4626
+ publishedAt: revision.publishedAt,
4627
+ purgedAt: revision.purgedAt,
4628
+ reference: revision.reference,
4629
+ totalBytes: revision.totalBytes,
4630
+ version: revision.version
4631
+ };
4632
+ }
4633
+ function normalizeLocalDirectory(value) {
4634
+ if (!value || isAbsolute2(value) || value.includes("\\") || /[\p{Cc}]/u.test(value)) return null;
4635
+ const normalized = value.startsWith("./") || value.startsWith("../") ? value : `./${value}`;
4636
+ return normalized.replace(/\/+$/u, "");
4637
+ }
4638
+ function parsePurgeConfirmation(value) {
4639
+ const separator = value.lastIndexOf("@");
4640
+ if (separator <= 0 || value.length > 160) return null;
4641
+ const reference = value.slice(0, separator);
4642
+ const version = value.slice(separator + 1);
4643
+ if (!tileflowIconSetReferenceSchema.safeParse(reference).success) return null;
4644
+ if (!/^[1-9][0-9]*$/u.test(version) || !Number.isSafeInteger(Number(version))) return null;
4645
+ return { slug: reference.split("/")[1], team: reference.split("/")[0], version: Number(version) };
4646
+ }
4647
+ function isIconSetSlug(value) {
4648
+ return value.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value);
4649
+ }
4650
+ function isIdempotencyKey(value) {
4651
+ return value.length >= 8 && value.length <= 128 && /^[A-Za-z0-9._:-]+$/u.test(value);
4652
+ }
4653
+ var cursorSchema = z4.string().min(1).max(2048).nullable();
4654
+ var isoDateSchema = z4.iso.datetime({ offset: true });
4655
+ var setSchema = z4.object({
4656
+ id: z4.string().regex(/^ics_[A-Za-z0-9_-]{16}$/u),
4657
+ reference: tileflowIconSetReferenceSchema,
4658
+ name: z4.string().max(200),
4659
+ description: z4.string().max(2e3),
4660
+ archivedAt: isoDateSchema.nullable(),
4661
+ latestVersion: z4.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
4662
+ createdAt: isoDateSchema,
4663
+ updatedAt: isoDateSchema
4664
+ }).strict();
4665
+ var revisionBaseSchema = z4.object({
4666
+ id: z4.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
4667
+ reference: tileflowIconSetReferenceSchema,
4668
+ version: z4.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4669
+ publishedAt: isoDateSchema,
4670
+ purgedAt: isoDateSchema.nullable(),
4671
+ iconCount: z4.number().int().min(0).max(tileflowIconPackageLimits3.maxIconCount),
4672
+ totalBytes: z4.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
4673
+ publisher: z4.object({ kind: z4.enum(["credential", "membership"]), id: z4.string().max(200).nullable() }).strict()
4674
+ }).strict();
4675
+ var revisionSchema = revisionBaseSchema.extend({ pin: tileflowIconSetPinSchema.nullable() }).transform(({ id, reference, version, publishedAt, purgedAt, iconCount, totalBytes, pin }) => ({
4676
+ iconCount,
4677
+ id,
4678
+ pin,
4679
+ publishedAt,
4680
+ purgedAt,
4681
+ reference,
4682
+ totalBytes,
4683
+ version
4684
+ }));
4685
+ var revisionSummarySchema = revisionBaseSchema.extend({ packageId: tileflowIconSetPackageIdSchema.nullable() }).transform(
4686
+ ({ id, reference, version, publishedAt, purgedAt, iconCount, totalBytes, packageId }) => ({
4687
+ iconCount,
4688
+ id,
4689
+ packageId,
4690
+ publishedAt,
4691
+ purgedAt,
4692
+ reference,
4693
+ totalBytes,
4694
+ version
4695
+ })
4696
+ );
4697
+ var catalogListResponseSchema = z4.object({
4698
+ schemaVersion: z4.literal(1),
4699
+ sets: z4.array(setSchema).max(pageSize),
4700
+ nextCursor: cursorSchema.optional()
4701
+ }).strict();
4702
+ var catalogDetailResponseSchema = z4.object({ schemaVersion: z4.literal(1), set: setSchema }).strict();
4703
+ var versionsResponseSchema = z4.object({
4704
+ schemaVersion: z4.literal(1),
4705
+ versions: z4.array(revisionSummarySchema).max(pageSize),
4706
+ nextCursor: cursorSchema.optional()
4707
+ }).strict();
4708
+ var revisionResponseSchema = z4.object({
4709
+ schemaVersion: z4.literal(1),
4710
+ archived: z4.boolean(),
4711
+ revision: revisionSchema
4712
+ }).strict();
4713
+ var publishResponseSchema = z4.object({ schemaVersion: z4.literal(1), changed: z4.boolean(), revision: revisionSchema }).strict();
4714
+ var usesResponseSchema = z4.object({
4715
+ schemaVersion: z4.literal(1),
4716
+ uses: z4.array(
4717
+ z4.object({
4718
+ deploymentId: z4.string().min(1).max(160),
4719
+ deploymentVersion: z4.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4720
+ mapId: z4.string().min(1).max(160),
4721
+ mapName: z4.string().max(200).nullable(),
4722
+ mapState: z4.string().max(64),
4723
+ versionId: z4.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
4724
+ version: z4.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4725
+ ordinal: z4.number().int().min(0).max(31),
4726
+ relationship: z4.enum(["active", "retained"])
4727
+ }).strict()
4728
+ ).max(pageSize),
4729
+ nextCursor: cursorSchema.optional(),
4730
+ coverage: z4.literal("known-hosted-deployments"),
4731
+ includesUndeployedRepositoryLocks: z4.literal(false)
4732
+ }).strict();
4733
+ var purgeResponseSchema = z4.object({
4734
+ schemaVersion: z4.literal(1),
4735
+ result: z4.object({
4736
+ schemaVersion: z4.literal(1),
4737
+ versionId: z4.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
4738
+ version: z4.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4739
+ purged: z4.boolean(),
4740
+ knownRetainedDeployments: z4.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
4741
+ physicalBytesReleased: z4.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
4742
+ unknownRepositoryLocks: z4.boolean(),
4743
+ warning: z4.string().max(400)
4744
+ }).strict()
4745
+ }).strict();
4746
+
4747
+ // src/icon-lock-command.ts
4748
+ function registerIconLockCommands(program2, dependencies) {
4749
+ const icons = program2.commands.find((command2) => command2.name() === "icons") ?? program2.command("icons").description("Inspect managed Tileflow icons");
4750
+ withLockOptions(
4751
+ icons.command("install").description("Lock every declared Icon Set reference that has no exact pin yet").argument("[references...]", "declared @team/set references; omit for every declared set"),
4752
+ dependencies
4753
+ ).action(async (references, options) => {
4754
+ await maintainLock("install", references, options);
4755
+ });
4756
+ withLockOptions(
4757
+ icons.command("update").description("Move selected declared Icon Set references to their current latest revision").argument("[references...]", "declared @team/set references; omit for every declared set"),
4758
+ dependencies
4759
+ ).action(async (references, options) => {
4760
+ await maintainLock("update", references, options);
4761
+ });
4762
+ withLockOptions(
4763
+ icons.command("pin").description("Lock one declared Icon Set reference to one exact revision").argument("<reference>", "declared @team/set reference").requiredOption("--version <version>", "exact positive revision"),
4764
+ dependencies
4765
+ ).action(async (reference, options) => {
4766
+ if (!/^[1-9][0-9]*$/u.test(options.version) || !Number.isSafeInteger(+options.version)) {
4767
+ return emitFailure(
4768
+ options.json,
4769
+ "invalid_icon_set_version",
4770
+ "Pin requires one exact positive revision."
4771
+ );
4772
+ }
4773
+ await maintainLock("pin", [reference], options, Number(options.version));
4774
+ });
4775
+ async function maintainLock(operation, requested, options, version) {
4776
+ let declared;
4777
+ let baseDirectory;
4778
+ try {
4779
+ const loaded = await withTileflowConfigSecretsHidden(
4780
+ () => loadValidTileflowConfigWithInputs3(options.config)
4781
+ );
4782
+ baseDirectory = dirname9(loaded.configFile);
4783
+ declared = collectDeclaredReferences(loaded.project);
4784
+ } catch (error) {
4785
+ return emitFailure(
4786
+ options.json,
4787
+ error instanceof TileflowValidationError4 ? "config_invalid" : "config_load_failed",
4788
+ error instanceof TileflowValidationError4 ? "Tileflow config has errors." : "Tileflow config could not be loaded."
4789
+ );
4790
+ }
4791
+ const selection = selectReferences(operation, requested, declared);
4792
+ if ("error" in selection) {
4793
+ return emitFailure(options.json, selection.error.code, selection.error.message);
4794
+ }
4795
+ let expected;
4796
+ let current;
4797
+ try {
4798
+ expected = await readTileflowIconsLockfileText(baseDirectory);
4799
+ current = expected === null ? {} : await readCurrentPins(expected);
4800
+ } catch {
4801
+ return emitFailure(
4802
+ options.json,
4803
+ "icon_lock_unreadable",
4804
+ `Existing ${tileflowIconsLockfileName} is unreadable; repair or remove it explicitly.`
4805
+ );
4806
+ }
4807
+ const targets = operation === "install" ? selection.selected.filter((reference) => !current[reference]) : selection.selected;
4808
+ if (targets.length === 0 && operation !== "install") {
4809
+ return emitFailure(
4810
+ options.json,
4811
+ "icon_set_not_declared",
4812
+ "Select at least one declared @team/set reference."
4813
+ );
4814
+ }
4815
+ const authority = targets.length === 0 ? null : await resolveTeamAuthority(options, ["icons:read"], dependencies);
4816
+ if (targets.length > 0 && !authority) return;
4817
+ const resolved = {};
4818
+ for (const reference of targets) {
4819
+ const result = await resolvePin(authority, reference, version);
4820
+ if ("error" in result) {
4821
+ return emitFailure(options.json, result.error.code, result.error.message);
4822
+ }
4823
+ resolved[reference] = result.pin;
4824
+ }
4825
+ const sets = {};
4826
+ for (const reference of declared.references) {
4827
+ const pin = resolved[reference] ?? current[reference];
4828
+ if (pin) sets[reference] = pin;
4829
+ }
4830
+ if (expected !== null || Object.keys(sets).length > 0) {
4831
+ try {
4832
+ await writeTileflowIconsLockfile(baseDirectory, { lockfileVersion: 1, sets }, expected);
4833
+ } catch (error) {
4834
+ const code = error !== null && typeof error === "object" && "code" in error ? String(error.code) : "icon_lock_write_failed";
4835
+ return emitFailure(
4836
+ options.json,
4837
+ code,
4838
+ code === "ICON_LOCK_CONFLICT" ? "The icon lock changed while this command ran; rerun it without discarding other pins." : "The icon lock could not be written."
4839
+ );
4840
+ }
4841
+ }
4842
+ const missing = declared.references.filter((reference) => !sets[reference]);
4843
+ const document = {
4844
+ command: `icons ${operation}`,
4845
+ lockfile: tileflowIconsLockfileName,
4846
+ missing,
4847
+ schemaVersion: 1,
4848
+ sets: declared.references.filter((reference) => sets[reference]).map((reference) => ({
4849
+ changed: Boolean(resolved[reference]),
4850
+ contentHash: sets[reference].contentHash,
4851
+ packageId: sets[reference].packageId,
4852
+ reference,
4853
+ version: sets[reference].version,
4854
+ versionId: sets[reference].versionId
4855
+ }))
4856
+ };
4857
+ if (options.json) return emitJson(document);
4858
+ if (document.sets.length === 0) return console.log(pc3.gray("No Icon Set references declared."));
4859
+ for (const entry of document.sets) {
4860
+ console.log(
4861
+ ` ${pc3.bold(entry.reference)} ${pc3.gray(`v${String(entry.version)}`)}${entry.changed ? "" : pc3.gray(" (unchanged)")}`
4862
+ );
4863
+ }
4864
+ for (const reference of missing) {
4865
+ console.log(pc3.yellow(`! ${reference} is declared but not locked.`));
4866
+ }
4867
+ }
4868
+ async function resolvePin(authority, reference, version) {
4869
+ const slug = reference.split("/")[1];
4870
+ const selector = version === void 0 ? "latest" : String(version);
4871
+ const response = await authorizedTeamRequest(
4872
+ authority,
4873
+ `/v1/icon-sets/${encodeURIComponent(slug)}/versions/${encodeURIComponent(selector)}`,
4874
+ "GET"
4875
+ );
4876
+ if (!response.ok) {
4877
+ return {
4878
+ error: {
4879
+ code: `http_${response.status}`,
4880
+ message: `Resolving ${reference}@${selector} failed (${response.status}).`
4881
+ }
4882
+ };
4883
+ }
4884
+ const parsed = revisionResponseSchema.safeParse(response.body);
4885
+ if (!parsed.success) {
4886
+ return {
4887
+ error: {
4888
+ code: "icon_set_response_invalid",
4889
+ message: `Resolving ${reference}@${selector} returned an invalid response.`
4890
+ }
4891
+ };
4892
+ }
4893
+ const revision = parsed.data.revision;
4894
+ if (revision.reference !== reference) {
4895
+ return {
4896
+ error: {
4897
+ code: "icon_set_reference_mismatch",
4898
+ message: `Resolving ${reference} returned a different Icon Set.`
4899
+ }
4900
+ };
4901
+ }
4902
+ if (!revision.pin || revision.purgedAt !== null) {
4903
+ return {
4904
+ error: {
4905
+ code: "icon_set_revision_unavailable",
4906
+ message: `${reference}@${selector} has no retained delivery artifact.`
4907
+ }
4908
+ };
4909
+ }
4910
+ if (version !== void 0 && revision.version !== version) {
4911
+ return {
4912
+ error: {
4913
+ code: "icon_set_revision_mismatch",
4914
+ message: `${reference}@${selector} returned revision ${revision.version}.`
4915
+ }
4916
+ };
4917
+ }
4918
+ if (authority.team && revision.pin.teamId !== authority.team.id) {
4919
+ return {
4920
+ error: {
4921
+ code: "icon_set_team_mismatch",
4922
+ message: `${reference} is owned by a different Team.`
4923
+ }
4924
+ };
4925
+ }
4926
+ return { pin: revision.pin };
4927
+ }
4928
+ }
4929
+ function withLockOptions(command2, dependencies) {
4930
+ return command2.option("-c, --config <path>", "config path", dependencies.defaultConfigPath).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");
4931
+ }
4932
+ function collectDeclaredReferences(project) {
4933
+ const references = [];
4934
+ const seen = /* @__PURE__ */ new Set();
4935
+ for (const mapName of getTileflowMapNames4(project).sort(compareCodeUnits7)) {
4936
+ const sources = parseResolvedTileflowMap3(project.maps[mapName]).icons ?? [];
4937
+ for (const reference of collectTileflowIconSetReferences(sources)) {
4938
+ if (seen.has(reference)) continue;
4939
+ seen.add(reference);
4940
+ references.push(reference);
4941
+ }
4942
+ }
4943
+ return { references: references.sort(compareCodeUnits7) };
4944
+ }
4945
+ function selectReferences(operation, requested, declared) {
4946
+ if (requested.length === 0) {
4947
+ if (operation === "pin") {
4948
+ return { error: { code: "icon_set_not_selected", message: "Pin one @team/set reference." } };
4949
+ }
4950
+ return { selected: [...declared.references] };
4951
+ }
4952
+ const selected = [];
4953
+ for (const reference of requested) {
4954
+ if (!tileflowIconSetReferenceSchema2.safeParse(reference).success) {
4955
+ return {
4956
+ error: {
4957
+ code: "invalid_icon_set_reference",
4958
+ message: `${reference} is not a canonical @team/set reference.`
4959
+ }
4960
+ };
4961
+ }
4962
+ if (!declared.references.includes(reference)) {
4963
+ return {
4964
+ error: {
4965
+ code: "icon_set_not_declared",
4966
+ message: `${reference} is not declared by any map; add iconSet('${reference}') first.`
4967
+ }
4968
+ };
4969
+ }
4970
+ if (!selected.includes(reference)) {
4971
+ selected.push(reference);
4972
+ }
4973
+ }
4974
+ return { selected: selected.sort(compareCodeUnits7) };
4975
+ }
4976
+ async function readCurrentPins(contents) {
4977
+ return (await parseTileflowIconsLockfile(contents)).sets;
4978
+ }
4979
+
3349
4980
  // src/language-command.ts
3350
4981
  import { readFile as readFile4 } from "fs/promises";
3351
4982
  import { fileURLToPath } from "url";
3352
4983
  import {
3353
- serializeCanonicalJson,
4984
+ serializeCanonicalJson as serializeCanonicalJson3,
3354
4985
  tileflowAuthoringManifest
3355
4986
  } from "@tileflow/core";
3356
4987
  import {
@@ -3378,21 +5009,21 @@ async function runLanguageCommand(command2, options, operation) {
3378
5009
  try {
3379
5010
  await operation();
3380
5011
  } catch (error) {
3381
- const failure = createTileflowCommandFailureDocument3(command2, error, process.cwd(), {
5012
+ const failure2 = createTileflowCommandFailureDocument3(command2, error, process.cwd(), {
3382
5013
  code: command2 === "language-manifest" ? "LANGUAGE_MANIFEST_FAILED" : "LANGUAGE_SCHEMA_FAILED",
3383
5014
  phase: "language-discovery"
3384
5015
  });
3385
- if (options.json) process.stderr.write(serializeTileflowCommandDocument3(failure));
5016
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument3(failure2));
3386
5017
  else {
3387
- console.error(`${failure.message} [${failure.code}; ${failure.phase}]`);
3388
- console.error(`Suggestion: ${failure.suggestion}`);
5018
+ console.error(`${failure2.message} [${failure2.code}; ${failure2.phase}]`);
5019
+ console.error(`Suggestion: ${failure2.suggestion}`);
3389
5020
  }
3390
5021
  process.exitCode = 1;
3391
5022
  }
3392
5023
  }
3393
5024
  function printManifest(manifest, options) {
3394
5025
  if (options.json) {
3395
- process.stdout.write(`${serializeCanonicalJson(manifest)}
5026
+ process.stdout.write(`${serializeCanonicalJson3(manifest)}
3396
5027
  `);
3397
5028
  return;
3398
5029
  }
@@ -3403,7 +5034,7 @@ function printManifest(manifest, options) {
3403
5034
  }
3404
5035
  function printSchema(schema, options) {
3405
5036
  if (options.json) {
3406
- process.stdout.write(`${serializeCanonicalJson(schema)}
5037
+ process.stdout.write(`${serializeCanonicalJson3(schema)}
3407
5038
  `);
3408
5039
  return;
3409
5040
  }
@@ -3423,6 +5054,62 @@ function isRecord(value) {
3423
5054
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
3424
5055
  }
3425
5056
 
5057
+ // src/native-preview-command.ts
5058
+ import { tileflowNativeProfile } from "@tileflow/core/native-profile";
5059
+ import {
5060
+ createTileflowDevRequestHandler
5061
+ } from "@tileflow/dev/server";
5062
+ var tileflowNativePreviewBasePath = "/native";
5063
+ var tileflowNativePreviewProfile = tileflowNativeProfile.id;
5064
+ function getTileflowNativePreviewManifestUrl(origin) {
5065
+ return `${origin}${tileflowNativePreviewBasePath}/manifest.json`;
5066
+ }
5067
+ function resolveTileflowNativePreviewSelection(manifest, options) {
5068
+ const mapNames = Object.keys(manifest.maps).sort();
5069
+ const mapName = options.map ?? (mapNames.length === 1 ? mapNames[0] : void 0);
5070
+ if (!mapName || !Object.hasOwn(manifest.maps, mapName)) {
5071
+ throw new Error(
5072
+ options.map ? "The selected map is not present in the native artifact manifest." : "Select one configured map with --map for native artifact preview."
5073
+ );
5074
+ }
5075
+ const map = manifest.maps[mapName];
5076
+ const themeName = options.theme ?? map.defaultTheme;
5077
+ if (!Object.hasOwn(map.themes, themeName)) {
5078
+ throw new Error("The selected theme is not present in the native artifact manifest.");
5079
+ }
5080
+ return Object.freeze({ mapName, themeName });
5081
+ }
5082
+ var nativePreviewUnavailablePaths = /* @__PURE__ */ new Set([
5083
+ `${tileflowNativePreviewBasePath}/__events`,
5084
+ `${tileflowNativePreviewBasePath}/__status`
5085
+ ]);
5086
+ function createTileflowNativePreviewRequestHandler(options) {
5087
+ const delegate = createTileflowDevRequestHandler({
5088
+ ...options,
5089
+ basePath: tileflowNativePreviewBasePath
5090
+ });
5091
+ const notFound = () => new Response(JSON.stringify({ error: "Not found" }), {
5092
+ headers: { "Content-Type": "application/json; charset=utf-8" },
5093
+ status: 404
5094
+ });
5095
+ const handler = async (request) => {
5096
+ let pathname;
5097
+ try {
5098
+ pathname = new URL(request.url).pathname;
5099
+ } catch {
5100
+ return notFound();
5101
+ }
5102
+ if (pathname === tileflowNativePreviewBasePath || pathname === `${tileflowNativePreviewBasePath}/` || nativePreviewUnavailablePaths.has(pathname) || pathname.startsWith(`${tileflowNativePreviewBasePath}/__runtime/`) || pathname.startsWith(`${tileflowNativePreviewBasePath}/__inspection/`)) {
5103
+ return notFound();
5104
+ }
5105
+ return delegate(request);
5106
+ };
5107
+ return Object.assign(handler, {
5108
+ close: delegate.close,
5109
+ refresh: delegate.refresh
5110
+ });
5111
+ }
5112
+
3426
5113
  // src/open-external.ts
3427
5114
  import { spawn as spawnChild } from "child_process";
3428
5115
  import { platform as hostPlatform } from "os";
@@ -3455,7 +5142,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
3455
5142
  }
3456
5143
 
3457
5144
  // src/project-commands.ts
3458
- import pc from "picocolors";
5145
+ import pc4 from "picocolors";
3459
5146
  function registerProjectCommands(program2, dependencies) {
3460
5147
  const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
3461
5148
  projects.command("list").description("List application destinations accessible to the authenticated Tileflow account").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--include-archived", "include archived application destinations").option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
@@ -3469,14 +5156,14 @@ function registerProjectCommands(program2, dependencies) {
3469
5156
  command: "projects list",
3470
5157
  projects: result.items
3471
5158
  };
3472
- if (options.json) return emitJson(document);
5159
+ if (options.json) return emitJson2(document);
3473
5160
  if (!result.items.length) {
3474
- console.log(pc.gray("No managed destinations found."));
5161
+ console.log(pc4.gray("No managed destinations found."));
3475
5162
  return;
3476
5163
  }
3477
5164
  for (const target of result.items) {
3478
5165
  console.log(
3479
- ` ${target.project.archivedAt ? pc.yellow("\u25CB") : pc.green("\u25CF")} ${pc.bold(target.project.name)} ${pc.gray(target.reference)}${target.project.archivedAt ? pc.gray(" (archived)") : ""}`
5166
+ ` ${target.project.archivedAt ? pc4.yellow("\u25CB") : pc4.green("\u25CF")} ${pc4.bold(target.project.name)} ${pc4.gray(target.reference)}${target.project.archivedAt ? pc4.gray(" (archived)") : ""}`
3480
5167
  );
3481
5168
  }
3482
5169
  });
@@ -3528,9 +5215,9 @@ function registerProjectCommands(program2, dependencies) {
3528
5215
  command: "projects create",
3529
5216
  ...asRecord2(result.body)
3530
5217
  };
3531
- if (options.json) return emitJson(document);
3532
- console.log(`${pc.green("\u2713")} Application ${pc.bold(options.name.trim())} is ready.`);
3533
- console.log(pc.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
5218
+ if (options.json) return emitJson2(document);
5219
+ console.log(`${pc4.green("\u2713")} Application ${pc4.bold(options.name.trim())} is ready.`);
5220
+ console.log(pc4.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
3534
5221
  }
3535
5222
  );
3536
5223
  projects.command("show <target>").description("Show one application destination").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--json", "print deterministic schema-version-1 JSON").action(async (target, options) => {
@@ -3547,13 +5234,13 @@ function registerProjectCommands(program2, dependencies) {
3547
5234
  );
3548
5235
  if (!result.ok) return emitRemoteFailure(options.json, result);
3549
5236
  const document = { schemaVersion: 1, command: "projects show", ...asRecord2(result.body) };
3550
- if (options.json) return emitJson(document);
5237
+ if (options.json) return emitJson2(document);
3551
5238
  const project = asRecord2(result.body).project;
3552
5239
  if (!isProjectItem(project)) {
3553
5240
  return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
3554
5241
  }
3555
- console.log(`${pc.bold(project.name)} ${pc.gray(target)}`);
3556
- console.log(project.archivedAt ? pc.yellow("Archived") : pc.green("Active"));
5242
+ console.log(`${pc4.bold(project.name)} ${pc4.gray(target)}`);
5243
+ console.log(project.archivedAt ? pc4.yellow("Archived") : pc4.green("Active"));
3557
5244
  });
3558
5245
  for (const archived of [true, false]) {
3559
5246
  const verb = archived ? "archive" : "unarchive";
@@ -3579,8 +5266,8 @@ function registerProjectCommands(program2, dependencies) {
3579
5266
  command: `projects ${verb}`,
3580
5267
  ...asRecord2(result.body)
3581
5268
  };
3582
- if (options.json) return emitJson(document);
3583
- console.log(`${pc.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
5269
+ if (options.json) return emitJson2(document);
5270
+ console.log(`${pc4.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
3584
5271
  });
3585
5272
  }
3586
5273
  }
@@ -3622,7 +5309,7 @@ async function requireCommandSession(options, dependencies) {
3622
5309
  try {
3623
5310
  config = await dependencies.loadAuthConfig();
3624
5311
  } catch (error) {
3625
- emitLocalFailure(options.json, "auth_state_unavailable", safeMessage(error));
5312
+ emitLocalFailure(options.json, "auth_state_unavailable", safeMessage2(error));
3626
5313
  return null;
3627
5314
  }
3628
5315
  const resolved = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
@@ -3661,7 +5348,7 @@ async function accountRequest(session, path, init) {
3661
5348
  }
3662
5349
  return response.ok ? { body: response.body, ok: true, status: response.status } : {
3663
5350
  body: response.body,
3664
- error: `Request failed (${response.status}).`,
5351
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Request failed (${response.status}).`,
3665
5352
  ok: false,
3666
5353
  status: response.status
3667
5354
  };
@@ -3701,13 +5388,13 @@ function emitRemoteFailure(json, result) {
3701
5388
  function emitLocalFailure(json, code, message, details = {}) {
3702
5389
  if (json) {
3703
5390
  console.error(message);
3704
- emitJson({ schemaVersion: 1, ok: false, error: { code, ...details } });
5391
+ emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
3705
5392
  } else {
3706
- console.error(`${pc.red("\u2715")} ${message}`);
5393
+ console.error(`${pc4.red("\u2715")} ${message}`);
3707
5394
  }
3708
5395
  process.exitCode = 1;
3709
5396
  }
3710
- function emitJson(value) {
5397
+ function emitJson2(value) {
3711
5398
  process.stdout.write(`${JSON.stringify(value)}
3712
5399
  `);
3713
5400
  }
@@ -3733,21 +5420,130 @@ function validName(value) {
3733
5420
  const normalized = value.trim();
3734
5421
  return normalized.length > 0 && normalized.length <= 200 && !/[\p{Cc}]/u.test(normalized);
3735
5422
  }
3736
- function safeMessage(error) {
5423
+ function safeMessage2(error) {
3737
5424
  return error instanceof Error && error.message.length <= 300 ? error.message : "Tileflow auth state is unavailable.";
3738
5425
  }
3739
5426
 
5427
+ // src/renderer-artifact-command.ts
5428
+ import { resolve as resolve5 } from "path";
5429
+ import { auditTileflowMapThemeValues } from "@tileflow/core";
5430
+ import {
5431
+ createTileflowNativeDiagnostic,
5432
+ resolveTileflowRenderer,
5433
+ TileflowNativeCompatibilityError
5434
+ } from "@tileflow/core/native-profile";
5435
+ import {
5436
+ createTileflowBuildArtifacts,
5437
+ disposeTileflowBuildArtifacts,
5438
+ writeTileflowArtifactPlan
5439
+ } from "@tileflow/dev/artifacts";
5440
+ import {
5441
+ createTileflowCommandFailureDocument as createTileflowCommandFailureDocument4,
5442
+ createTileflowCommandSummary as createTileflowCommandSummary2,
5443
+ createTileflowStructuredDiagnostics as createTileflowStructuredDiagnostics2,
5444
+ serializeTileflowCommandDocument as serializeTileflowCommandDocument4
5445
+ } from "@tileflow/dev/validation";
5446
+ async function runRendererArtifactCommand(command2, options) {
5447
+ let artifacts;
5448
+ try {
5449
+ const renderer = resolveTileflowRenderer(options.renderer);
5450
+ if (options.target !== "local" && options.target !== "hosted") {
5451
+ throw Object.assign(new Error("Expected a local or hosted target."), {
5452
+ code: "INVALID_TARGET",
5453
+ phase: "command-validation",
5454
+ path: "target"
5455
+ });
5456
+ }
5457
+ if (renderer === "native" && options.target === "hosted") {
5458
+ throw new TileflowNativeCompatibilityError([
5459
+ createTileflowNativeDiagnostic("NATIVE_RENDERER_UNSUPPORTED", "/target")
5460
+ ]);
5461
+ }
5462
+ if (command2 === "build" && options.target === "hosted") {
5463
+ throw Object.assign(
5464
+ new Error("Build prepares local artifacts; Hosted publication uses deploy."),
5465
+ {
5466
+ code: "HOSTED_INCOMPATIBLE",
5467
+ phase: "command-validation",
5468
+ path: "target",
5469
+ suggestion: "Use validate --target hosted for web preflight, or build --target local."
5470
+ }
5471
+ );
5472
+ }
5473
+ artifacts = await withTileflowConfigSecretsHidden(
5474
+ () => createTileflowBuildArtifacts({
5475
+ apiBaseUrl: options.apiBaseUrl,
5476
+ config: options.config,
5477
+ ...options.icons ? { icons: options.icons } : {},
5478
+ renderer,
5479
+ styleBaseUrl: ".",
5480
+ target: "production"
5481
+ })
5482
+ );
5483
+ const outDir = options.out ?? "dist/tileflow";
5484
+ if (command2 === "build") await writeTileflowArtifactPlan(artifacts, { outDir });
5485
+ const summary = createTileflowCommandSummary2({
5486
+ code: command2 === "validate" ? "VALIDATION_OK" : "BUILD_OK",
5487
+ command: command2,
5488
+ message: command2 === "validate" ? "Tileflow artifact validation passed." : "Built Tileflow artifacts.",
5489
+ ok: true,
5490
+ phase: command2 === "validate" ? "validation" : "build",
5491
+ severity: "info",
5492
+ suggestion: renderer === "native" ? "Use the prepared native manifest with a separately qualified native runtime." : "Serve the prepared artifact directory."
5493
+ });
5494
+ const warnings = Object.values(artifacts.project.maps).flatMap(
5495
+ (map) => auditTileflowMapThemeValues(map).filter(({ severity }) => severity === "warning")
5496
+ );
5497
+ const diagnostics = warnings.length ? createTileflowStructuredDiagnostics2({ diagnostics: warnings }, process.cwd(), {
5498
+ code: "VALIDATION_WARNING",
5499
+ phase: "theme-audit"
5500
+ }) : [];
5501
+ const document = {
5502
+ ...summary,
5503
+ target: options.target,
5504
+ renderer,
5505
+ ...artifacts.nativeBuild ? { profile: artifacts.nativeBuild.profile, validation: "static-artifacts" } : {},
5506
+ maps: Object.keys(artifacts.project.maps).sort(),
5507
+ diagnostics
5508
+ };
5509
+ if (options.json) process.stdout.write(serializeTileflowCommandDocument4(document));
5510
+ else {
5511
+ console.log(summary.message);
5512
+ for (const issue of diagnostics) console.warn(`${issue.path}: ${issue.message}`);
5513
+ if (artifacts.nativeBuild)
5514
+ console.log("Profile: native-v1 (static artifacts; no device qualification).");
5515
+ if (command2 === "build")
5516
+ console.log(`Output: ${resolve5(outDir, ...renderer === "native" ? ["native"] : [])}`);
5517
+ }
5518
+ } catch (error) {
5519
+ const document = createTileflowCommandFailureDocument4(command2, error, process.cwd(), {
5520
+ code: command2 === "validate" ? "VALIDATION_FAILED" : "BUILD_FAILED",
5521
+ phase: "artifact-preparation"
5522
+ });
5523
+ if (options.json) process.stderr.write(serializeTileflowCommandDocument4(document));
5524
+ else {
5525
+ for (const issue of document.diagnostics) {
5526
+ console.error(`${issue.code} ${issue.path || "(root)"}: ${issue.message}`);
5527
+ console.error(`Suggestion: ${issue.suggestion}`);
5528
+ }
5529
+ }
5530
+ process.exitCode = 1;
5531
+ } finally {
5532
+ if (artifacts) await disposeTileflowBuildArtifacts(artifacts);
5533
+ }
5534
+ }
5535
+
3740
5536
  // src/tileset-command.ts
3741
- import pc2 from "picocolors";
3742
- import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
3743
- import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
5537
+ import pc5 from "picocolors";
3744
5538
  import { z as z6 } from "zod";
5539
+ import { serializeCanonicalJson as serializeCanonicalJson5 } from "@tileflow/core";
5540
+ import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
3745
5541
 
3746
5542
  // src/tileset-upload.ts
3747
5543
  import { createHash } from "crypto";
3748
- import { open as open3, stat } from "fs/promises";
5544
+ import { open as open4, stat } from "fs/promises";
3749
5545
  import { z as z5 } from "zod";
3750
- import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
5546
+ import { serializeCanonicalJson as serializeCanonicalJson4 } from "@tileflow/core";
3751
5547
  var teamTilesetPartSize = 16777216;
3752
5548
  var maximumTeamTilesetBytes = 4e9;
3753
5549
  var authorizationBatchSize = 16;
@@ -3809,7 +5605,7 @@ async function inspectNodeTeamTilesetArchive(path) {
3809
5605
  return checkedIdentity(value.dev, value.ino, value.mtimeNs, value.size);
3810
5606
  }
3811
5607
  async function openNodeTeamTilesetArchive(path, expectedIdentity) {
3812
- const handle = await open3(path, "r");
5608
+ const handle = await open4(path, "r");
3813
5609
  const identity = async () => {
3814
5610
  let pathStat;
3815
5611
  let handleStat;
@@ -3887,7 +5683,7 @@ async function prepareTeamTilesetUpload(file) {
3887
5683
  };
3888
5684
  return Object.freeze({
3889
5685
  byteCount: identity.size,
3890
- contentHash: createHash("sha256").update(serializeCanonicalJson2(contentIdentity)).digest("hex"),
5686
+ contentHash: createHash("sha256").update(serializeCanonicalJson4(contentIdentity)).digest("hex"),
3891
5687
  contentHashAlgorithm: "sha256-fixed-parts-v1",
3892
5688
  identity,
3893
5689
  partSize: teamTilesetPartSize,
@@ -3903,7 +5699,7 @@ function createTeamTilesetUploadIdempotencyKey(tilesetId, upload) {
3903
5699
  );
3904
5700
  }
3905
5701
  return createHash("sha256").update(
3906
- serializeCanonicalJson2({
5702
+ serializeCanonicalJson4({
3907
5703
  byteCount: upload.byteCount,
3908
5704
  contentHash: upload.contentHash,
3909
5705
  contentHashAlgorithm: upload.contentHashAlgorithm,
@@ -4307,7 +6103,7 @@ function asRecord3(value) {
4307
6103
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4308
6104
  }
4309
6105
  function defaultSleep(milliseconds) {
4310
- return new Promise((resolve8) => setTimeout(resolve8, milliseconds));
6106
+ return new Promise((resolve9) => setTimeout(resolve9, milliseconds));
4311
6107
  }
4312
6108
  var identifierSchema = z5.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
4313
6109
  var uploadPartStatusSchema = z5.object({
@@ -4394,11 +6190,11 @@ function registerTilesetCommands(program2, dependencies) {
4394
6190
  sample: options.sample
4395
6191
  });
4396
6192
  if (options.json) {
4397
- process.stdout.write(`${serializeCanonicalJson3(inspection)}
6193
+ process.stdout.write(`${serializeCanonicalJson5(inspection)}
4398
6194
  `);
4399
6195
  return;
4400
6196
  }
4401
- console.log(`${pc2.green("\u2713")} PMTiles archive is valid.`);
6197
+ console.log(`${pc5.green("\u2713")} PMTiles archive is valid.`);
4402
6198
  console.log(` Type: ${inspection.contract.tileType}`);
4403
6199
  console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
4404
6200
  console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
@@ -4408,7 +6204,7 @@ function registerTilesetCommands(program2, dependencies) {
4408
6204
  );
4409
6205
  }
4410
6206
  for (const warning of inspection.warnings) {
4411
- console.log(`${pc2.yellow("!")} ${warning.message}`);
6207
+ console.log(`${pc5.yellow("!")} ${warning.message}`);
4412
6208
  }
4413
6209
  } catch (error) {
4414
6210
  const record = asRecord4(error);
@@ -4417,11 +6213,11 @@ function registerTilesetCommands(program2, dependencies) {
4417
6213
  const message = error instanceof Error ? error.message : "Tileset inspection failed.";
4418
6214
  if (options.json) {
4419
6215
  process.stderr.write(
4420
- `${serializeCanonicalJson3({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
6216
+ `${serializeCanonicalJson5({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
4421
6217
  `
4422
6218
  );
4423
6219
  } else {
4424
- console.error(`${pc2.red("Error:")} ${message}`);
6220
+ console.error(`${pc5.red("Error:")} ${message}`);
4425
6221
  }
4426
6222
  process.exitCode = 1;
4427
6223
  }
@@ -4503,9 +6299,9 @@ function registerTilesetCommands(program2, dependencies) {
4503
6299
  version: { id: publication.versionId, number: publication.version }
4504
6300
  }
4505
6301
  };
4506
- if (options.json) return emitJson2(document);
6302
+ if (options.json) return emitJson(document);
4507
6303
  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"}.`
6304
+ `${pc5.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc5.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
4509
6305
  );
4510
6306
  } catch (error) {
4511
6307
  if (error instanceof TeamTilesetUploadError) {
@@ -4524,10 +6320,10 @@ function registerTilesetCommands(program2, dependencies) {
4524
6320
  if (!authority) return;
4525
6321
  const items = await fetchAllTeamTilesets(authority);
4526
6322
  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."));
6323
+ if (options.json) return emitJson(document);
6324
+ if (items.length === 0) return console.log(pc5.gray("No Team tilesets."));
4529
6325
  for (const item of items) {
4530
- console.log(` ${pc2.bold(item.logicalName)} ${pc2.gray(item.status)}`);
6326
+ console.log(` ${pc5.bold(item.logicalName)} ${pc5.gray(item.status)}`);
4531
6327
  }
4532
6328
  } catch {
4533
6329
  emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
@@ -4553,9 +6349,9 @@ function registerTilesetCommands(program2, dependencies) {
4553
6349
  }
4554
6350
  const parsed = tilesetDetailResponseSchema.parse(response.body);
4555
6351
  const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
4556
- if (options.json) return emitJson2(document);
6352
+ if (options.json) return emitJson(document);
4557
6353
  const item = parsed.tileset;
4558
- console.log(`${pc2.bold(item.name)} ${pc2.gray(item.state)}`);
6354
+ console.log(`${pc5.bold(item.name)} ${pc5.gray(item.state)}`);
4559
6355
  console.log(` ID: ${item.id}`);
4560
6356
  console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
4561
6357
  } catch {
@@ -4599,94 +6395,17 @@ function registerTilesetCommands(program2, dependencies) {
4599
6395
  }
4600
6396
  return emitFailure(
4601
6397
  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
- });
4614
- }
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;
6398
+ `http_${response.status}`,
6399
+ `Tileset purge failed (${response.status}).`
6400
+ );
6401
+ }
6402
+ const parsed = tilesetPurgeResponseSchema.parse(response.body);
6403
+ const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
6404
+ if (options.json) return emitJson(document);
6405
+ console.log(`${pc5.green("\u2713")} Purge ${parsed.reclamation}.`);
6406
+ } catch {
6407
+ emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
4635
6408
  }
4636
- return { apiOrigin, credential: options.apiKey, team: null };
4637
- }
4638
- let config;
4639
- try {
4640
- config = await dependencies.loadAuthConfig();
4641
- } catch (error) {
4642
- emitFailure(options.json, "auth_state_unavailable", safeMessage2(error));
4643
- return null;
4644
- }
4645
- const selected = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
4646
- if (selected.kind !== "selected") {
4647
- emitFailure(
4648
- options.json,
4649
- `account_session_${selected.kind}`,
4650
- selected.kind === "expired" ? "Account session expired. Run tileflow login." : "Run tileflow login."
4651
- );
4652
- return null;
4653
- }
4654
- const discovered = await listAccountTeams(selected.session);
4655
- if (!discovered.ok) {
4656
- emitFailure(options.json, "team_discovery_failed", discovered.error);
4657
- return null;
4658
- }
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;
4663
- }
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;
4674
- }
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
- };
4685
- }
4686
- function authorizedTeamRequest(authority, path, method) {
4687
- return requestHostedJson(authority.apiOrigin, path, {
4688
- headers: { Authorization: `Bearer ${authority.credential}` },
4689
- method
4690
6409
  });
4691
6410
  }
4692
6411
  async function fetchAllTeamTilesets(authority) {
@@ -4725,21 +6444,6 @@ async function fetchAllTeamTilesets(authority) {
4725
6444
  }
4726
6445
  throw new Error("Tileset list exceeded its safe page limit.");
4727
6446
  }
4728
- function emitJson2(value) {
4729
- process.stdout.write(`${serializeCanonicalJson3(value)}
4730
- `);
4731
- }
4732
- function emitFailure(json, code, message, context = {}) {
4733
- if (json) {
4734
- process.stderr.write(
4735
- `${serializeCanonicalJson3({ error: { code, ...context, message }, ok: false, schemaVersion: 1 })}
4736
- `
4737
- );
4738
- } else {
4739
- console.error(`${pc2.red("Error:")} ${message}`);
4740
- }
4741
- process.exitCode = 1;
4742
- }
4743
6447
  function isTilesetId(value) {
4744
6448
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value) && !["terrain", "world"].includes(value);
4745
6449
  }
@@ -4757,15 +6461,12 @@ function inspectionOptionError(message) {
4757
6461
  path: "includeValues"
4758
6462
  });
4759
6463
  }
4760
- function safeMessage2(error) {
4761
- return error instanceof Error ? error.message : "Tileset command failed.";
4762
- }
4763
6464
  function asRecord4(value) {
4764
6465
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4765
6466
  }
4766
6467
  var safeIdentifierSchema2 = z6.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
4767
6468
  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();
6469
+ var cursorSchema2 = z6.string().min(1).max(2048).nullable();
4769
6470
  var tilesetInventoryItemSchema = z6.object({
4770
6471
  currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
4771
6472
  hasCurrent: z6.boolean(),
@@ -4779,7 +6480,7 @@ var tilesetInventoryItemSchema = z6.object({
4779
6480
  usedByMapCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
4780
6481
  }).strict();
4781
6482
  var tilesetListResponseSchema = z6.object({
4782
- nextCursor: cursorSchema.optional(),
6483
+ nextCursor: cursorSchema2.optional(),
4783
6484
  schemaVersion: z6.literal(1),
4784
6485
  tilesets: z6.array(tilesetInventoryItemSchema).max(100)
4785
6486
  }).strict();
@@ -4797,7 +6498,7 @@ var tilesetDetailResponseSchema = z6.object({
4797
6498
  attribution: z6.string().max(4096).nullable(),
4798
6499
  currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
4799
6500
  dependencies: z6.array(tilesetDependencySchema).max(25),
4800
- dependencyNextCursor: cursorSchema,
6501
+ dependencyNextCursor: cursorSchema2,
4801
6502
  id: physicalTilesetIdSchema,
4802
6503
  name: z6.string().min(1).max(200),
4803
6504
  slug: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
@@ -4813,7 +6514,7 @@ var tilesetInUseResponseSchema = z6.object({
4813
6514
  code: z6.literal("TF_TILESET_IN_USE"),
4814
6515
  dependencies: z6.array(purgeDependencySchema).max(32),
4815
6516
  error: z6.literal("Tileset is used by retained Map deployments"),
4816
- nextCursor: cursorSchema
6517
+ nextCursor: cursorSchema2
4817
6518
  }).strict();
4818
6519
  var tilesetPurgeResponseSchema = z6.object({
4819
6520
  deliveryFence: z6.enum(["complete", "pending"]),
@@ -4824,8 +6525,8 @@ var tilesetPurgeResponseSchema = z6.object({
4824
6525
  }).strict();
4825
6526
 
4826
6527
  // src/visual-command.ts
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";
6528
+ import { lstat as lstat4, readFile as readFile6 } from "fs/promises";
6529
+ import { dirname as dirname11, isAbsolute as isAbsolute4, parse as parse2, relative as relative4, resolve as resolve7, sep as sep4 } from "path";
4829
6530
  import {
4830
6531
  analyzeTileflowCaptureReference,
4831
6532
  compareTileflowCaptureToBaseline,
@@ -4839,12 +6540,12 @@ import {
4839
6540
  tileflowVisualArtifactLimits,
4840
6541
  validateTileflowVisualReferencePng
4841
6542
  } from "@tileflow/capture";
4842
- import { compareCodeUnits as compareCodeUnits8 } from "@tileflow/core";
6543
+ import { compareCodeUnits as compareCodeUnits9 } from "@tileflow/core";
4843
6544
 
4844
6545
  // src/visual-compare-command.ts
4845
6546
  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";
6547
+ import { lstat as lstat3, readFile as readFile5, realpath } from "fs/promises";
6548
+ import { basename as basename3, dirname as dirname10, extname, isAbsolute as isAbsolute3, parse, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
4848
6549
  import {
4849
6550
  compareTileflowCapturesForReview,
4850
6551
  createTileflowCaptureSession as createTileflowCaptureSession2,
@@ -4853,16 +6554,16 @@ import {
4853
6554
  tileflowSyntheticAssetOrigin as tileflowSyntheticAssetOrigin2
4854
6555
  } from "@tileflow/capture";
4855
6556
  import {
4856
- compareCodeUnits as compareCodeUnits7,
6557
+ compareCodeUnits as compareCodeUnits8,
4857
6558
  normalizeTileflowCaptureScene as normalizeTileflowCaptureScene2,
4858
- parseResolvedTileflowMap as parseResolvedTileflowMap2,
6559
+ parseResolvedTileflowMap as parseResolvedTileflowMap4,
4859
6560
  resolveThemeSelection,
4860
6561
  tileflowCaptureSceneLimits
4861
6562
  } from "@tileflow/core";
4862
6563
  import {
4863
6564
  createTileflowArtifactDiagnostics as createTileflowArtifactDiagnostics2,
4864
6565
  createTileflowArtifactSession as createTileflowArtifactSession2,
4865
- createTileflowBuildArtifacts
6566
+ createTileflowBuildArtifacts as createTileflowBuildArtifacts2
4866
6567
  } from "@tileflow/dev/artifacts";
4867
6568
 
4868
6569
  // src/visual-compare-report.ts
@@ -5157,15 +6858,15 @@ async function parseVisualComparePlan(options) {
5157
6858
  }
5158
6859
  async function createArtifactPair(plan) {
5159
6860
  const cwd = process.cwd();
5160
- const leftPath = portablePathIdentity(resolve5(cwd, plan.config));
5161
- const rightPath = portablePathIdentity(resolve5(cwd, plan.againstConfig));
5162
- const left = await createTileflowBuildArtifacts({
6861
+ const leftPath = portablePathIdentity(resolve6(cwd, plan.config));
6862
+ const rightPath = portablePathIdentity(resolve6(cwd, plan.againstConfig));
6863
+ const left = await createTileflowBuildArtifacts2({
5163
6864
  assetBaseUrl: tileflowSyntheticAssetOrigin2,
5164
6865
  config: plan.config,
5165
6866
  cwd
5166
6867
  });
5167
6868
  if (leftPath === rightPath) return [left, left];
5168
- const right = await createTileflowBuildArtifacts({
6869
+ const right = await createTileflowBuildArtifacts2({
5169
6870
  assetBaseUrl: tileflowSyntheticAssetOrigin2,
5170
6871
  config: plan.againstConfig,
5171
6872
  cwd
@@ -5285,7 +6986,7 @@ async function prepareVisualCompareOutput(plan, leftArtifacts, rightArtifacts, c
5285
6986
  documentPath: relativePath(cwd, plan.output.documentPath),
5286
6987
  reportPath: relativePath(cwd, plan.output.reportPath)
5287
6988
  },
5288
- warnings: [...warnings].sort(compareCodeUnits7)
6989
+ warnings: [...warnings].sort(compareCodeUnits8)
5289
6990
  };
5290
6991
  const report = renderTileflowVisualCompareReport({
5291
6992
  generatedBy: "Tileflow CLI",
@@ -5310,7 +7011,7 @@ async function prepareVisualCompareOutput(plan, leftArtifacts, rightArtifacts, c
5310
7011
  files,
5311
7012
  incompatible,
5312
7013
  nextAssets: inventory.assets.map((asset) => ({
5313
- path: resolve5(plan.output.assetDirectory, asset.path),
7014
+ path: resolve6(plan.output.assetDirectory, asset.path),
5314
7015
  sha256: asset.sha256
5315
7016
  })),
5316
7017
  removePaths: [...new Set(removePaths)].filter((path) => !filePaths.has(path))
@@ -5329,7 +7030,7 @@ async function writePreparedVisualCompareOutput(output, prepared) {
5329
7030
  async function runVisualCompareWatch(plan, openReport) {
5330
7031
  const cwd = process.cwd();
5331
7032
  const controller = installSignalAbortController();
5332
- const sameConfig = portablePathIdentity(resolve5(cwd, plan.config)) === portablePathIdentity(resolve5(cwd, plan.againstConfig));
7033
+ const sameConfig = portablePathIdentity(resolve6(cwd, plan.config)) === portablePathIdentity(resolve6(cwd, plan.againstConfig));
5333
7034
  let leftSession;
5334
7035
  let rightSession;
5335
7036
  let captureSession;
@@ -5573,7 +7274,7 @@ async function runVisualCompareWatch(plan, openReport) {
5573
7274
  }
5574
7275
  }
5575
7276
  function resolveVisualCompareSide(artifacts, config, requestedMap, requestedTheme, role) {
5576
- const availableMaps = Object.keys(artifacts.project.maps).sort(compareCodeUnits7);
7277
+ const availableMaps = Object.keys(artifacts.project.maps).sort(compareCodeUnits8);
5577
7278
  const map = requestedMap ?? (availableMaps.length === 1 ? availableMaps[0] : void 0);
5578
7279
  if (!map) {
5579
7280
  throw new Error(
@@ -5586,7 +7287,7 @@ function resolveVisualCompareSide(artifacts, config, requestedMap, requestedThem
5586
7287
  `Unknown ${role} map "${map}". Available maps: ${availableMaps.join(", ") || "(none)"}.`
5587
7288
  );
5588
7289
  }
5589
- const resolvedMap = parseResolvedTileflowMap2(candidate);
7290
+ const resolvedMap = parseResolvedTileflowMap4(candidate);
5590
7291
  const theme = resolveThemeSelection(resolvedMap, requestedTheme).name;
5591
7292
  if (!artifacts.styles[map]?.[theme]) {
5592
7293
  throw new Error(`The ${role} map theme "${map}/${theme}" was not compiled.`);
@@ -5638,11 +7339,11 @@ function requireCapture(captures, scene, role) {
5638
7339
  return capture;
5639
7340
  }
5640
7341
  async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zooms) {
5641
- const reportPath = resolve5(cwd, value);
7342
+ const reportPath = resolve6(cwd, value);
5642
7343
  if (extname(reportPath).toLowerCase() !== ".html") {
5643
7344
  throw new Error("--report must name an .html file.");
5644
7345
  }
5645
- const reportDirectory = dirname8(reportPath);
7346
+ const reportDirectory = dirname10(reportPath);
5646
7347
  if (reportDirectory === cwd || reportDirectory === parse(reportPath).root) {
5647
7348
  throw new Error("Visual compare output must use a dedicated subdirectory.");
5648
7349
  }
@@ -5652,7 +7353,7 @@ async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zoo
5652
7353
  const stem = reportPath.slice(0, -".html".length);
5653
7354
  const documentPath = `${stem}.json`;
5654
7355
  const assetDirectory = `${stem}.assets`;
5655
- const inventoryPath = resolve5(assetDirectory, tileflowVisualCompareInventoryFileName);
7356
+ const inventoryPath = resolve6(assetDirectory, tileflowVisualCompareInventoryFileName);
5656
7357
  const boundary = isInside(cwd, reportDirectory) ? cwd : reportDirectory;
5657
7358
  await assertNoSymlinkComponents(reportDirectory, boundary);
5658
7359
  await assertNoSymlinkComponents(assetDirectory, boundary);
@@ -5692,16 +7393,16 @@ async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zoo
5692
7393
  function visualCompareRowPaths(output, index, zoom, includeDiff) {
5693
7394
  const prefix = `${String(index + 1).padStart(2, "0")}-z${zoomSlug(zoom)}`;
5694
7395
  return {
5695
- leftPath: resolve5(output.assetDirectory, `${prefix}.left.png`),
5696
- rightPath: resolve5(output.assetDirectory, `${prefix}.right.png`),
5697
- leftReceiptPath: resolve5(output.assetDirectory, `${prefix}.left.receipt.json`),
5698
- rightReceiptPath: resolve5(output.assetDirectory, `${prefix}.right.receipt.json`),
5699
- diffPath: includeDiff ? resolve5(output.assetDirectory, `${prefix}.diff.png`) : void 0
7396
+ leftPath: resolve6(output.assetDirectory, `${prefix}.left.png`),
7397
+ rightPath: resolve6(output.assetDirectory, `${prefix}.right.png`),
7398
+ leftReceiptPath: resolve6(output.assetDirectory, `${prefix}.left.receipt.json`),
7399
+ rightReceiptPath: resolve6(output.assetDirectory, `${prefix}.right.receipt.json`),
7400
+ diffPath: includeDiff ? resolve6(output.assetDirectory, `${prefix}.diff.png`) : void 0
5700
7401
  };
5701
7402
  }
5702
7403
  function sideJson(side, cwd) {
5703
7404
  return {
5704
- config: relativePath(cwd, resolve5(cwd, side.config)),
7405
+ config: relativePath(cwd, resolve6(cwd, side.config)),
5705
7406
  label: side.label,
5706
7407
  map: side.map,
5707
7408
  theme: side.theme
@@ -5709,13 +7410,13 @@ function sideJson(side, cwd) {
5709
7410
  }
5710
7411
  function watchSideLabel(cwd, config, map, theme) {
5711
7412
  const selection = map ? `${map} / ${theme ?? "default theme"}` : `singular map / ${theme ?? "default theme"}`;
5712
- return `${relativePath(cwd, resolve5(cwd, config))} (${selection})`;
7413
+ return `${relativePath(cwd, resolve6(cwd, config))} (${selection})`;
5713
7414
  }
5714
7415
  async function assertVisualCompareOutputDoesNotOverlapInputs(plan, leftArtifacts, rightArtifacts) {
5715
7416
  const cwd = process.cwd();
5716
7417
  const inputs = mergeVisualCompareInputPaths([
5717
- { kind: "file", path: resolve5(cwd, plan.config) },
5718
- { kind: "file", path: resolve5(cwd, plan.againstConfig) },
7418
+ { kind: "file", path: resolve6(cwd, plan.config) },
7419
+ { kind: "file", path: resolve6(cwd, plan.againstConfig) },
5719
7420
  ...visualCompareArtifactInputPaths(leftArtifacts),
5720
7421
  ...visualCompareArtifactInputPaths(rightArtifacts)
5721
7422
  ]);
@@ -5782,7 +7483,7 @@ function createVisualCompareInventory(assetDirectory, assets) {
5782
7483
  throw new Error("Visual compare produced an invalid managed asset path.");
5783
7484
  }
5784
7485
  return { path: relativeAssetPath, sha256: sha256Source(source) };
5785
- }).sort((left, right) => compareCodeUnits7(left.path, right.path))
7486
+ }).sort((left, right) => compareCodeUnits8(left.path, right.path))
5786
7487
  };
5787
7488
  }
5788
7489
  function serializeVisualCompareInventory(inventory) {
@@ -5790,10 +7491,10 @@ function serializeVisualCompareInventory(inventory) {
5790
7491
  `;
5791
7492
  }
5792
7493
  async function readVisualCompareInventory(assetDirectory, inventoryPath, boundary) {
5793
- await assertNoSymlinkComponents(dirname8(inventoryPath), boundary);
7494
+ await assertNoSymlinkComponents(dirname10(inventoryPath), boundary);
5794
7495
  let info;
5795
7496
  try {
5796
- info = await lstat2(inventoryPath);
7497
+ info = await lstat3(inventoryPath);
5797
7498
  } catch (error) {
5798
7499
  if (isMissingFile4(error)) return [];
5799
7500
  throw error;
@@ -5813,11 +7514,11 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
5813
7514
  const inventory = validateVisualCompareInventory(parsed);
5814
7515
  const assets = [];
5815
7516
  for (const asset of inventory.assets) {
5816
- const path = resolve5(assetDirectory, asset.path);
5817
- await assertNoSymlinkComponents(dirname8(path), boundary);
7517
+ const path = resolve6(assetDirectory, asset.path);
7518
+ await assertNoSymlinkComponents(dirname10(path), boundary);
5818
7519
  let assetInfo;
5819
7520
  try {
5820
- assetInfo = await lstat2(path);
7521
+ assetInfo = await lstat3(path);
5821
7522
  } catch (error) {
5822
7523
  if (isMissingFile4(error)) continue;
5823
7524
  throw error;
@@ -5857,7 +7558,7 @@ function validateVisualCompareInventory(input) {
5857
7558
  return { path: asset.path, sha256: asset.sha256 };
5858
7559
  });
5859
7560
  const paths = assets.map((asset) => asset.path);
5860
- const sortedPaths = [...paths].sort(compareCodeUnits7);
7561
+ const sortedPaths = [...paths].sort(compareCodeUnits8);
5861
7562
  if (new Set(paths).size !== paths.length || paths.some((path, index) => path !== sortedPaths[index])) {
5862
7563
  throw new Error("Visual compare inventory asset paths must be unique and sorted.");
5863
7564
  }
@@ -5877,25 +7578,25 @@ function isVisualCompareOwnedAssetName(value) {
5877
7578
  return Number.isInteger(row) && row >= 1 && row <= tileflowVisualCompareLimits.maximumRows;
5878
7579
  }
5879
7580
  function hasExactKeys(record, keys) {
5880
- const actual = Object.keys(record).sort(compareCodeUnits7);
5881
- const expected = [...keys].sort(compareCodeUnits7);
7581
+ const actual = Object.keys(record).sort(compareCodeUnits8);
7582
+ const expected = [...keys].sort(compareCodeUnits8);
5882
7583
  return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
5883
7584
  }
5884
7585
  function sha256Source(source) {
5885
7586
  return createHash3("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
5886
7587
  }
5887
7588
  async function canonicalPathIdentity(path) {
5888
- const absolute = resolve5(path);
7589
+ const absolute = resolve6(path);
5889
7590
  let current = absolute;
5890
7591
  const missingSegments = [];
5891
7592
  while (true) {
5892
7593
  try {
5893
7594
  const existing = await realpath(current);
5894
- const canonical = resolve5(existing, ...missingSegments.reverse()).normalize("NFC");
7595
+ const canonical = resolve6(existing, ...missingSegments.reverse()).normalize("NFC");
5895
7596
  return process.platform === "win32" ? canonical.toLowerCase() : canonical;
5896
7597
  } catch (error) {
5897
7598
  if (!isMissingPathComponent(error)) throw error;
5898
- const parent = dirname8(current);
7599
+ const parent = dirname10(current);
5899
7600
  if (parent === current) {
5900
7601
  const canonical = absolute.normalize("NFC");
5901
7602
  return process.platform === "win32" ? canonical.toLowerCase() : canonical;
@@ -5967,19 +7668,19 @@ function formatCoordinate(value) {
5967
7668
  return String(Math.round(value * 1e6) / 1e6);
5968
7669
  }
5969
7670
  function portablePathIdentity(path) {
5970
- const normalized = resolve5(path).normalize("NFC");
7671
+ const normalized = resolve6(path).normalize("NFC");
5971
7672
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
5972
7673
  }
5973
7674
  function isInside(parent, child) {
5974
7675
  const path = relative3(parent, child);
5975
- return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !isAbsolute2(path);
7676
+ return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !isAbsolute3(path);
5976
7677
  }
5977
7678
  function pathsOverlap(left, right) {
5978
7679
  return isInside(left, right) || isInside(right, left);
5979
7680
  }
5980
7681
  async function assertDirectoryIfPresent(path) {
5981
7682
  try {
5982
- const info = await lstat2(path);
7683
+ const info = await lstat3(path);
5983
7684
  if (info.isSymbolicLink() || !info.isDirectory()) {
5984
7685
  throw new Error("Visual compare asset path must be a non-symlink directory.");
5985
7686
  }
@@ -5989,7 +7690,7 @@ async function assertDirectoryIfPresent(path) {
5989
7690
  }
5990
7691
  async function pathExists(path) {
5991
7692
  try {
5992
- await lstat2(path);
7693
+ await lstat3(path);
5993
7694
  return true;
5994
7695
  } catch (error) {
5995
7696
  if (isMissingFile4(error)) return false;
@@ -6052,7 +7753,7 @@ function createTileflowVisualDiffJson(comparisons, cwd) {
6052
7753
  return {
6053
7754
  schemaVersion: 1,
6054
7755
  command: "visual.diff",
6055
- comparisons: [...comparisons].sort((left, right) => compareCodeUnits8(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
7756
+ comparisons: [...comparisons].sort((left, right) => compareCodeUnits9(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
6056
7757
  ...createTileflowVisualComparisonDocument(comparison),
6057
7758
  baselinePath: relativePath(cwd, baselinePath),
6058
7759
  actualPath: relativePath(cwd, actualPath),
@@ -6080,14 +7781,14 @@ async function runVisualAnalyze(scene, options) {
6080
7781
  validateVisualSelection([scene]);
6081
7782
  const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
6082
7783
  const cwd = process.cwd();
6083
- const referencePath = resolve6(cwd, options.reference);
7784
+ const referencePath = resolve7(cwd, options.reference);
6084
7785
  const referencePng = await readVisualReference(referencePath);
6085
7786
  const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
6086
7787
  if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
6087
7788
  const outputDirectory = await resolveVisualDirectory(cwd, options.outputDir, "output");
6088
- const actualPath = resolve6(outputDirectory.path, `${scene}.actual.png`);
6089
- const managedDiffPath = resolve6(outputDirectory.path, `${scene}.diff.png`);
6090
- const reportPath = resolve6(outputDirectory.path, `${scene}.analysis.json`);
7789
+ const actualPath = resolve7(outputDirectory.path, `${scene}.actual.png`);
7790
+ const managedDiffPath = resolve7(outputDirectory.path, `${scene}.diff.png`);
7791
+ const reportPath = resolve7(outputDirectory.path, `${scene}.analysis.json`);
6091
7792
  assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
6092
7793
  const controller = installSignalAbortController();
6093
7794
  const session = createTileflowCaptureSession3({
@@ -6176,15 +7877,15 @@ async function runVisualDiff(scenes, options) {
6176
7877
  capture,
6177
7878
  files.kind === "complete" ? files.baseline : void 0
6178
7879
  );
6179
- const actualPath = resolve6(outputDirectory.path, `${capture.scene}.actual.png`);
6180
- const diffPath = comparison.diffPng ? resolve6(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
7880
+ const actualPath = resolve7(outputDirectory.path, `${capture.scene}.actual.png`);
7881
+ const diffPath = comparison.diffPng ? resolve7(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
6181
7882
  prepared.push({
6182
7883
  capture,
6183
7884
  comparison,
6184
7885
  baselinePath: files.pngPath,
6185
7886
  actualPath,
6186
7887
  diffPath,
6187
- reportPath: resolve6(outputDirectory.path, `${capture.scene}.visual.json`)
7888
+ reportPath: resolve7(outputDirectory.path, `${capture.scene}.visual.json`)
6188
7889
  });
6189
7890
  }
6190
7891
  const document = createTileflowVisualDiffJson(prepared, cwd);
@@ -6204,7 +7905,7 @@ async function runVisualDiff(scenes, options) {
6204
7905
  }
6205
7906
  ]),
6206
7907
  removePaths: prepared.flatMap(
6207
- (item) => item.diffPath ? [] : [resolve6(outputDirectory.path, `${item.capture.scene}.diff.png`)]
7908
+ (item) => item.diffPath ? [] : [resolve7(outputDirectory.path, `${item.capture.scene}.diff.png`)]
6208
7909
  )
6209
7910
  });
6210
7911
  if (options.json) {
@@ -6267,7 +7968,7 @@ async function runVisualUpdate(scenes, options) {
6267
7968
  renderer: capture.renderer,
6268
7969
  networkDependent: capture.networkDependent
6269
7970
  },
6270
- warnings: [...capture.warnings].sort(compareCodeUnits8)
7971
+ warnings: [...capture.warnings].sort(compareCodeUnits9)
6271
7972
  });
6272
7973
  outputFiles.push(
6273
7974
  { path: files.pngPath, source: capture.png },
@@ -6284,7 +7985,7 @@ async function runVisualUpdate(scenes, options) {
6284
7985
  const document = {
6285
7986
  schemaVersion: 1,
6286
7987
  command: "visual.update",
6287
- updates: updates.sort((left, right) => compareCodeUnits8(left.scene, right.scene))
7988
+ updates: updates.sort((left, right) => compareCodeUnits9(left.scene, right.scene))
6288
7989
  };
6289
7990
  if (options.json) {
6290
7991
  process.stdout.write(serializeTileflowVisualCommandJson(document));
@@ -6325,7 +8026,7 @@ async function resolvePreviousBaseline(capture, files) {
6325
8026
  }
6326
8027
  }
6327
8028
  async function readBaselineFiles(directory, scene) {
6328
- const pngPath = resolve6(directory.path, `${scene}.png`);
8029
+ const pngPath = resolve7(directory.path, `${scene}.png`);
6329
8030
  const receiptPath = captureReceiptPath(pngPath);
6330
8031
  const [png, receipt] = await Promise.all([
6331
8032
  readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits.maximumPngBytes),
@@ -6340,10 +8041,10 @@ async function readBaselineFiles(directory, scene) {
6340
8041
  return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
6341
8042
  }
6342
8043
  async function readOptionalArtifact(path, boundary, maximumBytes) {
6343
- await assertNoSymlinkComponents(dirname9(path), boundary);
8044
+ await assertNoSymlinkComponents(dirname11(path), boundary);
6344
8045
  let info;
6345
8046
  try {
6346
- info = await lstat3(path);
8047
+ info = await lstat4(path);
6347
8048
  } catch (error) {
6348
8049
  if (isMissingFile5(error)) return void 0;
6349
8050
  throw error;
@@ -6363,7 +8064,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
6363
8064
  async function readVisualReference(path) {
6364
8065
  let info;
6365
8066
  try {
6366
- info = await lstat3(path);
8067
+ info = await lstat4(path);
6367
8068
  } catch (error) {
6368
8069
  throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
6369
8070
  cause: error
@@ -6385,12 +8086,12 @@ async function readVisualReference(path) {
6385
8086
  return new Uint8Array(await readFile6(path));
6386
8087
  }
6387
8088
  async function resolveVisualDirectory(cwd, value, kind) {
6388
- const path = resolve6(cwd, value);
8089
+ const path = resolve7(cwd, value);
6389
8090
  if (path === cwd || path === parse2(path).root) {
6390
8091
  throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
6391
8092
  }
6392
8093
  if (kind === "baseline") {
6393
- for (const managed of [resolve6(cwd, ".tileflow/captures"), resolve6(cwd, ".tileflow/diffs")]) {
8094
+ for (const managed of [resolve7(cwd, ".tileflow/captures"), resolve7(cwd, ".tileflow/diffs")]) {
6394
8095
  if (pathsOverlap2(path, managed)) {
6395
8096
  throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
6396
8097
  }
@@ -6399,7 +8100,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
6399
8100
  const boundary = isInside2(cwd, path) ? cwd : path;
6400
8101
  await assertNoSymlinkComponents(path, boundary);
6401
8102
  try {
6402
- const info = await lstat3(path);
8103
+ const info = await lstat4(path);
6403
8104
  if (info.isSymbolicLink() || !info.isDirectory()) {
6404
8105
  throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
6405
8106
  }
@@ -6426,11 +8127,11 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
6426
8127
  }
6427
8128
  }
6428
8129
  function portablePathIdentity2(path) {
6429
- return resolve6(path).normalize("NFC").toLowerCase();
8130
+ return resolve7(path).normalize("NFC").toLowerCase();
6430
8131
  }
6431
8132
  function isInside2(parent, child) {
6432
8133
  const path = relative4(parent, child);
6433
- return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute3(path);
8134
+ return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
6434
8135
  }
6435
8136
  function isMissingFile5(error) {
6436
8137
  return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
@@ -6445,12 +8146,14 @@ var defaultApiUrl = defaultTileflowApiUrl;
6445
8146
  var defaultAppUrl = "https://tileflow.dev";
6446
8147
  var defaultConfigPath = defaultTileflowConfigPath;
6447
8148
  var defaultManifestPath = defaultTileflowManifestPath;
6448
- program.name("tileflow").description("Beautiful maps from config.").version(packageJson.version).showHelpAfterError(pc3.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
8149
+ program.name("tileflow").description("Beautiful maps from config.").showHelpAfterError(pc6.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
8150
+ program.addHelpText("after", "\n -V, --version output the version number\n");
6449
8151
  registerLanguageCommand(program);
6450
8152
  registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
8153
+ registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
6451
8154
  program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
6452
- const configPath = resolve7(process.cwd(), defaultConfigPath);
6453
- const ignorePath = resolve7(process.cwd(), ".gitignore");
8155
+ const configPath = resolve8(process.cwd(), defaultConfigPath);
8156
+ const ignorePath = resolve8(process.cwd(), ".gitignore");
6454
8157
  if (existsSync(configPath) && !options.force) {
6455
8158
  logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
6456
8159
  printNextSteps([`Run ${command("tileflow init --force")} to replace it.`]);
@@ -6466,9 +8169,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
6466
8169
  }
6467
8170
  logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
6468
8171
  printKeyValue("Path", pathLabel(configPath));
6469
- printKeyValue("Map", pc3.bold("madrid"));
8172
+ printKeyValue("Map", pc6.bold("madrid"));
6470
8173
  printNextSteps([
6471
- `Install ${pc3.cyan("@tileflow/core")} and ${pc3.cyan("@tileflow/maps")} in this project if needed.`,
8174
+ `Install ${pc6.cyan("@tileflow/core")} and ${pc6.cyan("@tileflow/maps")} in this project if needed.`,
6472
8175
  `Preview locally with ${command("tileflow preview")}.`,
6473
8176
  `Validate the config with ${command("tileflow validate")}.`
6474
8177
  ]);
@@ -6557,179 +8260,244 @@ program.command("whoami").description("Show the authenticated Tileflow account")
6557
8260
  logSuccess("Tileflow CLI is authenticated.");
6558
8261
  printAccountDetails(selected.session);
6559
8262
  });
6560
- program.command("validate").description("Validate a Tileflow config").option("-c, --config <path>", "config path", defaultConfigPath).option("--target <target>", "validation target: local or hosted", "local").option(
8263
+ program.command("validate").description("Validate a Tileflow config").option("-c, --config <path>", "config path", defaultConfigPath).option("--target <target>", "validation target: local or hosted", "local").option("--renderer <renderer>", "artifact renderer: web or native", "web").option(
6561
8264
  "--api-base-url <url>",
6562
8265
  "Tileflow API base URL used to resolve official map assets",
6563
8266
  process.env.TILEFLOW_API_URL ?? defaultApiUrl
6564
- ).option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
6565
- if (options.target !== "local" && options.target !== "hosted") {
6566
- if (options.json) {
6567
- const failure = createTileflowCommandFailureDocument4(
6568
- "validate",
6569
- {
6570
- code: "INVALID_TARGET",
6571
- issues: [
6572
- {
6573
- code: "INVALID_TARGET",
6574
- message: `Invalid validation target: ${options.target}`,
6575
- path: "target",
6576
- phase: "command-validation"
6577
- }
6578
- ],
6579
- phase: "command-validation"
6580
- },
6581
- process.cwd(),
6582
- { code: "INVALID_TARGET", phase: "command-validation" }
6583
- );
6584
- process.stderr.write(serializeTileflowCommandDocument4(failure));
6585
- process.exitCode = 1;
8267
+ ).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--json", "print deterministic schema-version-1 JSON").action(
8268
+ async (options) => {
8269
+ if (options.renderer !== "web") {
8270
+ await runRendererArtifactCommand("validate", {
8271
+ ...options,
8272
+ icons: resolveIconOptions(options)
8273
+ });
6586
8274
  return;
6587
8275
  }
6588
- logError(`Invalid validation target: ${options.target}`);
6589
- printNextSteps([
6590
- `Use ${command("tileflow validate --target local")} or ${command("tileflow validate --target hosted")}.`
6591
- ]);
6592
- process.exitCode = 1;
6593
- return;
6594
- }
6595
- let failureDefaults = { code: "CONFIG_LOAD_FAILED", phase: "config-load" };
6596
- try {
6597
- if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
6598
- const loaded = await withTileflowConfigSecretsHidden(
6599
- () => loadTileflowConfigWithInputs3(options.config)
6600
- );
6601
- const project = loaded.project;
6602
- const baseDirectory = dirname10(loaded.configFile);
6603
- failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
6604
- assertValidTileflowConfig2(project);
6605
- const mapNames = getTileflowMapNames3(project).sort();
6606
- failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
6607
- const compiledIcons = await compileTileflowIconPackages2(project, {
6608
- baseDirectory,
6609
- cwd: process.cwd(),
6610
- target: options.target
6611
- });
6612
- const mapAssets = createCompiledMapAssets(
6613
- compiledIcons,
6614
- (binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`
6615
- );
6616
- failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
6617
- const compiledStyles = createTileflowStyles(project, {
6618
- apiBaseUrl: options.apiBaseUrl,
6619
- mapAssets
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
- });
6627
- failureDefaults = { code: "FONT_COMPILATION_FAILED", phase: "font-compilation" };
6628
- const { styles } = await prepareTileflowStyleFonts(project, localTilesets.styles, {
6629
- assetBaseUrl: "/tileflow",
6630
- baseDirectory,
6631
- cwd: process.cwd(),
6632
- target: options.target
6633
- });
6634
- failureDefaults = { code: "HOSTED_INCOMPATIBLE", phase: "hosted-validation" };
6635
- const hostedIssues = options.target === "hosted" ? inspectTileflowHostedCompatibility(project, styles) : [];
6636
- if (hostedIssues.length > 0) {
6637
- if (!options.json) {
6638
- printHostedCompatibilityIssues(hostedIssues);
8276
+ if (options.target !== "local" && options.target !== "hosted") {
8277
+ if (options.json) {
8278
+ const failure2 = createTileflowCommandFailureDocument5(
8279
+ "validate",
8280
+ {
8281
+ code: "INVALID_TARGET",
8282
+ issues: [
8283
+ {
8284
+ code: "INVALID_TARGET",
8285
+ message: `Invalid validation target: ${options.target}`,
8286
+ path: "target",
8287
+ phase: "command-validation"
8288
+ }
8289
+ ],
8290
+ phase: "command-validation"
8291
+ },
8292
+ process.cwd(),
8293
+ { code: "INVALID_TARGET", phase: "command-validation" }
8294
+ );
8295
+ process.stderr.write(serializeTileflowCommandDocument5(failure2));
8296
+ process.exitCode = 1;
6639
8297
  return;
6640
8298
  }
6641
- throw Object.assign(new Error("Tileflow config is not Hosted-compatible."), {
6642
- code: "HOSTED_INCOMPATIBLE",
6643
- issues: hostedIssues.map((issue) => ({
8299
+ logError(`Invalid validation target: ${options.target}`);
8300
+ printNextSteps([
8301
+ `Use ${command("tileflow validate --target local")} or ${command("tileflow validate --target hosted")}.`
8302
+ ]);
8303
+ process.exitCode = 1;
8304
+ return;
8305
+ }
8306
+ let failureDefaults = { code: "CONFIG_LOAD_FAILED", phase: "config-load" };
8307
+ try {
8308
+ if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
8309
+ const loaded = await withTileflowConfigSecretsHidden(
8310
+ () => loadTileflowConfigWithInputs4(options.config)
8311
+ );
8312
+ const project = loaded.project;
8313
+ const baseDirectory = dirname12(loaded.configFile);
8314
+ failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
8315
+ assertValidTileflowConfig3(project);
8316
+ const mapNames = getTileflowMapNames5(project).sort();
8317
+ failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
8318
+ const compiledIcons = await compileTileflowIconPackages3(project, {
8319
+ baseDirectory,
8320
+ cwd: process.cwd(),
8321
+ icons: resolveIconOptions(options),
8322
+ target: options.target
8323
+ });
8324
+ const mapAssets = createCompiledMapAssets(
8325
+ compiledIcons,
8326
+ (binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`
8327
+ );
8328
+ failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
8329
+ const compiledStyles = createTileflowStyles2(project, {
8330
+ apiBaseUrl: options.apiBaseUrl,
8331
+ mapAssets
8332
+ });
8333
+ failureDefaults = { code: "TF_LOCAL_TILESET_INVALID", phase: "local-tileset-resolution" };
8334
+ const localTilesets = await prepareTileflowLocalTilesets2(project, compiledStyles, {
8335
+ assetBaseUrl: "/tileflow",
8336
+ baseDirectory,
8337
+ cwd: process.cwd()
8338
+ });
8339
+ failureDefaults = { code: "FONT_COMPILATION_FAILED", phase: "font-compilation" };
8340
+ const { styles } = await prepareTileflowStyleFonts2(project, localTilesets.styles, {
8341
+ assetBaseUrl: "/tileflow",
8342
+ baseDirectory,
8343
+ cwd: process.cwd(),
8344
+ target: options.target
8345
+ });
8346
+ failureDefaults = { code: "HOSTED_INCOMPATIBLE", phase: "hosted-validation" };
8347
+ const hostedIssues = options.target === "hosted" ? inspectTileflowHostedCompatibility(project, styles) : [];
8348
+ if (hostedIssues.length > 0) {
8349
+ if (!options.json) {
8350
+ printHostedCompatibilityIssues(hostedIssues);
8351
+ return;
8352
+ }
8353
+ throw Object.assign(new Error("Tileflow config is not Hosted-compatible."), {
6644
8354
  code: "HOSTED_INCOMPATIBLE",
6645
- message: issue.message,
6646
- path: issue.path,
8355
+ issues: hostedIssues.map((issue) => ({
8356
+ code: "HOSTED_INCOMPATIBLE",
8357
+ message: issue.message,
8358
+ path: issue.path,
8359
+ phase: "hosted-validation"
8360
+ })),
6647
8361
  phase: "hosted-validation"
6648
- })),
6649
- phase: "hosted-validation"
6650
- });
6651
- }
6652
- const diagnostics = createTileflowStructuredDiagnostics2(
6653
- {
6654
- diagnostics: mapNames.flatMap(
6655
- (mapName) => auditTileflowMapThemeValues(project.maps[mapName]).filter(
6656
- ({ severity }) => severity === "warning"
6657
- )
8362
+ });
8363
+ }
8364
+ const themeWarnings = mapNames.flatMap(
8365
+ (mapName) => auditTileflowMapThemeValues2(project.maps[mapName]).filter(
8366
+ ({ severity }) => severity === "warning"
6658
8367
  )
6659
- },
6660
- process.cwd(),
6661
- { code: "VALIDATION_WARNING", phase: "theme-audit" }
6662
- );
6663
- const checks = [
6664
- "Config schema",
6665
- "Icon asset closure",
6666
- "Text provider closure",
6667
- "Named map styles",
6668
- "MapLibre style semantics",
6669
- ...options.target === "hosted" ? ["Hosted compatibility"] : []
6670
- ];
6671
- if (options.json) {
6672
- const summary = createTileflowCommandSummary2({
6673
- code: "VALIDATION_OK",
6674
- command: "validate",
6675
- message: `Tileflow config is valid (${plural(mapNames.length, "map")}).`,
6676
- ok: true,
6677
- path: "",
6678
- phase: "validation",
6679
- severity: "info",
6680
- suggestion: "No changes are required."
8368
+ );
8369
+ const diagnostics = themeWarnings.length === 0 ? [] : createTileflowStructuredDiagnostics3({ diagnostics: themeWarnings }, process.cwd(), {
8370
+ code: "VALIDATION_WARNING",
8371
+ phase: "theme-audit"
6681
8372
  });
6682
- process.stdout.write(
6683
- serializeTileflowCommandDocument4({
6684
- ...summary,
6685
- target: options.target,
6686
- maps: mapNames,
6687
- checks,
6688
- diagnostics
6689
- })
8373
+ const sharedIconMaps = Object.keys(compiledIcons.compositions).sort();
8374
+ const checks = [
8375
+ "Config schema",
8376
+ "Icon asset closure",
8377
+ ...sharedIconMaps.length > 0 ? ["Locked Icon Set composition"] : [],
8378
+ "Text provider closure",
8379
+ "Named map styles",
8380
+ "MapLibre style semantics",
8381
+ ...options.target === "hosted" ? ["Hosted compatibility"] : []
8382
+ ];
8383
+ if (options.json) {
8384
+ const summary = createTileflowCommandSummary3({
8385
+ code: "VALIDATION_OK",
8386
+ command: "validate",
8387
+ message: `Tileflow config is valid (${plural(mapNames.length, "map")}).`,
8388
+ ok: true,
8389
+ path: "",
8390
+ phase: "validation",
8391
+ severity: "info",
8392
+ suggestion: "No changes are required."
8393
+ });
8394
+ process.stdout.write(
8395
+ serializeTileflowCommandDocument5({
8396
+ ...summary,
8397
+ target: options.target,
8398
+ maps: mapNames,
8399
+ checks,
8400
+ diagnostics
8401
+ })
8402
+ );
8403
+ return;
8404
+ }
8405
+ logSuccess(`Config is valid (${plural(mapNames.length, "map")}).`);
8406
+ printChecks(checks);
8407
+ for (const diagnostic of diagnostics) {
8408
+ logWarning(`${diagnostic.path || "(root)"}: ${diagnostic.message}`);
8409
+ }
8410
+ } catch (error) {
8411
+ if (!options.json) throw error;
8412
+ const failure2 = createTileflowCommandFailureDocument5(
8413
+ "validate",
8414
+ error,
8415
+ process.cwd(),
8416
+ failureDefaults
6690
8417
  );
6691
- return;
6692
- }
6693
- logSuccess(`Config is valid (${plural(mapNames.length, "map")}).`);
6694
- printChecks(checks);
6695
- for (const diagnostic of diagnostics) {
6696
- logWarning(`${diagnostic.path || "(root)"}: ${diagnostic.message}`);
8418
+ process.stderr.write(serializeTileflowCommandDocument5(failure2));
8419
+ process.exitCode = 1;
6697
8420
  }
6698
- } catch (error) {
6699
- if (!options.json) throw error;
6700
- const failure = createTileflowCommandFailureDocument4(
6701
- "validate",
6702
- error,
6703
- process.cwd(),
6704
- failureDefaults
6705
- );
6706
- process.stderr.write(serializeTileflowCommandDocument4(failure));
6707
- process.exitCode = 1;
6708
8421
  }
6709
- });
6710
- program.command("build").description("Generate static Tileflow styles").option("-c, --config <path>", "config path", defaultConfigPath).option("-o, --out <path>", "output directory", "dist/tileflow").option(
8422
+ );
8423
+ program.command("build").description("Generate static Tileflow styles").option("-c, --config <path>", "config path", defaultConfigPath).option("-o, --out <path>", "output directory", "dist/tileflow").option("--renderer <renderer>", "artifact renderer: web or native", "web").option("--target <target>", "artifact target: local or hosted (build supports local)", "local").option("--json", "print deterministic schema-version-1 JSON").option(
6711
8424
  "--api-base-url <url>",
6712
8425
  "Tileflow API base URL used to resolve official map assets",
6713
8426
  process.env.TILEFLOW_API_URL ?? defaultApiUrl
6714
- ).action(async (options) => {
6715
- logInfo(`Building ${pathLabel(options.config)}.`);
6716
- await withTileflowConfigSecretsHidden(
6717
- () => writeTileflowBuildArtifacts({
6718
- config: options.config,
6719
- outDir: options.out,
6720
- styleBaseUrl: ".",
6721
- apiBaseUrl: options.apiBaseUrl
6722
- })
6723
- );
6724
- logSuccess("Built Tileflow artifacts.");
6725
- printKeyValue("Output", pathLabel(resolve7(process.cwd(), options.out)));
6726
- });
6727
- program.command("preview").alias("dev").description("Run a local map preview").option("-c, --config <path>", "config path", defaultConfigPath).option("--against-config <path>", "second config for the comparison workbench").option("--against-map <name>", "map on the right; defaults to --map").option("--against-scene <name>", "committed standalone scene on the right").option("--against-theme <name>", "concrete theme on the right; defaults to --theme").option("--host <host>", "bind host: an explicit IP address or localhost", defaultTileflowDevHost).option("--map <name>", "preview one configured map").option("--theme <name>", "preview one concrete theme; defaults to the map default").option("-p, --port <port>", "preview port", "3333").option("--scene <name>", "preview one committed standalone map scene").option(
8427
+ ).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
8428
+ async (options) => {
8429
+ const icons = resolveIconOptions(options);
8430
+ if (options.renderer !== "web" || options.target !== "local" || options.json) {
8431
+ await runRendererArtifactCommand("build", { ...options, icons });
8432
+ return;
8433
+ }
8434
+ logInfo(`Building ${pathLabel(options.config)}.`);
8435
+ await withTileflowConfigSecretsHidden(
8436
+ () => writeTileflowBuildArtifacts({
8437
+ config: options.config,
8438
+ outDir: options.out,
8439
+ styleBaseUrl: ".",
8440
+ apiBaseUrl: options.apiBaseUrl,
8441
+ ...icons ? { icons } : {}
8442
+ })
8443
+ );
8444
+ logSuccess("Built Tileflow artifacts.");
8445
+ printKeyValue("Output", pathLabel(resolve8(process.cwd(), options.out)));
8446
+ }
8447
+ );
8448
+ program.command("preview").alias("dev").description("Run a local map preview").option("-c, --config <path>", "config path", defaultConfigPath).option("--against-config <path>", "second config for the comparison workbench").option("--against-map <name>", "map on the right; defaults to --map").option("--against-scene <name>", "committed standalone scene on the right").option("--against-theme <name>", "concrete theme on the right; defaults to --theme").option("--host <host>", "bind host: an explicit IP address or localhost", defaultTileflowDevHost).option("--map <name>", "preview one configured map").option("--theme <name>", "preview one concrete theme; defaults to the map default").option("--renderer <renderer>", "artifact renderer: web or native", "web").option("-p, --port <port>", "preview port", "3333").option("--scene <name>", "preview one committed standalone map scene").option(
6728
8449
  "--api-base-url <url>",
6729
8450
  "Tileflow API base URL used to resolve official map assets",
6730
8451
  process.env.TILEFLOW_API_URL ?? defaultApiUrl
6731
- ).option("--json", "emit schema-version-1 NDJSON lifecycle events").action(
8452
+ ).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--json", "emit schema-version-1 NDJSON lifecycle events").action(
6732
8453
  async (options) => {
8454
+ let renderer;
8455
+ try {
8456
+ renderer = resolveTileflowRenderer2(options.renderer);
8457
+ } catch (error) {
8458
+ const diagnostics = createTileflowArtifactDiagnostics3(error, process.cwd());
8459
+ if (options.json) {
8460
+ const first = diagnostics[0];
8461
+ process.stdout.write(
8462
+ `${JSON.stringify({
8463
+ schemaVersion: 1,
8464
+ command: "dev",
8465
+ event: "error",
8466
+ code: first?.code ?? "NATIVE_RENDERER_UNSUPPORTED",
8467
+ phase: first?.phase ?? "command-validation",
8468
+ diagnostics
8469
+ })}
8470
+ `
8471
+ );
8472
+ } else {
8473
+ logError(diagnostics[0]?.message ?? "Unsupported artifact renderer.");
8474
+ }
8475
+ process.exitCode = 1;
8476
+ return;
8477
+ }
8478
+ const comparisonRequested = options.againstConfig !== void 0 || options.againstMap !== void 0 || options.againstScene !== void 0 || options.againstTheme !== void 0;
8479
+ if (renderer === "native" && (options.scene !== void 0 || comparisonRequested)) {
8480
+ if (options.json) {
8481
+ process.stdout.write(
8482
+ `${JSON.stringify({
8483
+ schemaVersion: 1,
8484
+ command: "dev",
8485
+ event: "error",
8486
+ code: "NATIVE_RENDERER_UNSUPPORTED",
8487
+ phase: "command-validation",
8488
+ renderer: "native",
8489
+ profile: tileflowNativePreviewProfile
8490
+ })}
8491
+ `
8492
+ );
8493
+ } else {
8494
+ logError(
8495
+ "Native preview serves assets only; scenes and comparison workbench options are unavailable."
8496
+ );
8497
+ }
8498
+ process.exitCode = 1;
8499
+ return;
8500
+ }
6733
8501
  const port = parsePort(options.port);
6734
8502
  if (port === null) {
6735
8503
  logError(`Invalid port: ${options.port}`);
@@ -6774,28 +8542,46 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6774
8542
  process.exitCode = 1;
6775
8543
  return;
6776
8544
  }
6777
- const comparisonRequested = options.againstConfig !== void 0 || options.againstMap !== void 0 || options.againstScene !== void 0 || options.againstTheme !== void 0;
6778
8545
  if (comparisonRequested) {
6779
8546
  await runTileflowComparisonPreview(options, { host, port });
6780
8547
  return;
6781
8548
  }
6782
8549
  await withTileflowConfigSecretsHidden(async () => {
6783
8550
  const origin = tileflowDevOrigin(host, port);
8551
+ const manifestUrl = renderer === "native" ? getTileflowNativePreviewManifestUrl(origin) : void 0;
8552
+ const nativeLifecycleFields = manifestUrl ? {
8553
+ renderer: "native",
8554
+ profile: tileflowNativePreviewProfile,
8555
+ manifest: manifestUrl,
8556
+ assetsOnly: true
8557
+ } : {};
8558
+ const icons = resolveIconOptions(options);
6784
8559
  const session = await createTileflowArtifactSession3({
6785
8560
  assetBaseUrl: origin,
6786
8561
  apiBaseUrl: options.apiBaseUrl,
6787
8562
  config: options.config,
8563
+ ...icons ? { icons } : {},
8564
+ // Locally built sprites and fonts are served over HTTP from this exact origin only.
8565
+ ...renderer === "native" ? { developmentOrigin: origin, renderer: "native" } : {},
6788
8566
  styleBaseUrl: origin,
6789
8567
  watch: true
6790
8568
  });
6791
8569
  const initialArtifacts = session.getLastGoodArtifacts();
8570
+ let nativeSelection;
6792
8571
  try {
6793
8572
  if (initialArtifacts) {
6794
- resolveTileflowPreview(initialArtifacts.project, {
6795
- map: options.map,
6796
- scene: options.scene,
6797
- theme: options.theme
6798
- });
8573
+ if (renderer === "native") {
8574
+ nativeSelection = resolveTileflowNativePreviewSelection(initialArtifacts.manifest, {
8575
+ map: options.map,
8576
+ theme: options.theme
8577
+ });
8578
+ } else {
8579
+ resolveTileflowPreview(initialArtifacts.project, {
8580
+ map: options.map,
8581
+ scene: options.scene,
8582
+ theme: options.theme
8583
+ });
8584
+ }
6799
8585
  }
6800
8586
  } catch (error) {
6801
8587
  await session.close();
@@ -6803,7 +8589,12 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6803
8589
  process.exitCode = 1;
6804
8590
  return;
6805
8591
  }
6806
- const fetch2 = createTileflowDevRequestHandler({
8592
+ const fetch2 = renderer === "native" ? createTileflowNativePreviewRequestHandler({
8593
+ config: options.config,
8594
+ apiBaseUrl: options.apiBaseUrl,
8595
+ onError: printTileflowPreviewError,
8596
+ session
8597
+ }) : createTileflowDevRequestHandler2({
6807
8598
  config: options.config,
6808
8599
  apiBaseUrl: options.apiBaseUrl,
6809
8600
  map: options.map,
@@ -6828,7 +8619,8 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6828
8619
  ..."lastGoodGeneration" in state && state.lastGoodGeneration !== void 0 ? { lastGoodGeneration: state.lastGoodGeneration } : {},
6829
8620
  ...firstDiagnostic?.code ? { code: firstDiagnostic.code } : {},
6830
8621
  ...firstDiagnostic?.phase ? { phase: firstDiagnostic.phase } : {},
6831
- ...state.status === "invalid" ? { diagnostics: state.diagnostics } : {}
8622
+ ...state.status === "invalid" ? { diagnostics: state.diagnostics } : {},
8623
+ ...nativeLifecycleFields
6832
8624
  })}
6833
8625
  `
6834
8626
  );
@@ -6837,14 +8629,16 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6837
8629
  if (state.status === "invalid") {
6838
8630
  console.error(
6839
8631
  [
6840
- `Tileflow generation ${state.generation} is invalid; preserving the last valid preview.`,
8632
+ renderer === "native" ? `Tileflow generation ${state.generation} is invalid; preserving the last valid native artifact family.` : `Tileflow generation ${state.generation} is invalid; preserving the last valid preview.`,
6841
8633
  ...state.diagnostics.map(
6842
8634
  (diagnostic) => `- ${diagnostic.path || "(root)"}: ${diagnostic.message}`
6843
8635
  )
6844
8636
  ].join("\n")
6845
8637
  );
6846
8638
  } else if (recovered) {
6847
- logSuccess(`Tileflow preview recovered at generation ${state.generation}.`);
8639
+ logSuccess(
8640
+ renderer === "native" ? `Tileflow native artifact preview recovered at generation ${state.generation}.` : `Tileflow preview recovered at generation ${state.generation}.`
8641
+ );
6848
8642
  }
6849
8643
  };
6850
8644
  emitState(session.getState());
@@ -6857,13 +8651,26 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6857
8651
  server = createdServer;
6858
8652
  });
6859
8653
  if (!options.json) {
6860
- logSuccess("Tileflow preview is running and watching for changes.");
6861
- printKeyValue("Local", link(origin));
6862
- printKeyValue("Config", pathLabel(options.config));
6863
- if (options.map) printKeyValue("Map", options.map);
6864
- if (options.theme) printKeyValue("Theme", options.theme);
6865
- if (options.scene) printKeyValue("Scene", options.scene);
6866
- logMuted("Press Ctrl+C to stop.");
8654
+ if (renderer === "native") {
8655
+ logSuccess("Tileflow native artifact preview is running and watching for changes.");
8656
+ printKeyValue("Manifest", link(manifestUrl));
8657
+ printKeyValue("Profile", tileflowNativePreviewProfile);
8658
+ printKeyValue("Endpoint", "assets-only");
8659
+ printKeyValue("Config", pathLabel(options.config));
8660
+ if (nativeSelection) {
8661
+ printKeyValue("Map", nativeSelection.mapName);
8662
+ printKeyValue("Theme", nativeSelection.themeName);
8663
+ }
8664
+ logMuted("Metro remains the JavaScript development server. Press Ctrl+C to stop.");
8665
+ } else {
8666
+ logSuccess("Tileflow preview is running and watching for changes.");
8667
+ printKeyValue("Local", link(origin));
8668
+ printKeyValue("Config", pathLabel(options.config));
8669
+ if (options.map) printKeyValue("Map", options.map);
8670
+ if (options.theme) printKeyValue("Theme", options.theme);
8671
+ if (options.scene) printKeyValue("Scene", options.scene);
8672
+ logMuted("Press Ctrl+C to stop.");
8673
+ }
6867
8674
  }
6868
8675
  await waitForTerminationSignal(server);
6869
8676
  } finally {
@@ -6873,7 +8680,13 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6873
8680
  await session.close();
6874
8681
  if (options.json) {
6875
8682
  process.stdout.write(
6876
- `${JSON.stringify({ schemaVersion: 1, command: "dev", event: "stopped", generation })}
8683
+ `${JSON.stringify({
8684
+ schemaVersion: 1,
8685
+ command: "dev",
8686
+ event: "stopped",
8687
+ generation,
8688
+ ...nativeLifecycleFields
8689
+ })}
6877
8690
  `
6878
8691
  );
6879
8692
  }
@@ -6881,10 +8694,10 @@ program.command("preview").alias("dev").description("Run a local map preview").o
6881
8694
  });
6882
8695
  }
6883
8696
  );
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(
8697
+ 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("--with-native", "publish web and native-v1 together for the selected managed Map").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
6885
8698
  "--overwrite-self-hosted-manifest",
6886
8699
  "explicitly replace an existing self-hosted manifest at --manifest"
6887
- ).action(
8700
+ ).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
6888
8701
  async (options) => {
6889
8702
  const source = resolveDeploySource(process.env);
6890
8703
  const resolveApi = (selectedMap) => requireApiOptions(options, {
@@ -6902,19 +8715,39 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
6902
8715
  options.apiUrl ?? defaultApiUrl,
6903
8716
  ...options.overwriteSelfHostedManifest ? ["--overwrite-self-hosted-manifest"] : [],
6904
8717
  ...options.mapId ? ["--map-id", options.mapId] : [],
6905
- ...options.map && selectedMap ? ["--map", selectedMap] : []
8718
+ ...options.map && selectedMap ? ["--map", selectedMap] : [],
8719
+ ...options.withNative ? ["--with-native"] : []
6906
8720
  ])
6907
8721
  });
6908
8722
  const apiUrl = normalizeApiOrigin(options.apiUrl ?? defaultApiUrl);
6909
8723
  delete process.env.TILEFLOW_API_KEY;
8724
+ if (options.withNative) {
8725
+ const result = await runHostedNativeDeploy(
8726
+ { ...options, apiUrl },
8727
+ {
8728
+ source,
8729
+ resolveApi,
8730
+ loadManifest: loadExistingDeployManifest,
8731
+ writeManifest: writeDeployManifest
8732
+ }
8733
+ );
8734
+ if (result) {
8735
+ logSuccess(
8736
+ `${result.changed ? "Published" : "Unchanged"} ${pc6.bold(result.mapName)} web + native-v1 (v${result.version}).`
8737
+ );
8738
+ printKeyValue("Manifest", pathLabel(result.manifestPath));
8739
+ printKeyValue("Native", link(result.nativeManifestUrl));
8740
+ }
8741
+ return;
8742
+ }
6910
8743
  logInfo(`Deploying ${pathLabel(options.config)}.`);
6911
8744
  const loaded = await withTileflowConfigSecretsHidden(
6912
- () => loadTileflowConfigWithInputs3(options.config)
8745
+ () => loadTileflowConfigWithInputs4(options.config)
6913
8746
  );
6914
8747
  const project = loaded.project;
6915
- const baseDirectory = dirname10(loaded.configFile);
6916
- assertValidTileflowConfig2(project);
6917
- const configuredMapNames = getTileflowMapNames3(project);
8748
+ const baseDirectory = dirname12(loaded.configFile);
8749
+ assertValidTileflowConfig3(project);
8750
+ const configuredMapNames = getTileflowMapNames5(project);
6918
8751
  const mapNames = selectManagedDeployMaps(configuredMapNames, options);
6919
8752
  if (!mapNames) return;
6920
8753
  if (!validateDeployTarget(options)) return;
@@ -6925,9 +8758,10 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
6925
8758
  mapNames.map((mapName) => [mapName, project.maps[mapName]])
6926
8759
  )
6927
8760
  };
6928
- const compiledIcons = await compileTileflowIconPackages2(deploymentProject, {
8761
+ const compiledIcons = await compileTileflowIconPackages3(deploymentProject, {
6929
8762
  baseDirectory,
6930
8763
  cwd: process.cwd(),
8764
+ icons: resolveIconOptions(options),
6931
8765
  target: "hosted"
6932
8766
  });
6933
8767
  const bindingsByMap = new Map(
@@ -6940,16 +8774,16 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
6940
8774
  compiledIcons,
6941
8775
  (binding) => `${apiUrl}/sprites/preflight/${binding.packageHash}/sprite`
6942
8776
  );
6943
- const compiledPreflightStyles = createTileflowStyles(deploymentProject, {
8777
+ const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
6944
8778
  apiBaseUrl: apiUrl,
6945
8779
  mapAssets: preflightMapAssets
6946
8780
  });
6947
- const preflightLocalTilesets = await prepareTileflowLocalTilesets(
8781
+ const preflightLocalTilesets = await prepareTileflowLocalTilesets2(
6948
8782
  deploymentProject,
6949
8783
  compiledPreflightStyles,
6950
8784
  { assetBaseUrl: apiUrl, baseDirectory, cwd: process.cwd() }
6951
8785
  );
6952
- const preflightFonts = await prepareTileflowStyleFonts(
8786
+ const preflightFonts = await prepareTileflowStyleFonts2(
6953
8787
  deploymentProject,
6954
8788
  preflightLocalTilesets.styles,
6955
8789
  {
@@ -7008,7 +8842,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7008
8842
  );
7009
8843
  const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
7010
8844
  logSuccess(
7011
- `${action} icon package ${pc3.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
8845
+ `${action} icon package ${pc6.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
7012
8846
  );
7013
8847
  hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
7014
8848
  }
@@ -7017,16 +8851,16 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7017
8851
  if (!sprite) throw new Error(`Missing hosted sprite URL for map ${binding.mapName}`);
7018
8852
  return sprite;
7019
8853
  });
7020
- const compiledHostedStyles = createTileflowStyles(deploymentProject, {
8854
+ const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
7021
8855
  apiBaseUrl: api.apiUrl,
7022
8856
  mapAssets: hostedMapAssets
7023
8857
  });
7024
- const hostedLocalTilesets = await prepareTileflowLocalTilesets(
8858
+ const hostedLocalTilesets = await prepareTileflowLocalTilesets2(
7025
8859
  deploymentProject,
7026
8860
  compiledHostedStyles,
7027
8861
  { assetBaseUrl: api.apiUrl, baseDirectory, cwd: process.cwd() }
7028
8862
  );
7029
- const hostedFonts = await prepareTileflowStyleFonts(
8863
+ const hostedFonts = await prepareTileflowStyleFonts2(
7030
8864
  deploymentProject,
7031
8865
  hostedLocalTilesets.styles,
7032
8866
  {
@@ -7091,7 +8925,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7091
8925
  const styles = Object.fromEntries(
7092
8926
  Object.entries(hostedThemeFamilies).map(([mapName, family]) => [mapName, family.styles])
7093
8927
  );
7094
- const buildManifest = await createTileflowMapBuildManifest(
8928
+ const buildManifest = await createTileflowMapBuildManifest2(
7095
8929
  Object.fromEntries(
7096
8930
  mapNames.map((mapName) => {
7097
8931
  const map = deploymentProject.maps[mapName];
@@ -7114,14 +8948,15 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7114
8948
  map,
7115
8949
  sourceAssets: {
7116
8950
  fonts: hostedFonts.sourceIdentities[mapName] ?? [],
7117
- icons: compiledIcons.sourceIdentities[mapName] ?? []
8951
+ icons: compiledIcons.sourceIdentities[mapName] ?? [],
8952
+ ...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {}
7118
8953
  },
7119
8954
  styles: styles[mapName]
7120
8955
  }
7121
8956
  ];
7122
8957
  })
7123
8958
  ),
7124
- { provenance: await createTileflowBuildProvenance(process.cwd()) }
8959
+ { provenance: await createTileflowBuildProvenance2(process.cwd()) }
7125
8960
  );
7126
8961
  const deployments = mapNames.map((mapName) => {
7127
8962
  const iconBinding = bindingsByMap.get(mapName);
@@ -7129,6 +8964,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7129
8964
  const fontBundle = hostedFonts.bundles[mapName];
7130
8965
  return {
7131
8966
  iconBinding,
8967
+ iconComposition: compiledIcons.compositions[mapName],
7132
8968
  iconPackage,
7133
8969
  fontBundle,
7134
8970
  mapName,
@@ -7147,14 +8983,20 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7147
8983
  buildManifest: buildManifest2,
7148
8984
  fontBundle,
7149
8985
  iconBinding,
8986
+ iconComposition,
7150
8987
  iconPackage,
7151
8988
  mapName,
7152
8989
  teamSources,
7153
8990
  styles: themeStyles
7154
8991
  } = deployment;
8992
+ if (iconComposition && iconComposition.packageHash !== iconPackage?.contentHash) {
8993
+ logError(`Icon composition does not match the effective package for ${mapName}.`);
8994
+ process.exitCode = 1;
8995
+ return;
8996
+ }
7155
8997
  const themeNames = Object.keys(themeStyles).sort();
7156
8998
  logInfo(
7157
- `Deploying compiled map ${pc3.bold(mapName)} (${plural(themeNames.length, "theme")}).`
8999
+ `Deploying compiled map ${pc6.bold(mapName)} (${plural(themeNames.length, "theme")}).`
7158
9000
  );
7159
9001
  const response = await publishHostedStyle(
7160
9002
  api,
@@ -7176,6 +9018,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7176
9018
  label: iconBinding.label
7177
9019
  }
7178
9020
  } : {},
9021
+ ...iconComposition ? { iconComposition } : {},
7179
9022
  ...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
7180
9023
  source
7181
9024
  },
@@ -7204,7 +9047,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7204
9047
  process.exitCode = 1;
7205
9048
  return;
7206
9049
  }
7207
- const resolvedMap = parseResolvedTileflowMap3(deploymentProject.maps[mapName]);
9050
+ const resolvedMap = parseResolvedTileflowMap5(deploymentProject.maps[mapName]);
7208
9051
  const mapBuild = buildManifest2.maps[mapName];
7209
9052
  deployedMaps[mapName] = {
7210
9053
  defaultTheme: resolvedMap.defaultTheme,
@@ -7214,7 +9057,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7214
9057
  themes: Object.fromEntries(
7215
9058
  themeNames.map((themeName) => {
7216
9059
  const deployedTheme = body.themes[themeName];
7217
- const fontFaces = getTileflowStyleFontFaces(themeStyles[themeName]);
9060
+ const fontFaces = getTileflowStyleFontFaces2(themeStyles[themeName]);
7218
9061
  return [
7219
9062
  themeName,
7220
9063
  {
@@ -7233,9 +9076,9 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
7233
9076
  };
7234
9077
  const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
7235
9078
  if (body.changed === false) {
7236
- logSuccess(`Unchanged ${pc3.bold(mapName)}${versionLabel}.`);
9079
+ logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
7237
9080
  } else {
7238
- logSuccess(`Published ${pc3.bold(mapName)}${versionLabel}.`);
9081
+ logSuccess(`Published ${pc6.bold(mapName)}${versionLabel}.`);
7239
9082
  }
7240
9083
  }
7241
9084
  const manifestPath = await writeDeployManifest(options.manifest, {
@@ -7309,6 +9152,7 @@ program.command("status").description("Show deployed compiled styles").option("-
7309
9152
  printMapStatus(status, api.apiUrl);
7310
9153
  });
7311
9154
  var iconsCommand = program.command("icons").description("Inspect managed Tileflow icons");
9155
+ registerIconLockCommands(program, { defaultApiUrl, defaultConfigPath, loadAuthConfig });
7312
9156
  registerIconListCommand(iconsCommand, { defaultConfigPath });
7313
9157
  registerIconDiffCommand(iconsCommand, {
7314
9158
  defaultApiUrl,
@@ -7341,6 +9185,7 @@ registerConfigInspectCommand(inspectCommand, { defaultConfigPath });
7341
9185
  registerFeatureInspectCommand(inspectCommand, { defaultConfigPath });
7342
9186
  registerAiCommands(program, { defaultApiUrl, defaultConfigPath });
7343
9187
  registerCaptureCommands(program, { defaultConfigPath });
9188
+ registerCoordinatesCommands(program, { createLocalCoordinates, setupCoordinates });
7344
9189
  registerVisualCommands(program, {
7345
9190
  defaultConfigPath,
7346
9191
  openReport: (path) => openBrowser(path, true)
@@ -7349,10 +9194,15 @@ registerProjectCommands(program, {
7349
9194
  defaultApiUrl,
7350
9195
  loadAuthConfig
7351
9196
  });
7352
- program.parseAsync().catch((error) => {
7353
- printCliError(error);
7354
- process.exitCode = 1;
7355
- });
9197
+ if (isRootCliVersionRequest(process.argv.slice(2))) {
9198
+ process.stdout.write(`${packageJson.version}
9199
+ `);
9200
+ } else {
9201
+ program.parseAsync().catch((error) => {
9202
+ printCliError(error);
9203
+ process.exitCode = 1;
9204
+ });
9205
+ }
7356
9206
  async function runTileflowComparisonPreview(options, address) {
7357
9207
  await withTileflowConfigSecretsHidden(async () => {
7358
9208
  const origin = tileflowDevOrigin(address.host, address.port);
@@ -7397,10 +9247,12 @@ async function runTileflowComparisonPreview(options, address) {
7397
9247
  try {
7398
9248
  const leftCaptureConfig = comparisonCaptureConfigArgument(options.config);
7399
9249
  const rightCaptureConfig = comparisonCaptureConfigArgument(rightConfig);
9250
+ const comparisonIcons = resolveIconOptions(options);
7400
9251
  leftSession = await createTileflowArtifactSession3({
7401
9252
  apiBaseUrl: options.apiBaseUrl,
7402
9253
  assetBaseUrl: `${origin}${leftBasePath}`,
7403
9254
  config: options.config,
9255
+ ...comparisonIcons ? { icons: comparisonIcons } : {},
7404
9256
  inspection: true,
7405
9257
  styleBaseUrl: `${origin}${leftBasePath}`,
7406
9258
  watch: true
@@ -7409,6 +9261,7 @@ async function runTileflowComparisonPreview(options, address) {
7409
9261
  apiBaseUrl: options.apiBaseUrl,
7410
9262
  assetBaseUrl: `${origin}${rightBasePath}`,
7411
9263
  config: rightConfig,
9264
+ ...comparisonIcons ? { icons: comparisonIcons } : {},
7412
9265
  inspection: true,
7413
9266
  styleBaseUrl: `${origin}${rightBasePath}`,
7414
9267
  watch: true
@@ -7453,7 +9306,7 @@ async function runTileflowComparisonPreview(options, address) {
7453
9306
  scene: rightScene,
7454
9307
  theme: rightTheme
7455
9308
  });
7456
- const leftHandler = createTileflowDevRequestHandler({
9309
+ const leftHandler = createTileflowDevRequestHandler2({
7457
9310
  apiBaseUrl: options.apiBaseUrl,
7458
9311
  basePath: leftBasePath,
7459
9312
  config: options.config,
@@ -7463,7 +9316,7 @@ async function runTileflowComparisonPreview(options, address) {
7463
9316
  session: leftSession,
7464
9317
  theme: options.theme
7465
9318
  });
7466
- const rightHandler = createTileflowDevRequestHandler({
9319
+ const rightHandler = createTileflowDevRequestHandler2({
7467
9320
  apiBaseUrl: options.apiBaseUrl,
7468
9321
  basePath: rightBasePath,
7469
9322
  config: rightConfig,
@@ -7534,13 +9387,13 @@ async function runTileflowComparisonPreview(options, address) {
7534
9387
  }
7535
9388
  function comparisonCaptureConfigArgument(configPath) {
7536
9389
  const cwd = realpathSync(process.cwd());
7537
- const requestedConfigPath = resolve7(process.cwd(), configPath);
7538
- const defaultRequestedPath = resolve7(process.cwd(), defaultConfigPath);
9390
+ const requestedConfigPath = resolve8(process.cwd(), configPath);
9391
+ const defaultRequestedPath = resolve8(process.cwd(), defaultConfigPath);
7539
9392
  const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
7540
9393
  const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
7541
9394
  if (absoluteConfigPath === absoluteDefaultPath) return void 0;
7542
9395
  const relativeConfigPath = relative5(cwd, absoluteConfigPath);
7543
- if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute4(relativeConfigPath)) {
9396
+ if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
7544
9397
  throw new Error(
7545
9398
  "Comparison configs must be inside the current working directory so Copy command can remain reproducible."
7546
9399
  );
@@ -7579,7 +9432,7 @@ async function loginWithDeviceFlow(options) {
7579
9432
  codeChallenge,
7580
9433
  deviceName: hostname()
7581
9434
  });
7582
- printKeyValue("Code", pc3.bold(authorization.userCode));
9435
+ printKeyValue("Code", pc6.bold(authorization.userCode));
7583
9436
  printKeyValue("URL", link(authorization.verificationUriComplete));
7584
9437
  if (options.noBrowser) {
7585
9438
  logMuted("Open the URL above to continue.");
@@ -7589,7 +9442,7 @@ async function loginWithDeviceFlow(options) {
7589
9442
  output: process.stdout
7590
9443
  });
7591
9444
  try {
7592
- await readline.question(`${pc3.cyan("?")} Press Enter to open Tileflow Dashboard...`);
9445
+ await readline.question(`${pc6.cyan("?")} Press Enter to open Tileflow Dashboard...`);
7593
9446
  openBrowser(authorization.verificationUriComplete);
7594
9447
  } finally {
7595
9448
  readline.close();
@@ -7640,9 +9493,9 @@ function openBrowser(url, errorsToStderr = false) {
7640
9493
  });
7641
9494
  }
7642
9495
  async function writeDeployManifest(manifestPath, manifest) {
7643
- const outputPath = resolve7(process.cwd(), manifestPath);
9496
+ const outputPath = resolve8(process.cwd(), manifestPath);
7644
9497
  await writeAtomicFile({
7645
- boundaryPath: dirname10(outputPath),
9498
+ boundaryPath: dirname12(outputPath),
7646
9499
  force: true,
7647
9500
  label: "Deploy manifest",
7648
9501
  managed: true,
@@ -7652,11 +9505,24 @@ async function writeDeployManifest(manifestPath, manifest) {
7652
9505
  });
7653
9506
  return manifestPath;
7654
9507
  }
9508
+ function resolveIconOptions(options) {
9509
+ const icons = {
9510
+ ...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
9511
+ ...options.offline ? { offline: true } : {}
9512
+ };
9513
+ return Object.keys(icons).length > 0 ? icons : void 0;
9514
+ }
7655
9515
  function createCompiledMapAssets(compiled, resolveSprite) {
7656
9516
  return Object.fromEntries(
7657
9517
  compiled.bindings.map((binding) => [
7658
9518
  binding.mapName,
7659
- { icons: { ids: binding.iconIds, sprite: resolveSprite(binding) } }
9519
+ {
9520
+ icons: {
9521
+ ids: binding.iconIds,
9522
+ sprite: resolveSprite(binding),
9523
+ ...binding.appearances ? { appearances: binding.appearances } : {}
9524
+ }
9525
+ }
7660
9526
  ])
7661
9527
  );
7662
9528
  }
@@ -7713,7 +9579,7 @@ function validateDeployManifestMapNames(mapNames) {
7713
9579
  async function loadExistingDeployManifest(manifestPath, options) {
7714
9580
  let source;
7715
9581
  try {
7716
- source = await readFile7(resolve7(process.cwd(), manifestPath), "utf8");
9582
+ source = await readFile7(resolve8(process.cwd(), manifestPath), "utf8");
7717
9583
  } catch (error) {
7718
9584
  if (error.code === "ENOENT") return null;
7719
9585
  throw new Error("Existing Tileflow manifest could not be read.");
@@ -7727,7 +9593,7 @@ async function loadExistingDeployManifest(manifestPath, options) {
7727
9593
  }
7728
9594
  let manifest;
7729
9595
  try {
7730
- manifest = parseTileflowRuntimeManifest(value);
9596
+ manifest = parseTileflowRuntimeManifest2(value);
7731
9597
  } catch {
7732
9598
  throw new Error(
7733
9599
  "Existing Tileflow manifest does not match the strict theme manifest contract."
@@ -7766,7 +9632,7 @@ async function requireApiOptions(options, behavior = {}) {
7766
9632
  process.exitCode = 1;
7767
9633
  return null;
7768
9634
  }
7769
- if (profile.value.mapId !== requestedMapId) {
9635
+ if (!apiProfileCanTargetMap(profile.value, requestedMapId)) {
7770
9636
  if (!behavior.silent) logError("This API key belongs to another Map.");
7771
9637
  process.exitCode = 1;
7772
9638
  return null;
@@ -7779,6 +9645,11 @@ async function requireApiOptions(options, behavior = {}) {
7779
9645
  process.exitCode = 1;
7780
9646
  return null;
7781
9647
  }
9648
+ if (profile.value.credentialType === "team_api_key") {
9649
+ if (!behavior.silent) logError("Team credentials require an explicit --map-id.");
9650
+ process.exitCode = 1;
9651
+ return null;
9652
+ }
7782
9653
  if (requested && projectReference(profile.value) !== options.project) {
7783
9654
  if (!behavior.silent) {
7784
9655
  logError(
@@ -7798,9 +9669,9 @@ async function requireApiOptions(options, behavior = {}) {
7798
9669
  const allowStoredCredential = behavior.allowStoredCredential !== false && allowsStoredDeployCredential(resolveDeploySource(process.env));
7799
9670
  if (!allowStoredCredential) {
7800
9671
  if (!behavior.silent) {
7801
- logError("CI requires an explicit Map-scoped Tileflow API key.");
9672
+ logError("CI requires an explicit Tileflow credential with access to the target Map.");
7802
9673
  printNextSteps([
7803
- `Set ${pc3.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
9674
+ `Set ${pc6.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
7804
9675
  "The saved personal account session is never used in CI."
7805
9676
  ]);
7806
9677
  }
@@ -7880,7 +9751,7 @@ function printTileflowPreviewError(error) {
7880
9751
  printValidationErrors(error.issues);
7881
9752
  return;
7882
9753
  }
7883
- if (error instanceof TileflowValidationError4) {
9754
+ if (error instanceof TileflowValidationError5) {
7884
9755
  printValidationErrors(error.messages);
7885
9756
  return;
7886
9757
  }
@@ -7892,7 +9763,7 @@ function printCliError(error) {
7892
9763
  printValidationErrors(error.issues);
7893
9764
  return;
7894
9765
  }
7895
- if (error instanceof TileflowValidationError4) {
9766
+ if (error instanceof TileflowValidationError5) {
7896
9767
  printValidationErrors(error.messages);
7897
9768
  return;
7898
9769
  }
@@ -7906,17 +9777,17 @@ function printCliError(error) {
7906
9777
  }
7907
9778
  const message = error instanceof Error ? error.message : "Command failed.";
7908
9779
  if (message.includes("Cannot find module '@tileflow/core'")) {
7909
- logError(`Cannot load ${pc3.cyan("@tileflow/core")} from this project.`);
9780
+ logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
7910
9781
  printNextSteps([
7911
- `Install ${pc3.cyan("@tileflow/core")} in this project.`,
9782
+ `Install ${pc6.cyan("@tileflow/core")} in this project.`,
7912
9783
  `Run the command again from the project root.`
7913
9784
  ]);
7914
9785
  return;
7915
9786
  }
7916
9787
  if (message.includes("Cannot find module '@tileflow/maps'")) {
7917
- logError(`Cannot load ${pc3.cyan("@tileflow/maps")} from this project.`);
9788
+ logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
7918
9789
  printNextSteps([
7919
- `Install ${pc3.cyan("@tileflow/maps")} in this project.`,
9790
+ `Install ${pc6.cyan("@tileflow/maps")} in this project.`,
7920
9791
  `Run the command again from the project root.`
7921
9792
  ]);
7922
9793
  return;
@@ -7927,51 +9798,51 @@ function printCliError(error) {
7927
9798
  }
7928
9799
  }
7929
9800
  function logSuccess(message) {
7930
- console.log(`${pc3.green("\u2713")} ${message}`);
9801
+ console.log(`${pc6.green("\u2713")} ${message}`);
7931
9802
  }
7932
9803
  function logError(message) {
7933
- console.log(`${pc3.red("\u2715")} ${message}`);
9804
+ console.log(`${pc6.red("\u2715")} ${message}`);
7934
9805
  }
7935
9806
  function logWarning(message) {
7936
- console.log(`${pc3.yellow("!")} ${message}`);
9807
+ console.log(`${pc6.yellow("!")} ${message}`);
7937
9808
  }
7938
9809
  function logInfo(message) {
7939
- console.log(`${pc3.cyan("i")} ${message}`);
9810
+ console.log(`${pc6.cyan("i")} ${message}`);
7940
9811
  }
7941
9812
  function logMuted(message) {
7942
- console.log(pc3.gray(message));
9813
+ console.log(pc6.gray(message));
7943
9814
  }
7944
9815
  function printTitle(title, subtitle) {
7945
- console.log(pc3.bold(title));
9816
+ console.log(pc6.bold(title));
7946
9817
  if (subtitle) {
7947
- console.log(pc3.gray(subtitle));
9818
+ console.log(pc6.gray(subtitle));
7948
9819
  }
7949
9820
  }
7950
9821
  function printKeyValue(label, value) {
7951
- console.log(`${pc3.gray(`${label}:`.padEnd(13))} ${value}`);
9822
+ console.log(`${pc6.gray(`${label}:`.padEnd(13))} ${value}`);
7952
9823
  }
7953
9824
  function printChecks(items) {
7954
9825
  for (const item of items) {
7955
- console.log(` ${pc3.green("\u2713")} ${item}`);
9826
+ console.log(` ${pc6.green("\u2713")} ${item}`);
7956
9827
  }
7957
9828
  }
7958
9829
  function printNextSteps(steps) {
7959
9830
  if (steps.length === 0) return;
7960
9831
  console.log(`
7961
- ${pc3.bold("Next steps")}`);
9832
+ ${pc6.bold("Next steps")}`);
7962
9833
  for (const step of steps) {
7963
- console.log(` ${pc3.cyan("\u2022")} ${step}`);
9834
+ console.log(` ${pc6.cyan("\u2022")} ${step}`);
7964
9835
  }
7965
9836
  }
7966
9837
  function printValidationErrors(messages) {
7967
9838
  logError("Tileflow config has errors.");
7968
9839
  for (const message of messages) {
7969
- const path = message.path ? pc3.cyan(message.path) : pc3.cyan("(root)");
7970
- console.log(` ${pc3.red("\u2715")} ${path} ${pc3.gray("-")} ${message.message}`);
9840
+ const path = message.path ? pc6.cyan(message.path) : pc6.cyan("(root)");
9841
+ console.log(` ${pc6.red("\u2715")} ${path} ${pc6.gray("-")} ${message.message}`);
7971
9842
  }
7972
9843
  }
7973
9844
  function printAccountDetails(session) {
7974
- printKeyValue("Account", pc3.bold(session.account.name));
9845
+ printKeyValue("Account", pc6.bold(session.account.name));
7975
9846
  printKeyValue("Email", session.account.email);
7976
9847
  printKeyValue("API", link(session.apiOrigin));
7977
9848
  printKeyValue("Expires", formatDate(session.expiresAt));
@@ -8000,11 +9871,11 @@ function printDeployedMaps(maps) {
8000
9871
  const entries = Object.entries(maps);
8001
9872
  if (entries.length === 0) return;
8002
9873
  console.log(`
8003
- ${pc3.bold("Maps")}`);
9874
+ ${pc6.bold("Maps")}`);
8004
9875
  for (const [name, map] of entries) {
8005
9876
  for (const [themeName, theme] of Object.entries(map.themes)) {
8006
9877
  const label = `${name}/${themeName}`;
8007
- console.log(` ${pc3.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
9878
+ console.log(` ${pc6.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
8008
9879
  }
8009
9880
  }
8010
9881
  }
@@ -8022,7 +9893,7 @@ function plural(count, singular, pluralForm = `${singular}s`) {
8022
9893
  return `${count} ${count === 1 ? singular : pluralForm}`;
8023
9894
  }
8024
9895
  function command(value) {
8025
- return pc3.cyan(value);
9896
+ return pc6.cyan(value);
8026
9897
  }
8027
9898
  function cliInvocation(parts) {
8028
9899
  return parts.map(quoteCliArgument).join(" ");
@@ -8031,10 +9902,10 @@ function quoteCliArgument(value) {
8031
9902
  return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
8032
9903
  }
8033
9904
  function link(value) {
8034
- return pc3.cyan(value);
9905
+ return pc6.cyan(value);
8035
9906
  }
8036
9907
  function pathLabel(value) {
8037
- return pc3.cyan(value);
9908
+ return pc6.cyan(value);
8038
9909
  }
8039
9910
  function starterConfig() {
8040
9911
  return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
@@ -8080,15 +9951,15 @@ function printHostedCompatibilityIssues(issues) {
8080
9951
  }
8081
9952
  function printMapStatus(status, apiUrl) {
8082
9953
  printTitle("Tileflow status");
8083
- printKeyValue("Map", pc3.bold(status.mapId));
9954
+ printKeyValue("Map", pc6.bold(status.mapId));
8084
9955
  console.log(`
8085
- ${pc3.bold("Styles")}`);
9956
+ ${pc6.bold("Styles")}`);
8086
9957
  if (status.styles.length === 0) {
8087
9958
  logMuted(" No styles deployed.");
8088
9959
  }
8089
9960
  for (const style of status.styles) {
8090
9961
  console.log(
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))}`
9962
+ ` ${pc6.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes3(style.size).padStart(10)} ${pc6.gray(formatDate(style.uploaded))}`
8092
9963
  );
8093
9964
  }
8094
9965
  }