tileflow 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +49 -0
- package/dist/index.js +2155 -675
- package/docs/agent-icon-composition.md +27 -12
- package/docs/build-artifacts.md +7 -0
- package/docs/command-families.md +13 -1
- package/docs/hosted-authentication-and-deploy.md +3 -2
- package/docs/native-artifacts.md +169 -0
- package/docs/team-icon-sets.md +121 -0
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -7,55 +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
13
|
import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
|
|
14
14
|
import {
|
|
15
|
-
auditTileflowMapThemeValues,
|
|
16
|
-
getTileflowStyleFontFaces,
|
|
17
|
-
parseResolvedTileflowMap as
|
|
15
|
+
auditTileflowMapThemeValues as auditTileflowMapThemeValues2,
|
|
16
|
+
getTileflowStyleFontFaces as getTileflowStyleFontFaces2,
|
|
17
|
+
parseResolvedTileflowMap as parseResolvedTileflowMap5,
|
|
18
18
|
tileflowMapIdSchema as tileflowMapIdSchema2
|
|
19
19
|
} from "@tileflow/core";
|
|
20
20
|
import {
|
|
21
|
-
createTileflowMapBuildManifest
|
|
21
|
+
createTileflowMapBuildManifest as createTileflowMapBuildManifest2
|
|
22
22
|
} from "@tileflow/core/build";
|
|
23
23
|
import {
|
|
24
|
-
parseTileflowRuntimeManifest
|
|
24
|
+
parseTileflowRuntimeManifest as parseTileflowRuntimeManifest2
|
|
25
25
|
} from "@tileflow/core/manifest";
|
|
26
|
+
import { resolveTileflowRenderer as resolveTileflowRenderer2 } from "@tileflow/core/native-profile";
|
|
26
27
|
import {
|
|
27
28
|
createTileflowArtifactDiagnostics as createTileflowArtifactDiagnostics3,
|
|
28
29
|
createTileflowArtifactSession as createTileflowArtifactSession3,
|
|
29
|
-
createTileflowBuildProvenance,
|
|
30
|
-
createTileflowStyles,
|
|
31
|
-
prepareTileflowLocalTilesets,
|
|
30
|
+
createTileflowBuildProvenance as createTileflowBuildProvenance2,
|
|
31
|
+
createTileflowStyles as createTileflowStyles2,
|
|
32
|
+
prepareTileflowLocalTilesets as prepareTileflowLocalTilesets2,
|
|
32
33
|
writeTileflowBuildArtifacts
|
|
33
34
|
} from "@tileflow/dev/artifacts";
|
|
34
35
|
import {
|
|
35
|
-
assertValidTileflowConfig as
|
|
36
|
+
assertValidTileflowConfig as assertValidTileflowConfig3,
|
|
36
37
|
defaultTileflowApiUrl,
|
|
37
38
|
defaultTileflowConfigPath,
|
|
38
39
|
defaultTileflowManifestPath,
|
|
39
|
-
getTileflowMapNames as
|
|
40
|
-
loadTileflowConfigWithInputs as
|
|
41
|
-
TileflowValidationError as
|
|
40
|
+
getTileflowMapNames as getTileflowMapNames5,
|
|
41
|
+
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs4,
|
|
42
|
+
TileflowValidationError as TileflowValidationError5
|
|
42
43
|
} from "@tileflow/dev/config";
|
|
43
44
|
import {
|
|
44
45
|
bindTileflowStyleFontBundle,
|
|
45
|
-
prepareTileflowStyleFonts,
|
|
46
|
+
prepareTileflowStyleFonts as prepareTileflowStyleFonts2,
|
|
46
47
|
TileflowFontCompilationError
|
|
47
48
|
} from "@tileflow/dev/fonts";
|
|
48
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
compileTileflowIconPackages as compileTileflowIconPackages3,
|
|
51
|
+
TileflowIconCompilationError as TileflowIconCompilationError3
|
|
52
|
+
} from "@tileflow/dev/icons";
|
|
49
53
|
import { resolveTileflowPreview } from "@tileflow/dev/preview";
|
|
50
54
|
import {
|
|
51
55
|
createTileflowComparisonRequestHandler,
|
|
52
|
-
createTileflowDevRequestHandler
|
|
56
|
+
createTileflowDevRequestHandler as createTileflowDevRequestHandler2
|
|
53
57
|
} from "@tileflow/dev/server";
|
|
54
58
|
import {
|
|
55
|
-
createTileflowCommandFailureDocument as
|
|
56
|
-
createTileflowCommandSummary as
|
|
57
|
-
createTileflowStructuredDiagnostics as
|
|
58
|
-
serializeTileflowCommandDocument as
|
|
59
|
+
createTileflowCommandFailureDocument as createTileflowCommandFailureDocument5,
|
|
60
|
+
createTileflowCommandSummary as createTileflowCommandSummary3,
|
|
61
|
+
createTileflowStructuredDiagnostics as createTileflowStructuredDiagnostics3,
|
|
62
|
+
serializeTileflowCommandDocument as serializeTileflowCommandDocument5,
|
|
59
63
|
TileflowStyleValidationError as TileflowStyleValidationError2
|
|
60
64
|
} from "@tileflow/dev/validation";
|
|
61
65
|
|
|
@@ -382,14 +386,14 @@ async function runExplain(options, compileStyle) {
|
|
|
382
386
|
});
|
|
383
387
|
if (!selection) throw new Error("Explain selection was not resolved.");
|
|
384
388
|
if (!output.result.ok) {
|
|
385
|
-
const
|
|
389
|
+
const failure2 = createTileflowCommandFailureDocument(
|
|
386
390
|
"explain",
|
|
387
391
|
{ diagnostics: output.structuredDiagnostics },
|
|
388
392
|
process.cwd(),
|
|
389
393
|
{ code: "COMPILATION_FAILED", phase: "compilation" }
|
|
390
394
|
);
|
|
391
395
|
const document2 = {
|
|
392
|
-
...
|
|
396
|
+
...failure2,
|
|
393
397
|
authoringManifestSchemaVersion: tileflowAuthoringManifestSchemaVersion,
|
|
394
398
|
selection,
|
|
395
399
|
compilation: output.compilation
|
|
@@ -419,12 +423,12 @@ async function runExplain(options, compileStyle) {
|
|
|
419
423
|
if (options.json) process.stdout.write(serializeTileflowCommandDocument(document));
|
|
420
424
|
else printHumanExplain(document);
|
|
421
425
|
} catch (error) {
|
|
422
|
-
const
|
|
426
|
+
const failure2 = createTileflowCommandFailureDocument("explain", error, process.cwd(), {
|
|
423
427
|
code: "EXPLAIN_FAILED",
|
|
424
428
|
phase: "compilation"
|
|
425
429
|
});
|
|
426
|
-
if (options.json) process.stderr.write(serializeTileflowCommandDocument(
|
|
427
|
-
else printHumanCommandFailure(
|
|
430
|
+
if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure2));
|
|
431
|
+
else printHumanCommandFailure(failure2);
|
|
428
432
|
process.exitCode = 1;
|
|
429
433
|
}
|
|
430
434
|
}
|
|
@@ -468,12 +472,12 @@ async function runSemanticDiff(options, defaultConfigPath2) {
|
|
|
468
472
|
if (options.json) process.stdout.write(serializeTileflowCommandDocument(document));
|
|
469
473
|
else printHumanSemanticDiff(difference);
|
|
470
474
|
} catch (error) {
|
|
471
|
-
const
|
|
475
|
+
const failure2 = createTileflowCommandFailureDocument("semantic-diff", error, process.cwd(), {
|
|
472
476
|
code: "SEMANTIC_DIFF_FAILED",
|
|
473
477
|
phase: "semantic-diff"
|
|
474
478
|
});
|
|
475
|
-
if (options.json) process.stderr.write(serializeTileflowCommandDocument(
|
|
476
|
-
else printHumanCommandFailure(
|
|
479
|
+
if (options.json) process.stderr.write(serializeTileflowCommandDocument(failure2));
|
|
480
|
+
else printHumanCommandFailure(failure2);
|
|
477
481
|
process.exitCode = 1;
|
|
478
482
|
}
|
|
479
483
|
}
|
|
@@ -1212,14 +1216,14 @@ async function runCaptureWatchCommand(positionalScenes, options) {
|
|
|
1212
1216
|
} catch (error) {
|
|
1213
1217
|
if (signal.aborted || stopped) return;
|
|
1214
1218
|
unresolvedFailure = true;
|
|
1215
|
-
const
|
|
1219
|
+
const failure2 = createTileflowCaptureFailureJson(error, cwd);
|
|
1216
1220
|
emit({
|
|
1217
1221
|
event: "failed",
|
|
1218
1222
|
generation,
|
|
1219
|
-
code:
|
|
1220
|
-
phase:
|
|
1221
|
-
diagnostics:
|
|
1222
|
-
...
|
|
1223
|
+
code: failure2.code,
|
|
1224
|
+
phase: failure2.phase,
|
|
1225
|
+
diagnostics: failure2.diagnostics,
|
|
1226
|
+
...failure2.resources ? { resources: failure2.resources } : {}
|
|
1223
1227
|
});
|
|
1224
1228
|
}
|
|
1225
1229
|
};
|
|
@@ -1545,6 +1549,11 @@ function runGit(cwd, args) {
|
|
|
1545
1549
|
});
|
|
1546
1550
|
}
|
|
1547
1551
|
|
|
1552
|
+
// src/cli-version.ts
|
|
1553
|
+
function isRootCliVersionRequest(arguments_) {
|
|
1554
|
+
return arguments_.length === 1 && (arguments_[0] === "--version" || arguments_[0] === "-V");
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1548
1557
|
// src/config-inspect-command.ts
|
|
1549
1558
|
import { inspectTileflowConfig } from "@tileflow/dev/inspect";
|
|
1550
1559
|
import {
|
|
@@ -1563,18 +1572,18 @@ function registerConfigInspectCommand(inspect, dependencies) {
|
|
|
1563
1572
|
}
|
|
1564
1573
|
printHumanConfigInspection(document);
|
|
1565
1574
|
} catch (error) {
|
|
1566
|
-
const
|
|
1575
|
+
const failure2 = createTileflowCommandFailureDocument2("inspect", error, process.cwd(), {
|
|
1567
1576
|
code: "INSPECTION_FAILED",
|
|
1568
1577
|
phase: "config-inspection"
|
|
1569
1578
|
});
|
|
1570
1579
|
if (options.json) {
|
|
1571
|
-
process.stderr.write(serializeTileflowCommandDocument2(
|
|
1580
|
+
process.stderr.write(serializeTileflowCommandDocument2(failure2));
|
|
1572
1581
|
} else {
|
|
1573
|
-
console.error(`${
|
|
1574
|
-
for (const diagnostic of
|
|
1582
|
+
console.error(`${failure2.message} [${failure2.code}; ${failure2.phase}]`);
|
|
1583
|
+
for (const diagnostic of failure2.diagnostics.slice(1)) {
|
|
1575
1584
|
console.error(`- ${diagnostic.path || "(root)"}: ${diagnostic.message}`);
|
|
1576
1585
|
}
|
|
1577
|
-
console.error(`Suggestion: ${
|
|
1586
|
+
console.error(`Suggestion: ${failure2.suggestion}`);
|
|
1578
1587
|
}
|
|
1579
1588
|
process.exitCode = 1;
|
|
1580
1589
|
}
|
|
@@ -1654,19 +1663,19 @@ function registerCoordinatesCommands(program2, dependencies) {
|
|
|
1654
1663
|
coordinates,
|
|
1655
1664
|
"search",
|
|
1656
1665
|
dependencies,
|
|
1657
|
-
(options) =>
|
|
1666
|
+
(options) => parseCoordinatesRequest("search", { query: options.query })
|
|
1658
1667
|
);
|
|
1659
1668
|
registerOperation(
|
|
1660
1669
|
coordinates,
|
|
1661
1670
|
"describe",
|
|
1662
1671
|
dependencies,
|
|
1663
|
-
(options) =>
|
|
1672
|
+
(options) => parseCoordinatesRequest("describe", { id: options.id })
|
|
1664
1673
|
);
|
|
1665
1674
|
registerOperation(
|
|
1666
1675
|
coordinates,
|
|
1667
1676
|
"operations",
|
|
1668
1677
|
dependencies,
|
|
1669
|
-
(options) =>
|
|
1678
|
+
(options) => parseCoordinatesRequest("operations", { from: options.from, to: options.to })
|
|
1670
1679
|
);
|
|
1671
1680
|
registerOperation(
|
|
1672
1681
|
coordinates,
|
|
@@ -1765,9 +1774,6 @@ function hasConvenienceOptions(command2, options) {
|
|
|
1765
1774
|
const transform = options;
|
|
1766
1775
|
return transform.from !== void 0 || transform.to !== void 0 || transform.positions !== void 0;
|
|
1767
1776
|
}
|
|
1768
|
-
function parseRequest(command2, options, request) {
|
|
1769
|
-
return parseCoordinatesRequest(command2, request);
|
|
1770
|
-
}
|
|
1771
1777
|
function parseTransformRequest(options) {
|
|
1772
1778
|
let positions;
|
|
1773
1779
|
try {
|
|
@@ -1812,7 +1818,7 @@ async function readCoordinatesInput(input, signal) {
|
|
|
1812
1818
|
async function readStandardInput(signal) {
|
|
1813
1819
|
const chunks = [];
|
|
1814
1820
|
let length = 0;
|
|
1815
|
-
return new Promise((
|
|
1821
|
+
return new Promise((resolve9, reject) => {
|
|
1816
1822
|
const finish = (result) => {
|
|
1817
1823
|
process.stdin.removeListener("data", onData);
|
|
1818
1824
|
process.stdin.removeListener("end", onEnd);
|
|
@@ -1834,7 +1840,7 @@ async function readStandardInput(signal) {
|
|
|
1834
1840
|
}
|
|
1835
1841
|
chunks.push(bytes);
|
|
1836
1842
|
};
|
|
1837
|
-
const onEnd = () => finish(() =>
|
|
1843
|
+
const onEnd = () => finish(() => resolve9(decodeInput(Buffer.concat(chunks))));
|
|
1838
1844
|
const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
|
|
1839
1845
|
if (signal.aborted) {
|
|
1840
1846
|
onAbort();
|
|
@@ -1892,10 +1898,10 @@ function serializeCoordinatesFailure(error, command2) {
|
|
|
1892
1898
|
if (!(error instanceof CoordinatesContractError)) return void 0;
|
|
1893
1899
|
const parsed = coordinatesFailureSchema.safeParse(error.toJSON());
|
|
1894
1900
|
if (!parsed.success) return void 0;
|
|
1895
|
-
const
|
|
1901
|
+
const failure2 = parsed.data;
|
|
1896
1902
|
return new CoordinatesContractError({
|
|
1897
|
-
...
|
|
1898
|
-
command:
|
|
1903
|
+
...failure2,
|
|
1904
|
+
command: failure2.command ?? command2 ?? null
|
|
1899
1905
|
}).toJSON();
|
|
1900
1906
|
}
|
|
1901
1907
|
function serializeSetupFailure(error) {
|
|
@@ -2217,6 +2223,7 @@ import { tileflowIconPackageLimits } from "@tileflow/core";
|
|
|
2217
2223
|
var maximumHostedResponseBytes = 1024 * 1024;
|
|
2218
2224
|
var safeTextSchema = z2.string().min(1).max(300).refine((value) => !/[\p{Cc}]/u.test(value));
|
|
2219
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 !== ".."));
|
|
2220
2227
|
var publicHttpUrlSchema = z2.url().max(2048).refine((value) => {
|
|
2221
2228
|
const url = new URL(value);
|
|
2222
2229
|
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && !url.hash;
|
|
@@ -2252,7 +2259,7 @@ var hostedFontBundleResponseSchema = z2.object({
|
|
|
2252
2259
|
});
|
|
2253
2260
|
var hostedStatusStyleSchema = z2.object({
|
|
2254
2261
|
environment: safeTextSchema,
|
|
2255
|
-
key:
|
|
2262
|
+
key: safeStorageKeySchema,
|
|
2256
2263
|
mapId: safeIdentifierSchema,
|
|
2257
2264
|
size: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
2258
2265
|
uploaded: z2.iso.datetime({ offset: true })
|
|
@@ -2343,7 +2350,7 @@ async function startDeviceAuthorization(appUrl, body, options = {}) {
|
|
|
2343
2350
|
async function pollDeviceToken(appUrl, authorization, input, options = {}) {
|
|
2344
2351
|
const expiresAt = Date.now() + authorization.expiresIn * 1e3;
|
|
2345
2352
|
const intervalMs = Math.max(authorization.interval, 1) * 1e3;
|
|
2346
|
-
const wait = input.sleep ?? ((milliseconds) => new Promise((
|
|
2353
|
+
const wait = input.sleep ?? ((milliseconds) => new Promise((resolve9) => setTimeout(resolve9, milliseconds)));
|
|
2347
2354
|
while (Date.now() < expiresAt) {
|
|
2348
2355
|
await wait(intervalMs);
|
|
2349
2356
|
const response = await requestHostedJson(
|
|
@@ -2673,7 +2680,7 @@ async function withHostedResponse(apiUrl, path, init, options, consume) {
|
|
|
2673
2680
|
}
|
|
2674
2681
|
function raceWithAbort(operation, signal) {
|
|
2675
2682
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
2676
|
-
return new Promise((
|
|
2683
|
+
return new Promise((resolve9, reject) => {
|
|
2677
2684
|
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
2678
2685
|
const onAbort = () => {
|
|
2679
2686
|
cleanup();
|
|
@@ -2683,7 +2690,7 @@ function raceWithAbort(operation, signal) {
|
|
|
2683
2690
|
operation.then(
|
|
2684
2691
|
(value) => {
|
|
2685
2692
|
cleanup();
|
|
2686
|
-
|
|
2693
|
+
resolve9(value);
|
|
2687
2694
|
},
|
|
2688
2695
|
(error) => {
|
|
2689
2696
|
cleanup();
|
|
@@ -2801,6 +2808,33 @@ function safeTransportError(error, fallback) {
|
|
|
2801
2808
|
return fallback;
|
|
2802
2809
|
}
|
|
2803
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
|
+
|
|
2804
2838
|
// src/hosted-preflight.ts
|
|
2805
2839
|
import {
|
|
2806
2840
|
compareCodeUnits as compareCodeUnits4,
|
|
@@ -2865,44 +2899,312 @@ function cloneStyle(style) {
|
|
|
2865
2899
|
return JSON.parse(JSON.stringify(style));
|
|
2866
2900
|
}
|
|
2867
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
|
+
|
|
2868
3175
|
// src/icon-diff-command.ts
|
|
2869
|
-
import { dirname as
|
|
2870
|
-
import { z as
|
|
3176
|
+
import { dirname as dirname7, resolve as resolve4 } from "path";
|
|
3177
|
+
import { z as z3 } from "zod";
|
|
2871
3178
|
import {
|
|
2872
3179
|
diffTileflowIconPackageManifests,
|
|
2873
3180
|
hashTileflowIconPackageManifest,
|
|
2874
3181
|
tileflowIconPackageContentHashSchema,
|
|
2875
3182
|
tileflowIconPackageLabelSchema,
|
|
2876
|
-
tileflowIconPackageLimits as
|
|
3183
|
+
tileflowIconPackageLimits as tileflowIconPackageLimits2,
|
|
2877
3184
|
tileflowIconPackageManifestSchema,
|
|
2878
3185
|
tileflowMapIdSchema
|
|
2879
3186
|
} from "@tileflow/core";
|
|
2880
3187
|
import {
|
|
2881
|
-
assertValidTileflowConfig,
|
|
2882
|
-
getTileflowMapNames,
|
|
2883
|
-
loadTileflowConfigWithInputs as
|
|
3188
|
+
assertValidTileflowConfig as assertValidTileflowConfig2,
|
|
3189
|
+
getTileflowMapNames as getTileflowMapNames2,
|
|
3190
|
+
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3
|
|
2884
3191
|
} from "@tileflow/dev/config";
|
|
2885
|
-
import {
|
|
3192
|
+
import {
|
|
3193
|
+
compileTileflowIconPackages as compileTileflowIconPackages2
|
|
3194
|
+
} from "@tileflow/dev/icons";
|
|
2886
3195
|
|
|
2887
3196
|
// src/icon-diff-report.ts
|
|
2888
3197
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2889
3198
|
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, unlink as unlink3, writeFile } from "fs/promises";
|
|
2890
|
-
import { basename as basename2, dirname as
|
|
2891
|
-
import { z as z3 } from "zod";
|
|
3199
|
+
import { basename as basename2, dirname as dirname6, join } from "path";
|
|
2892
3200
|
import {
|
|
2893
3201
|
compareCodeUnits as compareCodeUnits5,
|
|
2894
3202
|
sha256Hex,
|
|
2895
|
-
|
|
2896
|
-
tileflowIconPackageLimits as tileflowIconPackageLimits2
|
|
3203
|
+
tileflowIconSpriteIndexSchema
|
|
2897
3204
|
} from "@tileflow/core";
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
width: z3.number().int().positive().max(tileflowIconPackageLimits2.maxAtlasDimension),
|
|
2902
|
-
x: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxAtlasDimension),
|
|
2903
|
-
y: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxAtlasDimension)
|
|
2904
|
-
}).strict();
|
|
2905
|
-
var spriteIndexSchema = z3.record(tileflowIconIdSchema, spriteEntrySchema);
|
|
3205
|
+
import {
|
|
3206
|
+
renderTileflowIconAtlasPreview
|
|
3207
|
+
} from "@tileflow/dev/icons";
|
|
2906
3208
|
async function writeIconDiffReport(input) {
|
|
2907
3209
|
const [before, after] = await Promise.all([
|
|
2908
3210
|
input.baseline?.package ? loadRemoteSprite(input.baseline.package) : null,
|
|
@@ -2940,18 +3242,20 @@ function loadCompiledSprite(iconPackage) {
|
|
|
2940
3242
|
iconPackage.manifest.sprites.twoX
|
|
2941
3243
|
);
|
|
2942
3244
|
}
|
|
2943
|
-
function reportSpriteFromFiles(files, oneX, twoX) {
|
|
3245
|
+
async function reportSpriteFromFiles(files, oneX, twoX) {
|
|
2944
3246
|
const oneXJson = requiredFile(files, "sprite.json");
|
|
2945
3247
|
const oneXPng = requiredFile(files, "sprite.png");
|
|
2946
3248
|
const twoXJson = requiredFile(files, "sprite@2x.json");
|
|
2947
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);
|
|
2948
3252
|
return {
|
|
2949
|
-
indexOneX
|
|
2950
|
-
indexTwoX
|
|
2951
|
-
oneXDataUrl: pngDataUrl(oneXPng),
|
|
3253
|
+
indexOneX,
|
|
3254
|
+
indexTwoX,
|
|
3255
|
+
oneXDataUrl: pngDataUrl(await renderTileflowIconAtlasPreview(oneXPng, indexOneX)),
|
|
2952
3256
|
oneXHeight: oneX.height,
|
|
2953
3257
|
oneXWidth: oneX.width,
|
|
2954
|
-
twoXDataUrl: pngDataUrl(twoXPng),
|
|
3258
|
+
twoXDataUrl: pngDataUrl(await renderTileflowIconAtlasPreview(twoXPng, indexTwoX)),
|
|
2955
3259
|
twoXHeight: twoX.height,
|
|
2956
3260
|
twoXWidth: twoX.width
|
|
2957
3261
|
};
|
|
@@ -2963,7 +3267,7 @@ function parseIndex(source, pixelRatio, atlasWidth, atlasHeight) {
|
|
|
2963
3267
|
} catch {
|
|
2964
3268
|
throw new Error("Sprite report index is not valid UTF-8 JSON");
|
|
2965
3269
|
}
|
|
2966
|
-
const parsed =
|
|
3270
|
+
const parsed = tileflowIconSpriteIndexSchema.safeParse(value);
|
|
2967
3271
|
if (!parsed.success) {
|
|
2968
3272
|
throw new Error("Sprite report index does not match the MapLibre schema");
|
|
2969
3273
|
}
|
|
@@ -3358,9 +3662,9 @@ async function writeAtomicReport(path, html, force) {
|
|
|
3358
3662
|
} catch (error) {
|
|
3359
3663
|
if (!isMissingFile3(error)) throw error;
|
|
3360
3664
|
}
|
|
3361
|
-
await mkdir3(
|
|
3665
|
+
await mkdir3(dirname6(path), { recursive: true });
|
|
3362
3666
|
const temporaryPath = join(
|
|
3363
|
-
|
|
3667
|
+
dirname6(path),
|
|
3364
3668
|
`.${basename2(path)}.${process.pid}.${randomUUID2()}.tmp`
|
|
3365
3669
|
);
|
|
3366
3670
|
try {
|
|
@@ -3389,59 +3693,59 @@ function isMissingFile3(error) {
|
|
|
3389
3693
|
|
|
3390
3694
|
// src/icon-diff-command.ts
|
|
3391
3695
|
var environmentSchema = tileflowMapIdSchema;
|
|
3392
|
-
var publicHttpUrlSchema2 =
|
|
3696
|
+
var publicHttpUrlSchema2 = z3.url().max(2048).refine((value) => {
|
|
3393
3697
|
const url = new URL(value);
|
|
3394
3698
|
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
|
|
3395
3699
|
});
|
|
3396
|
-
var iconPackageBaselineResponseSchema =
|
|
3397
|
-
baseline:
|
|
3398
|
-
deployedAt:
|
|
3399
|
-
deploymentId:
|
|
3400
|
-
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({
|
|
3401
3705
|
contentHash: tileflowIconPackageContentHashSchema,
|
|
3402
3706
|
label: tileflowIconPackageLabelSchema,
|
|
3403
3707
|
manifest: tileflowIconPackageManifestSchema,
|
|
3404
3708
|
spriteUrl: publicHttpUrlSchema2,
|
|
3405
|
-
totalBytes:
|
|
3709
|
+
totalBytes: z3.number().int().positive().max(tileflowIconPackageLimits2.maxGeneratedPackageBytes)
|
|
3406
3710
|
}).strict().nullable(),
|
|
3407
|
-
version:
|
|
3711
|
+
version: z3.number().int().positive()
|
|
3408
3712
|
}).strict().nullable(),
|
|
3409
3713
|
environment: environmentSchema,
|
|
3410
|
-
schemaVersion:
|
|
3714
|
+
schemaVersion: z3.literal(1)
|
|
3411
3715
|
}).strict();
|
|
3412
|
-
var packageSummarySchema =
|
|
3716
|
+
var packageSummarySchema = z3.object({
|
|
3413
3717
|
contentHash: tileflowIconPackageContentHashSchema,
|
|
3414
|
-
iconCount:
|
|
3718
|
+
iconCount: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxIconCount),
|
|
3415
3719
|
label: tileflowIconPackageLabelSchema,
|
|
3416
|
-
totalBytes:
|
|
3720
|
+
totalBytes: z3.number().int().nonnegative().max(tileflowIconPackageLimits2.maxGeneratedPackageBytes)
|
|
3417
3721
|
}).strict();
|
|
3418
|
-
var tileflowIconDiffDocumentSchema =
|
|
3419
|
-
schemaVersion:
|
|
3722
|
+
var tileflowIconDiffDocumentSchema = z3.object({
|
|
3723
|
+
schemaVersion: z3.literal(1),
|
|
3420
3724
|
environment: environmentSchema,
|
|
3421
|
-
baseline:
|
|
3422
|
-
deploymentId:
|
|
3725
|
+
baseline: z3.object({
|
|
3726
|
+
deploymentId: z3.string(),
|
|
3423
3727
|
package: packageSummarySchema.nullable(),
|
|
3424
|
-
version:
|
|
3728
|
+
version: z3.number().int().positive()
|
|
3425
3729
|
}).strict().nullable(),
|
|
3426
|
-
proposed:
|
|
3730
|
+
proposed: z3.object({
|
|
3427
3731
|
package: packageSummarySchema.nullable()
|
|
3428
3732
|
}).strict(),
|
|
3429
|
-
icons:
|
|
3430
|
-
added:
|
|
3431
|
-
removed:
|
|
3432
|
-
modified:
|
|
3433
|
-
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()
|
|
3434
3738
|
}).strict(),
|
|
3435
|
-
generatedBytes:
|
|
3436
|
-
before:
|
|
3437
|
-
after:
|
|
3438
|
-
delta:
|
|
3739
|
+
generatedBytes: z3.object({
|
|
3740
|
+
before: z3.number().int().nonnegative(),
|
|
3741
|
+
after: z3.number().int().nonnegative(),
|
|
3742
|
+
delta: z3.number().int()
|
|
3439
3743
|
}).strict(),
|
|
3440
|
-
artifacts:
|
|
3441
|
-
hasChanges:
|
|
3744
|
+
artifacts: z3.object({ report: z3.string().nullable() }).strict(),
|
|
3745
|
+
hasChanges: z3.boolean()
|
|
3442
3746
|
}).strict();
|
|
3443
3747
|
function registerIconDiffCommand(icons, dependencies) {
|
|
3444
|
-
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) => {
|
|
3445
3749
|
try {
|
|
3446
3750
|
await runIconDiff(options, dependencies);
|
|
3447
3751
|
} catch (error) {
|
|
@@ -3462,11 +3766,11 @@ async function runIconDiff(options, dependencies) {
|
|
|
3462
3766
|
}
|
|
3463
3767
|
const environment = environmentSchema.parse(options.against);
|
|
3464
3768
|
const loaded = await withTileflowConfigSecretsHidden(
|
|
3465
|
-
() =>
|
|
3769
|
+
() => loadTileflowConfigWithInputs3(options.config)
|
|
3466
3770
|
);
|
|
3467
3771
|
const project = loaded.project;
|
|
3468
|
-
|
|
3469
|
-
const mapNames =
|
|
3772
|
+
assertValidTileflowConfig2(project);
|
|
3773
|
+
const mapNames = getTileflowMapNames2(project);
|
|
3470
3774
|
if (!Object.hasOwn(project.maps, environment)) {
|
|
3471
3775
|
throw new Error(
|
|
3472
3776
|
`Unknown map environment "${environment}". Available maps: ${mapNames.join(", ") || "(none)"}`
|
|
@@ -3477,9 +3781,14 @@ async function runIconDiff(options, dependencies) {
|
|
|
3477
3781
|
throw new Error("Missing Tileflow API key. Run tileflow login or set TILEFLOW_API_KEY.");
|
|
3478
3782
|
}
|
|
3479
3783
|
const selectedProject = { ...project, maps: { [environment]: project.maps[environment] } };
|
|
3480
|
-
const
|
|
3481
|
-
|
|
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),
|
|
3482
3790
|
cwd: process.cwd(),
|
|
3791
|
+
...Object.keys(icons).length > 0 ? { icons } : {},
|
|
3483
3792
|
target: "hosted"
|
|
3484
3793
|
});
|
|
3485
3794
|
const binding = compiled.bindings.find((candidate) => candidate.mapName === environment);
|
|
@@ -3621,10 +3930,10 @@ function formatSignedBytes(bytes) {
|
|
|
3621
3930
|
}
|
|
3622
3931
|
|
|
3623
3932
|
// src/icon-list-command.ts
|
|
3624
|
-
import { dirname as
|
|
3933
|
+
import { dirname as dirname8 } from "path";
|
|
3625
3934
|
import { compareCodeUnits as compareCodeUnits6 } from "@tileflow/core";
|
|
3626
3935
|
import {
|
|
3627
|
-
getTileflowMapNames as
|
|
3936
|
+
getTileflowMapNames as getTileflowMapNames3,
|
|
3628
3937
|
loadValidTileflowConfigWithInputs as loadValidTileflowConfigWithInputs2,
|
|
3629
3938
|
TileflowValidationError as TileflowValidationError3
|
|
3630
3939
|
} from "@tileflow/dev/config";
|
|
@@ -3633,7 +3942,7 @@ import {
|
|
|
3633
3942
|
TileflowIconCompilationError as TileflowIconCompilationError2
|
|
3634
3943
|
} from "@tileflow/dev/icons";
|
|
3635
3944
|
function registerIconListCommand(icons, dependencies) {
|
|
3636
|
-
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) => {
|
|
3637
3946
|
if (!options.json) {
|
|
3638
3947
|
console.error(
|
|
3639
3948
|
"tileflow icons list currently requires --json. Run tileflow icons list --json."
|
|
@@ -3651,7 +3960,7 @@ function registerIconListCommand(icons, dependencies) {
|
|
|
3651
3960
|
}
|
|
3652
3961
|
function createTileflowIconListJson(inspection) {
|
|
3653
3962
|
return {
|
|
3654
|
-
schemaVersion:
|
|
3963
|
+
schemaVersion: 3,
|
|
3655
3964
|
pathBase: "cwd",
|
|
3656
3965
|
maps: [...inspection.maps].sort((left, right) => compareCodeUnits6(left.name, right.name)).map((map) => createMapJson(map, inspection.catalogs))
|
|
3657
3966
|
};
|
|
@@ -3665,15 +3974,20 @@ async function runIconList(options) {
|
|
|
3665
3974
|
() => loadValidTileflowConfigWithInputs2(options.config)
|
|
3666
3975
|
);
|
|
3667
3976
|
const project = loaded.project;
|
|
3668
|
-
const mapIds =
|
|
3977
|
+
const mapIds = getTileflowMapNames3(project).sort(compareCodeUnits6);
|
|
3669
3978
|
if (options.map && !Object.hasOwn(project.maps, options.map)) {
|
|
3670
3979
|
throw new Error(
|
|
3671
3980
|
`Unknown map "${options.map}". Available maps: ${mapIds.join(", ") || "(none)"}`
|
|
3672
3981
|
);
|
|
3673
3982
|
}
|
|
3983
|
+
const icons = {
|
|
3984
|
+
...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
|
|
3985
|
+
...options.offline ? { offline: true } : {}
|
|
3986
|
+
};
|
|
3674
3987
|
const inspection = await inspectTileflowIconCatalogs(project, {
|
|
3675
|
-
baseDirectory:
|
|
3988
|
+
baseDirectory: dirname8(loaded.configFile),
|
|
3676
3989
|
cwd: process.cwd(),
|
|
3990
|
+
...Object.keys(icons).length > 0 ? { icons } : {},
|
|
3677
3991
|
...options.map ? { mapNames: [options.map] } : {}
|
|
3678
3992
|
});
|
|
3679
3993
|
process.stdout.write(serializeTileflowIconListJson(createTileflowIconListJson(inspection)));
|
|
@@ -3684,7 +3998,7 @@ function createMapJson(map, catalogs) {
|
|
|
3684
3998
|
}
|
|
3685
3999
|
const mapIcons = map.icons;
|
|
3686
4000
|
const catalog = catalogs.find(
|
|
3687
|
-
(candidate) => candidate.compiledPackage.contentHash === mapIcons.packageHash &&
|
|
4001
|
+
(candidate) => candidate.compiledPackage.contentHash === mapIcons.packageHash && sameContributors(candidate.contributors, mapIcons.contributors)
|
|
3688
4002
|
);
|
|
3689
4003
|
if (!catalog) {
|
|
3690
4004
|
throw new Error(`Missing inspected icon catalog for map ${map.name}`);
|
|
@@ -3692,9 +4006,21 @@ function createMapJson(map, catalogs) {
|
|
|
3692
4006
|
return {
|
|
3693
4007
|
id: map.name,
|
|
3694
4008
|
icons: {
|
|
3695
|
-
kind: "
|
|
3696
|
-
|
|
4009
|
+
kind: "sources",
|
|
4010
|
+
contributors: mapIcons.contributors.map(createContributorJson),
|
|
3697
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
|
+
} : {},
|
|
3698
4024
|
insideWorkingTree: catalog.insideWorkingTree,
|
|
3699
4025
|
replacements: catalog.replacements.map((replacement) => ({
|
|
3700
4026
|
id: replacement.id,
|
|
@@ -3702,18 +4028,43 @@ function createMapJson(map, catalogs) {
|
|
|
3702
4028
|
winner: replacement.winner
|
|
3703
4029
|
})),
|
|
3704
4030
|
packageHash: mapIcons.packageHash,
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
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
|
+
)
|
|
3712
4049
|
}
|
|
3713
4050
|
};
|
|
3714
4051
|
}
|
|
3715
|
-
function
|
|
3716
|
-
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);
|
|
3717
4068
|
}
|
|
3718
4069
|
function printIconListError(error) {
|
|
3719
4070
|
if (error instanceof TileflowValidationError3) {
|
|
@@ -3732,116 +4083,1066 @@ function printIssues(heading, issues) {
|
|
|
3732
4083
|
);
|
|
3733
4084
|
}
|
|
3734
4085
|
|
|
3735
|
-
// src/
|
|
3736
|
-
import {
|
|
3737
|
-
import
|
|
4086
|
+
// src/icon-lock-command.ts
|
|
4087
|
+
import { dirname as dirname9 } from "path";
|
|
4088
|
+
import pc3 from "picocolors";
|
|
3738
4089
|
import {
|
|
3739
|
-
|
|
3740
|
-
|
|
4090
|
+
collectTileflowIconSetReferences,
|
|
4091
|
+
compareCodeUnits as compareCodeUnits7,
|
|
4092
|
+
parseResolvedTileflowMap as parseResolvedTileflowMap3,
|
|
4093
|
+
parseTileflowIconsLockfile,
|
|
4094
|
+
tileflowIconSetReferenceSchema as tileflowIconSetReferenceSchema2,
|
|
4095
|
+
tileflowIconsLockfileName
|
|
3741
4096
|
} from "@tileflow/core";
|
|
3742
4097
|
import {
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
);
|
|
3755
|
-
language.command("schema").description("Print the generated authoring and resolved JSON Schema").option("--json", "print deterministic JSON").action(
|
|
3756
|
-
async (options) => runLanguageCommand(
|
|
3757
|
-
"language-schema",
|
|
3758
|
-
options,
|
|
3759
|
-
async () => printSchema(await (dependencies.loadSchema ?? loadPackagedSchema)(), options)
|
|
3760
|
-
)
|
|
3761
|
-
);
|
|
3762
|
-
}
|
|
3763
|
-
async function runLanguageCommand(command2, options, operation) {
|
|
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;
|
|
3764
4109
|
try {
|
|
3765
|
-
|
|
4110
|
+
apiOrigin = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
|
|
3766
4111
|
} catch (error) {
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
phase: "language-discovery"
|
|
3770
|
-
});
|
|
3771
|
-
if (options.json) process.stderr.write(serializeTileflowCommandDocument3(failure));
|
|
3772
|
-
else {
|
|
3773
|
-
console.error(`${failure.message} [${failure.code}; ${failure.phase}]`);
|
|
3774
|
-
console.error(`Suggestion: ${failure.suggestion}`);
|
|
3775
|
-
}
|
|
3776
|
-
process.exitCode = 1;
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
function printManifest(manifest, options) {
|
|
3780
|
-
if (options.json) {
|
|
3781
|
-
process.stdout.write(`${serializeCanonicalJson(manifest)}
|
|
3782
|
-
`);
|
|
3783
|
-
return;
|
|
4112
|
+
emitFailure(options.json, "invalid_api_url", safeMessage(error));
|
|
4113
|
+
return null;
|
|
3784
4114
|
}
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
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 };
|
|
3795
4129
|
}
|
|
3796
|
-
|
|
3797
|
-
`Tileflow config reference schema ${String(schema.schemaVersion)}: ${String(schema.$id)}.`
|
|
3798
|
-
);
|
|
3799
|
-
console.log("Run tileflow language schema --json for the complete machine-readable document.");
|
|
3800
|
-
}
|
|
3801
|
-
async function loadPackagedSchema() {
|
|
3802
|
-
const schemaUrl = import.meta.resolve("@tileflow/core/config-reference.json");
|
|
3803
|
-
const parsed = JSON.parse(await readFile4(fileURLToPath(schemaUrl), "utf8"));
|
|
3804
|
-
if (!isRecord(parsed))
|
|
3805
|
-
throw new Error("Packaged Tileflow config reference is not a JSON object.");
|
|
3806
|
-
return parsed;
|
|
3807
|
-
}
|
|
3808
|
-
function isRecord(value) {
|
|
3809
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3810
|
-
}
|
|
3811
|
-
|
|
3812
|
-
// src/open-external.ts
|
|
3813
|
-
import { spawn as spawnChild } from "child_process";
|
|
3814
|
-
import { platform as hostPlatform } from "os";
|
|
3815
|
-
function openTileflowExternal(target, dependencies = {}) {
|
|
3816
|
-
const invocation = resolveTileflowExternalOpenInvocation(
|
|
3817
|
-
target,
|
|
3818
|
-
dependencies.platform ?? hostPlatform()
|
|
3819
|
-
);
|
|
3820
|
-
const spawn2 = dependencies.spawn ?? spawnChild;
|
|
3821
|
-
let child;
|
|
4130
|
+
let config;
|
|
3822
4131
|
try {
|
|
3823
|
-
|
|
3824
|
-
detached: true,
|
|
3825
|
-
shell: false,
|
|
3826
|
-
stdio: "ignore",
|
|
3827
|
-
windowsHide: true
|
|
3828
|
-
});
|
|
4132
|
+
config = await dependencies.loadAuthConfig();
|
|
3829
4133
|
} catch (error) {
|
|
3830
|
-
|
|
3831
|
-
return;
|
|
4134
|
+
emitFailure(options.json, "auth_state_unavailable", safeMessage(error));
|
|
4135
|
+
return null;
|
|
3832
4136
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
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
|
+
|
|
4980
|
+
// src/language-command.ts
|
|
4981
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
4982
|
+
import { fileURLToPath } from "url";
|
|
4983
|
+
import {
|
|
4984
|
+
serializeCanonicalJson as serializeCanonicalJson3,
|
|
4985
|
+
tileflowAuthoringManifest
|
|
4986
|
+
} from "@tileflow/core";
|
|
4987
|
+
import {
|
|
4988
|
+
createTileflowCommandFailureDocument as createTileflowCommandFailureDocument3,
|
|
4989
|
+
serializeTileflowCommandDocument as serializeTileflowCommandDocument3
|
|
4990
|
+
} from "@tileflow/dev/validation";
|
|
4991
|
+
function registerLanguageCommand(program2, dependencies = {}) {
|
|
4992
|
+
const language = program2.command("language").description("Inspect the complete machine-readable Tileflow semantic language");
|
|
4993
|
+
language.command("manifest").description("Print the semantic compiler authoring manifest").option("--json", "print deterministic JSON").action(
|
|
4994
|
+
async (options) => runLanguageCommand(
|
|
4995
|
+
"language-manifest",
|
|
4996
|
+
options,
|
|
4997
|
+
() => printManifest((dependencies.loadManifest ?? (() => tileflowAuthoringManifest))(), options)
|
|
4998
|
+
)
|
|
4999
|
+
);
|
|
5000
|
+
language.command("schema").description("Print the generated authoring and resolved JSON Schema").option("--json", "print deterministic JSON").action(
|
|
5001
|
+
async (options) => runLanguageCommand(
|
|
5002
|
+
"language-schema",
|
|
5003
|
+
options,
|
|
5004
|
+
async () => printSchema(await (dependencies.loadSchema ?? loadPackagedSchema)(), options)
|
|
5005
|
+
)
|
|
5006
|
+
);
|
|
5007
|
+
}
|
|
5008
|
+
async function runLanguageCommand(command2, options, operation) {
|
|
5009
|
+
try {
|
|
5010
|
+
await operation();
|
|
5011
|
+
} catch (error) {
|
|
5012
|
+
const failure2 = createTileflowCommandFailureDocument3(command2, error, process.cwd(), {
|
|
5013
|
+
code: command2 === "language-manifest" ? "LANGUAGE_MANIFEST_FAILED" : "LANGUAGE_SCHEMA_FAILED",
|
|
5014
|
+
phase: "language-discovery"
|
|
5015
|
+
});
|
|
5016
|
+
if (options.json) process.stderr.write(serializeTileflowCommandDocument3(failure2));
|
|
5017
|
+
else {
|
|
5018
|
+
console.error(`${failure2.message} [${failure2.code}; ${failure2.phase}]`);
|
|
5019
|
+
console.error(`Suggestion: ${failure2.suggestion}`);
|
|
5020
|
+
}
|
|
5021
|
+
process.exitCode = 1;
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
function printManifest(manifest, options) {
|
|
5025
|
+
if (options.json) {
|
|
5026
|
+
process.stdout.write(`${serializeCanonicalJson3(manifest)}
|
|
5027
|
+
`);
|
|
5028
|
+
return;
|
|
5029
|
+
}
|
|
5030
|
+
console.log(
|
|
5031
|
+
`${manifest.language} (${manifest.compiler.name} v${manifest.compiler.version}); ${manifest.domains.length} domains, ${manifest.expressions.builders.length} expression builders.`
|
|
5032
|
+
);
|
|
5033
|
+
console.log("Run tileflow language manifest --json for the complete machine-readable document.");
|
|
5034
|
+
}
|
|
5035
|
+
function printSchema(schema, options) {
|
|
5036
|
+
if (options.json) {
|
|
5037
|
+
process.stdout.write(`${serializeCanonicalJson3(schema)}
|
|
5038
|
+
`);
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
console.log(
|
|
5042
|
+
`Tileflow config reference schema ${String(schema.schemaVersion)}: ${String(schema.$id)}.`
|
|
5043
|
+
);
|
|
5044
|
+
console.log("Run tileflow language schema --json for the complete machine-readable document.");
|
|
5045
|
+
}
|
|
5046
|
+
async function loadPackagedSchema() {
|
|
5047
|
+
const schemaUrl = import.meta.resolve("@tileflow/core/config-reference.json");
|
|
5048
|
+
const parsed = JSON.parse(await readFile4(fileURLToPath(schemaUrl), "utf8"));
|
|
5049
|
+
if (!isRecord(parsed))
|
|
5050
|
+
throw new Error("Packaged Tileflow config reference is not a JSON object.");
|
|
5051
|
+
return parsed;
|
|
5052
|
+
}
|
|
5053
|
+
function isRecord(value) {
|
|
5054
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
5055
|
+
}
|
|
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
|
+
|
|
5113
|
+
// src/open-external.ts
|
|
5114
|
+
import { spawn as spawnChild } from "child_process";
|
|
5115
|
+
import { platform as hostPlatform } from "os";
|
|
5116
|
+
function openTileflowExternal(target, dependencies = {}) {
|
|
5117
|
+
const invocation = resolveTileflowExternalOpenInvocation(
|
|
5118
|
+
target,
|
|
5119
|
+
dependencies.platform ?? hostPlatform()
|
|
5120
|
+
);
|
|
5121
|
+
const spawn2 = dependencies.spawn ?? spawnChild;
|
|
5122
|
+
let child;
|
|
5123
|
+
try {
|
|
5124
|
+
child = spawn2(invocation.command, invocation.args, {
|
|
5125
|
+
detached: true,
|
|
5126
|
+
shell: false,
|
|
5127
|
+
stdio: "ignore",
|
|
5128
|
+
windowsHide: true
|
|
5129
|
+
});
|
|
5130
|
+
} catch (error) {
|
|
5131
|
+
dependencies.onError?.(error);
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
child.once("error", (error) => dependencies.onError?.(error));
|
|
5135
|
+
child.unref();
|
|
5136
|
+
}
|
|
5137
|
+
function resolveTileflowExternalOpenInvocation(target, platform) {
|
|
5138
|
+
if (!target || target.includes("\0")) throw new Error("The external target is invalid.");
|
|
5139
|
+
if (platform === "darwin") return { command: "open", args: [target] };
|
|
5140
|
+
if (platform === "win32") return { command: "explorer.exe", args: [target] };
|
|
3840
5141
|
return { command: "xdg-open", args: [target] };
|
|
3841
5142
|
}
|
|
3842
5143
|
|
|
3843
5144
|
// src/project-commands.ts
|
|
3844
|
-
import
|
|
5145
|
+
import pc4 from "picocolors";
|
|
3845
5146
|
function registerProjectCommands(program2, dependencies) {
|
|
3846
5147
|
const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
|
|
3847
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) => {
|
|
@@ -3855,14 +5156,14 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3855
5156
|
command: "projects list",
|
|
3856
5157
|
projects: result.items
|
|
3857
5158
|
};
|
|
3858
|
-
if (options.json) return
|
|
5159
|
+
if (options.json) return emitJson2(document);
|
|
3859
5160
|
if (!result.items.length) {
|
|
3860
|
-
console.log(
|
|
5161
|
+
console.log(pc4.gray("No managed destinations found."));
|
|
3861
5162
|
return;
|
|
3862
5163
|
}
|
|
3863
5164
|
for (const target of result.items) {
|
|
3864
5165
|
console.log(
|
|
3865
|
-
` ${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)") : ""}`
|
|
3866
5167
|
);
|
|
3867
5168
|
}
|
|
3868
5169
|
});
|
|
@@ -3914,9 +5215,9 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3914
5215
|
command: "projects create",
|
|
3915
5216
|
...asRecord2(result.body)
|
|
3916
5217
|
};
|
|
3917
|
-
if (options.json) return
|
|
3918
|
-
console.log(`${
|
|
3919
|
-
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}.`));
|
|
3920
5221
|
}
|
|
3921
5222
|
);
|
|
3922
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) => {
|
|
@@ -3933,13 +5234,13 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3933
5234
|
);
|
|
3934
5235
|
if (!result.ok) return emitRemoteFailure(options.json, result);
|
|
3935
5236
|
const document = { schemaVersion: 1, command: "projects show", ...asRecord2(result.body) };
|
|
3936
|
-
if (options.json) return
|
|
5237
|
+
if (options.json) return emitJson2(document);
|
|
3937
5238
|
const project = asRecord2(result.body).project;
|
|
3938
5239
|
if (!isProjectItem(project)) {
|
|
3939
5240
|
return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
|
|
3940
5241
|
}
|
|
3941
|
-
console.log(`${
|
|
3942
|
-
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"));
|
|
3943
5244
|
});
|
|
3944
5245
|
for (const archived of [true, false]) {
|
|
3945
5246
|
const verb = archived ? "archive" : "unarchive";
|
|
@@ -3965,8 +5266,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3965
5266
|
command: `projects ${verb}`,
|
|
3966
5267
|
...asRecord2(result.body)
|
|
3967
5268
|
};
|
|
3968
|
-
if (options.json) return
|
|
3969
|
-
console.log(`${
|
|
5269
|
+
if (options.json) return emitJson2(document);
|
|
5270
|
+
console.log(`${pc4.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
|
|
3970
5271
|
});
|
|
3971
5272
|
}
|
|
3972
5273
|
}
|
|
@@ -4008,7 +5309,7 @@ async function requireCommandSession(options, dependencies) {
|
|
|
4008
5309
|
try {
|
|
4009
5310
|
config = await dependencies.loadAuthConfig();
|
|
4010
5311
|
} catch (error) {
|
|
4011
|
-
emitLocalFailure(options.json, "auth_state_unavailable",
|
|
5312
|
+
emitLocalFailure(options.json, "auth_state_unavailable", safeMessage2(error));
|
|
4012
5313
|
return null;
|
|
4013
5314
|
}
|
|
4014
5315
|
const resolved = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
|
|
@@ -4087,13 +5388,13 @@ function emitRemoteFailure(json, result) {
|
|
|
4087
5388
|
function emitLocalFailure(json, code, message, details = {}) {
|
|
4088
5389
|
if (json) {
|
|
4089
5390
|
console.error(message);
|
|
4090
|
-
|
|
5391
|
+
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
4091
5392
|
} else {
|
|
4092
|
-
console.error(`${
|
|
5393
|
+
console.error(`${pc4.red("\u2715")} ${message}`);
|
|
4093
5394
|
}
|
|
4094
5395
|
process.exitCode = 1;
|
|
4095
5396
|
}
|
|
4096
|
-
function
|
|
5397
|
+
function emitJson2(value) {
|
|
4097
5398
|
process.stdout.write(`${JSON.stringify(value)}
|
|
4098
5399
|
`);
|
|
4099
5400
|
}
|
|
@@ -4119,21 +5420,130 @@ function validName(value) {
|
|
|
4119
5420
|
const normalized = value.trim();
|
|
4120
5421
|
return normalized.length > 0 && normalized.length <= 200 && !/[\p{Cc}]/u.test(normalized);
|
|
4121
5422
|
}
|
|
4122
|
-
function
|
|
5423
|
+
function safeMessage2(error) {
|
|
4123
5424
|
return error instanceof Error && error.message.length <= 300 ? error.message : "Tileflow auth state is unavailable.";
|
|
4124
5425
|
}
|
|
4125
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
|
+
|
|
4126
5536
|
// src/tileset-command.ts
|
|
4127
|
-
import
|
|
4128
|
-
import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
|
|
4129
|
-
import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
|
|
5537
|
+
import pc5 from "picocolors";
|
|
4130
5538
|
import { z as z6 } from "zod";
|
|
5539
|
+
import { serializeCanonicalJson as serializeCanonicalJson5 } from "@tileflow/core";
|
|
5540
|
+
import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
|
|
4131
5541
|
|
|
4132
5542
|
// src/tileset-upload.ts
|
|
4133
5543
|
import { createHash } from "crypto";
|
|
4134
5544
|
import { open as open4, stat } from "fs/promises";
|
|
4135
5545
|
import { z as z5 } from "zod";
|
|
4136
|
-
import { serializeCanonicalJson as
|
|
5546
|
+
import { serializeCanonicalJson as serializeCanonicalJson4 } from "@tileflow/core";
|
|
4137
5547
|
var teamTilesetPartSize = 16777216;
|
|
4138
5548
|
var maximumTeamTilesetBytes = 4e9;
|
|
4139
5549
|
var authorizationBatchSize = 16;
|
|
@@ -4273,7 +5683,7 @@ async function prepareTeamTilesetUpload(file) {
|
|
|
4273
5683
|
};
|
|
4274
5684
|
return Object.freeze({
|
|
4275
5685
|
byteCount: identity.size,
|
|
4276
|
-
contentHash: createHash("sha256").update(
|
|
5686
|
+
contentHash: createHash("sha256").update(serializeCanonicalJson4(contentIdentity)).digest("hex"),
|
|
4277
5687
|
contentHashAlgorithm: "sha256-fixed-parts-v1",
|
|
4278
5688
|
identity,
|
|
4279
5689
|
partSize: teamTilesetPartSize,
|
|
@@ -4289,7 +5699,7 @@ function createTeamTilesetUploadIdempotencyKey(tilesetId, upload) {
|
|
|
4289
5699
|
);
|
|
4290
5700
|
}
|
|
4291
5701
|
return createHash("sha256").update(
|
|
4292
|
-
|
|
5702
|
+
serializeCanonicalJson4({
|
|
4293
5703
|
byteCount: upload.byteCount,
|
|
4294
5704
|
contentHash: upload.contentHash,
|
|
4295
5705
|
contentHashAlgorithm: upload.contentHashAlgorithm,
|
|
@@ -4693,7 +6103,7 @@ function asRecord3(value) {
|
|
|
4693
6103
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4694
6104
|
}
|
|
4695
6105
|
function defaultSleep(milliseconds) {
|
|
4696
|
-
return new Promise((
|
|
6106
|
+
return new Promise((resolve9) => setTimeout(resolve9, milliseconds));
|
|
4697
6107
|
}
|
|
4698
6108
|
var identifierSchema = z5.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
|
|
4699
6109
|
var uploadPartStatusSchema = z5.object({
|
|
@@ -4780,11 +6190,11 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4780
6190
|
sample: options.sample
|
|
4781
6191
|
});
|
|
4782
6192
|
if (options.json) {
|
|
4783
|
-
process.stdout.write(`${
|
|
6193
|
+
process.stdout.write(`${serializeCanonicalJson5(inspection)}
|
|
4784
6194
|
`);
|
|
4785
6195
|
return;
|
|
4786
6196
|
}
|
|
4787
|
-
console.log(`${
|
|
6197
|
+
console.log(`${pc5.green("\u2713")} PMTiles archive is valid.`);
|
|
4788
6198
|
console.log(` Type: ${inspection.contract.tileType}`);
|
|
4789
6199
|
console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
|
|
4790
6200
|
console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
|
|
@@ -4794,7 +6204,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4794
6204
|
);
|
|
4795
6205
|
}
|
|
4796
6206
|
for (const warning of inspection.warnings) {
|
|
4797
|
-
console.log(`${
|
|
6207
|
+
console.log(`${pc5.yellow("!")} ${warning.message}`);
|
|
4798
6208
|
}
|
|
4799
6209
|
} catch (error) {
|
|
4800
6210
|
const record = asRecord4(error);
|
|
@@ -4803,11 +6213,11 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4803
6213
|
const message = error instanceof Error ? error.message : "Tileset inspection failed.";
|
|
4804
6214
|
if (options.json) {
|
|
4805
6215
|
process.stderr.write(
|
|
4806
|
-
`${
|
|
6216
|
+
`${serializeCanonicalJson5({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
|
|
4807
6217
|
`
|
|
4808
6218
|
);
|
|
4809
6219
|
} else {
|
|
4810
|
-
console.error(`${
|
|
6220
|
+
console.error(`${pc5.red("Error:")} ${message}`);
|
|
4811
6221
|
}
|
|
4812
6222
|
process.exitCode = 1;
|
|
4813
6223
|
}
|
|
@@ -4889,9 +6299,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4889
6299
|
version: { id: publication.versionId, number: publication.version }
|
|
4890
6300
|
}
|
|
4891
6301
|
};
|
|
4892
|
-
if (options.json) return
|
|
6302
|
+
if (options.json) return emitJson(document);
|
|
4893
6303
|
console.log(
|
|
4894
|
-
`${
|
|
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"}.`
|
|
4895
6305
|
);
|
|
4896
6306
|
} catch (error) {
|
|
4897
6307
|
if (error instanceof TeamTilesetUploadError) {
|
|
@@ -4910,10 +6320,10 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4910
6320
|
if (!authority) return;
|
|
4911
6321
|
const items = await fetchAllTeamTilesets(authority);
|
|
4912
6322
|
const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
|
|
4913
|
-
if (options.json) return
|
|
4914
|
-
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."));
|
|
4915
6325
|
for (const item of items) {
|
|
4916
|
-
console.log(` ${
|
|
6326
|
+
console.log(` ${pc5.bold(item.logicalName)} ${pc5.gray(item.status)}`);
|
|
4917
6327
|
}
|
|
4918
6328
|
} catch {
|
|
4919
6329
|
emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
|
|
@@ -4939,9 +6349,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4939
6349
|
}
|
|
4940
6350
|
const parsed = tilesetDetailResponseSchema.parse(response.body);
|
|
4941
6351
|
const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
|
|
4942
|
-
if (options.json) return
|
|
6352
|
+
if (options.json) return emitJson(document);
|
|
4943
6353
|
const item = parsed.tileset;
|
|
4944
|
-
console.log(`${
|
|
6354
|
+
console.log(`${pc5.bold(item.name)} ${pc5.gray(item.state)}`);
|
|
4945
6355
|
console.log(` ID: ${item.id}`);
|
|
4946
6356
|
console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
|
|
4947
6357
|
} catch {
|
|
@@ -4982,97 +6392,20 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4982
6392
|
nextCursor: inUse.data.nextCursor
|
|
4983
6393
|
}
|
|
4984
6394
|
);
|
|
4985
|
-
}
|
|
4986
|
-
return emitFailure(
|
|
4987
|
-
options.json,
|
|
4988
|
-
`http_${response.status}`,
|
|
4989
|
-
`Tileset purge failed (${response.status}).`
|
|
4990
|
-
);
|
|
4991
|
-
}
|
|
4992
|
-
const parsed = tilesetPurgeResponseSchema.parse(response.body);
|
|
4993
|
-
const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
|
|
4994
|
-
if (options.json) return
|
|
4995
|
-
console.log(`${
|
|
4996
|
-
} catch {
|
|
4997
|
-
emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
|
|
4998
|
-
}
|
|
4999
|
-
});
|
|
5000
|
-
}
|
|
5001
|
-
async function resolveTeamAuthority(options, scopes, dependencies) {
|
|
5002
|
-
let apiOrigin;
|
|
5003
|
-
try {
|
|
5004
|
-
apiOrigin = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
|
|
5005
|
-
} catch (error) {
|
|
5006
|
-
emitFailure(options.json, "invalid_api_url", safeMessage2(error));
|
|
5007
|
-
return null;
|
|
5008
|
-
}
|
|
5009
|
-
if (options.apiKey) {
|
|
5010
|
-
if (!/^tf_live_[0-9a-f]{48}$/u.test(options.apiKey)) {
|
|
5011
|
-
emitFailure(options.json, "invalid_team_data_key", "Team data API key is invalid.");
|
|
5012
|
-
return null;
|
|
5013
|
-
}
|
|
5014
|
-
if (options.team) {
|
|
5015
|
-
emitFailure(
|
|
5016
|
-
options.json,
|
|
5017
|
-
"team_selector_with_api_key",
|
|
5018
|
-
"A Team data key already selects its Team; omit --team."
|
|
5019
|
-
);
|
|
5020
|
-
return null;
|
|
6395
|
+
}
|
|
6396
|
+
return emitFailure(
|
|
6397
|
+
options.json,
|
|
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.");
|
|
5021
6408
|
}
|
|
5022
|
-
return { apiOrigin, credential: options.apiKey, team: null };
|
|
5023
|
-
}
|
|
5024
|
-
let config;
|
|
5025
|
-
try {
|
|
5026
|
-
config = await dependencies.loadAuthConfig();
|
|
5027
|
-
} catch (error) {
|
|
5028
|
-
emitFailure(options.json, "auth_state_unavailable", safeMessage2(error));
|
|
5029
|
-
return null;
|
|
5030
|
-
}
|
|
5031
|
-
const selected = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
|
|
5032
|
-
if (selected.kind !== "selected") {
|
|
5033
|
-
emitFailure(
|
|
5034
|
-
options.json,
|
|
5035
|
-
`account_session_${selected.kind}`,
|
|
5036
|
-
selected.kind === "expired" ? "Account session expired. Run tileflow login." : "Run tileflow login."
|
|
5037
|
-
);
|
|
5038
|
-
return null;
|
|
5039
|
-
}
|
|
5040
|
-
const discovered = await listAccountTeams(selected.session);
|
|
5041
|
-
if (!discovered.ok) {
|
|
5042
|
-
emitFailure(options.json, "team_discovery_failed", discovered.error);
|
|
5043
|
-
return null;
|
|
5044
|
-
}
|
|
5045
|
-
const requested = options.team ? /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(options.team) : null;
|
|
5046
|
-
if (options.team && !requested) {
|
|
5047
|
-
emitFailure(options.json, "invalid_team", "Team must use @team syntax.");
|
|
5048
|
-
return null;
|
|
5049
|
-
}
|
|
5050
|
-
const team = requested ? discovered.teams.find(({ slug }) => slug === requested[1]) : discovered.teams.length === 1 ? discovered.teams[0] : null;
|
|
5051
|
-
if (!team) {
|
|
5052
|
-
const options_ = discovered.teams.map(({ slug }) => `@${slug}`);
|
|
5053
|
-
emitFailure(
|
|
5054
|
-
options.json,
|
|
5055
|
-
options_.length > 1 ? "team_ambiguous" : "team_not_found",
|
|
5056
|
-
options_.length > 1 ? `Choose one Team with --team: ${options_.join(", ")}.` : "No matching Team is available.",
|
|
5057
|
-
{ options: options_ }
|
|
5058
|
-
);
|
|
5059
|
-
return null;
|
|
5060
|
-
}
|
|
5061
|
-
const capability = await requestTeamCapability(selected.session, `@${team.slug}`, scopes);
|
|
5062
|
-
if (!capability.ok) {
|
|
5063
|
-
emitFailure(options.json, "team_capability_failed", capability.error);
|
|
5064
|
-
return null;
|
|
5065
|
-
}
|
|
5066
|
-
return {
|
|
5067
|
-
apiOrigin: selected.session.apiOrigin,
|
|
5068
|
-
credential: capability.capability,
|
|
5069
|
-
team
|
|
5070
|
-
};
|
|
5071
|
-
}
|
|
5072
|
-
function authorizedTeamRequest(authority, path, method) {
|
|
5073
|
-
return requestHostedJson(authority.apiOrigin, path, {
|
|
5074
|
-
headers: { Authorization: `Bearer ${authority.credential}` },
|
|
5075
|
-
method
|
|
5076
6409
|
});
|
|
5077
6410
|
}
|
|
5078
6411
|
async function fetchAllTeamTilesets(authority) {
|
|
@@ -5111,21 +6444,6 @@ async function fetchAllTeamTilesets(authority) {
|
|
|
5111
6444
|
}
|
|
5112
6445
|
throw new Error("Tileset list exceeded its safe page limit.");
|
|
5113
6446
|
}
|
|
5114
|
-
function emitJson2(value) {
|
|
5115
|
-
process.stdout.write(`${serializeCanonicalJson3(value)}
|
|
5116
|
-
`);
|
|
5117
|
-
}
|
|
5118
|
-
function emitFailure(json, code, message, context = {}) {
|
|
5119
|
-
if (json) {
|
|
5120
|
-
process.stderr.write(
|
|
5121
|
-
`${serializeCanonicalJson3({ error: { code, ...context, message }, ok: false, schemaVersion: 1 })}
|
|
5122
|
-
`
|
|
5123
|
-
);
|
|
5124
|
-
} else {
|
|
5125
|
-
console.error(`${pc2.red("Error:")} ${message}`);
|
|
5126
|
-
}
|
|
5127
|
-
process.exitCode = 1;
|
|
5128
|
-
}
|
|
5129
6447
|
function isTilesetId(value) {
|
|
5130
6448
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value) && !["terrain", "world"].includes(value);
|
|
5131
6449
|
}
|
|
@@ -5143,15 +6461,12 @@ function inspectionOptionError(message) {
|
|
|
5143
6461
|
path: "includeValues"
|
|
5144
6462
|
});
|
|
5145
6463
|
}
|
|
5146
|
-
function safeMessage2(error) {
|
|
5147
|
-
return error instanceof Error ? error.message : "Tileset command failed.";
|
|
5148
|
-
}
|
|
5149
6464
|
function asRecord4(value) {
|
|
5150
6465
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5151
6466
|
}
|
|
5152
6467
|
var safeIdentifierSchema2 = z6.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
|
|
5153
6468
|
var physicalTilesetIdSchema = z6.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
|
|
5154
|
-
var
|
|
6469
|
+
var cursorSchema2 = z6.string().min(1).max(2048).nullable();
|
|
5155
6470
|
var tilesetInventoryItemSchema = z6.object({
|
|
5156
6471
|
currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
5157
6472
|
hasCurrent: z6.boolean(),
|
|
@@ -5165,7 +6480,7 @@ var tilesetInventoryItemSchema = z6.object({
|
|
|
5165
6480
|
usedByMapCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
|
|
5166
6481
|
}).strict();
|
|
5167
6482
|
var tilesetListResponseSchema = z6.object({
|
|
5168
|
-
nextCursor:
|
|
6483
|
+
nextCursor: cursorSchema2.optional(),
|
|
5169
6484
|
schemaVersion: z6.literal(1),
|
|
5170
6485
|
tilesets: z6.array(tilesetInventoryItemSchema).max(100)
|
|
5171
6486
|
}).strict();
|
|
@@ -5183,7 +6498,7 @@ var tilesetDetailResponseSchema = z6.object({
|
|
|
5183
6498
|
attribution: z6.string().max(4096).nullable(),
|
|
5184
6499
|
currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
5185
6500
|
dependencies: z6.array(tilesetDependencySchema).max(25),
|
|
5186
|
-
dependencyNextCursor:
|
|
6501
|
+
dependencyNextCursor: cursorSchema2,
|
|
5187
6502
|
id: physicalTilesetIdSchema,
|
|
5188
6503
|
name: z6.string().min(1).max(200),
|
|
5189
6504
|
slug: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
@@ -5199,7 +6514,7 @@ var tilesetInUseResponseSchema = z6.object({
|
|
|
5199
6514
|
code: z6.literal("TF_TILESET_IN_USE"),
|
|
5200
6515
|
dependencies: z6.array(purgeDependencySchema).max(32),
|
|
5201
6516
|
error: z6.literal("Tileset is used by retained Map deployments"),
|
|
5202
|
-
nextCursor:
|
|
6517
|
+
nextCursor: cursorSchema2
|
|
5203
6518
|
}).strict();
|
|
5204
6519
|
var tilesetPurgeResponseSchema = z6.object({
|
|
5205
6520
|
deliveryFence: z6.enum(["complete", "pending"]),
|
|
@@ -5211,7 +6526,7 @@ var tilesetPurgeResponseSchema = z6.object({
|
|
|
5211
6526
|
|
|
5212
6527
|
// src/visual-command.ts
|
|
5213
6528
|
import { lstat as lstat4, readFile as readFile6 } from "fs/promises";
|
|
5214
|
-
import { dirname as
|
|
6529
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, parse as parse2, relative as relative4, resolve as resolve7, sep as sep4 } from "path";
|
|
5215
6530
|
import {
|
|
5216
6531
|
analyzeTileflowCaptureReference,
|
|
5217
6532
|
compareTileflowCaptureToBaseline,
|
|
@@ -5225,12 +6540,12 @@ import {
|
|
|
5225
6540
|
tileflowVisualArtifactLimits,
|
|
5226
6541
|
validateTileflowVisualReferencePng
|
|
5227
6542
|
} from "@tileflow/capture";
|
|
5228
|
-
import { compareCodeUnits as
|
|
6543
|
+
import { compareCodeUnits as compareCodeUnits9 } from "@tileflow/core";
|
|
5229
6544
|
|
|
5230
6545
|
// src/visual-compare-command.ts
|
|
5231
6546
|
import { createHash as createHash3 } from "crypto";
|
|
5232
6547
|
import { lstat as lstat3, readFile as readFile5, realpath } from "fs/promises";
|
|
5233
|
-
import { basename as basename3, dirname as
|
|
6548
|
+
import { basename as basename3, dirname as dirname10, extname, isAbsolute as isAbsolute3, parse, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
5234
6549
|
import {
|
|
5235
6550
|
compareTileflowCapturesForReview,
|
|
5236
6551
|
createTileflowCaptureSession as createTileflowCaptureSession2,
|
|
@@ -5239,16 +6554,16 @@ import {
|
|
|
5239
6554
|
tileflowSyntheticAssetOrigin as tileflowSyntheticAssetOrigin2
|
|
5240
6555
|
} from "@tileflow/capture";
|
|
5241
6556
|
import {
|
|
5242
|
-
compareCodeUnits as
|
|
6557
|
+
compareCodeUnits as compareCodeUnits8,
|
|
5243
6558
|
normalizeTileflowCaptureScene as normalizeTileflowCaptureScene2,
|
|
5244
|
-
parseResolvedTileflowMap as
|
|
6559
|
+
parseResolvedTileflowMap as parseResolvedTileflowMap4,
|
|
5245
6560
|
resolveThemeSelection,
|
|
5246
6561
|
tileflowCaptureSceneLimits
|
|
5247
6562
|
} from "@tileflow/core";
|
|
5248
6563
|
import {
|
|
5249
6564
|
createTileflowArtifactDiagnostics as createTileflowArtifactDiagnostics2,
|
|
5250
6565
|
createTileflowArtifactSession as createTileflowArtifactSession2,
|
|
5251
|
-
createTileflowBuildArtifacts
|
|
6566
|
+
createTileflowBuildArtifacts as createTileflowBuildArtifacts2
|
|
5252
6567
|
} from "@tileflow/dev/artifacts";
|
|
5253
6568
|
|
|
5254
6569
|
// src/visual-compare-report.ts
|
|
@@ -5543,15 +6858,15 @@ async function parseVisualComparePlan(options) {
|
|
|
5543
6858
|
}
|
|
5544
6859
|
async function createArtifactPair(plan) {
|
|
5545
6860
|
const cwd = process.cwd();
|
|
5546
|
-
const leftPath = portablePathIdentity(
|
|
5547
|
-
const rightPath = portablePathIdentity(
|
|
5548
|
-
const left = await
|
|
6861
|
+
const leftPath = portablePathIdentity(resolve6(cwd, plan.config));
|
|
6862
|
+
const rightPath = portablePathIdentity(resolve6(cwd, plan.againstConfig));
|
|
6863
|
+
const left = await createTileflowBuildArtifacts2({
|
|
5549
6864
|
assetBaseUrl: tileflowSyntheticAssetOrigin2,
|
|
5550
6865
|
config: plan.config,
|
|
5551
6866
|
cwd
|
|
5552
6867
|
});
|
|
5553
6868
|
if (leftPath === rightPath) return [left, left];
|
|
5554
|
-
const right = await
|
|
6869
|
+
const right = await createTileflowBuildArtifacts2({
|
|
5555
6870
|
assetBaseUrl: tileflowSyntheticAssetOrigin2,
|
|
5556
6871
|
config: plan.againstConfig,
|
|
5557
6872
|
cwd
|
|
@@ -5671,7 +6986,7 @@ async function prepareVisualCompareOutput(plan, leftArtifacts, rightArtifacts, c
|
|
|
5671
6986
|
documentPath: relativePath(cwd, plan.output.documentPath),
|
|
5672
6987
|
reportPath: relativePath(cwd, plan.output.reportPath)
|
|
5673
6988
|
},
|
|
5674
|
-
warnings: [...warnings].sort(
|
|
6989
|
+
warnings: [...warnings].sort(compareCodeUnits8)
|
|
5675
6990
|
};
|
|
5676
6991
|
const report = renderTileflowVisualCompareReport({
|
|
5677
6992
|
generatedBy: "Tileflow CLI",
|
|
@@ -5696,7 +7011,7 @@ async function prepareVisualCompareOutput(plan, leftArtifacts, rightArtifacts, c
|
|
|
5696
7011
|
files,
|
|
5697
7012
|
incompatible,
|
|
5698
7013
|
nextAssets: inventory.assets.map((asset) => ({
|
|
5699
|
-
path:
|
|
7014
|
+
path: resolve6(plan.output.assetDirectory, asset.path),
|
|
5700
7015
|
sha256: asset.sha256
|
|
5701
7016
|
})),
|
|
5702
7017
|
removePaths: [...new Set(removePaths)].filter((path) => !filePaths.has(path))
|
|
@@ -5715,7 +7030,7 @@ async function writePreparedVisualCompareOutput(output, prepared) {
|
|
|
5715
7030
|
async function runVisualCompareWatch(plan, openReport) {
|
|
5716
7031
|
const cwd = process.cwd();
|
|
5717
7032
|
const controller = installSignalAbortController();
|
|
5718
|
-
const sameConfig = portablePathIdentity(
|
|
7033
|
+
const sameConfig = portablePathIdentity(resolve6(cwd, plan.config)) === portablePathIdentity(resolve6(cwd, plan.againstConfig));
|
|
5719
7034
|
let leftSession;
|
|
5720
7035
|
let rightSession;
|
|
5721
7036
|
let captureSession;
|
|
@@ -5959,7 +7274,7 @@ async function runVisualCompareWatch(plan, openReport) {
|
|
|
5959
7274
|
}
|
|
5960
7275
|
}
|
|
5961
7276
|
function resolveVisualCompareSide(artifacts, config, requestedMap, requestedTheme, role) {
|
|
5962
|
-
const availableMaps = Object.keys(artifacts.project.maps).sort(
|
|
7277
|
+
const availableMaps = Object.keys(artifacts.project.maps).sort(compareCodeUnits8);
|
|
5963
7278
|
const map = requestedMap ?? (availableMaps.length === 1 ? availableMaps[0] : void 0);
|
|
5964
7279
|
if (!map) {
|
|
5965
7280
|
throw new Error(
|
|
@@ -5972,7 +7287,7 @@ function resolveVisualCompareSide(artifacts, config, requestedMap, requestedThem
|
|
|
5972
7287
|
`Unknown ${role} map "${map}". Available maps: ${availableMaps.join(", ") || "(none)"}.`
|
|
5973
7288
|
);
|
|
5974
7289
|
}
|
|
5975
|
-
const resolvedMap =
|
|
7290
|
+
const resolvedMap = parseResolvedTileflowMap4(candidate);
|
|
5976
7291
|
const theme = resolveThemeSelection(resolvedMap, requestedTheme).name;
|
|
5977
7292
|
if (!artifacts.styles[map]?.[theme]) {
|
|
5978
7293
|
throw new Error(`The ${role} map theme "${map}/${theme}" was not compiled.`);
|
|
@@ -6024,11 +7339,11 @@ function requireCapture(captures, scene, role) {
|
|
|
6024
7339
|
return capture;
|
|
6025
7340
|
}
|
|
6026
7341
|
async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zooms) {
|
|
6027
|
-
const reportPath =
|
|
7342
|
+
const reportPath = resolve6(cwd, value);
|
|
6028
7343
|
if (extname(reportPath).toLowerCase() !== ".html") {
|
|
6029
7344
|
throw new Error("--report must name an .html file.");
|
|
6030
7345
|
}
|
|
6031
|
-
const reportDirectory =
|
|
7346
|
+
const reportDirectory = dirname10(reportPath);
|
|
6032
7347
|
if (reportDirectory === cwd || reportDirectory === parse(reportPath).root) {
|
|
6033
7348
|
throw new Error("Visual compare output must use a dedicated subdirectory.");
|
|
6034
7349
|
}
|
|
@@ -6038,7 +7353,7 @@ async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zoo
|
|
|
6038
7353
|
const stem = reportPath.slice(0, -".html".length);
|
|
6039
7354
|
const documentPath = `${stem}.json`;
|
|
6040
7355
|
const assetDirectory = `${stem}.assets`;
|
|
6041
|
-
const inventoryPath =
|
|
7356
|
+
const inventoryPath = resolve6(assetDirectory, tileflowVisualCompareInventoryFileName);
|
|
6042
7357
|
const boundary = isInside(cwd, reportDirectory) ? cwd : reportDirectory;
|
|
6043
7358
|
await assertNoSymlinkComponents(reportDirectory, boundary);
|
|
6044
7359
|
await assertNoSymlinkComponents(assetDirectory, boundary);
|
|
@@ -6078,16 +7393,16 @@ async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zoo
|
|
|
6078
7393
|
function visualCompareRowPaths(output, index, zoom, includeDiff) {
|
|
6079
7394
|
const prefix = `${String(index + 1).padStart(2, "0")}-z${zoomSlug(zoom)}`;
|
|
6080
7395
|
return {
|
|
6081
|
-
leftPath:
|
|
6082
|
-
rightPath:
|
|
6083
|
-
leftReceiptPath:
|
|
6084
|
-
rightReceiptPath:
|
|
6085
|
-
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
|
|
6086
7401
|
};
|
|
6087
7402
|
}
|
|
6088
7403
|
function sideJson(side, cwd) {
|
|
6089
7404
|
return {
|
|
6090
|
-
config: relativePath(cwd,
|
|
7405
|
+
config: relativePath(cwd, resolve6(cwd, side.config)),
|
|
6091
7406
|
label: side.label,
|
|
6092
7407
|
map: side.map,
|
|
6093
7408
|
theme: side.theme
|
|
@@ -6095,13 +7410,13 @@ function sideJson(side, cwd) {
|
|
|
6095
7410
|
}
|
|
6096
7411
|
function watchSideLabel(cwd, config, map, theme) {
|
|
6097
7412
|
const selection = map ? `${map} / ${theme ?? "default theme"}` : `singular map / ${theme ?? "default theme"}`;
|
|
6098
|
-
return `${relativePath(cwd,
|
|
7413
|
+
return `${relativePath(cwd, resolve6(cwd, config))} (${selection})`;
|
|
6099
7414
|
}
|
|
6100
7415
|
async function assertVisualCompareOutputDoesNotOverlapInputs(plan, leftArtifacts, rightArtifacts) {
|
|
6101
7416
|
const cwd = process.cwd();
|
|
6102
7417
|
const inputs = mergeVisualCompareInputPaths([
|
|
6103
|
-
{ kind: "file", path:
|
|
6104
|
-
{ kind: "file", path:
|
|
7418
|
+
{ kind: "file", path: resolve6(cwd, plan.config) },
|
|
7419
|
+
{ kind: "file", path: resolve6(cwd, plan.againstConfig) },
|
|
6105
7420
|
...visualCompareArtifactInputPaths(leftArtifacts),
|
|
6106
7421
|
...visualCompareArtifactInputPaths(rightArtifacts)
|
|
6107
7422
|
]);
|
|
@@ -6168,7 +7483,7 @@ function createVisualCompareInventory(assetDirectory, assets) {
|
|
|
6168
7483
|
throw new Error("Visual compare produced an invalid managed asset path.");
|
|
6169
7484
|
}
|
|
6170
7485
|
return { path: relativeAssetPath, sha256: sha256Source(source) };
|
|
6171
|
-
}).sort((left, right) =>
|
|
7486
|
+
}).sort((left, right) => compareCodeUnits8(left.path, right.path))
|
|
6172
7487
|
};
|
|
6173
7488
|
}
|
|
6174
7489
|
function serializeVisualCompareInventory(inventory) {
|
|
@@ -6176,7 +7491,7 @@ function serializeVisualCompareInventory(inventory) {
|
|
|
6176
7491
|
`;
|
|
6177
7492
|
}
|
|
6178
7493
|
async function readVisualCompareInventory(assetDirectory, inventoryPath, boundary) {
|
|
6179
|
-
await assertNoSymlinkComponents(
|
|
7494
|
+
await assertNoSymlinkComponents(dirname10(inventoryPath), boundary);
|
|
6180
7495
|
let info;
|
|
6181
7496
|
try {
|
|
6182
7497
|
info = await lstat3(inventoryPath);
|
|
@@ -6199,8 +7514,8 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
|
|
|
6199
7514
|
const inventory = validateVisualCompareInventory(parsed);
|
|
6200
7515
|
const assets = [];
|
|
6201
7516
|
for (const asset of inventory.assets) {
|
|
6202
|
-
const path =
|
|
6203
|
-
await assertNoSymlinkComponents(
|
|
7517
|
+
const path = resolve6(assetDirectory, asset.path);
|
|
7518
|
+
await assertNoSymlinkComponents(dirname10(path), boundary);
|
|
6204
7519
|
let assetInfo;
|
|
6205
7520
|
try {
|
|
6206
7521
|
assetInfo = await lstat3(path);
|
|
@@ -6243,7 +7558,7 @@ function validateVisualCompareInventory(input) {
|
|
|
6243
7558
|
return { path: asset.path, sha256: asset.sha256 };
|
|
6244
7559
|
});
|
|
6245
7560
|
const paths = assets.map((asset) => asset.path);
|
|
6246
|
-
const sortedPaths = [...paths].sort(
|
|
7561
|
+
const sortedPaths = [...paths].sort(compareCodeUnits8);
|
|
6247
7562
|
if (new Set(paths).size !== paths.length || paths.some((path, index) => path !== sortedPaths[index])) {
|
|
6248
7563
|
throw new Error("Visual compare inventory asset paths must be unique and sorted.");
|
|
6249
7564
|
}
|
|
@@ -6263,25 +7578,25 @@ function isVisualCompareOwnedAssetName(value) {
|
|
|
6263
7578
|
return Number.isInteger(row) && row >= 1 && row <= tileflowVisualCompareLimits.maximumRows;
|
|
6264
7579
|
}
|
|
6265
7580
|
function hasExactKeys(record, keys) {
|
|
6266
|
-
const actual = Object.keys(record).sort(
|
|
6267
|
-
const expected = [...keys].sort(
|
|
7581
|
+
const actual = Object.keys(record).sort(compareCodeUnits8);
|
|
7582
|
+
const expected = [...keys].sort(compareCodeUnits8);
|
|
6268
7583
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
6269
7584
|
}
|
|
6270
7585
|
function sha256Source(source) {
|
|
6271
7586
|
return createHash3("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
|
|
6272
7587
|
}
|
|
6273
7588
|
async function canonicalPathIdentity(path) {
|
|
6274
|
-
const absolute =
|
|
7589
|
+
const absolute = resolve6(path);
|
|
6275
7590
|
let current = absolute;
|
|
6276
7591
|
const missingSegments = [];
|
|
6277
7592
|
while (true) {
|
|
6278
7593
|
try {
|
|
6279
7594
|
const existing = await realpath(current);
|
|
6280
|
-
const canonical =
|
|
7595
|
+
const canonical = resolve6(existing, ...missingSegments.reverse()).normalize("NFC");
|
|
6281
7596
|
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
6282
7597
|
} catch (error) {
|
|
6283
7598
|
if (!isMissingPathComponent(error)) throw error;
|
|
6284
|
-
const parent =
|
|
7599
|
+
const parent = dirname10(current);
|
|
6285
7600
|
if (parent === current) {
|
|
6286
7601
|
const canonical = absolute.normalize("NFC");
|
|
6287
7602
|
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
@@ -6353,12 +7668,12 @@ function formatCoordinate(value) {
|
|
|
6353
7668
|
return String(Math.round(value * 1e6) / 1e6);
|
|
6354
7669
|
}
|
|
6355
7670
|
function portablePathIdentity(path) {
|
|
6356
|
-
const normalized =
|
|
7671
|
+
const normalized = resolve6(path).normalize("NFC");
|
|
6357
7672
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
6358
7673
|
}
|
|
6359
7674
|
function isInside(parent, child) {
|
|
6360
7675
|
const path = relative3(parent, child);
|
|
6361
|
-
return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !
|
|
7676
|
+
return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !isAbsolute3(path);
|
|
6362
7677
|
}
|
|
6363
7678
|
function pathsOverlap(left, right) {
|
|
6364
7679
|
return isInside(left, right) || isInside(right, left);
|
|
@@ -6438,7 +7753,7 @@ function createTileflowVisualDiffJson(comparisons, cwd) {
|
|
|
6438
7753
|
return {
|
|
6439
7754
|
schemaVersion: 1,
|
|
6440
7755
|
command: "visual.diff",
|
|
6441
|
-
comparisons: [...comparisons].sort((left, right) =>
|
|
7756
|
+
comparisons: [...comparisons].sort((left, right) => compareCodeUnits9(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
|
|
6442
7757
|
...createTileflowVisualComparisonDocument(comparison),
|
|
6443
7758
|
baselinePath: relativePath(cwd, baselinePath),
|
|
6444
7759
|
actualPath: relativePath(cwd, actualPath),
|
|
@@ -6466,14 +7781,14 @@ async function runVisualAnalyze(scene, options) {
|
|
|
6466
7781
|
validateVisualSelection([scene]);
|
|
6467
7782
|
const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
|
|
6468
7783
|
const cwd = process.cwd();
|
|
6469
|
-
const referencePath =
|
|
7784
|
+
const referencePath = resolve7(cwd, options.reference);
|
|
6470
7785
|
const referencePng = await readVisualReference(referencePath);
|
|
6471
7786
|
const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
|
|
6472
7787
|
if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
|
|
6473
7788
|
const outputDirectory = await resolveVisualDirectory(cwd, options.outputDir, "output");
|
|
6474
|
-
const actualPath =
|
|
6475
|
-
const managedDiffPath =
|
|
6476
|
-
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`);
|
|
6477
7792
|
assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
|
|
6478
7793
|
const controller = installSignalAbortController();
|
|
6479
7794
|
const session = createTileflowCaptureSession3({
|
|
@@ -6562,15 +7877,15 @@ async function runVisualDiff(scenes, options) {
|
|
|
6562
7877
|
capture,
|
|
6563
7878
|
files.kind === "complete" ? files.baseline : void 0
|
|
6564
7879
|
);
|
|
6565
|
-
const actualPath =
|
|
6566
|
-
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;
|
|
6567
7882
|
prepared.push({
|
|
6568
7883
|
capture,
|
|
6569
7884
|
comparison,
|
|
6570
7885
|
baselinePath: files.pngPath,
|
|
6571
7886
|
actualPath,
|
|
6572
7887
|
diffPath,
|
|
6573
|
-
reportPath:
|
|
7888
|
+
reportPath: resolve7(outputDirectory.path, `${capture.scene}.visual.json`)
|
|
6574
7889
|
});
|
|
6575
7890
|
}
|
|
6576
7891
|
const document = createTileflowVisualDiffJson(prepared, cwd);
|
|
@@ -6590,7 +7905,7 @@ async function runVisualDiff(scenes, options) {
|
|
|
6590
7905
|
}
|
|
6591
7906
|
]),
|
|
6592
7907
|
removePaths: prepared.flatMap(
|
|
6593
|
-
(item) => item.diffPath ? [] : [
|
|
7908
|
+
(item) => item.diffPath ? [] : [resolve7(outputDirectory.path, `${item.capture.scene}.diff.png`)]
|
|
6594
7909
|
)
|
|
6595
7910
|
});
|
|
6596
7911
|
if (options.json) {
|
|
@@ -6653,7 +7968,7 @@ async function runVisualUpdate(scenes, options) {
|
|
|
6653
7968
|
renderer: capture.renderer,
|
|
6654
7969
|
networkDependent: capture.networkDependent
|
|
6655
7970
|
},
|
|
6656
|
-
warnings: [...capture.warnings].sort(
|
|
7971
|
+
warnings: [...capture.warnings].sort(compareCodeUnits9)
|
|
6657
7972
|
});
|
|
6658
7973
|
outputFiles.push(
|
|
6659
7974
|
{ path: files.pngPath, source: capture.png },
|
|
@@ -6670,7 +7985,7 @@ async function runVisualUpdate(scenes, options) {
|
|
|
6670
7985
|
const document = {
|
|
6671
7986
|
schemaVersion: 1,
|
|
6672
7987
|
command: "visual.update",
|
|
6673
|
-
updates: updates.sort((left, right) =>
|
|
7988
|
+
updates: updates.sort((left, right) => compareCodeUnits9(left.scene, right.scene))
|
|
6674
7989
|
};
|
|
6675
7990
|
if (options.json) {
|
|
6676
7991
|
process.stdout.write(serializeTileflowVisualCommandJson(document));
|
|
@@ -6711,7 +8026,7 @@ async function resolvePreviousBaseline(capture, files) {
|
|
|
6711
8026
|
}
|
|
6712
8027
|
}
|
|
6713
8028
|
async function readBaselineFiles(directory, scene) {
|
|
6714
|
-
const pngPath =
|
|
8029
|
+
const pngPath = resolve7(directory.path, `${scene}.png`);
|
|
6715
8030
|
const receiptPath = captureReceiptPath(pngPath);
|
|
6716
8031
|
const [png, receipt] = await Promise.all([
|
|
6717
8032
|
readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits.maximumPngBytes),
|
|
@@ -6726,7 +8041,7 @@ async function readBaselineFiles(directory, scene) {
|
|
|
6726
8041
|
return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
|
|
6727
8042
|
}
|
|
6728
8043
|
async function readOptionalArtifact(path, boundary, maximumBytes) {
|
|
6729
|
-
await assertNoSymlinkComponents(
|
|
8044
|
+
await assertNoSymlinkComponents(dirname11(path), boundary);
|
|
6730
8045
|
let info;
|
|
6731
8046
|
try {
|
|
6732
8047
|
info = await lstat4(path);
|
|
@@ -6771,12 +8086,12 @@ async function readVisualReference(path) {
|
|
|
6771
8086
|
return new Uint8Array(await readFile6(path));
|
|
6772
8087
|
}
|
|
6773
8088
|
async function resolveVisualDirectory(cwd, value, kind) {
|
|
6774
|
-
const path =
|
|
8089
|
+
const path = resolve7(cwd, value);
|
|
6775
8090
|
if (path === cwd || path === parse2(path).root) {
|
|
6776
8091
|
throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
|
|
6777
8092
|
}
|
|
6778
8093
|
if (kind === "baseline") {
|
|
6779
|
-
for (const managed of [
|
|
8094
|
+
for (const managed of [resolve7(cwd, ".tileflow/captures"), resolve7(cwd, ".tileflow/diffs")]) {
|
|
6780
8095
|
if (pathsOverlap2(path, managed)) {
|
|
6781
8096
|
throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
|
|
6782
8097
|
}
|
|
@@ -6812,11 +8127,11 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
|
|
|
6812
8127
|
}
|
|
6813
8128
|
}
|
|
6814
8129
|
function portablePathIdentity2(path) {
|
|
6815
|
-
return
|
|
8130
|
+
return resolve7(path).normalize("NFC").toLowerCase();
|
|
6816
8131
|
}
|
|
6817
8132
|
function isInside2(parent, child) {
|
|
6818
8133
|
const path = relative4(parent, child);
|
|
6819
|
-
return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !
|
|
8134
|
+
return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
|
|
6820
8135
|
}
|
|
6821
8136
|
function isMissingFile5(error) {
|
|
6822
8137
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
@@ -6831,12 +8146,14 @@ var defaultApiUrl = defaultTileflowApiUrl;
|
|
|
6831
8146
|
var defaultAppUrl = "https://tileflow.dev";
|
|
6832
8147
|
var defaultConfigPath = defaultTileflowConfigPath;
|
|
6833
8148
|
var defaultManifestPath = defaultTileflowManifestPath;
|
|
6834
|
-
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");
|
|
6835
8151
|
registerLanguageCommand(program);
|
|
6836
8152
|
registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
8153
|
+
registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
6837
8154
|
program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
|
|
6838
|
-
const configPath =
|
|
6839
|
-
const ignorePath =
|
|
8155
|
+
const configPath = resolve8(process.cwd(), defaultConfigPath);
|
|
8156
|
+
const ignorePath = resolve8(process.cwd(), ".gitignore");
|
|
6840
8157
|
if (existsSync(configPath) && !options.force) {
|
|
6841
8158
|
logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
|
|
6842
8159
|
printNextSteps([`Run ${command("tileflow init --force")} to replace it.`]);
|
|
@@ -6852,9 +8169,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
6852
8169
|
}
|
|
6853
8170
|
logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
|
|
6854
8171
|
printKeyValue("Path", pathLabel(configPath));
|
|
6855
|
-
printKeyValue("Map",
|
|
8172
|
+
printKeyValue("Map", pc6.bold("madrid"));
|
|
6856
8173
|
printNextSteps([
|
|
6857
|
-
`Install ${
|
|
8174
|
+
`Install ${pc6.cyan("@tileflow/core")} and ${pc6.cyan("@tileflow/maps")} in this project if needed.`,
|
|
6858
8175
|
`Preview locally with ${command("tileflow preview")}.`,
|
|
6859
8176
|
`Validate the config with ${command("tileflow validate")}.`
|
|
6860
8177
|
]);
|
|
@@ -6943,177 +8260,244 @@ program.command("whoami").description("Show the authenticated Tileflow account")
|
|
|
6943
8260
|
logSuccess("Tileflow CLI is authenticated.");
|
|
6944
8261
|
printAccountDetails(selected.session);
|
|
6945
8262
|
});
|
|
6946
|
-
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(
|
|
6947
8264
|
"--api-base-url <url>",
|
|
6948
8265
|
"Tileflow API base URL used to resolve official map assets",
|
|
6949
8266
|
process.env.TILEFLOW_API_URL ?? defaultApiUrl
|
|
6950
|
-
).option("--json", "print deterministic schema-version-1 JSON").action(
|
|
6951
|
-
|
|
6952
|
-
if (options.
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
issues: [
|
|
6958
|
-
{
|
|
6959
|
-
code: "INVALID_TARGET",
|
|
6960
|
-
message: `Invalid validation target: ${options.target}`,
|
|
6961
|
-
path: "target",
|
|
6962
|
-
phase: "command-validation"
|
|
6963
|
-
}
|
|
6964
|
-
],
|
|
6965
|
-
phase: "command-validation"
|
|
6966
|
-
},
|
|
6967
|
-
process.cwd(),
|
|
6968
|
-
{ code: "INVALID_TARGET", phase: "command-validation" }
|
|
6969
|
-
);
|
|
6970
|
-
process.stderr.write(serializeTileflowCommandDocument4(failure));
|
|
6971
|
-
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
|
+
});
|
|
6972
8274
|
return;
|
|
6973
8275
|
}
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
cwd: process.cwd(),
|
|
6996
|
-
target: options.target
|
|
6997
|
-
});
|
|
6998
|
-
const mapAssets = createCompiledMapAssets(
|
|
6999
|
-
compiledIcons,
|
|
7000
|
-
(binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`
|
|
7001
|
-
);
|
|
7002
|
-
failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
|
|
7003
|
-
const compiledStyles = createTileflowStyles(project, {
|
|
7004
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
7005
|
-
mapAssets
|
|
7006
|
-
});
|
|
7007
|
-
failureDefaults = { code: "TF_LOCAL_TILESET_INVALID", phase: "local-tileset-resolution" };
|
|
7008
|
-
const localTilesets = await prepareTileflowLocalTilesets(project, compiledStyles, {
|
|
7009
|
-
assetBaseUrl: "/tileflow",
|
|
7010
|
-
baseDirectory,
|
|
7011
|
-
cwd: process.cwd()
|
|
7012
|
-
});
|
|
7013
|
-
failureDefaults = { code: "FONT_COMPILATION_FAILED", phase: "font-compilation" };
|
|
7014
|
-
const { styles } = await prepareTileflowStyleFonts(project, localTilesets.styles, {
|
|
7015
|
-
assetBaseUrl: "/tileflow",
|
|
7016
|
-
baseDirectory,
|
|
7017
|
-
cwd: process.cwd(),
|
|
7018
|
-
target: options.target
|
|
7019
|
-
});
|
|
7020
|
-
failureDefaults = { code: "HOSTED_INCOMPATIBLE", phase: "hosted-validation" };
|
|
7021
|
-
const hostedIssues = options.target === "hosted" ? inspectTileflowHostedCompatibility(project, styles) : [];
|
|
7022
|
-
if (hostedIssues.length > 0) {
|
|
7023
|
-
if (!options.json) {
|
|
7024
|
-
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;
|
|
7025
8297
|
return;
|
|
7026
8298
|
}
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
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."), {
|
|
7030
8354
|
code: "HOSTED_INCOMPATIBLE",
|
|
7031
|
-
|
|
7032
|
-
|
|
8355
|
+
issues: hostedIssues.map((issue) => ({
|
|
8356
|
+
code: "HOSTED_INCOMPATIBLE",
|
|
8357
|
+
message: issue.message,
|
|
8358
|
+
path: issue.path,
|
|
8359
|
+
phase: "hosted-validation"
|
|
8360
|
+
})),
|
|
7033
8361
|
phase: "hosted-validation"
|
|
7034
|
-
})
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
)
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
code: "VALIDATION_WARNING",
|
|
7045
|
-
phase: "theme-audit"
|
|
7046
|
-
});
|
|
7047
|
-
const checks = [
|
|
7048
|
-
"Config schema",
|
|
7049
|
-
"Icon asset closure",
|
|
7050
|
-
"Text provider closure",
|
|
7051
|
-
"Named map styles",
|
|
7052
|
-
"MapLibre style semantics",
|
|
7053
|
-
...options.target === "hosted" ? ["Hosted compatibility"] : []
|
|
7054
|
-
];
|
|
7055
|
-
if (options.json) {
|
|
7056
|
-
const summary = createTileflowCommandSummary2({
|
|
7057
|
-
code: "VALIDATION_OK",
|
|
7058
|
-
command: "validate",
|
|
7059
|
-
message: `Tileflow config is valid (${plural(mapNames.length, "map")}).`,
|
|
7060
|
-
ok: true,
|
|
7061
|
-
path: "",
|
|
7062
|
-
phase: "validation",
|
|
7063
|
-
severity: "info",
|
|
7064
|
-
suggestion: "No changes are required."
|
|
8362
|
+
});
|
|
8363
|
+
}
|
|
8364
|
+
const themeWarnings = mapNames.flatMap(
|
|
8365
|
+
(mapName) => auditTileflowMapThemeValues2(project.maps[mapName]).filter(
|
|
8366
|
+
({ severity }) => severity === "warning"
|
|
8367
|
+
)
|
|
8368
|
+
);
|
|
8369
|
+
const diagnostics = themeWarnings.length === 0 ? [] : createTileflowStructuredDiagnostics3({ diagnostics: themeWarnings }, process.cwd(), {
|
|
8370
|
+
code: "VALIDATION_WARNING",
|
|
8371
|
+
phase: "theme-audit"
|
|
7065
8372
|
});
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
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
|
|
7074
8417
|
);
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
logSuccess(`Config is valid (${plural(mapNames.length, "map")}).`);
|
|
7078
|
-
printChecks(checks);
|
|
7079
|
-
for (const diagnostic of diagnostics) {
|
|
7080
|
-
logWarning(`${diagnostic.path || "(root)"}: ${diagnostic.message}`);
|
|
8418
|
+
process.stderr.write(serializeTileflowCommandDocument5(failure2));
|
|
8419
|
+
process.exitCode = 1;
|
|
7081
8420
|
}
|
|
7082
|
-
} catch (error) {
|
|
7083
|
-
if (!options.json) throw error;
|
|
7084
|
-
const failure = createTileflowCommandFailureDocument4(
|
|
7085
|
-
"validate",
|
|
7086
|
-
error,
|
|
7087
|
-
process.cwd(),
|
|
7088
|
-
failureDefaults
|
|
7089
|
-
);
|
|
7090
|
-
process.stderr.write(serializeTileflowCommandDocument4(failure));
|
|
7091
|
-
process.exitCode = 1;
|
|
7092
8421
|
}
|
|
7093
|
-
|
|
7094
|
-
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(
|
|
7095
8424
|
"--api-base-url <url>",
|
|
7096
8425
|
"Tileflow API base URL used to resolve official map assets",
|
|
7097
8426
|
process.env.TILEFLOW_API_URL ?? defaultApiUrl
|
|
7098
|
-
).
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
()
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
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(
|
|
7112
8449
|
"--api-base-url <url>",
|
|
7113
8450
|
"Tileflow API base URL used to resolve official map assets",
|
|
7114
8451
|
process.env.TILEFLOW_API_URL ?? defaultApiUrl
|
|
7115
|
-
).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(
|
|
7116
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
|
+
}
|
|
7117
8501
|
const port = parsePort(options.port);
|
|
7118
8502
|
if (port === null) {
|
|
7119
8503
|
logError(`Invalid port: ${options.port}`);
|
|
@@ -7158,28 +8542,45 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7158
8542
|
process.exitCode = 1;
|
|
7159
8543
|
return;
|
|
7160
8544
|
}
|
|
7161
|
-
const comparisonRequested = options.againstConfig !== void 0 || options.againstMap !== void 0 || options.againstScene !== void 0 || options.againstTheme !== void 0;
|
|
7162
8545
|
if (comparisonRequested) {
|
|
7163
8546
|
await runTileflowComparisonPreview(options, { host, port });
|
|
7164
8547
|
return;
|
|
7165
8548
|
}
|
|
7166
8549
|
await withTileflowConfigSecretsHidden(async () => {
|
|
7167
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);
|
|
7168
8559
|
const session = await createTileflowArtifactSession3({
|
|
7169
8560
|
assetBaseUrl: origin,
|
|
7170
8561
|
apiBaseUrl: options.apiBaseUrl,
|
|
7171
8562
|
config: options.config,
|
|
8563
|
+
...icons ? { icons } : {},
|
|
8564
|
+
...renderer === "native" ? { renderer: "native" } : {},
|
|
7172
8565
|
styleBaseUrl: origin,
|
|
7173
8566
|
watch: true
|
|
7174
8567
|
});
|
|
7175
8568
|
const initialArtifacts = session.getLastGoodArtifacts();
|
|
8569
|
+
let nativeSelection;
|
|
7176
8570
|
try {
|
|
7177
8571
|
if (initialArtifacts) {
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
8572
|
+
if (renderer === "native") {
|
|
8573
|
+
nativeSelection = resolveTileflowNativePreviewSelection(initialArtifacts.manifest, {
|
|
8574
|
+
map: options.map,
|
|
8575
|
+
theme: options.theme
|
|
8576
|
+
});
|
|
8577
|
+
} else {
|
|
8578
|
+
resolveTileflowPreview(initialArtifacts.project, {
|
|
8579
|
+
map: options.map,
|
|
8580
|
+
scene: options.scene,
|
|
8581
|
+
theme: options.theme
|
|
8582
|
+
});
|
|
8583
|
+
}
|
|
7183
8584
|
}
|
|
7184
8585
|
} catch (error) {
|
|
7185
8586
|
await session.close();
|
|
@@ -7187,7 +8588,12 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7187
8588
|
process.exitCode = 1;
|
|
7188
8589
|
return;
|
|
7189
8590
|
}
|
|
7190
|
-
const fetch2 =
|
|
8591
|
+
const fetch2 = renderer === "native" ? createTileflowNativePreviewRequestHandler({
|
|
8592
|
+
config: options.config,
|
|
8593
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
8594
|
+
onError: printTileflowPreviewError,
|
|
8595
|
+
session
|
|
8596
|
+
}) : createTileflowDevRequestHandler2({
|
|
7191
8597
|
config: options.config,
|
|
7192
8598
|
apiBaseUrl: options.apiBaseUrl,
|
|
7193
8599
|
map: options.map,
|
|
@@ -7212,7 +8618,8 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7212
8618
|
..."lastGoodGeneration" in state && state.lastGoodGeneration !== void 0 ? { lastGoodGeneration: state.lastGoodGeneration } : {},
|
|
7213
8619
|
...firstDiagnostic?.code ? { code: firstDiagnostic.code } : {},
|
|
7214
8620
|
...firstDiagnostic?.phase ? { phase: firstDiagnostic.phase } : {},
|
|
7215
|
-
...state.status === "invalid" ? { diagnostics: state.diagnostics } : {}
|
|
8621
|
+
...state.status === "invalid" ? { diagnostics: state.diagnostics } : {},
|
|
8622
|
+
...nativeLifecycleFields
|
|
7216
8623
|
})}
|
|
7217
8624
|
`
|
|
7218
8625
|
);
|
|
@@ -7221,14 +8628,16 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7221
8628
|
if (state.status === "invalid") {
|
|
7222
8629
|
console.error(
|
|
7223
8630
|
[
|
|
7224
|
-
`Tileflow generation ${state.generation} is invalid; preserving the last valid preview.`,
|
|
8631
|
+
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.`,
|
|
7225
8632
|
...state.diagnostics.map(
|
|
7226
8633
|
(diagnostic) => `- ${diagnostic.path || "(root)"}: ${diagnostic.message}`
|
|
7227
8634
|
)
|
|
7228
8635
|
].join("\n")
|
|
7229
8636
|
);
|
|
7230
8637
|
} else if (recovered) {
|
|
7231
|
-
logSuccess(
|
|
8638
|
+
logSuccess(
|
|
8639
|
+
renderer === "native" ? `Tileflow native artifact preview recovered at generation ${state.generation}.` : `Tileflow preview recovered at generation ${state.generation}.`
|
|
8640
|
+
);
|
|
7232
8641
|
}
|
|
7233
8642
|
};
|
|
7234
8643
|
emitState(session.getState());
|
|
@@ -7241,13 +8650,26 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7241
8650
|
server = createdServer;
|
|
7242
8651
|
});
|
|
7243
8652
|
if (!options.json) {
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
8653
|
+
if (renderer === "native") {
|
|
8654
|
+
logSuccess("Tileflow native artifact preview is running and watching for changes.");
|
|
8655
|
+
printKeyValue("Manifest", link(manifestUrl));
|
|
8656
|
+
printKeyValue("Profile", tileflowNativePreviewProfile);
|
|
8657
|
+
printKeyValue("Endpoint", "assets-only");
|
|
8658
|
+
printKeyValue("Config", pathLabel(options.config));
|
|
8659
|
+
if (nativeSelection) {
|
|
8660
|
+
printKeyValue("Map", nativeSelection.mapName);
|
|
8661
|
+
printKeyValue("Theme", nativeSelection.themeName);
|
|
8662
|
+
}
|
|
8663
|
+
logMuted("Metro remains the JavaScript development server. Press Ctrl+C to stop.");
|
|
8664
|
+
} else {
|
|
8665
|
+
logSuccess("Tileflow preview is running and watching for changes.");
|
|
8666
|
+
printKeyValue("Local", link(origin));
|
|
8667
|
+
printKeyValue("Config", pathLabel(options.config));
|
|
8668
|
+
if (options.map) printKeyValue("Map", options.map);
|
|
8669
|
+
if (options.theme) printKeyValue("Theme", options.theme);
|
|
8670
|
+
if (options.scene) printKeyValue("Scene", options.scene);
|
|
8671
|
+
logMuted("Press Ctrl+C to stop.");
|
|
8672
|
+
}
|
|
7251
8673
|
}
|
|
7252
8674
|
await waitForTerminationSignal(server);
|
|
7253
8675
|
} finally {
|
|
@@ -7257,7 +8679,13 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7257
8679
|
await session.close();
|
|
7258
8680
|
if (options.json) {
|
|
7259
8681
|
process.stdout.write(
|
|
7260
|
-
`${JSON.stringify({
|
|
8682
|
+
`${JSON.stringify({
|
|
8683
|
+
schemaVersion: 1,
|
|
8684
|
+
command: "dev",
|
|
8685
|
+
event: "stopped",
|
|
8686
|
+
generation,
|
|
8687
|
+
...nativeLifecycleFields
|
|
8688
|
+
})}
|
|
7261
8689
|
`
|
|
7262
8690
|
);
|
|
7263
8691
|
}
|
|
@@ -7265,10 +8693,10 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7265
8693
|
});
|
|
7266
8694
|
}
|
|
7267
8695
|
);
|
|
7268
|
-
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(
|
|
8696
|
+
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(
|
|
7269
8697
|
"--overwrite-self-hosted-manifest",
|
|
7270
8698
|
"explicitly replace an existing self-hosted manifest at --manifest"
|
|
7271
|
-
).action(
|
|
8699
|
+
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
|
|
7272
8700
|
async (options) => {
|
|
7273
8701
|
const source = resolveDeploySource(process.env);
|
|
7274
8702
|
const resolveApi = (selectedMap) => requireApiOptions(options, {
|
|
@@ -7286,19 +8714,39 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7286
8714
|
options.apiUrl ?? defaultApiUrl,
|
|
7287
8715
|
...options.overwriteSelfHostedManifest ? ["--overwrite-self-hosted-manifest"] : [],
|
|
7288
8716
|
...options.mapId ? ["--map-id", options.mapId] : [],
|
|
7289
|
-
...options.map && selectedMap ? ["--map", selectedMap] : []
|
|
8717
|
+
...options.map && selectedMap ? ["--map", selectedMap] : [],
|
|
8718
|
+
...options.withNative ? ["--with-native"] : []
|
|
7290
8719
|
])
|
|
7291
8720
|
});
|
|
7292
8721
|
const apiUrl = normalizeApiOrigin(options.apiUrl ?? defaultApiUrl);
|
|
7293
8722
|
delete process.env.TILEFLOW_API_KEY;
|
|
8723
|
+
if (options.withNative) {
|
|
8724
|
+
const result = await runHostedNativeDeploy(
|
|
8725
|
+
{ ...options, apiUrl },
|
|
8726
|
+
{
|
|
8727
|
+
source,
|
|
8728
|
+
resolveApi,
|
|
8729
|
+
loadManifest: loadExistingDeployManifest,
|
|
8730
|
+
writeManifest: writeDeployManifest
|
|
8731
|
+
}
|
|
8732
|
+
);
|
|
8733
|
+
if (result) {
|
|
8734
|
+
logSuccess(
|
|
8735
|
+
`${result.changed ? "Published" : "Unchanged"} ${pc6.bold(result.mapName)} web + native-v1 (v${result.version}).`
|
|
8736
|
+
);
|
|
8737
|
+
printKeyValue("Manifest", pathLabel(result.manifestPath));
|
|
8738
|
+
printKeyValue("Native", link(result.nativeManifestUrl));
|
|
8739
|
+
}
|
|
8740
|
+
return;
|
|
8741
|
+
}
|
|
7294
8742
|
logInfo(`Deploying ${pathLabel(options.config)}.`);
|
|
7295
8743
|
const loaded = await withTileflowConfigSecretsHidden(
|
|
7296
|
-
() =>
|
|
8744
|
+
() => loadTileflowConfigWithInputs4(options.config)
|
|
7297
8745
|
);
|
|
7298
8746
|
const project = loaded.project;
|
|
7299
|
-
const baseDirectory =
|
|
7300
|
-
|
|
7301
|
-
const configuredMapNames =
|
|
8747
|
+
const baseDirectory = dirname12(loaded.configFile);
|
|
8748
|
+
assertValidTileflowConfig3(project);
|
|
8749
|
+
const configuredMapNames = getTileflowMapNames5(project);
|
|
7302
8750
|
const mapNames = selectManagedDeployMaps(configuredMapNames, options);
|
|
7303
8751
|
if (!mapNames) return;
|
|
7304
8752
|
if (!validateDeployTarget(options)) return;
|
|
@@ -7309,9 +8757,10 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7309
8757
|
mapNames.map((mapName) => [mapName, project.maps[mapName]])
|
|
7310
8758
|
)
|
|
7311
8759
|
};
|
|
7312
|
-
const compiledIcons = await
|
|
8760
|
+
const compiledIcons = await compileTileflowIconPackages3(deploymentProject, {
|
|
7313
8761
|
baseDirectory,
|
|
7314
8762
|
cwd: process.cwd(),
|
|
8763
|
+
icons: resolveIconOptions(options),
|
|
7315
8764
|
target: "hosted"
|
|
7316
8765
|
});
|
|
7317
8766
|
const bindingsByMap = new Map(
|
|
@@ -7324,16 +8773,16 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7324
8773
|
compiledIcons,
|
|
7325
8774
|
(binding) => `${apiUrl}/sprites/preflight/${binding.packageHash}/sprite`
|
|
7326
8775
|
);
|
|
7327
|
-
const compiledPreflightStyles =
|
|
8776
|
+
const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
|
|
7328
8777
|
apiBaseUrl: apiUrl,
|
|
7329
8778
|
mapAssets: preflightMapAssets
|
|
7330
8779
|
});
|
|
7331
|
-
const preflightLocalTilesets = await
|
|
8780
|
+
const preflightLocalTilesets = await prepareTileflowLocalTilesets2(
|
|
7332
8781
|
deploymentProject,
|
|
7333
8782
|
compiledPreflightStyles,
|
|
7334
8783
|
{ assetBaseUrl: apiUrl, baseDirectory, cwd: process.cwd() }
|
|
7335
8784
|
);
|
|
7336
|
-
const preflightFonts = await
|
|
8785
|
+
const preflightFonts = await prepareTileflowStyleFonts2(
|
|
7337
8786
|
deploymentProject,
|
|
7338
8787
|
preflightLocalTilesets.styles,
|
|
7339
8788
|
{
|
|
@@ -7392,7 +8841,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7392
8841
|
);
|
|
7393
8842
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
7394
8843
|
logSuccess(
|
|
7395
|
-
`${action} icon package ${
|
|
8844
|
+
`${action} icon package ${pc6.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
|
|
7396
8845
|
);
|
|
7397
8846
|
hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
|
|
7398
8847
|
}
|
|
@@ -7401,16 +8850,16 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7401
8850
|
if (!sprite) throw new Error(`Missing hosted sprite URL for map ${binding.mapName}`);
|
|
7402
8851
|
return sprite;
|
|
7403
8852
|
});
|
|
7404
|
-
const compiledHostedStyles =
|
|
8853
|
+
const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
|
|
7405
8854
|
apiBaseUrl: api.apiUrl,
|
|
7406
8855
|
mapAssets: hostedMapAssets
|
|
7407
8856
|
});
|
|
7408
|
-
const hostedLocalTilesets = await
|
|
8857
|
+
const hostedLocalTilesets = await prepareTileflowLocalTilesets2(
|
|
7409
8858
|
deploymentProject,
|
|
7410
8859
|
compiledHostedStyles,
|
|
7411
8860
|
{ assetBaseUrl: api.apiUrl, baseDirectory, cwd: process.cwd() }
|
|
7412
8861
|
);
|
|
7413
|
-
const hostedFonts = await
|
|
8862
|
+
const hostedFonts = await prepareTileflowStyleFonts2(
|
|
7414
8863
|
deploymentProject,
|
|
7415
8864
|
hostedLocalTilesets.styles,
|
|
7416
8865
|
{
|
|
@@ -7475,7 +8924,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7475
8924
|
const styles = Object.fromEntries(
|
|
7476
8925
|
Object.entries(hostedThemeFamilies).map(([mapName, family]) => [mapName, family.styles])
|
|
7477
8926
|
);
|
|
7478
|
-
const buildManifest = await
|
|
8927
|
+
const buildManifest = await createTileflowMapBuildManifest2(
|
|
7479
8928
|
Object.fromEntries(
|
|
7480
8929
|
mapNames.map((mapName) => {
|
|
7481
8930
|
const map = deploymentProject.maps[mapName];
|
|
@@ -7498,14 +8947,15 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7498
8947
|
map,
|
|
7499
8948
|
sourceAssets: {
|
|
7500
8949
|
fonts: hostedFonts.sourceIdentities[mapName] ?? [],
|
|
7501
|
-
icons: compiledIcons.sourceIdentities[mapName] ?? []
|
|
8950
|
+
icons: compiledIcons.sourceIdentities[mapName] ?? [],
|
|
8951
|
+
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {}
|
|
7502
8952
|
},
|
|
7503
8953
|
styles: styles[mapName]
|
|
7504
8954
|
}
|
|
7505
8955
|
];
|
|
7506
8956
|
})
|
|
7507
8957
|
),
|
|
7508
|
-
{ provenance: await
|
|
8958
|
+
{ provenance: await createTileflowBuildProvenance2(process.cwd()) }
|
|
7509
8959
|
);
|
|
7510
8960
|
const deployments = mapNames.map((mapName) => {
|
|
7511
8961
|
const iconBinding = bindingsByMap.get(mapName);
|
|
@@ -7513,6 +8963,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7513
8963
|
const fontBundle = hostedFonts.bundles[mapName];
|
|
7514
8964
|
return {
|
|
7515
8965
|
iconBinding,
|
|
8966
|
+
iconComposition: compiledIcons.compositions[mapName],
|
|
7516
8967
|
iconPackage,
|
|
7517
8968
|
fontBundle,
|
|
7518
8969
|
mapName,
|
|
@@ -7531,14 +8982,20 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7531
8982
|
buildManifest: buildManifest2,
|
|
7532
8983
|
fontBundle,
|
|
7533
8984
|
iconBinding,
|
|
8985
|
+
iconComposition,
|
|
7534
8986
|
iconPackage,
|
|
7535
8987
|
mapName,
|
|
7536
8988
|
teamSources,
|
|
7537
8989
|
styles: themeStyles
|
|
7538
8990
|
} = deployment;
|
|
8991
|
+
if (iconComposition && iconComposition.packageHash !== iconPackage?.contentHash) {
|
|
8992
|
+
logError(`Icon composition does not match the effective package for ${mapName}.`);
|
|
8993
|
+
process.exitCode = 1;
|
|
8994
|
+
return;
|
|
8995
|
+
}
|
|
7539
8996
|
const themeNames = Object.keys(themeStyles).sort();
|
|
7540
8997
|
logInfo(
|
|
7541
|
-
`Deploying compiled map ${
|
|
8998
|
+
`Deploying compiled map ${pc6.bold(mapName)} (${plural(themeNames.length, "theme")}).`
|
|
7542
8999
|
);
|
|
7543
9000
|
const response = await publishHostedStyle(
|
|
7544
9001
|
api,
|
|
@@ -7560,6 +9017,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7560
9017
|
label: iconBinding.label
|
|
7561
9018
|
}
|
|
7562
9019
|
} : {},
|
|
9020
|
+
...iconComposition ? { iconComposition } : {},
|
|
7563
9021
|
...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
|
|
7564
9022
|
source
|
|
7565
9023
|
},
|
|
@@ -7588,7 +9046,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7588
9046
|
process.exitCode = 1;
|
|
7589
9047
|
return;
|
|
7590
9048
|
}
|
|
7591
|
-
const resolvedMap =
|
|
9049
|
+
const resolvedMap = parseResolvedTileflowMap5(deploymentProject.maps[mapName]);
|
|
7592
9050
|
const mapBuild = buildManifest2.maps[mapName];
|
|
7593
9051
|
deployedMaps[mapName] = {
|
|
7594
9052
|
defaultTheme: resolvedMap.defaultTheme,
|
|
@@ -7598,7 +9056,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7598
9056
|
themes: Object.fromEntries(
|
|
7599
9057
|
themeNames.map((themeName) => {
|
|
7600
9058
|
const deployedTheme = body.themes[themeName];
|
|
7601
|
-
const fontFaces =
|
|
9059
|
+
const fontFaces = getTileflowStyleFontFaces2(themeStyles[themeName]);
|
|
7602
9060
|
return [
|
|
7603
9061
|
themeName,
|
|
7604
9062
|
{
|
|
@@ -7617,9 +9075,9 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7617
9075
|
};
|
|
7618
9076
|
const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
|
|
7619
9077
|
if (body.changed === false) {
|
|
7620
|
-
logSuccess(`Unchanged ${
|
|
9078
|
+
logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
|
|
7621
9079
|
} else {
|
|
7622
|
-
logSuccess(`Published ${
|
|
9080
|
+
logSuccess(`Published ${pc6.bold(mapName)}${versionLabel}.`);
|
|
7623
9081
|
}
|
|
7624
9082
|
}
|
|
7625
9083
|
const manifestPath = await writeDeployManifest(options.manifest, {
|
|
@@ -7693,6 +9151,7 @@ program.command("status").description("Show deployed compiled styles").option("-
|
|
|
7693
9151
|
printMapStatus(status, api.apiUrl);
|
|
7694
9152
|
});
|
|
7695
9153
|
var iconsCommand = program.command("icons").description("Inspect managed Tileflow icons");
|
|
9154
|
+
registerIconLockCommands(program, { defaultApiUrl, defaultConfigPath, loadAuthConfig });
|
|
7696
9155
|
registerIconListCommand(iconsCommand, { defaultConfigPath });
|
|
7697
9156
|
registerIconDiffCommand(iconsCommand, {
|
|
7698
9157
|
defaultApiUrl,
|
|
@@ -7734,10 +9193,15 @@ registerProjectCommands(program, {
|
|
|
7734
9193
|
defaultApiUrl,
|
|
7735
9194
|
loadAuthConfig
|
|
7736
9195
|
});
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
}
|
|
9196
|
+
if (isRootCliVersionRequest(process.argv.slice(2))) {
|
|
9197
|
+
process.stdout.write(`${packageJson.version}
|
|
9198
|
+
`);
|
|
9199
|
+
} else {
|
|
9200
|
+
program.parseAsync().catch((error) => {
|
|
9201
|
+
printCliError(error);
|
|
9202
|
+
process.exitCode = 1;
|
|
9203
|
+
});
|
|
9204
|
+
}
|
|
7741
9205
|
async function runTileflowComparisonPreview(options, address) {
|
|
7742
9206
|
await withTileflowConfigSecretsHidden(async () => {
|
|
7743
9207
|
const origin = tileflowDevOrigin(address.host, address.port);
|
|
@@ -7782,10 +9246,12 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7782
9246
|
try {
|
|
7783
9247
|
const leftCaptureConfig = comparisonCaptureConfigArgument(options.config);
|
|
7784
9248
|
const rightCaptureConfig = comparisonCaptureConfigArgument(rightConfig);
|
|
9249
|
+
const comparisonIcons = resolveIconOptions(options);
|
|
7785
9250
|
leftSession = await createTileflowArtifactSession3({
|
|
7786
9251
|
apiBaseUrl: options.apiBaseUrl,
|
|
7787
9252
|
assetBaseUrl: `${origin}${leftBasePath}`,
|
|
7788
9253
|
config: options.config,
|
|
9254
|
+
...comparisonIcons ? { icons: comparisonIcons } : {},
|
|
7789
9255
|
inspection: true,
|
|
7790
9256
|
styleBaseUrl: `${origin}${leftBasePath}`,
|
|
7791
9257
|
watch: true
|
|
@@ -7794,6 +9260,7 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7794
9260
|
apiBaseUrl: options.apiBaseUrl,
|
|
7795
9261
|
assetBaseUrl: `${origin}${rightBasePath}`,
|
|
7796
9262
|
config: rightConfig,
|
|
9263
|
+
...comparisonIcons ? { icons: comparisonIcons } : {},
|
|
7797
9264
|
inspection: true,
|
|
7798
9265
|
styleBaseUrl: `${origin}${rightBasePath}`,
|
|
7799
9266
|
watch: true
|
|
@@ -7838,7 +9305,7 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7838
9305
|
scene: rightScene,
|
|
7839
9306
|
theme: rightTheme
|
|
7840
9307
|
});
|
|
7841
|
-
const leftHandler =
|
|
9308
|
+
const leftHandler = createTileflowDevRequestHandler2({
|
|
7842
9309
|
apiBaseUrl: options.apiBaseUrl,
|
|
7843
9310
|
basePath: leftBasePath,
|
|
7844
9311
|
config: options.config,
|
|
@@ -7848,7 +9315,7 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7848
9315
|
session: leftSession,
|
|
7849
9316
|
theme: options.theme
|
|
7850
9317
|
});
|
|
7851
|
-
const rightHandler =
|
|
9318
|
+
const rightHandler = createTileflowDevRequestHandler2({
|
|
7852
9319
|
apiBaseUrl: options.apiBaseUrl,
|
|
7853
9320
|
basePath: rightBasePath,
|
|
7854
9321
|
config: rightConfig,
|
|
@@ -7919,13 +9386,13 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7919
9386
|
}
|
|
7920
9387
|
function comparisonCaptureConfigArgument(configPath) {
|
|
7921
9388
|
const cwd = realpathSync(process.cwd());
|
|
7922
|
-
const requestedConfigPath =
|
|
7923
|
-
const defaultRequestedPath =
|
|
9389
|
+
const requestedConfigPath = resolve8(process.cwd(), configPath);
|
|
9390
|
+
const defaultRequestedPath = resolve8(process.cwd(), defaultConfigPath);
|
|
7924
9391
|
const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
|
|
7925
9392
|
const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
|
|
7926
9393
|
if (absoluteConfigPath === absoluteDefaultPath) return void 0;
|
|
7927
9394
|
const relativeConfigPath = relative5(cwd, absoluteConfigPath);
|
|
7928
|
-
if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) ||
|
|
9395
|
+
if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
|
|
7929
9396
|
throw new Error(
|
|
7930
9397
|
"Comparison configs must be inside the current working directory so Copy command can remain reproducible."
|
|
7931
9398
|
);
|
|
@@ -7964,7 +9431,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
7964
9431
|
codeChallenge,
|
|
7965
9432
|
deviceName: hostname()
|
|
7966
9433
|
});
|
|
7967
|
-
printKeyValue("Code",
|
|
9434
|
+
printKeyValue("Code", pc6.bold(authorization.userCode));
|
|
7968
9435
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
7969
9436
|
if (options.noBrowser) {
|
|
7970
9437
|
logMuted("Open the URL above to continue.");
|
|
@@ -7974,7 +9441,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
7974
9441
|
output: process.stdout
|
|
7975
9442
|
});
|
|
7976
9443
|
try {
|
|
7977
|
-
await readline.question(`${
|
|
9444
|
+
await readline.question(`${pc6.cyan("?")} Press Enter to open Tileflow Dashboard...`);
|
|
7978
9445
|
openBrowser(authorization.verificationUriComplete);
|
|
7979
9446
|
} finally {
|
|
7980
9447
|
readline.close();
|
|
@@ -8025,9 +9492,9 @@ function openBrowser(url, errorsToStderr = false) {
|
|
|
8025
9492
|
});
|
|
8026
9493
|
}
|
|
8027
9494
|
async function writeDeployManifest(manifestPath, manifest) {
|
|
8028
|
-
const outputPath =
|
|
9495
|
+
const outputPath = resolve8(process.cwd(), manifestPath);
|
|
8029
9496
|
await writeAtomicFile({
|
|
8030
|
-
boundaryPath:
|
|
9497
|
+
boundaryPath: dirname12(outputPath),
|
|
8031
9498
|
force: true,
|
|
8032
9499
|
label: "Deploy manifest",
|
|
8033
9500
|
managed: true,
|
|
@@ -8037,11 +9504,24 @@ async function writeDeployManifest(manifestPath, manifest) {
|
|
|
8037
9504
|
});
|
|
8038
9505
|
return manifestPath;
|
|
8039
9506
|
}
|
|
9507
|
+
function resolveIconOptions(options) {
|
|
9508
|
+
const icons = {
|
|
9509
|
+
...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
|
|
9510
|
+
...options.offline ? { offline: true } : {}
|
|
9511
|
+
};
|
|
9512
|
+
return Object.keys(icons).length > 0 ? icons : void 0;
|
|
9513
|
+
}
|
|
8040
9514
|
function createCompiledMapAssets(compiled, resolveSprite) {
|
|
8041
9515
|
return Object.fromEntries(
|
|
8042
9516
|
compiled.bindings.map((binding) => [
|
|
8043
9517
|
binding.mapName,
|
|
8044
|
-
{
|
|
9518
|
+
{
|
|
9519
|
+
icons: {
|
|
9520
|
+
ids: binding.iconIds,
|
|
9521
|
+
sprite: resolveSprite(binding),
|
|
9522
|
+
...binding.appearances ? { appearances: binding.appearances } : {}
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
8045
9525
|
])
|
|
8046
9526
|
);
|
|
8047
9527
|
}
|
|
@@ -8098,7 +9578,7 @@ function validateDeployManifestMapNames(mapNames) {
|
|
|
8098
9578
|
async function loadExistingDeployManifest(manifestPath, options) {
|
|
8099
9579
|
let source;
|
|
8100
9580
|
try {
|
|
8101
|
-
source = await readFile7(
|
|
9581
|
+
source = await readFile7(resolve8(process.cwd(), manifestPath), "utf8");
|
|
8102
9582
|
} catch (error) {
|
|
8103
9583
|
if (error.code === "ENOENT") return null;
|
|
8104
9584
|
throw new Error("Existing Tileflow manifest could not be read.");
|
|
@@ -8112,7 +9592,7 @@ async function loadExistingDeployManifest(manifestPath, options) {
|
|
|
8112
9592
|
}
|
|
8113
9593
|
let manifest;
|
|
8114
9594
|
try {
|
|
8115
|
-
manifest =
|
|
9595
|
+
manifest = parseTileflowRuntimeManifest2(value);
|
|
8116
9596
|
} catch {
|
|
8117
9597
|
throw new Error(
|
|
8118
9598
|
"Existing Tileflow manifest does not match the strict theme manifest contract."
|
|
@@ -8190,7 +9670,7 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
8190
9670
|
if (!behavior.silent) {
|
|
8191
9671
|
logError("CI requires an explicit Tileflow credential with access to the target Map.");
|
|
8192
9672
|
printNextSteps([
|
|
8193
|
-
`Set ${
|
|
9673
|
+
`Set ${pc6.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
|
|
8194
9674
|
"The saved personal account session is never used in CI."
|
|
8195
9675
|
]);
|
|
8196
9676
|
}
|
|
@@ -8270,7 +9750,7 @@ function printTileflowPreviewError(error) {
|
|
|
8270
9750
|
printValidationErrors(error.issues);
|
|
8271
9751
|
return;
|
|
8272
9752
|
}
|
|
8273
|
-
if (error instanceof
|
|
9753
|
+
if (error instanceof TileflowValidationError5) {
|
|
8274
9754
|
printValidationErrors(error.messages);
|
|
8275
9755
|
return;
|
|
8276
9756
|
}
|
|
@@ -8282,7 +9762,7 @@ function printCliError(error) {
|
|
|
8282
9762
|
printValidationErrors(error.issues);
|
|
8283
9763
|
return;
|
|
8284
9764
|
}
|
|
8285
|
-
if (error instanceof
|
|
9765
|
+
if (error instanceof TileflowValidationError5) {
|
|
8286
9766
|
printValidationErrors(error.messages);
|
|
8287
9767
|
return;
|
|
8288
9768
|
}
|
|
@@ -8296,17 +9776,17 @@ function printCliError(error) {
|
|
|
8296
9776
|
}
|
|
8297
9777
|
const message = error instanceof Error ? error.message : "Command failed.";
|
|
8298
9778
|
if (message.includes("Cannot find module '@tileflow/core'")) {
|
|
8299
|
-
logError(`Cannot load ${
|
|
9779
|
+
logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
|
|
8300
9780
|
printNextSteps([
|
|
8301
|
-
`Install ${
|
|
9781
|
+
`Install ${pc6.cyan("@tileflow/core")} in this project.`,
|
|
8302
9782
|
`Run the command again from the project root.`
|
|
8303
9783
|
]);
|
|
8304
9784
|
return;
|
|
8305
9785
|
}
|
|
8306
9786
|
if (message.includes("Cannot find module '@tileflow/maps'")) {
|
|
8307
|
-
logError(`Cannot load ${
|
|
9787
|
+
logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
|
|
8308
9788
|
printNextSteps([
|
|
8309
|
-
`Install ${
|
|
9789
|
+
`Install ${pc6.cyan("@tileflow/maps")} in this project.`,
|
|
8310
9790
|
`Run the command again from the project root.`
|
|
8311
9791
|
]);
|
|
8312
9792
|
return;
|
|
@@ -8317,51 +9797,51 @@ function printCliError(error) {
|
|
|
8317
9797
|
}
|
|
8318
9798
|
}
|
|
8319
9799
|
function logSuccess(message) {
|
|
8320
|
-
console.log(`${
|
|
9800
|
+
console.log(`${pc6.green("\u2713")} ${message}`);
|
|
8321
9801
|
}
|
|
8322
9802
|
function logError(message) {
|
|
8323
|
-
console.log(`${
|
|
9803
|
+
console.log(`${pc6.red("\u2715")} ${message}`);
|
|
8324
9804
|
}
|
|
8325
9805
|
function logWarning(message) {
|
|
8326
|
-
console.log(`${
|
|
9806
|
+
console.log(`${pc6.yellow("!")} ${message}`);
|
|
8327
9807
|
}
|
|
8328
9808
|
function logInfo(message) {
|
|
8329
|
-
console.log(`${
|
|
9809
|
+
console.log(`${pc6.cyan("i")} ${message}`);
|
|
8330
9810
|
}
|
|
8331
9811
|
function logMuted(message) {
|
|
8332
|
-
console.log(
|
|
9812
|
+
console.log(pc6.gray(message));
|
|
8333
9813
|
}
|
|
8334
9814
|
function printTitle(title, subtitle) {
|
|
8335
|
-
console.log(
|
|
9815
|
+
console.log(pc6.bold(title));
|
|
8336
9816
|
if (subtitle) {
|
|
8337
|
-
console.log(
|
|
9817
|
+
console.log(pc6.gray(subtitle));
|
|
8338
9818
|
}
|
|
8339
9819
|
}
|
|
8340
9820
|
function printKeyValue(label, value) {
|
|
8341
|
-
console.log(`${
|
|
9821
|
+
console.log(`${pc6.gray(`${label}:`.padEnd(13))} ${value}`);
|
|
8342
9822
|
}
|
|
8343
9823
|
function printChecks(items) {
|
|
8344
9824
|
for (const item of items) {
|
|
8345
|
-
console.log(` ${
|
|
9825
|
+
console.log(` ${pc6.green("\u2713")} ${item}`);
|
|
8346
9826
|
}
|
|
8347
9827
|
}
|
|
8348
9828
|
function printNextSteps(steps) {
|
|
8349
9829
|
if (steps.length === 0) return;
|
|
8350
9830
|
console.log(`
|
|
8351
|
-
${
|
|
9831
|
+
${pc6.bold("Next steps")}`);
|
|
8352
9832
|
for (const step of steps) {
|
|
8353
|
-
console.log(` ${
|
|
9833
|
+
console.log(` ${pc6.cyan("\u2022")} ${step}`);
|
|
8354
9834
|
}
|
|
8355
9835
|
}
|
|
8356
9836
|
function printValidationErrors(messages) {
|
|
8357
9837
|
logError("Tileflow config has errors.");
|
|
8358
9838
|
for (const message of messages) {
|
|
8359
|
-
const path = message.path ?
|
|
8360
|
-
console.log(` ${
|
|
9839
|
+
const path = message.path ? pc6.cyan(message.path) : pc6.cyan("(root)");
|
|
9840
|
+
console.log(` ${pc6.red("\u2715")} ${path} ${pc6.gray("-")} ${message.message}`);
|
|
8361
9841
|
}
|
|
8362
9842
|
}
|
|
8363
9843
|
function printAccountDetails(session) {
|
|
8364
|
-
printKeyValue("Account",
|
|
9844
|
+
printKeyValue("Account", pc6.bold(session.account.name));
|
|
8365
9845
|
printKeyValue("Email", session.account.email);
|
|
8366
9846
|
printKeyValue("API", link(session.apiOrigin));
|
|
8367
9847
|
printKeyValue("Expires", formatDate(session.expiresAt));
|
|
@@ -8390,11 +9870,11 @@ function printDeployedMaps(maps) {
|
|
|
8390
9870
|
const entries = Object.entries(maps);
|
|
8391
9871
|
if (entries.length === 0) return;
|
|
8392
9872
|
console.log(`
|
|
8393
|
-
${
|
|
9873
|
+
${pc6.bold("Maps")}`);
|
|
8394
9874
|
for (const [name, map] of entries) {
|
|
8395
9875
|
for (const [themeName, theme] of Object.entries(map.themes)) {
|
|
8396
9876
|
const label = `${name}/${themeName}`;
|
|
8397
|
-
console.log(` ${
|
|
9877
|
+
console.log(` ${pc6.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
|
|
8398
9878
|
}
|
|
8399
9879
|
}
|
|
8400
9880
|
}
|
|
@@ -8412,7 +9892,7 @@ function plural(count, singular, pluralForm = `${singular}s`) {
|
|
|
8412
9892
|
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
8413
9893
|
}
|
|
8414
9894
|
function command(value) {
|
|
8415
|
-
return
|
|
9895
|
+
return pc6.cyan(value);
|
|
8416
9896
|
}
|
|
8417
9897
|
function cliInvocation(parts) {
|
|
8418
9898
|
return parts.map(quoteCliArgument).join(" ");
|
|
@@ -8421,10 +9901,10 @@ function quoteCliArgument(value) {
|
|
|
8421
9901
|
return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
8422
9902
|
}
|
|
8423
9903
|
function link(value) {
|
|
8424
|
-
return
|
|
9904
|
+
return pc6.cyan(value);
|
|
8425
9905
|
}
|
|
8426
9906
|
function pathLabel(value) {
|
|
8427
|
-
return
|
|
9907
|
+
return pc6.cyan(value);
|
|
8428
9908
|
}
|
|
8429
9909
|
function starterConfig() {
|
|
8430
9910
|
return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
|
|
@@ -8470,15 +9950,15 @@ function printHostedCompatibilityIssues(issues) {
|
|
|
8470
9950
|
}
|
|
8471
9951
|
function printMapStatus(status, apiUrl) {
|
|
8472
9952
|
printTitle("Tileflow status");
|
|
8473
|
-
printKeyValue("Map",
|
|
9953
|
+
printKeyValue("Map", pc6.bold(status.mapId));
|
|
8474
9954
|
console.log(`
|
|
8475
|
-
${
|
|
9955
|
+
${pc6.bold("Styles")}`);
|
|
8476
9956
|
if (status.styles.length === 0) {
|
|
8477
9957
|
logMuted(" No styles deployed.");
|
|
8478
9958
|
}
|
|
8479
9959
|
for (const style of status.styles) {
|
|
8480
9960
|
console.log(
|
|
8481
|
-
` ${
|
|
9961
|
+
` ${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))}`
|
|
8482
9962
|
);
|
|
8483
9963
|
}
|
|
8484
9964
|
}
|