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/README.md +203 -650
- package/dist/index.js +2469 -598
- package/docs/agent-icon-composition.md +68 -0
- package/docs/build-artifacts.md +40 -0
- package/docs/cli-setup-and-defaults.md +56 -0
- package/docs/command-families.md +32 -0
- package/docs/coordinates.md +68 -0
- package/docs/deterministic-agent-diagnostics.md +47 -0
- package/docs/hosted-authentication-and-deploy.md +191 -0
- package/docs/inspect-vector-features.md +19 -0
- package/docs/local-visual-feedback.md +268 -0
- package/docs/native-artifacts.md +174 -0
- package/docs/read-only-icon-comparison.md +81 -0
- package/docs/team-icon-sets.md +130 -0
- package/package.json +10 -10
- package/GENERATED_OUTPUT_LICENSE.md +0 -21
- package/NOTICE +0 -9
- package/TRADEMARKS.md +0 -10
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
|
|
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
|
|
12
|
+
import pc6 from "picocolors";
|
|
13
|
+
import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
|
|
13
14
|
import {
|
|
14
|
-
auditTileflowMapThemeValues,
|
|
15
|
-
getTileflowStyleFontFaces,
|
|
16
|
-
parseResolvedTileflowMap as
|
|
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
|
|
36
|
+
assertValidTileflowConfig as assertValidTileflowConfig3,
|
|
35
37
|
defaultTileflowApiUrl,
|
|
36
38
|
defaultTileflowConfigPath,
|
|
37
39
|
defaultTileflowManifestPath,
|
|
38
|
-
getTileflowMapNames as
|
|
39
|
-
loadTileflowConfigWithInputs as
|
|
40
|
-
TileflowValidationError as
|
|
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 {
|
|
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
|
|
55
|
-
createTileflowCommandSummary as
|
|
56
|
-
createTileflowStructuredDiagnostics as
|
|
57
|
-
serializeTileflowCommandDocument as
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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(
|
|
425
|
-
else printHumanCommandFailure(
|
|
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
|
|
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(
|
|
474
|
-
else printHumanCommandFailure(
|
|
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
|
|
1219
|
+
const failure2 = createTileflowCaptureFailureJson(error, cwd);
|
|
1214
1220
|
emit({
|
|
1215
1221
|
event: "failed",
|
|
1216
1222
|
generation,
|
|
1217
|
-
code:
|
|
1218
|
-
phase:
|
|
1219
|
-
diagnostics:
|
|
1220
|
-
...
|
|
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
|
|
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(
|
|
1580
|
+
process.stderr.write(serializeTileflowCommandDocument2(failure2));
|
|
1570
1581
|
} else {
|
|
1571
|
-
console.error(`${
|
|
1572
|
-
for (const diagnostic of
|
|
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: ${
|
|
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:
|
|
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((
|
|
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 {
|
|
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 {
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2484
|
-
import { z as
|
|
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
|
|
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
|
|
3188
|
+
assertValidTileflowConfig as assertValidTileflowConfig2,
|
|
3189
|
+
getTileflowMapNames as getTileflowMapNames2,
|
|
3190
|
+
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3
|
|
2498
3191
|
} from "@tileflow/dev/config";
|
|
2499
|
-
import {
|
|
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
|
|
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
|
-
|
|
2510
|
-
tileflowIconPackageLimits as tileflowIconPackageLimits2
|
|
3203
|
+
tileflowIconSpriteIndexSchema
|
|
2511
3204
|
} from "@tileflow/core";
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
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
|
|
2564
|
-
indexTwoX
|
|
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 =
|
|
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(
|
|
3665
|
+
await mkdir3(dirname6(path), { recursive: true });
|
|
2976
3666
|
const temporaryPath = join(
|
|
2977
|
-
|
|
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 =
|
|
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 =
|
|
3011
|
-
baseline:
|
|
3012
|
-
deployedAt:
|
|
3013
|
-
deploymentId:
|
|
3014
|
-
package:
|
|
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:
|
|
3709
|
+
totalBytes: z3.number().int().positive().max(tileflowIconPackageLimits2.maxGeneratedPackageBytes)
|
|
3020
3710
|
}).strict().nullable(),
|
|
3021
|
-
version:
|
|
3711
|
+
version: z3.number().int().positive()
|
|
3022
3712
|
}).strict().nullable(),
|
|
3023
3713
|
environment: environmentSchema,
|
|
3024
|
-
schemaVersion:
|
|
3714
|
+
schemaVersion: z3.literal(1)
|
|
3025
3715
|
}).strict();
|
|
3026
|
-
var packageSummarySchema =
|
|
3716
|
+
var packageSummarySchema = z3.object({
|
|
3027
3717
|
contentHash: tileflowIconPackageContentHashSchema,
|
|
3028
|
-
iconCount:
|
|
3718
|
+
iconCount: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxIconCount),
|
|
3029
3719
|
label: tileflowIconPackageLabelSchema,
|
|
3030
|
-
totalBytes:
|
|
3720
|
+
totalBytes: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxGeneratedPackageBytes)
|
|
3031
3721
|
}).strict();
|
|
3032
|
-
var tileflowIconDiffDocumentSchema =
|
|
3033
|
-
schemaVersion:
|
|
3722
|
+
var tileflowIconDiffDocumentSchema = z3.object({
|
|
3723
|
+
schemaVersion: z3.literal(1),
|
|
3034
3724
|
environment: environmentSchema,
|
|
3035
|
-
baseline:
|
|
3036
|
-
deploymentId:
|
|
3725
|
+
baseline: z3.object({
|
|
3726
|
+
deploymentId: z3.string(),
|
|
3037
3727
|
package: packageSummarySchema.nullable(),
|
|
3038
|
-
version:
|
|
3728
|
+
version: z3.number().int().positive()
|
|
3039
3729
|
}).strict().nullable(),
|
|
3040
|
-
proposed:
|
|
3730
|
+
proposed: z3.object({
|
|
3041
3731
|
package: packageSummarySchema.nullable()
|
|
3042
3732
|
}).strict(),
|
|
3043
|
-
icons:
|
|
3044
|
-
added:
|
|
3045
|
-
removed:
|
|
3046
|
-
modified:
|
|
3047
|
-
unchangedCount:
|
|
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:
|
|
3050
|
-
before:
|
|
3051
|
-
after:
|
|
3052
|
-
delta:
|
|
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:
|
|
3055
|
-
hasChanges:
|
|
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
|
-
() =>
|
|
3769
|
+
() => loadTileflowConfigWithInputs3(options.config)
|
|
3080
3770
|
);
|
|
3081
3771
|
const project = loaded.project;
|
|
3082
|
-
|
|
3083
|
-
const mapNames =
|
|
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
|
|
3095
|
-
|
|
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
|
|
3933
|
+
import { dirname as dirname8 } from "path";
|
|
3239
3934
|
import { compareCodeUnits as compareCodeUnits6 } from "@tileflow/core";
|
|
3240
3935
|
import {
|
|
3241
|
-
getTileflowMapNames as
|
|
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-
|
|
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:
|
|
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 =
|
|
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:
|
|
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 &&
|
|
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: "
|
|
3310
|
-
|
|
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
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
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
|
|
3330
|
-
return
|
|
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
|
|
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(
|
|
5016
|
+
if (options.json) process.stderr.write(serializeTileflowCommandDocument3(failure2));
|
|
3386
5017
|
else {
|
|
3387
|
-
console.error(`${
|
|
3388
|
-
console.error(`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(`${
|
|
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(`${
|
|
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
|
|
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
|
|
5159
|
+
if (options.json) return emitJson2(document);
|
|
3473
5160
|
if (!result.items.length) {
|
|
3474
|
-
console.log(
|
|
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 ?
|
|
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
|
|
3532
|
-
console.log(`${
|
|
3533
|
-
console.log(
|
|
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
|
|
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(`${
|
|
3556
|
-
console.log(project.archivedAt ?
|
|
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
|
|
3583
|
-
console.log(`${
|
|
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",
|
|
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
|
-
|
|
5391
|
+
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
3705
5392
|
} else {
|
|
3706
|
-
console.error(`${
|
|
5393
|
+
console.error(`${pc4.red("\u2715")} ${message}`);
|
|
3707
5394
|
}
|
|
3708
5395
|
process.exitCode = 1;
|
|
3709
5396
|
}
|
|
3710
|
-
function
|
|
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
|
|
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
|
|
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
|
|
5544
|
+
import { open as open4, stat } from "fs/promises";
|
|
3749
5545
|
import { z as z5 } from "zod";
|
|
3750
|
-
import { serializeCanonicalJson as
|
|
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
|
|
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(
|
|
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
|
-
|
|
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((
|
|
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(`${
|
|
6193
|
+
process.stdout.write(`${serializeCanonicalJson5(inspection)}
|
|
4398
6194
|
`);
|
|
4399
6195
|
return;
|
|
4400
6196
|
}
|
|
4401
|
-
console.log(`${
|
|
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(`${
|
|
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
|
-
`${
|
|
6216
|
+
`${serializeCanonicalJson5({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
|
|
4421
6217
|
`
|
|
4422
6218
|
);
|
|
4423
6219
|
} else {
|
|
4424
|
-
console.error(`${
|
|
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
|
|
6302
|
+
if (options.json) return emitJson(document);
|
|
4507
6303
|
console.log(
|
|
4508
|
-
`${
|
|
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
|
|
4528
|
-
if (items.length === 0) return console.log(
|
|
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(` ${
|
|
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
|
|
6352
|
+
if (options.json) return emitJson(document);
|
|
4557
6353
|
const item = parsed.tileset;
|
|
4558
|
-
console.log(`${
|
|
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
|
|
4609
|
-
console.log(`${
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
4828
|
-
import { dirname as
|
|
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
|
|
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
|
|
4847
|
-
import { basename as basename3, dirname as
|
|
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
|
|
6557
|
+
compareCodeUnits as compareCodeUnits8,
|
|
4857
6558
|
normalizeTileflowCaptureScene as normalizeTileflowCaptureScene2,
|
|
4858
|
-
parseResolvedTileflowMap as
|
|
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(
|
|
5161
|
-
const rightPath = portablePathIdentity(
|
|
5162
|
-
const left = await
|
|
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
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
5696
|
-
rightPath:
|
|
5697
|
-
leftReceiptPath:
|
|
5698
|
-
rightReceiptPath:
|
|
5699
|
-
diffPath: includeDiff ?
|
|
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,
|
|
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,
|
|
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:
|
|
5718
|
-
{ kind: "file", path:
|
|
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) =>
|
|
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(
|
|
7494
|
+
await assertNoSymlinkComponents(dirname10(inventoryPath), boundary);
|
|
5794
7495
|
let info;
|
|
5795
7496
|
try {
|
|
5796
|
-
info = await
|
|
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 =
|
|
5817
|
-
await assertNoSymlinkComponents(
|
|
7517
|
+
const path = resolve6(assetDirectory, asset.path);
|
|
7518
|
+
await assertNoSymlinkComponents(dirname10(path), boundary);
|
|
5818
7519
|
let assetInfo;
|
|
5819
7520
|
try {
|
|
5820
|
-
assetInfo = await
|
|
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(
|
|
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(
|
|
5881
|
-
const expected = [...keys].sort(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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}`) && !
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
6089
|
-
const managedDiffPath =
|
|
6090
|
-
const reportPath =
|
|
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 =
|
|
6180
|
-
const diffPath = comparison.diffPng ?
|
|
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:
|
|
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 ? [] : [
|
|
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(
|
|
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) =>
|
|
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 =
|
|
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(
|
|
8044
|
+
await assertNoSymlinkComponents(dirname11(path), boundary);
|
|
6344
8045
|
let info;
|
|
6345
8046
|
try {
|
|
6346
|
-
info = await
|
|
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
|
|
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 =
|
|
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 [
|
|
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
|
|
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
|
|
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}`) && !
|
|
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.").
|
|
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 =
|
|
6453
|
-
const ignorePath =
|
|
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",
|
|
8172
|
+
printKeyValue("Map", pc6.bold("madrid"));
|
|
6470
8173
|
printNextSteps([
|
|
6471
|
-
`Install ${
|
|
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(
|
|
6565
|
-
|
|
6566
|
-
if (options.
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
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
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
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
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
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
|
-
|
|
6646
|
-
|
|
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
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
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
|
-
|
|
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
|
-
).
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
()
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
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
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
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({
|
|
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
|
-
() =>
|
|
8745
|
+
() => loadTileflowConfigWithInputs4(options.config)
|
|
6913
8746
|
);
|
|
6914
8747
|
const project = loaded.project;
|
|
6915
|
-
const baseDirectory =
|
|
6916
|
-
|
|
6917
|
-
const configuredMapNames =
|
|
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
|
|
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 =
|
|
8777
|
+
const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
|
|
6944
8778
|
apiBaseUrl: apiUrl,
|
|
6945
8779
|
mapAssets: preflightMapAssets
|
|
6946
8780
|
});
|
|
6947
|
-
const preflightLocalTilesets = await
|
|
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
|
|
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 ${
|
|
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 =
|
|
8854
|
+
const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
|
|
7021
8855
|
apiBaseUrl: api.apiUrl,
|
|
7022
8856
|
mapAssets: hostedMapAssets
|
|
7023
8857
|
});
|
|
7024
|
-
const hostedLocalTilesets = await
|
|
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
|
|
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
|
|
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
|
|
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 ${
|
|
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 =
|
|
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 =
|
|
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 ${
|
|
9079
|
+
logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
|
|
7237
9080
|
} else {
|
|
7238
|
-
logSuccess(`Published ${
|
|
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
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7538
|
-
const defaultRequestedPath =
|
|
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}`) ||
|
|
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",
|
|
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(`${
|
|
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 =
|
|
9496
|
+
const outputPath = resolve8(process.cwd(), manifestPath);
|
|
7644
9497
|
await writeAtomicFile({
|
|
7645
|
-
boundaryPath:
|
|
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
|
-
{
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
9672
|
+
logError("CI requires an explicit Tileflow credential with access to the target Map.");
|
|
7802
9673
|
printNextSteps([
|
|
7803
|
-
`Set ${
|
|
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
|
|
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
|
|
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 ${
|
|
9780
|
+
logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
|
|
7910
9781
|
printNextSteps([
|
|
7911
|
-
`Install ${
|
|
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 ${
|
|
9788
|
+
logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
|
|
7918
9789
|
printNextSteps([
|
|
7919
|
-
`Install ${
|
|
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(`${
|
|
9801
|
+
console.log(`${pc6.green("\u2713")} ${message}`);
|
|
7931
9802
|
}
|
|
7932
9803
|
function logError(message) {
|
|
7933
|
-
console.log(`${
|
|
9804
|
+
console.log(`${pc6.red("\u2715")} ${message}`);
|
|
7934
9805
|
}
|
|
7935
9806
|
function logWarning(message) {
|
|
7936
|
-
console.log(`${
|
|
9807
|
+
console.log(`${pc6.yellow("!")} ${message}`);
|
|
7937
9808
|
}
|
|
7938
9809
|
function logInfo(message) {
|
|
7939
|
-
console.log(`${
|
|
9810
|
+
console.log(`${pc6.cyan("i")} ${message}`);
|
|
7940
9811
|
}
|
|
7941
9812
|
function logMuted(message) {
|
|
7942
|
-
console.log(
|
|
9813
|
+
console.log(pc6.gray(message));
|
|
7943
9814
|
}
|
|
7944
9815
|
function printTitle(title, subtitle) {
|
|
7945
|
-
console.log(
|
|
9816
|
+
console.log(pc6.bold(title));
|
|
7946
9817
|
if (subtitle) {
|
|
7947
|
-
console.log(
|
|
9818
|
+
console.log(pc6.gray(subtitle));
|
|
7948
9819
|
}
|
|
7949
9820
|
}
|
|
7950
9821
|
function printKeyValue(label, value) {
|
|
7951
|
-
console.log(`${
|
|
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(` ${
|
|
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
|
-
${
|
|
9832
|
+
${pc6.bold("Next steps")}`);
|
|
7962
9833
|
for (const step of steps) {
|
|
7963
|
-
console.log(` ${
|
|
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 ?
|
|
7970
|
-
console.log(` ${
|
|
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",
|
|
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
|
-
${
|
|
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(` ${
|
|
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
|
|
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
|
|
9905
|
+
return pc6.cyan(value);
|
|
8035
9906
|
}
|
|
8036
9907
|
function pathLabel(value) {
|
|
8037
|
-
return
|
|
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",
|
|
9954
|
+
printKeyValue("Map", pc6.bold(status.mapId));
|
|
8084
9955
|
console.log(`
|
|
8085
|
-
${
|
|
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
|
-
` ${
|
|
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
|
}
|