tileflow 0.1.0-alpha.22 → 0.1.0-alpha.23
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 +26 -20
- package/REFERENCE.md +429 -0
- package/dist/build-RQF35KW6.js +21 -0
- package/dist/chunk-43VQMAQO.js +52 -0
- package/dist/chunk-5U4J6E6Y.js +4911 -0
- package/dist/{chunk-5QK6JCLT.js → chunk-EXDH7EHK.js} +726 -68
- package/dist/chunk-JVTGXPGV.js +382 -0
- package/dist/{cli-main-HRFGWUWR.js → cli-main-K3PKVCIP.js} +1099 -351
- package/dist/errors-C3VZMCM5.js +20 -0
- package/dist/index.js +2 -2
- package/dist/inspect-source-5CD3AV5J.js +227 -0
- package/dist/recipe-3BPMNDPX.js +23 -0
- package/dist/{tileset-entry-ITUSGCM3.js → tileset-entry-V5RUQGZG.js} +4 -1
- package/package.json +14 -5
- package/schemas/tileset-recipe.v1.json +171 -0
|
@@ -4,24 +4,32 @@ import {
|
|
|
4
4
|
isRootCliVersionRequest
|
|
5
5
|
} from "./chunk-GPPOECR5.js";
|
|
6
6
|
import {
|
|
7
|
+
apiOriginOption,
|
|
7
8
|
apiProfileCanTargetMap,
|
|
8
9
|
assertHostedStyleBudget,
|
|
9
10
|
authorizedTeamRequest,
|
|
11
|
+
configureJsonErrors,
|
|
10
12
|
createCliProgram,
|
|
11
13
|
createManagedMapSetup,
|
|
14
|
+
deploymentRestoreSteps,
|
|
12
15
|
emitFailure,
|
|
16
|
+
emitGuidedFailure,
|
|
13
17
|
emitJson,
|
|
18
|
+
emitRestoreFailure,
|
|
14
19
|
fetchHostedMapStatus,
|
|
15
20
|
hostedApiKeyOption,
|
|
16
21
|
hostedApiUrlOption,
|
|
17
22
|
installAccountSession,
|
|
18
23
|
isHostedApiKey,
|
|
24
|
+
isRestoreJsonCommand,
|
|
19
25
|
listMapSetupTeams,
|
|
20
26
|
loadAuthConfig,
|
|
21
27
|
normalizeApiOrigin,
|
|
22
28
|
parseProjectReference,
|
|
29
|
+
parseRestoreJson,
|
|
23
30
|
pollDeviceToken,
|
|
24
31
|
prepareAccountLogin,
|
|
32
|
+
printNextSteps,
|
|
25
33
|
publishHostedStyle,
|
|
26
34
|
registerTilesetCommands,
|
|
27
35
|
registerTilesetInspectCommand,
|
|
@@ -40,7 +48,7 @@ import {
|
|
|
40
48
|
validateAccountSession,
|
|
41
49
|
validateApiKey,
|
|
42
50
|
writeAuthFileAtomic
|
|
43
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-EXDH7EHK.js";
|
|
44
52
|
|
|
45
53
|
// src/cli-main.ts
|
|
46
54
|
import { serve } from "@hono/node-server";
|
|
@@ -48,9 +56,9 @@ import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
|
48
56
|
import { existsSync, realpathSync } from "fs";
|
|
49
57
|
import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
50
58
|
import { hostname } from "os";
|
|
51
|
-
import { dirname as dirname12, isAbsolute as isAbsolute5, relative as relative6, resolve as
|
|
59
|
+
import { dirname as dirname12, isAbsolute as isAbsolute5, relative as relative6, resolve as resolve12, sep as sep5 } from "path";
|
|
52
60
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
53
|
-
import
|
|
61
|
+
import pc8 from "picocolors";
|
|
54
62
|
import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
|
|
55
63
|
import {
|
|
56
64
|
auditTileflowMapThemeValues as auditTileflowMapThemeValues2,
|
|
@@ -1002,13 +1010,13 @@ async function saveCaptureRecord(capture, cwd) {
|
|
|
1002
1010
|
throw new Error(
|
|
1003
1011
|
"Capture has no normalized scene definition. Rebuild @tileflow/capture before using this CLI."
|
|
1004
1012
|
);
|
|
1005
|
-
const
|
|
1013
|
+
const record2 = {
|
|
1006
1014
|
schemaVersion: 1,
|
|
1007
1015
|
definition: capture.definition,
|
|
1008
1016
|
receipt: capture.receipt,
|
|
1009
1017
|
warnings: capture.warnings
|
|
1010
1018
|
};
|
|
1011
|
-
const source = serializeCanonicalJson(
|
|
1019
|
+
const source = serializeCanonicalJson(record2);
|
|
1012
1020
|
if (Buffer.byteLength(source) > maximumRecordBytes)
|
|
1013
1021
|
throw new Error("Capture record exceeds its byte limit.");
|
|
1014
1022
|
const id = await sha256Hex(source);
|
|
@@ -1033,11 +1041,11 @@ async function readCaptureRecord(id, directory) {
|
|
|
1033
1041
|
const source = await readBoundedFile(resolve2(root, `${id}.json`), maximumRecordBytes);
|
|
1034
1042
|
if (await sha256Hex(source) !== id)
|
|
1035
1043
|
throw new Error("Saved capture record does not match its ID.");
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
1038
|
-
const receipt = parseTileflowCaptureReceipt(JSON.stringify(
|
|
1044
|
+
const record2 = JSON.parse(source.toString("utf8"));
|
|
1045
|
+
if (record2.schemaVersion !== 1) throw new Error("Unsupported capture record version.");
|
|
1046
|
+
const receipt = parseTileflowCaptureReceipt(JSON.stringify(record2.receipt));
|
|
1039
1047
|
const definition = normalizeTileflowCaptureScene(
|
|
1040
|
-
tileflowCaptureSceneSchema.parse(
|
|
1048
|
+
tileflowCaptureSceneSchema.parse(record2.definition)
|
|
1041
1049
|
);
|
|
1042
1050
|
if (!definition.theme) throw new Error("Saved capture requires a concrete theme.");
|
|
1043
1051
|
if (await sha256Hex(serializeCanonicalJson(definition)) !== receipt.scene.sha256)
|
|
@@ -1048,7 +1056,7 @@ async function readCaptureRecord(id, directory) {
|
|
|
1048
1056
|
);
|
|
1049
1057
|
if (await sha256Hex(png) !== receipt.image.sha256)
|
|
1050
1058
|
throw new Error("Saved capture PNG does not match its receipt.");
|
|
1051
|
-
if (!Array.isArray(
|
|
1059
|
+
if (!Array.isArray(record2.warnings) || record2.warnings.some((warning) => typeof warning !== "string"))
|
|
1052
1060
|
throw new Error("Invalid capture warnings.");
|
|
1053
1061
|
return {
|
|
1054
1062
|
definition: { ...definition, theme: definition.theme },
|
|
@@ -1067,7 +1075,7 @@ async function readCaptureRecord(id, directory) {
|
|
|
1067
1075
|
dpr: receipt.image.dpr,
|
|
1068
1076
|
renderer: receipt.renderer,
|
|
1069
1077
|
networkDependent: receipt.networkDependent,
|
|
1070
|
-
warnings:
|
|
1078
|
+
warnings: record2.warnings
|
|
1071
1079
|
}
|
|
1072
1080
|
};
|
|
1073
1081
|
}
|
|
@@ -2929,11 +2937,11 @@ function validateVisualCompareInventory(input) {
|
|
|
2929
2937
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
2930
2938
|
throw new Error("Visual compare inventory has an invalid shape.");
|
|
2931
2939
|
}
|
|
2932
|
-
const
|
|
2933
|
-
if (!hasExactKeys(
|
|
2940
|
+
const record2 = input;
|
|
2941
|
+
if (!hasExactKeys(record2, ["assets", "kind", "schemaVersion"]) || record2.schemaVersion !== tileflowVisualCompareInventorySchemaVersion || record2.kind !== "tileflow-visual-compare-assets" || !Array.isArray(record2.assets) || record2.assets.length > tileflowVisualCompareLimits.maximumRows * 5) {
|
|
2934
2942
|
throw new Error("Visual compare inventory has an invalid shape.");
|
|
2935
2943
|
}
|
|
2936
|
-
const assets =
|
|
2944
|
+
const assets = record2.assets.map((entry) => {
|
|
2937
2945
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
2938
2946
|
throw new Error("Visual compare inventory has an invalid asset entry.");
|
|
2939
2947
|
}
|
|
@@ -2963,8 +2971,8 @@ function isVisualCompareOwnedAssetName(value) {
|
|
|
2963
2971
|
const row = Number(match[1]);
|
|
2964
2972
|
return Number.isInteger(row) && row >= 1 && row <= tileflowVisualCompareLimits.maximumRows;
|
|
2965
2973
|
}
|
|
2966
|
-
function hasExactKeys(
|
|
2967
|
-
const actual = Object.keys(
|
|
2974
|
+
function hasExactKeys(record2, keys) {
|
|
2975
|
+
const actual = Object.keys(record2).sort(compareCodeUnits3);
|
|
2968
2976
|
const expected = [...keys].sort(compareCodeUnits3);
|
|
2969
2977
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
2970
2978
|
}
|
|
@@ -3323,7 +3331,7 @@ function registerOperation(coordinates, name, dependencies, parse3) {
|
|
|
3323
3331
|
const controller = (dependencies.createSignalController ?? installSignalAbortController)();
|
|
3324
3332
|
let local;
|
|
3325
3333
|
try {
|
|
3326
|
-
const
|
|
3334
|
+
const request2 = await resolveRequest(
|
|
3327
3335
|
name,
|
|
3328
3336
|
options,
|
|
3329
3337
|
parse3,
|
|
@@ -3336,7 +3344,7 @@ function registerOperation(coordinates, name, dependencies, parse3) {
|
|
|
3336
3344
|
allowDevelopment: options.development,
|
|
3337
3345
|
signal: controller.signal
|
|
3338
3346
|
});
|
|
3339
|
-
const response = await local[name](
|
|
3347
|
+
const response = await local[name](request2, { signal: controller.signal });
|
|
3340
3348
|
await local.close();
|
|
3341
3349
|
local = void 0;
|
|
3342
3350
|
printDocument(response);
|
|
@@ -3428,7 +3436,7 @@ async function readCoordinatesInput(input, signal) {
|
|
|
3428
3436
|
async function readStandardInput(signal) {
|
|
3429
3437
|
const chunks = [];
|
|
3430
3438
|
let length = 0;
|
|
3431
|
-
return new Promise((
|
|
3439
|
+
return new Promise((resolve13, reject) => {
|
|
3432
3440
|
const finish = (result) => {
|
|
3433
3441
|
process.stdin.removeListener("data", onData);
|
|
3434
3442
|
process.stdin.removeListener("end", onEnd);
|
|
@@ -3450,7 +3458,7 @@ async function readStandardInput(signal) {
|
|
|
3450
3458
|
}
|
|
3451
3459
|
chunks.push(bytes);
|
|
3452
3460
|
};
|
|
3453
|
-
const onEnd = () => finish(() =>
|
|
3461
|
+
const onEnd = () => finish(() => resolve13(decodeInput(Buffer.concat(chunks))));
|
|
3454
3462
|
const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
|
|
3455
3463
|
if (signal.aborted) {
|
|
3456
3464
|
onAbort();
|
|
@@ -3692,6 +3700,186 @@ function isSafeDeploySourceText(value) {
|
|
|
3692
3700
|
return true;
|
|
3693
3701
|
}
|
|
3694
3702
|
|
|
3703
|
+
// src/deployment-command.ts
|
|
3704
|
+
import pc from "picocolors";
|
|
3705
|
+
import { z } from "zod";
|
|
3706
|
+
var deploymentSchema = z.object({
|
|
3707
|
+
active: z.boolean(),
|
|
3708
|
+
createdAt: z.string().min(1).max(64),
|
|
3709
|
+
deploymentId: z.string().min(1).max(128),
|
|
3710
|
+
restoredFromVersion: z.number().int().positive().nullable(),
|
|
3711
|
+
source: z.object({ kind: z.string().min(1).max(32), revision: z.string().max(128).optional() }).strict().nullable(),
|
|
3712
|
+
version: z.number().int().positive()
|
|
3713
|
+
}).strict();
|
|
3714
|
+
var deploymentListResponseSchema = z.object({
|
|
3715
|
+
deployments: z.array(deploymentSchema).max(100),
|
|
3716
|
+
mapId: z.string().regex(/^map_[A-Za-z0-9_-]{16}$/u),
|
|
3717
|
+
schemaVersion: z.literal(1)
|
|
3718
|
+
}).strict();
|
|
3719
|
+
var deploymentRestoreResponseSchema = z.object({
|
|
3720
|
+
changed: z.boolean(),
|
|
3721
|
+
deploymentId: z.string().min(1).max(128),
|
|
3722
|
+
familyUrl: z.string().url().max(2048).optional(),
|
|
3723
|
+
mapId: z.string().regex(/^map_[A-Za-z0-9_-]{16}$/u),
|
|
3724
|
+
restoredFromVersion: z.number().int().positive(),
|
|
3725
|
+
schemaVersion: z.literal(1),
|
|
3726
|
+
version: z.number().int().positive()
|
|
3727
|
+
}).strict();
|
|
3728
|
+
var failureSchema = z.object({ code: z.string().max(80), error: z.string().max(400) }).partial();
|
|
3729
|
+
function registerDeploymentCommands(program2, dependencies) {
|
|
3730
|
+
const deployments = program2.command("deployments").description("List a Map\u2019s retained deployments and restore an earlier one");
|
|
3731
|
+
const withOptions = (command2) => command2.requiredOption("--map-id <id>", "managed Map destination").addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("Tileflow API key")).option("--json", "print deterministic schema-version-1 JSON");
|
|
3732
|
+
const readHistory = async (api, json2) => {
|
|
3733
|
+
const response = await request(api, "/v1/styles/deployments", "GET");
|
|
3734
|
+
if (!response.ok) {
|
|
3735
|
+
emitHttpFailure(json2, response.status, response.body, api);
|
|
3736
|
+
return null;
|
|
3737
|
+
}
|
|
3738
|
+
const parsed = deploymentListResponseSchema.safeParse(response.body);
|
|
3739
|
+
if (!parsed.success || parsed.data.mapId !== api.mapId) {
|
|
3740
|
+
emitGuidedFailure(
|
|
3741
|
+
json2,
|
|
3742
|
+
"deployment_list_invalid",
|
|
3743
|
+
"The deployment history was not understood.",
|
|
3744
|
+
[{ description: "Update to a CLI version that supports deployments, then try again." }]
|
|
3745
|
+
);
|
|
3746
|
+
return null;
|
|
3747
|
+
}
|
|
3748
|
+
return parsed.data.deployments;
|
|
3749
|
+
};
|
|
3750
|
+
withOptions(
|
|
3751
|
+
deployments.command("list").description("List the retained deployments, newest first")
|
|
3752
|
+
).action(async (options) => {
|
|
3753
|
+
const api = await dependencies.resolveApi(options, {
|
|
3754
|
+
capabilityScopes: ["status:read"],
|
|
3755
|
+
onFailure: options.json ? (code, message) => emitRestoreFailure(true, code, message) : void 0
|
|
3756
|
+
});
|
|
3757
|
+
if (!api) return;
|
|
3758
|
+
const history = await readHistory(api, options.json);
|
|
3759
|
+
if (!history) return;
|
|
3760
|
+
const earlier = history.find((item) => !item.active);
|
|
3761
|
+
const nextSteps = earlier ? [
|
|
3762
|
+
{
|
|
3763
|
+
description: `Publish an earlier version again, for example version ${earlier.version}. It becomes a new version and nothing is lost.`,
|
|
3764
|
+
command: `tileflow deployments restore ${earlier.version} --map-id ${api.mapId}${apiOriginOption(api.apiUrl)}`
|
|
3765
|
+
}
|
|
3766
|
+
] : [];
|
|
3767
|
+
if (options.json) {
|
|
3768
|
+
return emitJson({
|
|
3769
|
+
command: "deployments list",
|
|
3770
|
+
deployments: history,
|
|
3771
|
+
nextSteps,
|
|
3772
|
+
schemaVersion: 1
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
if (history.length === 0) return console.log(pc.gray("Nothing is published on this Map."));
|
|
3776
|
+
for (const item of history) {
|
|
3777
|
+
const origin = item.restoredFromVersion ? `restored from v${item.restoredFromVersion}` : "";
|
|
3778
|
+
console.log(
|
|
3779
|
+
` v${item.version} ${item.active ? pc.green("live") : pc.gray("superseded")} ${item.createdAt} ${[item.source?.kind ?? "unknown", origin].filter(Boolean).join(", ")}`
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
printNextSteps(nextSteps);
|
|
3783
|
+
});
|
|
3784
|
+
withOptions(
|
|
3785
|
+
deployments.command("restore").description("Publish an earlier deployment again, as a new version of the Map").argument("<version>", "the version to restore")
|
|
3786
|
+
).action(async (value, options) => {
|
|
3787
|
+
if (!/^[1-9][0-9]{0,9}$/u.test(value)) {
|
|
3788
|
+
return emitRestoreFailure(
|
|
3789
|
+
options.json,
|
|
3790
|
+
"invalid_version",
|
|
3791
|
+
"Version must be a positive integer."
|
|
3792
|
+
);
|
|
3793
|
+
}
|
|
3794
|
+
const version = Number(value);
|
|
3795
|
+
const api = await dependencies.resolveApi(options, {
|
|
3796
|
+
capabilityScopes: ["status:read", "styles:write"],
|
|
3797
|
+
onFailure: options.json ? (code, message) => emitRestoreFailure(true, code, message) : void 0
|
|
3798
|
+
});
|
|
3799
|
+
if (!api) return;
|
|
3800
|
+
const history = await readHistory(api, options.json);
|
|
3801
|
+
if (!history) return;
|
|
3802
|
+
const live = history.find((item) => item.active);
|
|
3803
|
+
if (!live) {
|
|
3804
|
+
return emitGuidedFailure(
|
|
3805
|
+
options.json,
|
|
3806
|
+
"nothing_published",
|
|
3807
|
+
"Nothing is published on this Map.",
|
|
3808
|
+
deploymentRestoreSteps({ code: "nothing_published", mapId: api.mapId, apiUrl: api.apiUrl })
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
if (!history.some((item) => item.version === version)) {
|
|
3812
|
+
return emitGuidedFailure(
|
|
3813
|
+
options.json,
|
|
3814
|
+
"deployment_not_found",
|
|
3815
|
+
`Version ${version} is not in this Map's retained history.`,
|
|
3816
|
+
deploymentRestoreSteps({
|
|
3817
|
+
code: "deployment_not_found",
|
|
3818
|
+
mapId: api.mapId,
|
|
3819
|
+
apiUrl: api.apiUrl
|
|
3820
|
+
})
|
|
3821
|
+
);
|
|
3822
|
+
}
|
|
3823
|
+
const response = await request(api, `/v1/styles/deployments/${version}/restore`, "POST", {
|
|
3824
|
+
expectedCurrentVersion: live.version,
|
|
3825
|
+
source: resolveDeploySource(process.env)
|
|
3826
|
+
});
|
|
3827
|
+
if (!response.ok) return emitHttpFailure(options.json, response.status, response.body, api);
|
|
3828
|
+
const parsed = deploymentRestoreResponseSchema.safeParse(response.body);
|
|
3829
|
+
if (!parsed.success || parsed.data.mapId !== api.mapId) {
|
|
3830
|
+
return emitGuidedFailure(
|
|
3831
|
+
options.json,
|
|
3832
|
+
"deployment_restore_unconfirmed",
|
|
3833
|
+
`The restore of version ${version} was not confirmed.`,
|
|
3834
|
+
deploymentRestoreSteps({
|
|
3835
|
+
code: "deployment_restore_unconfirmed",
|
|
3836
|
+
mapId: api.mapId,
|
|
3837
|
+
apiUrl: api.apiUrl
|
|
3838
|
+
})
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
const nextSteps = parsed.data.changed ? [
|
|
3842
|
+
{
|
|
3843
|
+
description: `Your repository still holds what version ${live.version} published, so the next deploy publishes it again. Revert the repository to keep this restore.`
|
|
3844
|
+
},
|
|
3845
|
+
{
|
|
3846
|
+
description: `To undo this restore, publish version ${live.version} again.`,
|
|
3847
|
+
command: `tileflow deployments restore ${live.version} --map-id ${api.mapId}${apiOriginOption(api.apiUrl)}`
|
|
3848
|
+
}
|
|
3849
|
+
] : [];
|
|
3850
|
+
if (options.json) {
|
|
3851
|
+
return emitJson({ command: "deployments restore", nextSteps, ok: true, ...parsed.data });
|
|
3852
|
+
}
|
|
3853
|
+
console.log(
|
|
3854
|
+
parsed.data.changed ? `${pc.green("\u2713")} Version ${version} is live again as version ${parsed.data.version}. Version ${live.version} stays in the history.` : `${pc.green("\u2713")} The content of version ${version} is already live, as version ${parsed.data.version}.`
|
|
3855
|
+
);
|
|
3856
|
+
if (parsed.data.changed) console.log(pc.gray(" New sessions pick it up within minutes."));
|
|
3857
|
+
printNextSteps(nextSteps);
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
function request(api, path, method, body) {
|
|
3861
|
+
return requestHostedJson(api.apiUrl, path, {
|
|
3862
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
3863
|
+
headers: {
|
|
3864
|
+
Authorization: `Bearer ${api.apiKey}`,
|
|
3865
|
+
"X-Tileflow-Map-Id": api.mapId,
|
|
3866
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
3867
|
+
},
|
|
3868
|
+
method
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
function emitHttpFailure(json2, status, body, api) {
|
|
3872
|
+
const failure2 = failureSchema.safeParse(body);
|
|
3873
|
+
const message = failure2.success ? failure2.data.error : void 0;
|
|
3874
|
+
const code = (failure2.success ? failure2.data.code : void 0) ?? `http_${status}`;
|
|
3875
|
+
return emitGuidedFailure(
|
|
3876
|
+
json2,
|
|
3877
|
+
code,
|
|
3878
|
+
message ? `${message.replace(/\.$/u, "")}.` : `The request failed with ${status}.`,
|
|
3879
|
+
deploymentRestoreSteps({ code, mapId: api.mapId, apiUrl: api.apiUrl })
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3695
3883
|
// src/describe-command.ts
|
|
3696
3884
|
import { tileflowAuthoringManifest as tileflowAuthoringManifest2 } from "@tileflow/core";
|
|
3697
3885
|
import {
|
|
@@ -3849,9 +4037,9 @@ function tileflowDevOrigin(host, port) {
|
|
|
3849
4037
|
return `http://${isIP(host) === 6 ? `[${host}]` : host}:${port}`;
|
|
3850
4038
|
}
|
|
3851
4039
|
var loopbackHosts = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
3852
|
-
function redirectTileflowDevNavigation(
|
|
3853
|
-
if (
|
|
3854
|
-
const url = new URL(
|
|
4040
|
+
function redirectTileflowDevNavigation(request2, origin) {
|
|
4041
|
+
if (request2.method !== "GET" && request2.method !== "HEAD") return;
|
|
4042
|
+
const url = new URL(request2.url);
|
|
3855
4043
|
const canonical = new URL(origin);
|
|
3856
4044
|
if (url.pathname !== "/" || url.origin === canonical.origin || !loopbackHosts.has(url.hostname) || !loopbackHosts.has(canonical.hostname) || url.protocol !== canonical.protocol || url.port !== canonical.port)
|
|
3857
4045
|
return;
|
|
@@ -4021,7 +4209,9 @@ function prepareTileflowHostedThemeFamily(mapName, map, styles) {
|
|
|
4021
4209
|
Object.entries(styles).sort(([left], [right]) => compareCodeUnits4(left, right)).map(([themeName, style]) => [themeName, cloneStyle(style)])
|
|
4022
4210
|
);
|
|
4023
4211
|
const teamSources = Object.fromEntries(
|
|
4024
|
-
Object.entries(map.sources ?? {}).
|
|
4212
|
+
Object.entries(map.sources ?? {}).flatMap(
|
|
4213
|
+
([sourceId, source]) => source.kind === "hosted-tileset" ? [[sourceId, source]] : []
|
|
4214
|
+
).sort(([left], [right]) => compareCodeUnits4(left, right)).map(([sourceId, source]) => {
|
|
4025
4215
|
for (const [themeName, style] of Object.entries(prepared)) {
|
|
4026
4216
|
const definition = style.sources[sourceId];
|
|
4027
4217
|
if (!definition) {
|
|
@@ -4415,7 +4605,7 @@ async function runHostedNativeDeploy(options, ports) {
|
|
|
4415
4605
|
|
|
4416
4606
|
// src/icon-diff-command.ts
|
|
4417
4607
|
import { dirname as dirname7, resolve as resolve6 } from "path";
|
|
4418
|
-
import { z } from "zod";
|
|
4608
|
+
import { z as z2 } from "zod";
|
|
4419
4609
|
import {
|
|
4420
4610
|
diffTileflowIconPackageManifests,
|
|
4421
4611
|
hashTileflowIconPackageManifest,
|
|
@@ -4812,7 +5002,7 @@ function technicalDetails(document, beforeIconCount, afterIconCount) {
|
|
|
4812
5002
|
["Icon count", String(beforeIconCount), String(afterIconCount)]
|
|
4813
5003
|
];
|
|
4814
5004
|
return `<details class="technical-details"><summary>Details</summary><div class="details-content"><p class="details-context">${escapeHtml2(context)}</p><table class="details-table"><thead><tr><th>Metric</th><th>Before</th><th>Next</th></tr></thead><tbody>${rows.map(
|
|
4815
|
-
([
|
|
5005
|
+
([metric2, previous, proposed]) => `<tr><td>${escapeHtml2(metric2 ?? "")}</td><td><code>${escapeHtml2(previous ?? "")}</code></td><td><code>${escapeHtml2(proposed ?? "")}</code></td></tr>`
|
|
4816
5006
|
).join("")}</tbody></table></div></details>`;
|
|
4817
5007
|
}
|
|
4818
5008
|
function imageDefinitions(before, after) {
|
|
@@ -4934,56 +5124,56 @@ function isMissingFile3(error) {
|
|
|
4934
5124
|
|
|
4935
5125
|
// src/icon-diff-command.ts
|
|
4936
5126
|
var environmentSchema = tileflowMapIdSchema;
|
|
4937
|
-
var publicHttpUrlSchema =
|
|
5127
|
+
var publicHttpUrlSchema = z2.url().max(2048).refine((value) => {
|
|
4938
5128
|
const url = new URL(value);
|
|
4939
5129
|
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
|
|
4940
5130
|
});
|
|
4941
|
-
var iconPackageBaselineResponseSchema =
|
|
4942
|
-
baseline:
|
|
4943
|
-
deployedAt:
|
|
4944
|
-
deploymentId:
|
|
4945
|
-
package:
|
|
5131
|
+
var iconPackageBaselineResponseSchema = z2.object({
|
|
5132
|
+
baseline: z2.object({
|
|
5133
|
+
deployedAt: z2.iso.datetime({ offset: true }),
|
|
5134
|
+
deploymentId: z2.string().min(1).max(128),
|
|
5135
|
+
package: z2.object({
|
|
4946
5136
|
contentHash: tileflowIconPackageContentHashSchema,
|
|
4947
5137
|
label: tileflowIconPackageLabelSchema,
|
|
4948
5138
|
manifest: tileflowIconPackageManifestSchema,
|
|
4949
5139
|
spriteUrl: publicHttpUrlSchema,
|
|
4950
|
-
totalBytes:
|
|
5140
|
+
totalBytes: z2.number().int().positive().max(tileflowIconPackageLimits.maxGeneratedPackageBytes)
|
|
4951
5141
|
}).strict().nullable(),
|
|
4952
|
-
version:
|
|
5142
|
+
version: z2.number().int().positive()
|
|
4953
5143
|
}).strict().nullable(),
|
|
4954
5144
|
environment: environmentSchema,
|
|
4955
|
-
schemaVersion:
|
|
5145
|
+
schemaVersion: z2.literal(1)
|
|
4956
5146
|
}).strict();
|
|
4957
|
-
var packageSummarySchema =
|
|
5147
|
+
var packageSummarySchema = z2.object({
|
|
4958
5148
|
contentHash: tileflowIconPackageContentHashSchema,
|
|
4959
|
-
iconCount:
|
|
5149
|
+
iconCount: z2.number().int().nonnegative().max(tileflowIconPackageLimits.maxIconCount),
|
|
4960
5150
|
label: tileflowIconPackageLabelSchema,
|
|
4961
|
-
totalBytes:
|
|
5151
|
+
totalBytes: z2.number().int().nonnegative().max(tileflowIconPackageLimits.maxGeneratedPackageBytes)
|
|
4962
5152
|
}).strict();
|
|
4963
|
-
var tileflowIconDiffDocumentSchema =
|
|
4964
|
-
schemaVersion:
|
|
5153
|
+
var tileflowIconDiffDocumentSchema = z2.object({
|
|
5154
|
+
schemaVersion: z2.literal(1),
|
|
4965
5155
|
environment: environmentSchema,
|
|
4966
|
-
baseline:
|
|
4967
|
-
deploymentId:
|
|
5156
|
+
baseline: z2.object({
|
|
5157
|
+
deploymentId: z2.string(),
|
|
4968
5158
|
package: packageSummarySchema.nullable(),
|
|
4969
|
-
version:
|
|
5159
|
+
version: z2.number().int().positive()
|
|
4970
5160
|
}).strict().nullable(),
|
|
4971
|
-
proposed:
|
|
5161
|
+
proposed: z2.object({
|
|
4972
5162
|
package: packageSummarySchema.nullable()
|
|
4973
5163
|
}).strict(),
|
|
4974
|
-
icons:
|
|
4975
|
-
added:
|
|
4976
|
-
removed:
|
|
4977
|
-
modified:
|
|
4978
|
-
unchangedCount:
|
|
5164
|
+
icons: z2.object({
|
|
5165
|
+
added: z2.array(z2.string()),
|
|
5166
|
+
removed: z2.array(z2.string()),
|
|
5167
|
+
modified: z2.array(z2.string()),
|
|
5168
|
+
unchangedCount: z2.number().int().nonnegative()
|
|
4979
5169
|
}).strict(),
|
|
4980
|
-
generatedBytes:
|
|
4981
|
-
before:
|
|
4982
|
-
after:
|
|
4983
|
-
delta:
|
|
5170
|
+
generatedBytes: z2.object({
|
|
5171
|
+
before: z2.number().int().nonnegative(),
|
|
5172
|
+
after: z2.number().int().nonnegative(),
|
|
5173
|
+
delta: z2.number().int()
|
|
4984
5174
|
}).strict(),
|
|
4985
|
-
artifacts:
|
|
4986
|
-
hasChanges:
|
|
5175
|
+
artifacts: z2.object({ report: z2.string().nullable() }).strict(),
|
|
5176
|
+
hasChanges: z2.boolean()
|
|
4987
5177
|
}).strict();
|
|
4988
5178
|
function registerIconDiffCommand(icons, dependencies) {
|
|
4989
5179
|
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).addOption(hostedApiUrlOption()).addOption(hostedApiKeyOption("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) => {
|
|
@@ -5316,7 +5506,7 @@ function sameContributors(left, right) {
|
|
|
5316
5506
|
|
|
5317
5507
|
// src/icon-lock-command.ts
|
|
5318
5508
|
import { dirname as dirname9 } from "path";
|
|
5319
|
-
import
|
|
5509
|
+
import pc3 from "picocolors";
|
|
5320
5510
|
import {
|
|
5321
5511
|
collectTileflowIconSetReferences,
|
|
5322
5512
|
compareCodeUnits as compareCodeUnits7,
|
|
@@ -5334,8 +5524,8 @@ import { readTileflowIconsLockfileText, writeTileflowIconsLockfile } from "@tile
|
|
|
5334
5524
|
|
|
5335
5525
|
// src/icon-set-command.ts
|
|
5336
5526
|
import { isAbsolute as isAbsolute3 } from "path";
|
|
5337
|
-
import
|
|
5338
|
-
import { z as
|
|
5527
|
+
import pc2 from "picocolors";
|
|
5528
|
+
import { z as z3 } from "zod";
|
|
5339
5529
|
import {
|
|
5340
5530
|
tileflowIconPackageLimits as tileflowIconPackageLimits2,
|
|
5341
5531
|
tileflowIconSetPackageIdSchema,
|
|
@@ -5399,7 +5589,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5399
5589
|
"PUT",
|
|
5400
5590
|
{ body, headers: { "Idempotency-Key": options.idempotencyKey } }
|
|
5401
5591
|
);
|
|
5402
|
-
if (!response.ok) return
|
|
5592
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, "publish");
|
|
5403
5593
|
const parsed = publishResponseSchema.parse(response.body);
|
|
5404
5594
|
if (parsed.revision.pin && parsed.revision.pin.contentHash !== iconPackage.contentHash) {
|
|
5405
5595
|
return emitFailure(
|
|
@@ -5460,7 +5650,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5460
5650
|
};
|
|
5461
5651
|
if (options.json) return emitJson(document);
|
|
5462
5652
|
console.log(
|
|
5463
|
-
`${
|
|
5653
|
+
`${pc2.green("\u2713")} ${parsed.changed ? "Published" : "Unchanged"} ${pc2.bold(parsed.revision.reference)} revision ${parsed.revision.version}.`
|
|
5464
5654
|
);
|
|
5465
5655
|
} catch (error) {
|
|
5466
5656
|
emitFailure(options.json, "icon_set_publish_failed", boundedMessage(error, "publish"));
|
|
@@ -5488,10 +5678,10 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5488
5678
|
team: describeTeam(authority)
|
|
5489
5679
|
};
|
|
5490
5680
|
if (options.json) return emitJson(document);
|
|
5491
|
-
if (sets.length === 0) return console.log(
|
|
5681
|
+
if (sets.length === 0) return console.log(pc2.gray("No Team Icon Sets."));
|
|
5492
5682
|
for (const set of sets) {
|
|
5493
5683
|
console.log(
|
|
5494
|
-
` ${
|
|
5684
|
+
` ${pc2.bold(set.reference)} ${pc2.gray(set.archivedAt ? "archived" : `v${String(set.latestVersion ?? 0)}`)}`
|
|
5495
5685
|
);
|
|
5496
5686
|
}
|
|
5497
5687
|
} catch (error) {
|
|
@@ -5573,7 +5763,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5573
5763
|
headers: { "Content-Type": "application/json" }
|
|
5574
5764
|
}
|
|
5575
5765
|
);
|
|
5576
|
-
if (!response.ok) return
|
|
5766
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, name);
|
|
5577
5767
|
const parsed = catalogDetailResponseSchema.parse(response.body);
|
|
5578
5768
|
const document = {
|
|
5579
5769
|
command: `icon-set ${name}`,
|
|
@@ -5583,7 +5773,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5583
5773
|
};
|
|
5584
5774
|
if (options.json) return emitJson(document);
|
|
5585
5775
|
console.log(
|
|
5586
|
-
`${
|
|
5776
|
+
`${pc2.green("\u2713")} ${archived ? "Archived" : "Restored"} ${pc2.bold(parsed.set.reference)}.`
|
|
5587
5777
|
);
|
|
5588
5778
|
} catch (error) {
|
|
5589
5779
|
emitFailure(options.json, `icon_set_${name}_failed`, boundedMessage(error, name));
|
|
@@ -5654,7 +5844,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5654
5844
|
}
|
|
5655
5845
|
}
|
|
5656
5846
|
);
|
|
5657
|
-
if (!response.ok) return
|
|
5847
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, "purge");
|
|
5658
5848
|
const parsed = purgeResponseSchema.parse(response.body);
|
|
5659
5849
|
const document = {
|
|
5660
5850
|
command: "icon-set purge",
|
|
@@ -5663,8 +5853,8 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5663
5853
|
team: describeTeam(authority)
|
|
5664
5854
|
};
|
|
5665
5855
|
if (options.json) return emitJson(document);
|
|
5666
|
-
console.log(`${
|
|
5667
|
-
console.log(
|
|
5856
|
+
console.log(`${pc2.green("\u2713")} Purged ${pc2.bold(options.confirm)}.`);
|
|
5857
|
+
console.log(pc2.yellow(`! ${parsed.result.warning}`));
|
|
5668
5858
|
} catch (error) {
|
|
5669
5859
|
emitFailure(options.json, "icon_set_purge_failed", boundedMessage(error, "purge"));
|
|
5670
5860
|
}
|
|
@@ -5678,7 +5868,7 @@ function registerIconSetCommands(program2, dependencies) {
|
|
|
5678
5868
|
const authority = await resolveTeamAuthority(options, ["icons:read"], dependencies);
|
|
5679
5869
|
if (!authority) return;
|
|
5680
5870
|
const result = await run(authority);
|
|
5681
|
-
if ("status" in result) return
|
|
5871
|
+
if ("status" in result) return emitHttpFailure2(options.json, result.status, command2);
|
|
5682
5872
|
const document = {
|
|
5683
5873
|
command: command2,
|
|
5684
5874
|
schemaVersion: 1,
|
|
@@ -5736,7 +5926,7 @@ var IconSetTransportError = class extends Error {
|
|
|
5736
5926
|
}
|
|
5737
5927
|
status;
|
|
5738
5928
|
};
|
|
5739
|
-
function
|
|
5929
|
+
function emitHttpFailure2(json2, status, command2) {
|
|
5740
5930
|
emitFailure(json2, `http_${status}`, `Icon Set ${command2} failed (${status}).`);
|
|
5741
5931
|
}
|
|
5742
5932
|
function boundedMessage(error, command2) {
|
|
@@ -5781,27 +5971,27 @@ function isIconSetSlug(value) {
|
|
|
5781
5971
|
function isIdempotencyKey(value) {
|
|
5782
5972
|
return value.length >= 8 && value.length <= 128 && /^[A-Za-z0-9._:-]+$/u.test(value);
|
|
5783
5973
|
}
|
|
5784
|
-
var cursorSchema =
|
|
5785
|
-
var isoDateSchema =
|
|
5786
|
-
var setSchema =
|
|
5787
|
-
id:
|
|
5974
|
+
var cursorSchema = z3.string().min(1).max(2048).nullable();
|
|
5975
|
+
var isoDateSchema = z3.iso.datetime({ offset: true });
|
|
5976
|
+
var setSchema = z3.object({
|
|
5977
|
+
id: z3.string().regex(/^ics_[A-Za-z0-9_-]{16}$/u),
|
|
5788
5978
|
reference: tileflowIconSetReferenceSchema,
|
|
5789
|
-
name:
|
|
5790
|
-
description:
|
|
5979
|
+
name: z3.string().max(200),
|
|
5980
|
+
description: z3.string().max(2e3),
|
|
5791
5981
|
archivedAt: isoDateSchema.nullable(),
|
|
5792
|
-
latestVersion:
|
|
5982
|
+
latestVersion: z3.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
5793
5983
|
createdAt: isoDateSchema,
|
|
5794
5984
|
updatedAt: isoDateSchema
|
|
5795
5985
|
}).strict();
|
|
5796
|
-
var revisionBaseSchema =
|
|
5797
|
-
id:
|
|
5986
|
+
var revisionBaseSchema = z3.object({
|
|
5987
|
+
id: z3.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
|
|
5798
5988
|
reference: tileflowIconSetReferenceSchema,
|
|
5799
|
-
version:
|
|
5989
|
+
version: z3.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
5800
5990
|
publishedAt: isoDateSchema,
|
|
5801
5991
|
purgedAt: isoDateSchema.nullable(),
|
|
5802
|
-
iconCount:
|
|
5803
|
-
totalBytes:
|
|
5804
|
-
publisher:
|
|
5992
|
+
iconCount: z3.number().int().min(0).max(tileflowIconPackageLimits2.maxIconCount),
|
|
5993
|
+
totalBytes: z3.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
5994
|
+
publisher: z3.object({ kind: z3.enum(["credential", "membership"]), id: z3.string().max(200).nullable() }).strict()
|
|
5805
5995
|
}).strict();
|
|
5806
5996
|
var revisionSchema = revisionBaseSchema.extend({ pin: tileflowIconSetPinSchema.nullable() }).transform(({ id, reference, version, publishedAt, purgedAt, iconCount, totalBytes, pin }) => ({
|
|
5807
5997
|
iconCount,
|
|
@@ -5825,53 +6015,53 @@ var revisionSummarySchema = revisionBaseSchema.extend({ packageId: tileflowIconS
|
|
|
5825
6015
|
version
|
|
5826
6016
|
})
|
|
5827
6017
|
);
|
|
5828
|
-
var catalogListResponseSchema =
|
|
5829
|
-
schemaVersion:
|
|
5830
|
-
sets:
|
|
6018
|
+
var catalogListResponseSchema = z3.object({
|
|
6019
|
+
schemaVersion: z3.literal(1),
|
|
6020
|
+
sets: z3.array(setSchema).max(pageSize),
|
|
5831
6021
|
nextCursor: cursorSchema.optional()
|
|
5832
6022
|
}).strict();
|
|
5833
|
-
var catalogDetailResponseSchema =
|
|
5834
|
-
var versionsResponseSchema =
|
|
5835
|
-
schemaVersion:
|
|
5836
|
-
versions:
|
|
6023
|
+
var catalogDetailResponseSchema = z3.object({ schemaVersion: z3.literal(1), set: setSchema }).strict();
|
|
6024
|
+
var versionsResponseSchema = z3.object({
|
|
6025
|
+
schemaVersion: z3.literal(1),
|
|
6026
|
+
versions: z3.array(revisionSummarySchema).max(pageSize),
|
|
5837
6027
|
nextCursor: cursorSchema.optional()
|
|
5838
6028
|
}).strict();
|
|
5839
|
-
var revisionResponseSchema =
|
|
5840
|
-
schemaVersion:
|
|
5841
|
-
archived:
|
|
6029
|
+
var revisionResponseSchema = z3.object({
|
|
6030
|
+
schemaVersion: z3.literal(1),
|
|
6031
|
+
archived: z3.boolean(),
|
|
5842
6032
|
revision: revisionSchema
|
|
5843
6033
|
}).strict();
|
|
5844
|
-
var publishResponseSchema =
|
|
5845
|
-
var usesResponseSchema =
|
|
5846
|
-
schemaVersion:
|
|
5847
|
-
uses:
|
|
5848
|
-
|
|
5849
|
-
deploymentId:
|
|
5850
|
-
deploymentVersion:
|
|
5851
|
-
mapId:
|
|
5852
|
-
mapName:
|
|
5853
|
-
mapState:
|
|
5854
|
-
versionId:
|
|
5855
|
-
version:
|
|
5856
|
-
ordinal:
|
|
5857
|
-
relationship:
|
|
6034
|
+
var publishResponseSchema = z3.object({ schemaVersion: z3.literal(1), changed: z3.boolean(), revision: revisionSchema }).strict();
|
|
6035
|
+
var usesResponseSchema = z3.object({
|
|
6036
|
+
schemaVersion: z3.literal(1),
|
|
6037
|
+
uses: z3.array(
|
|
6038
|
+
z3.object({
|
|
6039
|
+
deploymentId: z3.string().min(1).max(160),
|
|
6040
|
+
deploymentVersion: z3.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
6041
|
+
mapId: z3.string().min(1).max(160),
|
|
6042
|
+
mapName: z3.string().max(200).nullable(),
|
|
6043
|
+
mapState: z3.string().max(64),
|
|
6044
|
+
versionId: z3.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
|
|
6045
|
+
version: z3.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
6046
|
+
ordinal: z3.number().int().min(0).max(31),
|
|
6047
|
+
relationship: z3.enum(["active", "retained"])
|
|
5858
6048
|
}).strict()
|
|
5859
6049
|
).max(pageSize),
|
|
5860
6050
|
nextCursor: cursorSchema.optional(),
|
|
5861
|
-
coverage:
|
|
5862
|
-
includesUndeployedRepositoryLocks:
|
|
6051
|
+
coverage: z3.literal("known-hosted-deployments"),
|
|
6052
|
+
includesUndeployedRepositoryLocks: z3.literal(false)
|
|
5863
6053
|
}).strict();
|
|
5864
|
-
var purgeResponseSchema =
|
|
5865
|
-
schemaVersion:
|
|
5866
|
-
result:
|
|
5867
|
-
schemaVersion:
|
|
5868
|
-
versionId:
|
|
5869
|
-
version:
|
|
5870
|
-
purged:
|
|
5871
|
-
knownRetainedDeployments:
|
|
5872
|
-
physicalBytesReleased:
|
|
5873
|
-
unknownRepositoryLocks:
|
|
5874
|
-
warning:
|
|
6054
|
+
var purgeResponseSchema = z3.object({
|
|
6055
|
+
schemaVersion: z3.literal(1),
|
|
6056
|
+
result: z3.object({
|
|
6057
|
+
schemaVersion: z3.literal(1),
|
|
6058
|
+
versionId: z3.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
|
|
6059
|
+
version: z3.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
6060
|
+
purged: z3.boolean(),
|
|
6061
|
+
knownRetainedDeployments: z3.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
6062
|
+
physicalBytesReleased: z3.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
6063
|
+
unknownRepositoryLocks: z3.boolean(),
|
|
6064
|
+
warning: z3.string().max(400)
|
|
5875
6065
|
}).strict()
|
|
5876
6066
|
}).strict();
|
|
5877
6067
|
|
|
@@ -5986,14 +6176,14 @@ function registerIconLockCommands(program2, dependencies) {
|
|
|
5986
6176
|
}))
|
|
5987
6177
|
};
|
|
5988
6178
|
if (options.json) return emitJson(document);
|
|
5989
|
-
if (document.sets.length === 0) return console.log(
|
|
6179
|
+
if (document.sets.length === 0) return console.log(pc3.gray("No Icon Set references declared."));
|
|
5990
6180
|
for (const entry of document.sets) {
|
|
5991
6181
|
console.log(
|
|
5992
|
-
` ${
|
|
6182
|
+
` ${pc3.bold(entry.reference)} ${pc3.gray(`v${String(entry.version)}`)}${entry.changed ? "" : pc3.gray(" (unchanged)")}`
|
|
5993
6183
|
);
|
|
5994
6184
|
}
|
|
5995
6185
|
for (const reference of missing) {
|
|
5996
|
-
console.log(
|
|
6186
|
+
console.log(pc3.yellow(`! ${reference} is declared but not locked.`));
|
|
5997
6187
|
}
|
|
5998
6188
|
}
|
|
5999
6189
|
async function resolvePin(authority, reference, version) {
|
|
@@ -6109,12 +6299,12 @@ async function readCurrentPins(contents) {
|
|
|
6109
6299
|
}
|
|
6110
6300
|
|
|
6111
6301
|
// src/map-commands.ts
|
|
6112
|
-
import
|
|
6302
|
+
import pc5 from "picocolors";
|
|
6113
6303
|
|
|
6114
6304
|
// src/map-setup.ts
|
|
6115
6305
|
import { randomBytes } from "crypto";
|
|
6116
6306
|
import { createInterface } from "readline/promises";
|
|
6117
|
-
import
|
|
6307
|
+
import pc4 from "picocolors";
|
|
6118
6308
|
var maximumOrigins = 20;
|
|
6119
6309
|
var exactHost = /^(?:[a-z0-9-]+(?:\.[a-z0-9-]+)*|\[[0-9a-f:.]+\])$/u;
|
|
6120
6310
|
var teamReference = /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u;
|
|
@@ -6252,9 +6442,9 @@ function createTerminalPrompts(json2 = false) {
|
|
|
6252
6442
|
if (json2 || !process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
6253
6443
|
return {
|
|
6254
6444
|
async chooseTeam(teams) {
|
|
6255
|
-
console.log(`${
|
|
6445
|
+
console.log(`${pc4.cyan("?")} Which Team should own the new Map?`);
|
|
6256
6446
|
teams.forEach(
|
|
6257
|
-
(team, index) => console.log(` ${index + 1}) ${team.name} ${
|
|
6447
|
+
(team, index) => console.log(` ${index + 1}) ${team.name} ${pc4.gray(`@${team.slug}`)}`)
|
|
6258
6448
|
);
|
|
6259
6449
|
const answer = await ask("Team number", (value) => {
|
|
6260
6450
|
const index = Number(value);
|
|
@@ -6263,7 +6453,7 @@ function createTerminalPrompts(json2 = false) {
|
|
|
6263
6453
|
return answer;
|
|
6264
6454
|
},
|
|
6265
6455
|
async chooseAccess() {
|
|
6266
|
-
console.log(`${
|
|
6456
|
+
console.log(`${pc4.cyan("?")} Which websites may load this Map in a browser?`);
|
|
6267
6457
|
console.log(" 1) Only selected websites (recommended)");
|
|
6268
6458
|
console.log(" 2) Any website");
|
|
6269
6459
|
const mode = await ask(
|
|
@@ -6274,7 +6464,7 @@ function createTerminalPrompts(json2 = false) {
|
|
|
6274
6464
|
if (mode === "any") return { kind: "any" };
|
|
6275
6465
|
return ask("Website origins, separated by spaces (https://example.com)", (value) => {
|
|
6276
6466
|
const parsed = parseOriginList(value.split(/[\s,]+/u).filter(Boolean));
|
|
6277
|
-
if (!parsed.ok) console.log(` ${
|
|
6467
|
+
if (!parsed.ok) console.log(` ${pc4.yellow(parsed.error)}`);
|
|
6278
6468
|
return parsed.ok ? parsed.value : null;
|
|
6279
6469
|
});
|
|
6280
6470
|
}
|
|
@@ -6329,8 +6519,8 @@ function registerMapCommands(program2, dependencies) {
|
|
|
6329
6519
|
}
|
|
6330
6520
|
printCreatedMap(result.value);
|
|
6331
6521
|
console.log(`
|
|
6332
|
-
${
|
|
6333
|
-
console.log(` ${
|
|
6522
|
+
${pc5.bold("Next step")}`);
|
|
6523
|
+
console.log(` ${pc5.cyan("\u2022")} Publish the repository map with ${pc5.cyan(next)}.`);
|
|
6334
6524
|
});
|
|
6335
6525
|
}
|
|
6336
6526
|
function addMapSetupOptions(command2) {
|
|
@@ -6346,7 +6536,7 @@ function hasMapSetupOptions(options) {
|
|
|
6346
6536
|
async function createMapForDeploy(options, dependencies) {
|
|
6347
6537
|
const session = await loadSession(options, dependencies);
|
|
6348
6538
|
if (!session) return null;
|
|
6349
|
-
console.log(`${
|
|
6539
|
+
console.log(`${pc5.cyan("i")} No --map-id given; creating a managed Map for this deploy.`);
|
|
6350
6540
|
const result = await setUpManagedMap(session, options, {
|
|
6351
6541
|
prompts: dependencies.prompts === void 0 ? createTerminalPrompts() : dependencies.prompts
|
|
6352
6542
|
});
|
|
@@ -6356,7 +6546,7 @@ async function createMapForDeploy(options, dependencies) {
|
|
|
6356
6546
|
}
|
|
6357
6547
|
printCreatedMap(result.value);
|
|
6358
6548
|
console.log(
|
|
6359
|
-
|
|
6549
|
+
pc5.gray(
|
|
6360
6550
|
`If this deploy is interrupted, rerun it with --setup-key ${result.setupKey} to reuse the same Map.`
|
|
6361
6551
|
)
|
|
6362
6552
|
);
|
|
@@ -6364,10 +6554,10 @@ async function createMapForDeploy(options, dependencies) {
|
|
|
6364
6554
|
}
|
|
6365
6555
|
function printCreatedMap(created) {
|
|
6366
6556
|
console.log(
|
|
6367
|
-
`${
|
|
6557
|
+
`${pc5.green("\u2713")} Created ${pc5.bold(created.map.id)} in ${created.team.name} ${pc5.gray(`@${created.team.slug}`)}.`
|
|
6368
6558
|
);
|
|
6369
6559
|
console.log(
|
|
6370
|
-
`${
|
|
6560
|
+
`${pc5.gray("Websites:".padEnd(13))} ${describeBrowserAccess(created.allowedOrigins)}`
|
|
6371
6561
|
);
|
|
6372
6562
|
}
|
|
6373
6563
|
async function loadSession(options, dependencies) {
|
|
@@ -6400,8 +6590,8 @@ async function loadSession(options, dependencies) {
|
|
|
6400
6590
|
// src/model-command.ts
|
|
6401
6591
|
import { lstat as lstat5 } from "fs/promises";
|
|
6402
6592
|
import { basename as basename4, dirname as dirname10, relative as relative4, resolve as resolve7 } from "path";
|
|
6403
|
-
import
|
|
6404
|
-
import { z as
|
|
6593
|
+
import pc6 from "picocolors";
|
|
6594
|
+
import { z as z4 } from "zod";
|
|
6405
6595
|
import { tileflowModelContentType } from "@tileflow/core";
|
|
6406
6596
|
import {
|
|
6407
6597
|
assertValidTileflowConfig as assertValidTileflowConfig3,
|
|
@@ -6464,7 +6654,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6464
6654
|
"PUT",
|
|
6465
6655
|
{ body }
|
|
6466
6656
|
);
|
|
6467
|
-
if (!response.ok) return
|
|
6657
|
+
if (!response.ok) return emitHttpFailure3(options.json, response.status, response.body, name);
|
|
6468
6658
|
const parsed = publishResponseSchema2.safeParse(response.body);
|
|
6469
6659
|
if (!parsed.success || parsed.data.model.current?.contentHash !== model.sha256) {
|
|
6470
6660
|
return emitFailure(
|
|
@@ -6478,7 +6668,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6478
6668
|
const current = parsed.data.model.current;
|
|
6479
6669
|
const action = parsed.data.changed ? "Published" : "Unchanged";
|
|
6480
6670
|
console.log(
|
|
6481
|
-
`${
|
|
6671
|
+
`${pc6.green("\u2713")} ${action} 3D model ${pc6.bold(name)} version ${current.version} (${formatBytes3(current.byteLength)}, ${formatCount(current.summary.levels[0]?.triangles ?? 0)} triangles).`
|
|
6482
6672
|
);
|
|
6483
6673
|
}
|
|
6484
6674
|
}
|
|
@@ -6510,7 +6700,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6510
6700
|
`/v1/models${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
|
|
6511
6701
|
"GET"
|
|
6512
6702
|
);
|
|
6513
|
-
if (!response.ok) return
|
|
6703
|
+
if (!response.ok) return emitHttpFailure3(options.json, response.status, response.body);
|
|
6514
6704
|
const parsed = listResponseSchema.safeParse(response.body);
|
|
6515
6705
|
if (!parsed.success)
|
|
6516
6706
|
return emitFailure(options.json, "invalid_response", "The model list was not readable.");
|
|
@@ -6527,7 +6717,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6527
6717
|
for (const model of listed) {
|
|
6528
6718
|
const current = model.current;
|
|
6529
6719
|
console.log(
|
|
6530
|
-
`${
|
|
6720
|
+
`${pc6.bold(model.name)} ${current ? `version ${current.version}, ${formatBytes3(current.byteLength)}` : "no current version"} ${model.usedByMaps === 1 ? "1 map" : `${model.usedByMaps} maps`}${model.state === "purging" ? pc6.yellow(" purging") : ""}`
|
|
6531
6721
|
);
|
|
6532
6722
|
}
|
|
6533
6723
|
}
|
|
@@ -6544,7 +6734,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6544
6734
|
if (options.json) return emitJson({ ...detail, command: "models status", ok: true });
|
|
6545
6735
|
const { model, versions, uses } = detail;
|
|
6546
6736
|
console.log(
|
|
6547
|
-
`${
|
|
6737
|
+
`${pc6.bold(model.name)}${model.state === "purging" ? pc6.yellow(" (purging)") : ""}`
|
|
6548
6738
|
);
|
|
6549
6739
|
for (const version of versions) {
|
|
6550
6740
|
const role = version.state === "current" ? "current" : version.state === "previous" ? "previous" : version.state;
|
|
@@ -6582,7 +6772,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6582
6772
|
headers: { "Content-Type": "application/json" }
|
|
6583
6773
|
}
|
|
6584
6774
|
);
|
|
6585
|
-
if (!response.ok) return
|
|
6775
|
+
if (!response.ok) return emitHttpFailure3(options.json, response.status, response.body, name);
|
|
6586
6776
|
const parsed = restoreResponseSchema.safeParse(response.body);
|
|
6587
6777
|
if (!parsed.success || parsed.data.model.current?.version !== previous.version) {
|
|
6588
6778
|
return emitFailure(
|
|
@@ -6599,7 +6789,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6599
6789
|
model: parsed.data.model
|
|
6600
6790
|
});
|
|
6601
6791
|
console.log(
|
|
6602
|
-
`${
|
|
6792
|
+
`${pc6.green("\u2713")} ${pc6.bold(name)} is at version ${previous.version} again; version ${current.version} is its previous one. Maps draw it on their next load.`
|
|
6603
6793
|
);
|
|
6604
6794
|
});
|
|
6605
6795
|
withTeamOptions2(
|
|
@@ -6624,7 +6814,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6624
6814
|
headers: { "X-Tileflow-Confirm-Model-Purge": name }
|
|
6625
6815
|
}
|
|
6626
6816
|
);
|
|
6627
|
-
if (!response.ok) return
|
|
6817
|
+
if (!response.ok) return emitHttpFailure3(options.json, response.status, response.body, name);
|
|
6628
6818
|
const parsed = purgeResponseSchema2.safeParse(response.body);
|
|
6629
6819
|
if (!parsed.success)
|
|
6630
6820
|
return emitFailure(options.json, "invalid_response", "The purge was not confirmed.");
|
|
@@ -6636,7 +6826,7 @@ function registerModelCommands(program2, dependencies) {
|
|
|
6636
6826
|
purge: parsed.data.purge
|
|
6637
6827
|
});
|
|
6638
6828
|
console.log(
|
|
6639
|
-
`${
|
|
6829
|
+
`${pc6.green("\u2713")} ${pc6.bold(name)} is being purged; its name is free once its files are deleted.`
|
|
6640
6830
|
);
|
|
6641
6831
|
});
|
|
6642
6832
|
}
|
|
@@ -6650,7 +6840,7 @@ async function readDetail(authority, name, json2) {
|
|
|
6650
6840
|
"GET"
|
|
6651
6841
|
);
|
|
6652
6842
|
if (!response.ok) {
|
|
6653
|
-
|
|
6843
|
+
emitHttpFailure3(json2, response.status, response.body, name);
|
|
6654
6844
|
return void 0;
|
|
6655
6845
|
}
|
|
6656
6846
|
const parsed = detailResponseSchema.safeParse(response.body);
|
|
@@ -6728,8 +6918,8 @@ function addModel(models, name, model) {
|
|
|
6728
6918
|
throw new Error(`Two different models are named ${name}; a Team has one model of each name.`);
|
|
6729
6919
|
models.set(name, model);
|
|
6730
6920
|
}
|
|
6731
|
-
function
|
|
6732
|
-
const error =
|
|
6921
|
+
function emitHttpFailure3(json2, status, body, name) {
|
|
6922
|
+
const error = z4.object({ error: z4.object({ code: z4.string().max(80), message: z4.string().max(400) }).partial() }).partial().safeParse(body);
|
|
6733
6923
|
const message = error.success ? error.data.error?.message : void 0;
|
|
6734
6924
|
const code = error.success ? error.data.error?.code : void 0;
|
|
6735
6925
|
if (status === 404)
|
|
@@ -6752,64 +6942,64 @@ function formatBytes3(bytes) {
|
|
|
6752
6942
|
function formatCount(count) {
|
|
6753
6943
|
return new Intl.NumberFormat("en-US").format(count);
|
|
6754
6944
|
}
|
|
6755
|
-
var summarySchema =
|
|
6756
|
-
levels:
|
|
6757
|
-
|
|
6758
|
-
level:
|
|
6759
|
-
vertices:
|
|
6760
|
-
triangles:
|
|
6945
|
+
var summarySchema = z4.object({
|
|
6946
|
+
levels: z4.array(
|
|
6947
|
+
z4.object({
|
|
6948
|
+
level: z4.enum(["near", "mid", "far"]),
|
|
6949
|
+
vertices: z4.number().int().nonnegative(),
|
|
6950
|
+
triangles: z4.number().int().nonnegative()
|
|
6761
6951
|
}).strict()
|
|
6762
6952
|
).min(1).max(3),
|
|
6763
|
-
width:
|
|
6764
|
-
depth:
|
|
6765
|
-
height:
|
|
6766
|
-
footprint:
|
|
6767
|
-
rings:
|
|
6768
|
-
points:
|
|
6769
|
-
area:
|
|
6953
|
+
width: z4.number().nonnegative(),
|
|
6954
|
+
depth: z4.number().nonnegative(),
|
|
6955
|
+
height: z4.number().nonnegative(),
|
|
6956
|
+
footprint: z4.object({
|
|
6957
|
+
rings: z4.number().int().nonnegative(),
|
|
6958
|
+
points: z4.number().int().nonnegative(),
|
|
6959
|
+
area: z4.number().nonnegative()
|
|
6770
6960
|
}).strict()
|
|
6771
6961
|
}).strict();
|
|
6772
|
-
var versionSchema =
|
|
6773
|
-
id:
|
|
6774
|
-
version:
|
|
6775
|
-
state:
|
|
6776
|
-
contentHash:
|
|
6777
|
-
byteLength:
|
|
6778
|
-
publishedAt:
|
|
6962
|
+
var versionSchema = z4.object({
|
|
6963
|
+
id: z4.string().regex(/^tmv_[A-Za-z0-9_-]{16,64}$/u),
|
|
6964
|
+
version: z4.number().int().positive(),
|
|
6965
|
+
state: z4.enum(["current", "previous", "gc_pending", "deleting", "deleted"]),
|
|
6966
|
+
contentHash: z4.string().regex(sha256),
|
|
6967
|
+
byteLength: z4.number().int().positive(),
|
|
6968
|
+
publishedAt: z4.iso.datetime({ offset: true }),
|
|
6779
6969
|
summary: summarySchema
|
|
6780
6970
|
}).strict();
|
|
6781
|
-
var modelSchema =
|
|
6782
|
-
id:
|
|
6783
|
-
name:
|
|
6784
|
-
state:
|
|
6971
|
+
var modelSchema = z4.object({
|
|
6972
|
+
id: z4.string().regex(/^tmd_[A-Za-z0-9_-]{16,64}$/u),
|
|
6973
|
+
name: z4.string().regex(modelName).max(64),
|
|
6974
|
+
state: z4.enum(["active", "purging"]),
|
|
6785
6975
|
current: versionSchema.nullable(),
|
|
6786
6976
|
previous: versionSchema.nullable(),
|
|
6787
|
-
url:
|
|
6788
|
-
usedByMaps:
|
|
6789
|
-
updatedAt:
|
|
6977
|
+
url: z4.url(),
|
|
6978
|
+
usedByMaps: z4.number().int().nonnegative(),
|
|
6979
|
+
updatedAt: z4.iso.datetime({ offset: true })
|
|
6790
6980
|
}).strict();
|
|
6791
|
-
var listResponseSchema =
|
|
6792
|
-
schemaVersion:
|
|
6793
|
-
models:
|
|
6794
|
-
nextCursor:
|
|
6981
|
+
var listResponseSchema = z4.object({
|
|
6982
|
+
schemaVersion: z4.literal(1),
|
|
6983
|
+
models: z4.array(modelSchema).max(100),
|
|
6984
|
+
nextCursor: z4.string().min(1).max(512).optional()
|
|
6795
6985
|
}).strict();
|
|
6796
|
-
var detailResponseSchema =
|
|
6797
|
-
schemaVersion:
|
|
6986
|
+
var detailResponseSchema = z4.object({
|
|
6987
|
+
schemaVersion: z4.literal(1),
|
|
6798
6988
|
model: modelSchema,
|
|
6799
|
-
versions:
|
|
6800
|
-
uses:
|
|
6801
|
-
|
|
6802
|
-
mapId:
|
|
6803
|
-
mapName:
|
|
6804
|
-
relationship:
|
|
6989
|
+
versions: z4.array(versionSchema).max(100),
|
|
6990
|
+
uses: z4.array(
|
|
6991
|
+
z4.object({
|
|
6992
|
+
mapId: z4.string().min(1).max(160),
|
|
6993
|
+
mapName: z4.string().max(200).nullable(),
|
|
6994
|
+
relationship: z4.enum(["active", "retained"])
|
|
6805
6995
|
}).strict()
|
|
6806
6996
|
).max(100)
|
|
6807
6997
|
}).strict();
|
|
6808
|
-
var publishResponseSchema2 =
|
|
6809
|
-
var restoreResponseSchema =
|
|
6810
|
-
var purgeResponseSchema2 =
|
|
6811
|
-
schemaVersion:
|
|
6812
|
-
purge:
|
|
6998
|
+
var publishResponseSchema2 = z4.object({ schemaVersion: z4.literal(1), changed: z4.boolean(), model: modelSchema }).strict();
|
|
6999
|
+
var restoreResponseSchema = z4.object({ schemaVersion: z4.literal(1), model: modelSchema }).strict();
|
|
7000
|
+
var purgeResponseSchema2 = z4.object({
|
|
7001
|
+
schemaVersion: z4.literal(1),
|
|
7002
|
+
purge: z4.object({ name: z4.string().regex(modelName), state: z4.enum(["purging", "purged"]) }).strict()
|
|
6813
7003
|
}).strict();
|
|
6814
7004
|
|
|
6815
7005
|
// src/native-preview-command.ts
|
|
@@ -6850,17 +7040,17 @@ function createTileflowNativePreviewRequestHandler(options) {
|
|
|
6850
7040
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
6851
7041
|
status: 404
|
|
6852
7042
|
});
|
|
6853
|
-
const handler = async (
|
|
7043
|
+
const handler = async (request2) => {
|
|
6854
7044
|
let pathname;
|
|
6855
7045
|
try {
|
|
6856
|
-
pathname = new URL(
|
|
7046
|
+
pathname = new URL(request2.url).pathname;
|
|
6857
7047
|
} catch {
|
|
6858
7048
|
return notFound();
|
|
6859
7049
|
}
|
|
6860
7050
|
if (pathname === tileflowNativePreviewBasePath || pathname === `${tileflowNativePreviewBasePath}/` || nativePreviewUnavailablePaths.has(pathname) || pathname.startsWith(`${tileflowNativePreviewBasePath}/__runtime/`) || pathname.startsWith(`${tileflowNativePreviewBasePath}/__inspection/`)) {
|
|
6861
7051
|
return notFound();
|
|
6862
7052
|
}
|
|
6863
|
-
return delegate(
|
|
7053
|
+
return delegate(request2);
|
|
6864
7054
|
};
|
|
6865
7055
|
return Object.assign(handler, {
|
|
6866
7056
|
close: delegate.close,
|
|
@@ -6900,7 +7090,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
|
|
|
6900
7090
|
}
|
|
6901
7091
|
|
|
6902
7092
|
// src/project-commands.ts
|
|
6903
|
-
import
|
|
7093
|
+
import pc7 from "picocolors";
|
|
6904
7094
|
function registerProjectCommands(program2, dependencies) {
|
|
6905
7095
|
const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
|
|
6906
7096
|
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) => {
|
|
@@ -6916,12 +7106,12 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
6916
7106
|
};
|
|
6917
7107
|
if (options.json) return emitJson2(document);
|
|
6918
7108
|
if (!result.items.length) {
|
|
6919
|
-
console.log(
|
|
7109
|
+
console.log(pc7.gray("No managed destinations found."));
|
|
6920
7110
|
return;
|
|
6921
7111
|
}
|
|
6922
7112
|
for (const target of result.items) {
|
|
6923
7113
|
console.log(
|
|
6924
|
-
` ${target.project.archivedAt ?
|
|
7114
|
+
` ${target.project.archivedAt ? pc7.yellow("\u25CB") : pc7.green("\u25CF")} ${pc7.bold(target.project.name)} ${pc7.gray(target.reference)}${target.project.archivedAt ? pc7.gray(" (archived)") : ""}`
|
|
6925
7115
|
);
|
|
6926
7116
|
}
|
|
6927
7117
|
});
|
|
@@ -6974,8 +7164,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
6974
7164
|
...asRecord(result.body)
|
|
6975
7165
|
};
|
|
6976
7166
|
if (options.json) return emitJson2(document);
|
|
6977
|
-
console.log(`${
|
|
6978
|
-
console.log(
|
|
7167
|
+
console.log(`${pc7.green("\u2713")} Application ${pc7.bold(options.name.trim())} is ready.`);
|
|
7168
|
+
console.log(pc7.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
|
|
6979
7169
|
}
|
|
6980
7170
|
);
|
|
6981
7171
|
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) => {
|
|
@@ -6997,8 +7187,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
6997
7187
|
if (!isProjectItem(project)) {
|
|
6998
7188
|
return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
|
|
6999
7189
|
}
|
|
7000
|
-
console.log(`${
|
|
7001
|
-
console.log(project.archivedAt ?
|
|
7190
|
+
console.log(`${pc7.bold(project.name)} ${pc7.gray(target)}`);
|
|
7191
|
+
console.log(project.archivedAt ? pc7.yellow("Archived") : pc7.green("Active"));
|
|
7002
7192
|
});
|
|
7003
7193
|
for (const archived of [true, false]) {
|
|
7004
7194
|
const verb = archived ? "archive" : "unarchive";
|
|
@@ -7025,7 +7215,7 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7025
7215
|
...asRecord(result.body)
|
|
7026
7216
|
};
|
|
7027
7217
|
if (options.json) return emitJson2(document);
|
|
7028
|
-
console.log(`${
|
|
7218
|
+
console.log(`${pc7.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
|
|
7029
7219
|
});
|
|
7030
7220
|
}
|
|
7031
7221
|
}
|
|
@@ -7136,7 +7326,7 @@ function emitLocalFailure(json2, code, message, details = {}) {
|
|
|
7136
7326
|
console.error(message);
|
|
7137
7327
|
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
7138
7328
|
} else {
|
|
7139
|
-
console.error(`${
|
|
7329
|
+
console.error(`${pc7.red("\u2715")} ${message}`);
|
|
7140
7330
|
}
|
|
7141
7331
|
process.exitCode = 1;
|
|
7142
7332
|
}
|
|
@@ -7281,7 +7471,7 @@ async function runRendererArtifactCommand(command2, options) {
|
|
|
7281
7471
|
|
|
7282
7472
|
// src/visual-command.ts
|
|
7283
7473
|
import { lstat as lstat6, readFile as readFile5 } from "fs/promises";
|
|
7284
|
-
import { dirname as dirname11, isAbsolute as isAbsolute4, parse as parse2, relative as relative5, resolve as
|
|
7474
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, parse as parse2, relative as relative5, resolve as resolve10, sep as sep4 } from "path";
|
|
7285
7475
|
import {
|
|
7286
7476
|
analyzeTileflowCaptureReference,
|
|
7287
7477
|
compareTileflowCaptureToBaseline,
|
|
@@ -7296,6 +7486,565 @@ import {
|
|
|
7296
7486
|
validateTileflowVisualReferencePng
|
|
7297
7487
|
} from "@tileflow/capture";
|
|
7298
7488
|
import { compareCodeUnits as compareCodeUnits8 } from "@tileflow/core";
|
|
7489
|
+
|
|
7490
|
+
// src/visual-approval-command.ts
|
|
7491
|
+
import { execFile } from "child_process";
|
|
7492
|
+
import { resolve as resolve9 } from "path";
|
|
7493
|
+
import { setTimeout as delay } from "timers/promises";
|
|
7494
|
+
import { TileflowVisualReviewError as TileflowVisualReviewError2 } from "@tileflow/capture";
|
|
7495
|
+
var statusName = "Tileflow visual review";
|
|
7496
|
+
var approveJobName = "tileflow-visual-review-approve";
|
|
7497
|
+
var defaultEvidenceBranch = "tileflow-visual-review";
|
|
7498
|
+
var defaultOutputDirectory = ".tileflow/diffs/review";
|
|
7499
|
+
var defaultTimeoutSeconds = 300;
|
|
7500
|
+
var pollIntervalMs = 5e3;
|
|
7501
|
+
var maximumReportBytes = 1024 * 1024;
|
|
7502
|
+
var maximumImageBytes = 5 * 1024 * 1024;
|
|
7503
|
+
var maximumHostOutputBytes = 16 * 1024 * 1024;
|
|
7504
|
+
var maximumText = 500;
|
|
7505
|
+
var scenePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
7506
|
+
var fullShaPattern = /^[0-9a-f]{40}$/u;
|
|
7507
|
+
var commitPattern = /^[0-9a-f]{7,40}$/u;
|
|
7508
|
+
var branchPattern = /^[A-Za-z0-9._/-]+$/u;
|
|
7509
|
+
var sceneStatuses = /* @__PURE__ */ new Set([
|
|
7510
|
+
"changed",
|
|
7511
|
+
"missing-baseline",
|
|
7512
|
+
"runtime-mismatch",
|
|
7513
|
+
"scene-mismatch",
|
|
7514
|
+
"unchanged"
|
|
7515
|
+
]);
|
|
7516
|
+
var pngSignature = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
7517
|
+
var imageKinds = ["before", "after", "diff"];
|
|
7518
|
+
function registerVisualApprovalCommands(visual) {
|
|
7519
|
+
const hostOptions = (command2) => command2.argument("[change]", "pull or merge request number; defaults to the current branch").option("--provider <provider>", "github or gitlab; detected from the origin remote").option(
|
|
7520
|
+
"--evidence-branch <branch>",
|
|
7521
|
+
"branch configured for the review evidence",
|
|
7522
|
+
defaultEvidenceBranch
|
|
7523
|
+
).option("--json", "print deterministic schema-version-1 JSON");
|
|
7524
|
+
hostOptions(
|
|
7525
|
+
visual.command("report").description("Show the visual review of a pull or merge request and download its images")
|
|
7526
|
+
).option("--output-dir <path>", "directory for downloaded images", defaultOutputDirectory).action(async (change, options) => {
|
|
7527
|
+
try {
|
|
7528
|
+
await runVisualReport(change, options);
|
|
7529
|
+
} catch (error) {
|
|
7530
|
+
printCaptureError(error, { command: "visual.report", json: options.json });
|
|
7531
|
+
process.exitCode = 1;
|
|
7532
|
+
}
|
|
7533
|
+
});
|
|
7534
|
+
hostOptions(
|
|
7535
|
+
visual.command("approve").description("Approve the reviewed renders of one exact commit after a person accepts them")
|
|
7536
|
+
).requiredOption("--commit <sha>", "the commit whose report was reviewed").option(
|
|
7537
|
+
"--timeout <seconds>",
|
|
7538
|
+
"seconds to wait for the result; 0 returns once requested",
|
|
7539
|
+
String(defaultTimeoutSeconds)
|
|
7540
|
+
).action(async (change, options) => {
|
|
7541
|
+
try {
|
|
7542
|
+
await runVisualApprove(change, options);
|
|
7543
|
+
} catch (error) {
|
|
7544
|
+
printCaptureError(error, { command: "visual.approve", json: options.json });
|
|
7545
|
+
process.exitCode = 1;
|
|
7546
|
+
}
|
|
7547
|
+
});
|
|
7548
|
+
}
|
|
7549
|
+
async function runVisualReport(change, options) {
|
|
7550
|
+
const branch = evidenceBranch(options.evidenceBranch);
|
|
7551
|
+
const cwd = process.cwd();
|
|
7552
|
+
const host = await createHost(options.provider, cwd);
|
|
7553
|
+
const pull = await host.findChange(changeNumber(change));
|
|
7554
|
+
const review = await loadReview(host, pull, branch);
|
|
7555
|
+
const paths = await downloadImages(host, pull, review, branch, cwd, options.outputDir);
|
|
7556
|
+
const document = createReportJson(host, pull, review, paths);
|
|
7557
|
+
if (options.json) {
|
|
7558
|
+
process.stdout.write(`${JSON.stringify(document, null, 2)}
|
|
7559
|
+
`);
|
|
7560
|
+
return;
|
|
7561
|
+
}
|
|
7562
|
+
process.stdout.write(formatReport(host, document));
|
|
7563
|
+
}
|
|
7564
|
+
async function runVisualApprove(change, options) {
|
|
7565
|
+
const branch = evidenceBranch(options.evidenceBranch);
|
|
7566
|
+
const timeoutSeconds = timeout(options.timeout);
|
|
7567
|
+
if (!commitPattern.test(options.commit)) {
|
|
7568
|
+
throw reviewError(
|
|
7569
|
+
"--commit expects a commit hash of 7 to 40 lowercase hexadecimal characters."
|
|
7570
|
+
);
|
|
7571
|
+
}
|
|
7572
|
+
const host = await createHost(options.provider, process.cwd());
|
|
7573
|
+
const pull = await host.findChange(changeNumber(change));
|
|
7574
|
+
if (!pull.headSha.startsWith(options.commit)) {
|
|
7575
|
+
throw reviewError(
|
|
7576
|
+
`The current commit is ${pull.headSha.slice(0, 12)}, not ${options.commit}. Review the latest report before approving.`
|
|
7577
|
+
);
|
|
7578
|
+
}
|
|
7579
|
+
const review = await loadReview(host, pull, branch);
|
|
7580
|
+
const finish = (state, approvedBy) => {
|
|
7581
|
+
const document = {
|
|
7582
|
+
schemaVersion: 1,
|
|
7583
|
+
command: "visual.approve",
|
|
7584
|
+
provider: host.provider,
|
|
7585
|
+
number: pull.number,
|
|
7586
|
+
commit: pull.headSha,
|
|
7587
|
+
state,
|
|
7588
|
+
approvedBy
|
|
7589
|
+
};
|
|
7590
|
+
process.stdout.write(
|
|
7591
|
+
options.json ? `${JSON.stringify(document, null, 2)}
|
|
7592
|
+
` : state === "approved" ? `Approved ${pull.headSha.slice(0, 12)}${approvedBy ? ` as ${clean(approvedBy)}` : ""}.
|
|
7593
|
+
` : `Approval requested for ${pull.headSha.slice(0, 12)}.
|
|
7594
|
+
`
|
|
7595
|
+
);
|
|
7596
|
+
};
|
|
7597
|
+
if (review.state === "approved") {
|
|
7598
|
+
finish("approved", review.approvedBy);
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7601
|
+
if (review.state !== "needs-approval") throw notApprovable(host, review);
|
|
7602
|
+
const poll = await host.requestApproval(pull);
|
|
7603
|
+
if (timeoutSeconds === 0) {
|
|
7604
|
+
finish("requested", null);
|
|
7605
|
+
return;
|
|
7606
|
+
}
|
|
7607
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
7608
|
+
for (; ; ) {
|
|
7609
|
+
const outcome = await poll();
|
|
7610
|
+
if (outcome.state === "approved") {
|
|
7611
|
+
finish("approved", outcome.by);
|
|
7612
|
+
return;
|
|
7613
|
+
}
|
|
7614
|
+
if (outcome.state === "refused") {
|
|
7615
|
+
throw reviewError(`Approval not recorded: ${outcome.reason}`);
|
|
7616
|
+
}
|
|
7617
|
+
if (Date.now() >= deadline) {
|
|
7618
|
+
throw reviewError(
|
|
7619
|
+
"The approval is still running. Check its result with tileflow visual report."
|
|
7620
|
+
);
|
|
7621
|
+
}
|
|
7622
|
+
await delay(pollIntervalMs);
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
function notApprovable(host, review) {
|
|
7626
|
+
if (review.state === "closed") return reviewError(`This ${host.noun} is closed.`);
|
|
7627
|
+
if (review.state === "pending") return reviewError("This commit has no report yet.");
|
|
7628
|
+
if (review.state === "passed") return reviewError("This report has nothing to approve.");
|
|
7629
|
+
return reviewError("This report cannot be approved. Read its failure and warnings.");
|
|
7630
|
+
}
|
|
7631
|
+
async function loadReview(host, pull, branch) {
|
|
7632
|
+
const status = await host.readStatus(pull.headSha);
|
|
7633
|
+
if (!pull.open) return { approvedBy: null, report: null, state: "closed", status };
|
|
7634
|
+
const raw = await host.readEvidence(
|
|
7635
|
+
branch,
|
|
7636
|
+
`pr-${pull.number}/${pull.headSha}/report.json`,
|
|
7637
|
+
maximumReportBytes
|
|
7638
|
+
);
|
|
7639
|
+
if (!raw) return { approvedBy: null, report: null, state: "pending", status };
|
|
7640
|
+
const report = parseEvidenceReport(parseJson(raw, "evidence report"), pull);
|
|
7641
|
+
const approvedBy = await readApprover(host, pull, branch);
|
|
7642
|
+
return { approvedBy, report, state: reviewState(report, approvedBy), status };
|
|
7643
|
+
}
|
|
7644
|
+
function reviewState(report, approvedBy) {
|
|
7645
|
+
if (approvedBy) return "approved";
|
|
7646
|
+
if (report.failure || !report.approvable) return "blocked";
|
|
7647
|
+
return report.scenes.some(({ needsApproval }) => needsApproval) ? "needs-approval" : "passed";
|
|
7648
|
+
}
|
|
7649
|
+
async function readApprover(host, pull, branch) {
|
|
7650
|
+
const raw = await host.readEvidence(
|
|
7651
|
+
branch,
|
|
7652
|
+
`approvals/pr-${pull.number}.json`,
|
|
7653
|
+
maximumReportBytes
|
|
7654
|
+
);
|
|
7655
|
+
if (!raw) return null;
|
|
7656
|
+
const approval = record(parseJson(raw, "approval"), "approval");
|
|
7657
|
+
return approval.headSha === pull.headSha ? text(approval.approvedBy) : null;
|
|
7658
|
+
}
|
|
7659
|
+
function parseEvidenceReport(value, pull) {
|
|
7660
|
+
const document = record(value, "report");
|
|
7661
|
+
if (document.schemaVersion !== 1) throw reviewError("Unsupported evidence report version.");
|
|
7662
|
+
if (document.headSha !== pull.headSha || !fullShaPattern.test(pull.headSha)) {
|
|
7663
|
+
throw reviewError("The evidence report describes another commit.");
|
|
7664
|
+
}
|
|
7665
|
+
return {
|
|
7666
|
+
approvable: document.approvable === true,
|
|
7667
|
+
cliVersion: text(document.cliVersion),
|
|
7668
|
+
failure: document.failure == null ? null : text(document.failure),
|
|
7669
|
+
scenes: list(document.scenes, "scenes").map(parseScene),
|
|
7670
|
+
semantic: list(document.semantic, "semantic diff"),
|
|
7671
|
+
warnings: list(document.warnings, "warnings").map(text)
|
|
7672
|
+
};
|
|
7673
|
+
}
|
|
7674
|
+
function parseScene(value) {
|
|
7675
|
+
const scene = record(value, "scene");
|
|
7676
|
+
if (typeof scene.scene !== "string" || !scenePattern.test(scene.scene)) {
|
|
7677
|
+
throw reviewError("The evidence report has an unsupported scene name.");
|
|
7678
|
+
}
|
|
7679
|
+
if (typeof scene.status !== "string" || !sceneStatuses.has(scene.status)) {
|
|
7680
|
+
throw reviewError("The evidence report has an unknown scene status.");
|
|
7681
|
+
}
|
|
7682
|
+
const images = record(scene.images, "scene images");
|
|
7683
|
+
const noise = scene.noise === true;
|
|
7684
|
+
return {
|
|
7685
|
+
exactPixels: metric(scene.exactPixels),
|
|
7686
|
+
images: {
|
|
7687
|
+
before: images.before === true,
|
|
7688
|
+
after: images.after === true,
|
|
7689
|
+
diff: images.diff === true
|
|
7690
|
+
},
|
|
7691
|
+
map: text(scene.map),
|
|
7692
|
+
needsApproval: scene.status !== "unchanged" && !noise,
|
|
7693
|
+
noise,
|
|
7694
|
+
perceptualPixels: metric(scene.perceptualPixels),
|
|
7695
|
+
scene: scene.scene,
|
|
7696
|
+
status: scene.status,
|
|
7697
|
+
totalPixels: metric(scene.totalPixels)
|
|
7698
|
+
};
|
|
7699
|
+
}
|
|
7700
|
+
async function downloadImages(host, pull, review, branch, cwd, outputDirectory) {
|
|
7701
|
+
const paths = /* @__PURE__ */ new Map();
|
|
7702
|
+
const scenes = review.report?.scenes.filter(({ needsApproval }) => needsApproval) ?? [];
|
|
7703
|
+
if (scenes.length === 0) return paths;
|
|
7704
|
+
const boundaryPath = resolve9(cwd, outputDirectory);
|
|
7705
|
+
const directory = resolve9(boundaryPath, `${pull.number}-${pull.headSha.slice(0, 12)}`);
|
|
7706
|
+
const files = [];
|
|
7707
|
+
for (const scene of scenes) {
|
|
7708
|
+
for (const kind of imageKinds) {
|
|
7709
|
+
if (!scene.images[kind]) continue;
|
|
7710
|
+
const name = `${scene.scene}.${kind}.png`;
|
|
7711
|
+
const bytes = await host.readEvidence(
|
|
7712
|
+
branch,
|
|
7713
|
+
`pr-${pull.number}/${pull.headSha}/${name}`,
|
|
7714
|
+
maximumImageBytes
|
|
7715
|
+
);
|
|
7716
|
+
if (!bytes) throw reviewError(`The evidence is missing ${name}.`);
|
|
7717
|
+
if (!isPng(bytes)) throw reviewError(`The evidence file ${name} is not a PNG.`);
|
|
7718
|
+
const path = resolve9(directory, name);
|
|
7719
|
+
files.push({ path, source: bytes });
|
|
7720
|
+
paths.set(`${scene.scene}.${kind}`, relativePath(cwd, path));
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
if (files.length > 0) {
|
|
7724
|
+
await writeAtomicFileSet({
|
|
7725
|
+
boundaryPath,
|
|
7726
|
+
files,
|
|
7727
|
+
force: true,
|
|
7728
|
+
label: "Visual review images",
|
|
7729
|
+
managed: false
|
|
7730
|
+
});
|
|
7731
|
+
}
|
|
7732
|
+
return paths;
|
|
7733
|
+
}
|
|
7734
|
+
function createReportJson(host, pull, review, paths) {
|
|
7735
|
+
const report = review.report;
|
|
7736
|
+
return {
|
|
7737
|
+
schemaVersion: 1,
|
|
7738
|
+
command: "visual.report",
|
|
7739
|
+
provider: host.provider,
|
|
7740
|
+
number: pull.number,
|
|
7741
|
+
url: pull.url,
|
|
7742
|
+
commit: pull.headSha,
|
|
7743
|
+
state: review.state,
|
|
7744
|
+
status: review.status,
|
|
7745
|
+
approvedBy: review.approvedBy,
|
|
7746
|
+
cliVersion: report?.cliVersion ?? null,
|
|
7747
|
+
failure: report?.failure ?? null,
|
|
7748
|
+
scenes: (report?.scenes ?? []).map(({ images: _images, ...scene }) => ({
|
|
7749
|
+
...scene,
|
|
7750
|
+
beforePath: paths.get(`${scene.scene}.before`) ?? null,
|
|
7751
|
+
afterPath: paths.get(`${scene.scene}.after`) ?? null,
|
|
7752
|
+
diffPath: paths.get(`${scene.scene}.diff`) ?? null
|
|
7753
|
+
})),
|
|
7754
|
+
semantic: report?.semantic ?? [],
|
|
7755
|
+
warnings: report?.warnings ?? [],
|
|
7756
|
+
nextStep: nextStep(pull, review),
|
|
7757
|
+
untrustedFields: ["failure", "scenes[].map", "semantic", "warnings"]
|
|
7758
|
+
};
|
|
7759
|
+
}
|
|
7760
|
+
function nextStep(pull, review) {
|
|
7761
|
+
if (review.state === "pending")
|
|
7762
|
+
return "Wait for the capture to finish, then read the report again.";
|
|
7763
|
+
if (review.state !== "needs-approval") return null;
|
|
7764
|
+
return `Show the before and after images to the person responsible for this map. Only after they accept them, run: tileflow visual approve ${pull.number} --commit ${pull.headSha}`;
|
|
7765
|
+
}
|
|
7766
|
+
function formatReport(host, document) {
|
|
7767
|
+
const lines = [
|
|
7768
|
+
`${host.noun} ${document.number} at ${document.commit.slice(0, 12)}: ${document.state}`,
|
|
7769
|
+
...document.approvedBy ? [`Approved by ${clean(document.approvedBy)}`] : [],
|
|
7770
|
+
...document.failure ? [`Failure: ${clean(document.failure)}`] : []
|
|
7771
|
+
];
|
|
7772
|
+
for (const scene of document.scenes) {
|
|
7773
|
+
lines.push(` ${scene.scene}: ${scene.noise ? "renderer noise" : scene.status}`);
|
|
7774
|
+
for (const path of [scene.beforePath, scene.afterPath, scene.diffPath]) {
|
|
7775
|
+
if (path) lines.push(` ${path}`);
|
|
7776
|
+
}
|
|
7777
|
+
}
|
|
7778
|
+
for (const warning of document.warnings) lines.push(`Warning: ${clean(warning)}`);
|
|
7779
|
+
if (document.nextStep) lines.push("", document.nextStep);
|
|
7780
|
+
return `${lines.join("\n")}
|
|
7781
|
+
`;
|
|
7782
|
+
}
|
|
7783
|
+
async function createHost(requested, cwd) {
|
|
7784
|
+
const provider = requested ?? await detectProvider(cwd);
|
|
7785
|
+
if (provider === "github") return createGitHubHost(cwd);
|
|
7786
|
+
if (provider === "gitlab") return createGitLabHost(cwd);
|
|
7787
|
+
throw reviewError("--provider expects github or gitlab.");
|
|
7788
|
+
}
|
|
7789
|
+
async function detectProvider(cwd) {
|
|
7790
|
+
const remote = await runProcess("git", ["remote", "get-url", "origin"], cwd).catch(() => null);
|
|
7791
|
+
const url = remote ? Buffer.from(remote).toString("utf8") : "";
|
|
7792
|
+
if (/github/iu.test(url)) return "github";
|
|
7793
|
+
if (/gitlab/iu.test(url)) return "gitlab";
|
|
7794
|
+
throw reviewError("Could not tell the Git host from the origin remote. Pass --provider.");
|
|
7795
|
+
}
|
|
7796
|
+
function createGitHubHost(cwd) {
|
|
7797
|
+
const repository = "repos/{owner}/{repo}";
|
|
7798
|
+
const gh = (args) => runProcess("gh", args, cwd);
|
|
7799
|
+
const api = async (path, ...args) => parseJson(await gh(["api", ...args, path]), "GitHub response");
|
|
7800
|
+
const readStatus = async (sha) => {
|
|
7801
|
+
const combined = record(
|
|
7802
|
+
await api(`${repository}/commits/${sha}/status?per_page=100`),
|
|
7803
|
+
"GitHub status"
|
|
7804
|
+
);
|
|
7805
|
+
const status = list(combined.statuses, "GitHub statuses").map((item) => record(item, "GitHub status")).find(({ context }) => context === statusName);
|
|
7806
|
+
return status ? { description: text(status.description), state: text(status.state) } : null;
|
|
7807
|
+
};
|
|
7808
|
+
return {
|
|
7809
|
+
provider: "github",
|
|
7810
|
+
noun: "pull request",
|
|
7811
|
+
async findChange(number) {
|
|
7812
|
+
const pull = record(
|
|
7813
|
+
parseJson(
|
|
7814
|
+
await gh([
|
|
7815
|
+
"pr",
|
|
7816
|
+
"view",
|
|
7817
|
+
...number === void 0 ? [] : [String(number)],
|
|
7818
|
+
"--json",
|
|
7819
|
+
"number,url,headRefOid,state"
|
|
7820
|
+
]),
|
|
7821
|
+
"GitHub pull request"
|
|
7822
|
+
),
|
|
7823
|
+
"GitHub pull request"
|
|
7824
|
+
);
|
|
7825
|
+
return {
|
|
7826
|
+
headSha: text(pull.headRefOid),
|
|
7827
|
+
number: Number(pull.number),
|
|
7828
|
+
open: pull.state === "OPEN",
|
|
7829
|
+
url: text(pull.url)
|
|
7830
|
+
};
|
|
7831
|
+
},
|
|
7832
|
+
async readEvidence(branch, path, maximumBytes) {
|
|
7833
|
+
return readOptional2(
|
|
7834
|
+
() => gh([
|
|
7835
|
+
"api",
|
|
7836
|
+
"-H",
|
|
7837
|
+
"Accept: application/vnd.github.raw+json",
|
|
7838
|
+
`${repository}/contents/${encodeURI(path)}?ref=${encodeURIComponent(branch)}`
|
|
7839
|
+
]),
|
|
7840
|
+
maximumBytes
|
|
7841
|
+
);
|
|
7842
|
+
},
|
|
7843
|
+
readStatus,
|
|
7844
|
+
async requestApproval(change) {
|
|
7845
|
+
const comment = record(
|
|
7846
|
+
await api(
|
|
7847
|
+
`${repository}/issues/${change.number}/comments`,
|
|
7848
|
+
"-X",
|
|
7849
|
+
"POST",
|
|
7850
|
+
"-f",
|
|
7851
|
+
`body=/tileflow approve ${change.headSha}`
|
|
7852
|
+
),
|
|
7853
|
+
"GitHub comment"
|
|
7854
|
+
);
|
|
7855
|
+
const query = `since=${encodeURIComponent(text(comment.created_at))}&per_page=100`;
|
|
7856
|
+
return async () => {
|
|
7857
|
+
const status = await readStatus(change.headSha);
|
|
7858
|
+
const approved = /^Approved by (.+)$/u.exec(status?.description ?? "");
|
|
7859
|
+
if (approved) return { state: "approved", by: approved[1] ?? null };
|
|
7860
|
+
const replies = list(
|
|
7861
|
+
await api(`${repository}/issues/${change.number}/comments?${query}`),
|
|
7862
|
+
"GitHub comments"
|
|
7863
|
+
).map((item) => record(item, "GitHub comment"));
|
|
7864
|
+
const refusal = replies.find(
|
|
7865
|
+
({ body, id }) => Number(id) > Number(comment.id) && isRefusal(body)
|
|
7866
|
+
);
|
|
7867
|
+
return refusal ? { state: "refused", reason: refusalReason(refusal.body) } : { state: "pending" };
|
|
7868
|
+
};
|
|
7869
|
+
}
|
|
7870
|
+
};
|
|
7871
|
+
}
|
|
7872
|
+
function createGitLabHost(cwd) {
|
|
7873
|
+
const project = "projects/:id";
|
|
7874
|
+
const glab = (args) => runProcess("glab", args, cwd);
|
|
7875
|
+
const api = async (path, ...args) => parseJson(await glab(["api", ...args, path]), "GitLab response");
|
|
7876
|
+
const change = (value) => {
|
|
7877
|
+
const mergeRequest = record(value, "GitLab merge request");
|
|
7878
|
+
return {
|
|
7879
|
+
headSha: text(mergeRequest.sha),
|
|
7880
|
+
number: Number(mergeRequest.iid),
|
|
7881
|
+
open: mergeRequest.state === "opened",
|
|
7882
|
+
url: text(mergeRequest.web_url)
|
|
7883
|
+
};
|
|
7884
|
+
};
|
|
7885
|
+
const refusalNote = async (number) => {
|
|
7886
|
+
const notes = list(
|
|
7887
|
+
await api(
|
|
7888
|
+
`${project}/merge_requests/${number}/notes?sort=desc&order_by=created_at&per_page=20`
|
|
7889
|
+
),
|
|
7890
|
+
"GitLab notes"
|
|
7891
|
+
).map((item) => record(item, "GitLab note"));
|
|
7892
|
+
const note = notes.find(({ body }) => isRefusal(body));
|
|
7893
|
+
return note ? refusalReason(note.body) : "the approve job failed. Read its log.";
|
|
7894
|
+
};
|
|
7895
|
+
return {
|
|
7896
|
+
provider: "gitlab",
|
|
7897
|
+
noun: "merge request",
|
|
7898
|
+
async findChange(number) {
|
|
7899
|
+
if (number !== void 0) return change(await api(`${project}/merge_requests/${number}`));
|
|
7900
|
+
const branch = Buffer.from(await runProcess("git", ["branch", "--show-current"], cwd)).toString("utf8").trim();
|
|
7901
|
+
const open4 = list(
|
|
7902
|
+
await api(
|
|
7903
|
+
`${project}/merge_requests?state=opened&source_branch=${encodeURIComponent(branch)}`
|
|
7904
|
+
),
|
|
7905
|
+
"GitLab merge requests"
|
|
7906
|
+
);
|
|
7907
|
+
if (open4.length === 0) throw reviewError(`No open merge request for branch ${branch}.`);
|
|
7908
|
+
return change(open4[0]);
|
|
7909
|
+
},
|
|
7910
|
+
async readEvidence(branch, path, maximumBytes) {
|
|
7911
|
+
return readOptional2(
|
|
7912
|
+
() => glab([
|
|
7913
|
+
"api",
|
|
7914
|
+
`${project}/repository/files/${encodeURIComponent(path)}/raw?ref=${encodeURIComponent(branch)}`
|
|
7915
|
+
]),
|
|
7916
|
+
maximumBytes
|
|
7917
|
+
);
|
|
7918
|
+
},
|
|
7919
|
+
async readStatus(sha) {
|
|
7920
|
+
const statuses = list(
|
|
7921
|
+
await api(`${project}/repository/commits/${sha}/statuses?all=true&per_page=100`),
|
|
7922
|
+
"GitLab statuses"
|
|
7923
|
+
).map((item) => record(item, "GitLab status")).filter(({ name }) => name === statusName).sort((left, right) => Number(right.id) - Number(left.id));
|
|
7924
|
+
const latest = statuses[0];
|
|
7925
|
+
return latest ? { description: text(latest.description), state: text(latest.status) } : null;
|
|
7926
|
+
},
|
|
7927
|
+
async requestApproval(pull) {
|
|
7928
|
+
const pipelines = list(
|
|
7929
|
+
await api(`${project}/merge_requests/${pull.number}/pipelines`),
|
|
7930
|
+
"GitLab pipelines"
|
|
7931
|
+
).map((item) => record(item, "GitLab pipeline"));
|
|
7932
|
+
const pipeline = pipelines.find(({ sha }) => sha === pull.headSha);
|
|
7933
|
+
if (!pipeline) throw reviewError("No pipeline exists for this commit.");
|
|
7934
|
+
const jobs = list(
|
|
7935
|
+
await api(`${project}/pipelines/${Number(pipeline.id)}/jobs?per_page=100`),
|
|
7936
|
+
"GitLab jobs"
|
|
7937
|
+
).map((item) => record(item, "GitLab job")).filter(({ name }) => name === approveJobName).sort((left, right) => Number(right.id) - Number(left.id));
|
|
7938
|
+
const job = jobs[0];
|
|
7939
|
+
if (!job) throw reviewError(`Pipeline ${Number(pipeline.id)} has no ${approveJobName} job.`);
|
|
7940
|
+
const action = job.status === "manual" ? "play" : "retry";
|
|
7941
|
+
const started = record(
|
|
7942
|
+
await api(`${project}/jobs/${Number(job.id)}/${action}`, "-X", "POST"),
|
|
7943
|
+
"GitLab job"
|
|
7944
|
+
);
|
|
7945
|
+
return async () => {
|
|
7946
|
+
const current = record(await api(`${project}/jobs/${Number(started.id)}`), "GitLab job");
|
|
7947
|
+
if (current.status === "success") {
|
|
7948
|
+
const user = current.user ? record(current.user, "GitLab user") : null;
|
|
7949
|
+
return { state: "approved", by: user ? text(user.username) : null };
|
|
7950
|
+
}
|
|
7951
|
+
if (current.status === "failed" || current.status === "canceled") {
|
|
7952
|
+
return { state: "refused", reason: await refusalNote(pull.number) };
|
|
7953
|
+
}
|
|
7954
|
+
return { state: "pending" };
|
|
7955
|
+
};
|
|
7956
|
+
}
|
|
7957
|
+
};
|
|
7958
|
+
}
|
|
7959
|
+
var refusalPrefix = "Approval not recorded: ";
|
|
7960
|
+
var isRefusal = (body) => typeof body === "string" && body.startsWith(refusalPrefix);
|
|
7961
|
+
var refusalReason = (body) => text(body).slice(refusalPrefix.length);
|
|
7962
|
+
async function readOptional2(read, maximumBytes) {
|
|
7963
|
+
try {
|
|
7964
|
+
const bytes = await read();
|
|
7965
|
+
if (bytes.length > maximumBytes) throw reviewError("An evidence file is too large.");
|
|
7966
|
+
return bytes;
|
|
7967
|
+
} catch (error) {
|
|
7968
|
+
if (error instanceof HostNotFoundError) return null;
|
|
7969
|
+
throw error;
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
var HostNotFoundError = class extends Error {
|
|
7973
|
+
};
|
|
7974
|
+
function runProcess(command2, args, cwd) {
|
|
7975
|
+
return new Promise((resolvePromise, reject) => {
|
|
7976
|
+
execFile(
|
|
7977
|
+
command2,
|
|
7978
|
+
args,
|
|
7979
|
+
{ cwd, encoding: "buffer", maxBuffer: maximumHostOutputBytes },
|
|
7980
|
+
(error, stdout, stderr) => {
|
|
7981
|
+
if (!error) {
|
|
7982
|
+
resolvePromise(stdout);
|
|
7983
|
+
return;
|
|
7984
|
+
}
|
|
7985
|
+
if (error.code === "ENOENT") {
|
|
7986
|
+
reject(reviewError(`${command2} is not installed. Install it and sign in with it first.`));
|
|
7987
|
+
return;
|
|
7988
|
+
}
|
|
7989
|
+
const detail = Buffer.from(stderr).toString("utf8").replace(/\s+/gu, " ").trim();
|
|
7990
|
+
reject(
|
|
7991
|
+
/HTTP 404|404 Not Found/u.test(detail) ? new HostNotFoundError(detail) : reviewError(`${command2} failed: ${detail.slice(0, maximumText) || error.message}`)
|
|
7992
|
+
);
|
|
7993
|
+
}
|
|
7994
|
+
);
|
|
7995
|
+
});
|
|
7996
|
+
}
|
|
7997
|
+
function changeNumber(value) {
|
|
7998
|
+
if (value === void 0) return void 0;
|
|
7999
|
+
if (!/^[1-9][0-9]{0,9}$/u.test(value)) {
|
|
8000
|
+
throw reviewError("The pull or merge request must be a positive number.");
|
|
8001
|
+
}
|
|
8002
|
+
return Number(value);
|
|
8003
|
+
}
|
|
8004
|
+
function evidenceBranch(value) {
|
|
8005
|
+
if (!branchPattern.test(value) || value.includes("..")) {
|
|
8006
|
+
throw reviewError("--evidence-branch is not a valid branch name.");
|
|
8007
|
+
}
|
|
8008
|
+
return value;
|
|
8009
|
+
}
|
|
8010
|
+
function timeout(value) {
|
|
8011
|
+
if (!/^[0-9]{1,5}$/u.test(value)) throw reviewError("--timeout expects whole seconds.");
|
|
8012
|
+
return Number(value);
|
|
8013
|
+
}
|
|
8014
|
+
function isPng(bytes) {
|
|
8015
|
+
return pngSignature.every((byte, index) => bytes[index] === byte);
|
|
8016
|
+
}
|
|
8017
|
+
function parseJson(bytes, label) {
|
|
8018
|
+
try {
|
|
8019
|
+
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
8020
|
+
} catch {
|
|
8021
|
+
throw reviewError(`The ${label} is not valid JSON.`);
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
8024
|
+
function record(value, label) {
|
|
8025
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
8026
|
+
throw reviewError(`The ${label} is invalid.`);
|
|
8027
|
+
}
|
|
8028
|
+
return value;
|
|
8029
|
+
}
|
|
8030
|
+
function list(value, label) {
|
|
8031
|
+
if (!Array.isArray(value)) throw reviewError(`The ${label} list is invalid.`);
|
|
8032
|
+
return value;
|
|
8033
|
+
}
|
|
8034
|
+
function text(value) {
|
|
8035
|
+
return String(value ?? "").slice(0, maximumText);
|
|
8036
|
+
}
|
|
8037
|
+
function metric(value) {
|
|
8038
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
8039
|
+
}
|
|
8040
|
+
function clean(value) {
|
|
8041
|
+
return value.replace(/\p{Cc}/gu, " ");
|
|
8042
|
+
}
|
|
8043
|
+
function reviewError(message) {
|
|
8044
|
+
return new TileflowVisualReviewError2(message);
|
|
8045
|
+
}
|
|
8046
|
+
|
|
8047
|
+
// src/visual-command.ts
|
|
7299
8048
|
function registerVisualCommands(program2, dependencies, canonical = false) {
|
|
7300
8049
|
const visual = program2.command(canonical ? "compare" : "visual").description("Review deliberate variants, external references or approved baselines");
|
|
7301
8050
|
registerVisualCompareCommand(visual, dependencies, canonical ? "styles" : "compare");
|
|
@@ -7332,6 +8081,7 @@ function registerVisualCommands(program2, dependencies, canonical = false) {
|
|
|
7332
8081
|
process.exitCode = 1;
|
|
7333
8082
|
}
|
|
7334
8083
|
});
|
|
8084
|
+
registerVisualApprovalCommands(visual);
|
|
7335
8085
|
return visual;
|
|
7336
8086
|
}
|
|
7337
8087
|
function createTileflowVisualDiffJson(comparisons, cwd) {
|
|
@@ -7366,14 +8116,14 @@ async function runVisualAnalyze(scene, options) {
|
|
|
7366
8116
|
validateVisualSelection([scene]);
|
|
7367
8117
|
const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
|
|
7368
8118
|
const cwd = process.cwd();
|
|
7369
|
-
const referencePath =
|
|
8119
|
+
const referencePath = resolve10(cwd, options.reference);
|
|
7370
8120
|
const referencePng = await readVisualReference(referencePath);
|
|
7371
8121
|
const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
|
|
7372
8122
|
if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
|
|
7373
8123
|
const outputDirectory = await resolveVisualDirectory(cwd, options.outputDir, "output");
|
|
7374
|
-
const actualPath =
|
|
7375
|
-
const managedDiffPath =
|
|
7376
|
-
const reportPath =
|
|
8124
|
+
const actualPath = resolve10(outputDirectory.path, `${scene}.actual.png`);
|
|
8125
|
+
const managedDiffPath = resolve10(outputDirectory.path, `${scene}.diff.png`);
|
|
8126
|
+
const reportPath = resolve10(outputDirectory.path, `${scene}.analysis.json`);
|
|
7377
8127
|
assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
|
|
7378
8128
|
const controller = installSignalAbortController();
|
|
7379
8129
|
const session = createTileflowCaptureSession3({
|
|
@@ -7462,15 +8212,15 @@ async function runVisualDiff(scenes, options) {
|
|
|
7462
8212
|
capture,
|
|
7463
8213
|
files.kind === "complete" ? files.baseline : void 0
|
|
7464
8214
|
);
|
|
7465
|
-
const actualPath =
|
|
7466
|
-
const diffPath = comparison.diffPng ?
|
|
8215
|
+
const actualPath = resolve10(outputDirectory.path, `${capture.scene}.actual.png`);
|
|
8216
|
+
const diffPath = comparison.diffPng ? resolve10(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
|
|
7467
8217
|
prepared.push({
|
|
7468
8218
|
capture,
|
|
7469
8219
|
comparison,
|
|
7470
8220
|
baselinePath: files.pngPath,
|
|
7471
8221
|
actualPath,
|
|
7472
8222
|
diffPath,
|
|
7473
|
-
reportPath:
|
|
8223
|
+
reportPath: resolve10(outputDirectory.path, `${capture.scene}.visual.json`)
|
|
7474
8224
|
});
|
|
7475
8225
|
}
|
|
7476
8226
|
const document = createTileflowVisualDiffJson(prepared, cwd);
|
|
@@ -7490,7 +8240,7 @@ async function runVisualDiff(scenes, options) {
|
|
|
7490
8240
|
}
|
|
7491
8241
|
]),
|
|
7492
8242
|
removePaths: prepared.flatMap(
|
|
7493
|
-
(item) => item.diffPath ? [] : [
|
|
8243
|
+
(item) => item.diffPath ? [] : [resolve10(outputDirectory.path, `${item.capture.scene}.diff.png`)]
|
|
7494
8244
|
)
|
|
7495
8245
|
});
|
|
7496
8246
|
if (options.json) {
|
|
@@ -7611,7 +8361,7 @@ async function resolvePreviousBaseline(capture, files) {
|
|
|
7611
8361
|
}
|
|
7612
8362
|
}
|
|
7613
8363
|
async function readBaselineFiles(directory, scene) {
|
|
7614
|
-
const pngPath =
|
|
8364
|
+
const pngPath = resolve10(directory.path, `${scene}.png`);
|
|
7615
8365
|
const receiptPath = captureReceiptPath(pngPath);
|
|
7616
8366
|
const [png, receipt] = await Promise.all([
|
|
7617
8367
|
readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits2.maximumPngBytes),
|
|
@@ -7671,12 +8421,12 @@ async function readVisualReference(path) {
|
|
|
7671
8421
|
return new Uint8Array(await readFile5(path));
|
|
7672
8422
|
}
|
|
7673
8423
|
async function resolveVisualDirectory(cwd, value, kind) {
|
|
7674
|
-
const path =
|
|
8424
|
+
const path = resolve10(cwd, value);
|
|
7675
8425
|
if (path === cwd || path === parse2(path).root) {
|
|
7676
8426
|
throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
|
|
7677
8427
|
}
|
|
7678
8428
|
if (kind === "baseline") {
|
|
7679
|
-
for (const managed of [
|
|
8429
|
+
for (const managed of [resolve10(cwd, ".tileflow/captures"), resolve10(cwd, ".tileflow/diffs")]) {
|
|
7680
8430
|
if (pathsOverlap2(path, managed)) {
|
|
7681
8431
|
throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
|
|
7682
8432
|
}
|
|
@@ -7712,7 +8462,7 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
|
|
|
7712
8462
|
}
|
|
7713
8463
|
}
|
|
7714
8464
|
function portablePathIdentity2(path) {
|
|
7715
|
-
return
|
|
8465
|
+
return resolve10(path).normalize("NFC").toLowerCase();
|
|
7716
8466
|
}
|
|
7717
8467
|
function isInside2(parent, child) {
|
|
7718
8468
|
const path = relative5(parent, child);
|
|
@@ -7724,8 +8474,8 @@ function isMissingFile4(error) {
|
|
|
7724
8474
|
|
|
7725
8475
|
// src/workbench-service.ts
|
|
7726
8476
|
import { opendir, stat } from "fs/promises";
|
|
7727
|
-
import { resolve as
|
|
7728
|
-
import { z as
|
|
8477
|
+
import { resolve as resolve11 } from "path";
|
|
8478
|
+
import { z as z5 } from "zod";
|
|
7729
8479
|
import {
|
|
7730
8480
|
compareTileflowCapturesForReview as compareTileflowCapturesForReview3,
|
|
7731
8481
|
createTileflowCaptureSession as createTileflowCaptureSession4,
|
|
@@ -7738,22 +8488,22 @@ import {
|
|
|
7738
8488
|
} from "@tileflow/core";
|
|
7739
8489
|
import { resolveTileflowPreview } from "@tileflow/dev/preview";
|
|
7740
8490
|
var prefix = "/__workbench";
|
|
7741
|
-
var captureInput =
|
|
7742
|
-
side:
|
|
7743
|
-
generation:
|
|
8491
|
+
var captureInput = z5.object({
|
|
8492
|
+
side: z5.enum(["left", "right"]).default("left"),
|
|
8493
|
+
generation: z5.number().int().nonnegative(),
|
|
7744
8494
|
name: tileflowCaptureSceneNameSchema2,
|
|
7745
8495
|
definition: tileflowCaptureSceneSchema3
|
|
7746
8496
|
}).strict();
|
|
7747
|
-
var compareInput =
|
|
7748
|
-
var deleteInput =
|
|
7749
|
-
var contextInput =
|
|
7750
|
-
client:
|
|
7751
|
-
side:
|
|
7752
|
-
generation:
|
|
7753
|
-
context:
|
|
8497
|
+
var compareInput = z5.object({ before: z5.string().regex(/^[a-f0-9]{64}$/u), after: z5.string().regex(/^[a-f0-9]{64}$/u) }).strict();
|
|
8498
|
+
var deleteInput = z5.object({ id: z5.string().regex(/^[a-f0-9]{64}$/u) }).strict();
|
|
8499
|
+
var contextInput = z5.object({
|
|
8500
|
+
client: z5.string().regex(/^[a-zA-Z0-9-]{1,80}$/u),
|
|
8501
|
+
side: z5.enum(["left", "right"]),
|
|
8502
|
+
generation: z5.number().int(),
|
|
8503
|
+
context: z5.record(z5.string(), z5.unknown())
|
|
7754
8504
|
}).strict();
|
|
7755
8505
|
function createWorkbenchService(options) {
|
|
7756
|
-
const directory =
|
|
8506
|
+
const directory = resolve11(options.cwd, captureRecordDirectory);
|
|
7757
8507
|
const contexts = /* @__PURE__ */ new Map();
|
|
7758
8508
|
let captureSession = options.captureSession;
|
|
7759
8509
|
let pending;
|
|
@@ -7825,7 +8575,7 @@ function createWorkbenchService(options) {
|
|
|
7825
8575
|
break;
|
|
7826
8576
|
}
|
|
7827
8577
|
if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) continue;
|
|
7828
|
-
const info = await stat(
|
|
8578
|
+
const info = await stat(resolve11(directory, entry.name));
|
|
7829
8579
|
entries.push({ id: entry.name.slice(0, -5), modified: info.mtimeMs });
|
|
7830
8580
|
}
|
|
7831
8581
|
} catch (error) {
|
|
@@ -7836,15 +8586,15 @@ function createWorkbenchService(options) {
|
|
|
7836
8586
|
const warnings = [];
|
|
7837
8587
|
for (const entry of entries.slice(offset, offset + 20)) {
|
|
7838
8588
|
try {
|
|
7839
|
-
const
|
|
8589
|
+
const record2 = await readCaptureRecord(entry.id, directory);
|
|
7840
8590
|
records.push({
|
|
7841
8591
|
id: entry.id,
|
|
7842
|
-
scene:
|
|
7843
|
-
map:
|
|
7844
|
-
theme:
|
|
7845
|
-
definition:
|
|
7846
|
-
styleSha256:
|
|
7847
|
-
warnings:
|
|
8592
|
+
scene: record2.capture.scene,
|
|
8593
|
+
map: record2.capture.map,
|
|
8594
|
+
theme: record2.capture.theme,
|
|
8595
|
+
definition: record2.definition,
|
|
8596
|
+
styleSha256: record2.capture.styleSha256,
|
|
8597
|
+
warnings: record2.capture.warnings,
|
|
7848
8598
|
savedAt: new Date(entry.modified).toISOString(),
|
|
7849
8599
|
imageUrl: `${prefix}/captures/${entry.id}.png`
|
|
7850
8600
|
});
|
|
@@ -7918,9 +8668,9 @@ function createWorkbenchService(options) {
|
|
|
7918
8668
|
}
|
|
7919
8669
|
return {
|
|
7920
8670
|
url: prefix,
|
|
7921
|
-
async handle(
|
|
7922
|
-
const url = new URL(
|
|
7923
|
-
if (url.origin !== options.origin ||
|
|
8671
|
+
async handle(request2) {
|
|
8672
|
+
const url = new URL(request2.url);
|
|
8673
|
+
if (url.origin !== options.origin || request2.headers.has("origin") && request2.headers.get("origin") !== options.origin || request2.headers.get("sec-fetch-site") === "cross-site")
|
|
7924
8674
|
return json(
|
|
7925
8675
|
{ code: "ORIGIN_MISMATCH", message: "Use Inspector from its local origin." },
|
|
7926
8676
|
403
|
|
@@ -7928,7 +8678,7 @@ function createWorkbenchService(options) {
|
|
|
7928
8678
|
if (closed) return json({ code: "SESSION_CLOSED", message: "Inspector has stopped." }, 503);
|
|
7929
8679
|
const path = url.pathname.slice(prefix.length);
|
|
7930
8680
|
try {
|
|
7931
|
-
if (
|
|
8681
|
+
if (request2.method === "GET") {
|
|
7932
8682
|
if (path === "" || path === "/")
|
|
7933
8683
|
return json({
|
|
7934
8684
|
capabilities: [
|
|
@@ -7964,13 +8714,13 @@ function createWorkbenchService(options) {
|
|
|
7964
8714
|
});
|
|
7965
8715
|
}
|
|
7966
8716
|
if (path === "/captures") {
|
|
7967
|
-
const offset =
|
|
8717
|
+
const offset = z5.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
|
|
7968
8718
|
return json(await history(offset));
|
|
7969
8719
|
}
|
|
7970
8720
|
const image = /^\/captures\/([a-f0-9]{64})\.png$/u.exec(path);
|
|
7971
8721
|
if (image) {
|
|
7972
|
-
const
|
|
7973
|
-
return new Response(Buffer.from(
|
|
8722
|
+
const record2 = await readCaptureRecord(image[1], directory);
|
|
8723
|
+
return new Response(Buffer.from(record2.capture.png), {
|
|
7974
8724
|
headers: {
|
|
7975
8725
|
"Content-Type": "image/png",
|
|
7976
8726
|
"Cache-Control": "no-store",
|
|
@@ -7980,14 +8730,14 @@ function createWorkbenchService(options) {
|
|
|
7980
8730
|
}
|
|
7981
8731
|
return json({ code: "NOT_FOUND", message: "Unknown Inspector endpoint." }, 404);
|
|
7982
8732
|
}
|
|
7983
|
-
if (
|
|
8733
|
+
if (request2.method !== "POST")
|
|
7984
8734
|
return json({ code: "METHOD_NOT_ALLOWED", message: "Use GET or POST." }, 405);
|
|
7985
|
-
if (
|
|
8735
|
+
if (request2.headers.get("x-tileflow-workbench") !== "1" || request2.headers.get("content-type")?.split(";")[0] !== "application/json")
|
|
7986
8736
|
return json(
|
|
7987
8737
|
{ code: "INVALID_REQUEST", message: "Send JSON with X-Tileflow-Workbench: 1." },
|
|
7988
8738
|
400
|
|
7989
8739
|
);
|
|
7990
|
-
const body = await readBody(
|
|
8740
|
+
const body = await readBody(request2);
|
|
7991
8741
|
if (path === "/context") {
|
|
7992
8742
|
const { client, ...value } = contextInput.parse(body);
|
|
7993
8743
|
if (!options.sides[value.side]) throw new Error("Unknown Inspector side.");
|
|
@@ -8025,8 +8775,8 @@ function createWorkbenchService(options) {
|
|
|
8025
8775
|
}
|
|
8026
8776
|
};
|
|
8027
8777
|
}
|
|
8028
|
-
async function readBody(
|
|
8029
|
-
const reader =
|
|
8778
|
+
async function readBody(request2) {
|
|
8779
|
+
const reader = request2.body?.getReader();
|
|
8030
8780
|
if (!reader) throw new Error("Missing JSON body.");
|
|
8031
8781
|
const chunks = [];
|
|
8032
8782
|
let bytes = 0;
|
|
@@ -8100,11 +8850,11 @@ registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
|
8100
8850
|
registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
8101
8851
|
registerModelCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
8102
8852
|
program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
|
|
8103
|
-
const configPath =
|
|
8104
|
-
const ignorePath =
|
|
8853
|
+
const configPath = resolve12(process.cwd(), defaultConfigPath);
|
|
8854
|
+
const ignorePath = resolve12(process.cwd(), ".gitignore");
|
|
8105
8855
|
if (existsSync(configPath) && !options.force) {
|
|
8106
8856
|
logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
|
|
8107
|
-
|
|
8857
|
+
printNextSteps2([`Run ${command("tileflow init --force")} to replace it.`]);
|
|
8108
8858
|
return;
|
|
8109
8859
|
}
|
|
8110
8860
|
await writeFile2(configPath, starterConfig(), "utf8");
|
|
@@ -8117,9 +8867,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
8117
8867
|
}
|
|
8118
8868
|
logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
|
|
8119
8869
|
printKeyValue("Path", pathLabel(configPath));
|
|
8120
|
-
printKeyValue("Map",
|
|
8121
|
-
|
|
8122
|
-
`Install ${
|
|
8870
|
+
printKeyValue("Map", pc8.bold("madrid"));
|
|
8871
|
+
printNextSteps2([
|
|
8872
|
+
`Install ${pc8.cyan("@tileflow/core")} and ${pc8.cyan("@tileflow/maps")} in this project if needed.`,
|
|
8123
8873
|
`Preview locally with ${command("tileflow preview")}.`,
|
|
8124
8874
|
`Validate the config with ${command("tileflow validate")}.`
|
|
8125
8875
|
]);
|
|
@@ -8185,7 +8935,7 @@ program.command("whoami").description("Show the authenticated Tileflow account")
|
|
|
8185
8935
|
return;
|
|
8186
8936
|
}
|
|
8187
8937
|
logError(message);
|
|
8188
|
-
|
|
8938
|
+
printNextSteps2([`Run ${command("tileflow login")} to authorize this machine.`]);
|
|
8189
8939
|
process.exitCode = 1;
|
|
8190
8940
|
return;
|
|
8191
8941
|
}
|
|
@@ -8245,7 +8995,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
|
|
|
8245
8995
|
return;
|
|
8246
8996
|
}
|
|
8247
8997
|
logError(`Invalid validation target: ${options.target}`);
|
|
8248
|
-
|
|
8998
|
+
printNextSteps2([
|
|
8249
8999
|
`Use ${command("tileflow validate --target local")} or ${command("tileflow validate --target hosted")}.`
|
|
8250
9000
|
]);
|
|
8251
9001
|
process.exitCode = 1;
|
|
@@ -8398,7 +9148,7 @@ program.command("build").description("Generate static Tileflow styles").option("
|
|
|
8398
9148
|
})
|
|
8399
9149
|
);
|
|
8400
9150
|
logSuccess("Built Tileflow artifacts.");
|
|
8401
|
-
printKeyValue("Output", pathLabel(
|
|
9151
|
+
printKeyValue("Output", pathLabel(resolve12(process.cwd(), options.out)));
|
|
8402
9152
|
}
|
|
8403
9153
|
);
|
|
8404
9154
|
program.command("preview").description("Open Inspector with inspection and optional comparison").option("-c, --config <path>", "config path", defaultConfigPath).option("--against-config <path>", "second config for live comparison in Inspector").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("--no-open", "start Inspector without opening the browser").option("--scene <name>", "preview one committed standalone map scene").option(
|
|
@@ -8631,7 +9381,7 @@ program.command("preview").description("Open Inspector with inspection and optio
|
|
|
8631
9381
|
await new Promise((resolveListening, rejectListening) => {
|
|
8632
9382
|
const createdServer = serve(
|
|
8633
9383
|
{
|
|
8634
|
-
fetch: (
|
|
9384
|
+
fetch: (request2) => (renderer === "web" ? redirectTileflowDevNavigation(request2, origin) : void 0) ?? fetch2(request2),
|
|
8635
9385
|
hostname: host,
|
|
8636
9386
|
port
|
|
8637
9387
|
},
|
|
@@ -8716,7 +9466,7 @@ addMapSetupOptions(
|
|
|
8716
9466
|
);
|
|
8717
9467
|
if (result) {
|
|
8718
9468
|
logSuccess(
|
|
8719
|
-
`${result.changed ? "Published" : "Unchanged"} ${
|
|
9469
|
+
`${result.changed ? "Published" : "Unchanged"} ${pc8.bold(result.mapName)} web + native-v1 (v${result.version}).`
|
|
8720
9470
|
);
|
|
8721
9471
|
printKeyValue("Manifest", pathLabel(result.manifestPath));
|
|
8722
9472
|
printKeyValue("Native", link(result.nativeManifestUrl));
|
|
@@ -8791,7 +9541,7 @@ addMapSetupOptions(
|
|
|
8791
9541
|
if (mapsWithHostedFontBundles.length > 0) {
|
|
8792
9542
|
logError("Hosted deploy does not yet support package-owned web fonts.");
|
|
8793
9543
|
printKeyValue("Maps", mapsWithHostedFontBundles.join(", "));
|
|
8794
|
-
|
|
9544
|
+
printNextSteps2(["Use self-hosted delivery or an explicit public glyph provider."]);
|
|
8795
9545
|
process.exitCode = 1;
|
|
8796
9546
|
return;
|
|
8797
9547
|
}
|
|
@@ -8866,8 +9616,8 @@ addMapSetupOptions(
|
|
|
8866
9616
|
if (!options.mapId) {
|
|
8867
9617
|
const deployedMapId2 = existingManifest?.maps[mapNames[0]]?.mapId;
|
|
8868
9618
|
if (deployedMapId2) {
|
|
8869
|
-
logError(`${
|
|
8870
|
-
|
|
9619
|
+
logError(`${pc8.bold(mapNames[0])} is already deployed to ${deployedMapId2}.`);
|
|
9620
|
+
printNextSteps2([
|
|
8871
9621
|
`Deploy it again with ${command(`tileflow deploy --map-id ${deployedMapId2}`)}.`
|
|
8872
9622
|
]);
|
|
8873
9623
|
process.exitCode = 1;
|
|
@@ -8896,7 +9646,7 @@ addMapSetupOptions(
|
|
|
8896
9646
|
);
|
|
8897
9647
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
8898
9648
|
logSuccess(
|
|
8899
|
-
`${action} icon package ${
|
|
9649
|
+
`${action} icon package ${pc8.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes4(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
|
|
8900
9650
|
);
|
|
8901
9651
|
hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
|
|
8902
9652
|
}
|
|
@@ -9059,7 +9809,7 @@ addMapSetupOptions(
|
|
|
9059
9809
|
}
|
|
9060
9810
|
const themeNames = Object.keys(themeStyles).sort();
|
|
9061
9811
|
logInfo(
|
|
9062
|
-
`Deploying compiled map ${
|
|
9812
|
+
`Deploying compiled map ${pc8.bold(mapName)} (${plural(themeNames.length, "theme")}).`
|
|
9063
9813
|
);
|
|
9064
9814
|
const response = await publishHostedStyle(
|
|
9065
9815
|
api,
|
|
@@ -9142,14 +9892,14 @@ addMapSetupOptions(
|
|
|
9142
9892
|
};
|
|
9143
9893
|
const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
|
|
9144
9894
|
if (body.changed === false) {
|
|
9145
|
-
logSuccess(`Unchanged ${
|
|
9895
|
+
logSuccess(`Unchanged ${pc8.bold(mapName)}${versionLabel}.`);
|
|
9146
9896
|
} else {
|
|
9147
|
-
logSuccess(`Published ${
|
|
9897
|
+
logSuccess(`Published ${pc8.bold(mapName)}${versionLabel}.`);
|
|
9148
9898
|
}
|
|
9149
9899
|
for (const teamModel of body.teamModels ?? []) {
|
|
9150
9900
|
if (teamModel.sources !== "differ") continue;
|
|
9151
9901
|
logWarning(
|
|
9152
|
-
`3D model ${
|
|
9902
|
+
`3D model ${pc8.bold(teamModel.model)} draws its published version ${teamModel.version}, which was not prepared from this project's files. Run \`tileflow models publish\` to publish them.`
|
|
9153
9903
|
);
|
|
9154
9904
|
}
|
|
9155
9905
|
}
|
|
@@ -9165,7 +9915,7 @@ addMapSetupOptions(
|
|
|
9165
9915
|
printDeployedMaps(deployedMaps);
|
|
9166
9916
|
const deployedMapId = Object.values(deployedMaps)[0]?.mapId;
|
|
9167
9917
|
if (deployedMapId) {
|
|
9168
|
-
|
|
9918
|
+
printNextSteps2([
|
|
9169
9919
|
...createdMapId ? [`Deploy later changes with ${command(`tileflow deploy --map-id ${createdMapId}`)}.`] : [],
|
|
9170
9920
|
`Check hosted state with ${command(`tileflow status --map-id ${deployedMapId}`)}.`
|
|
9171
9921
|
]);
|
|
@@ -9219,6 +9969,16 @@ program.command("status").description("Show deployed compiled styles").addOption
|
|
|
9219
9969
|
}
|
|
9220
9970
|
printMapStatus(status, api.apiUrl);
|
|
9221
9971
|
});
|
|
9972
|
+
registerDeploymentCommands(program, {
|
|
9973
|
+
resolveApi: (options, behavior) => {
|
|
9974
|
+
const source = resolveDeploySource(process.env);
|
|
9975
|
+
return requireApiOptions(options, {
|
|
9976
|
+
...behavior,
|
|
9977
|
+
allowStoredCredential: allowsStoredDeployCredential(source),
|
|
9978
|
+
assertExplicitKeyMap: true
|
|
9979
|
+
});
|
|
9980
|
+
}
|
|
9981
|
+
});
|
|
9222
9982
|
var iconsCommand = program.command("icons").description("Inspect managed Tileflow icons");
|
|
9223
9983
|
registerIconLockCommands(program, { defaultApiUrl, defaultConfigPath, loadAuthConfig });
|
|
9224
9984
|
registerIconListCommand(iconsCommand, { defaultConfigPath });
|
|
@@ -9271,6 +10031,8 @@ registerMapCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
|
9271
10031
|
if (isRootCliVersionRequest(process.argv.slice(2))) {
|
|
9272
10032
|
process.stdout.write(`${getCliVersion()}
|
|
9273
10033
|
`);
|
|
10034
|
+
} else if (isRestoreJsonCommand(process.argv.slice(2))) {
|
|
10035
|
+
await parseRestoreJson(program);
|
|
9274
10036
|
} else {
|
|
9275
10037
|
const structuredCommand = process.argv[2] ?? "tileflow";
|
|
9276
10038
|
const structuredErrors = process.argv.includes("--json") && [
|
|
@@ -9284,14 +10046,7 @@ if (isRootCliVersionRequest(process.argv.slice(2))) {
|
|
|
9284
10046
|
"semantic-diff",
|
|
9285
10047
|
"language"
|
|
9286
10048
|
].includes(structuredCommand);
|
|
9287
|
-
if (structuredErrors)
|
|
9288
|
-
const configureStructuredErrors = (command2) => {
|
|
9289
|
-
command2.exitOverride().configureOutput({ writeErr: () => {
|
|
9290
|
-
} });
|
|
9291
|
-
for (const child of command2.commands) configureStructuredErrors(child);
|
|
9292
|
-
};
|
|
9293
|
-
configureStructuredErrors(program);
|
|
9294
|
-
}
|
|
10049
|
+
if (structuredErrors) configureJsonErrors(program);
|
|
9295
10050
|
program.parseAsync().catch((error) => {
|
|
9296
10051
|
if (structuredErrors) {
|
|
9297
10052
|
if (typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 0)
|
|
@@ -9469,7 +10224,7 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
9469
10224
|
await new Promise((resolveListening, rejectListening) => {
|
|
9470
10225
|
const createdServer = serve(
|
|
9471
10226
|
{
|
|
9472
|
-
fetch: (
|
|
10227
|
+
fetch: (request2) => redirectTileflowDevNavigation(request2, origin) ?? fetch2(request2),
|
|
9473
10228
|
hostname: address.host,
|
|
9474
10229
|
port: address.port
|
|
9475
10230
|
},
|
|
@@ -9514,8 +10269,8 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
9514
10269
|
}
|
|
9515
10270
|
function comparisonCaptureConfigArgument(configPath) {
|
|
9516
10271
|
const cwd = realpathSync(process.cwd());
|
|
9517
|
-
const requestedConfigPath =
|
|
9518
|
-
const defaultRequestedPath =
|
|
10272
|
+
const requestedConfigPath = resolve12(process.cwd(), configPath);
|
|
10273
|
+
const defaultRequestedPath = resolve12(process.cwd(), defaultConfigPath);
|
|
9519
10274
|
const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
|
|
9520
10275
|
const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
|
|
9521
10276
|
if (absoluteConfigPath === absoluteDefaultPath) return void 0;
|
|
@@ -9569,7 +10324,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
9569
10324
|
throw new Error("The dashboard returned an unexpected Tileflow API origin.");
|
|
9570
10325
|
}
|
|
9571
10326
|
const previousSession = authState.kind === "sessions" ? authState.config.sessions[apiUrl] : void 0;
|
|
9572
|
-
printKeyValue("Code",
|
|
10327
|
+
printKeyValue("Code", pc8.bold(authorization.userCode));
|
|
9573
10328
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
9574
10329
|
if (options.noBrowser) {
|
|
9575
10330
|
logMuted("Open the URL above to continue.");
|
|
@@ -9579,7 +10334,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
9579
10334
|
output: process.stdout
|
|
9580
10335
|
});
|
|
9581
10336
|
try {
|
|
9582
|
-
await readline.question(`${
|
|
10337
|
+
await readline.question(`${pc8.cyan("?")} Press Enter to open Tileflow Dashboard...`);
|
|
9583
10338
|
openBrowser(authorization.verificationUriComplete);
|
|
9584
10339
|
} finally {
|
|
9585
10340
|
readline.close();
|
|
@@ -9644,7 +10399,7 @@ function openBrowser(url, errorsToStderr = false) {
|
|
|
9644
10399
|
});
|
|
9645
10400
|
}
|
|
9646
10401
|
async function writeDeployManifest(manifestPath, manifest) {
|
|
9647
|
-
const outputPath =
|
|
10402
|
+
const outputPath = resolve12(process.cwd(), manifestPath);
|
|
9648
10403
|
await writeAtomicFile({
|
|
9649
10404
|
boundaryPath: dirname12(outputPath),
|
|
9650
10405
|
force: true,
|
|
@@ -9696,7 +10451,7 @@ function selectManagedDeployMaps(mapNames, options) {
|
|
|
9696
10451
|
"This managed target requires an explicit map because the config contains multiple maps."
|
|
9697
10452
|
);
|
|
9698
10453
|
printKeyValue("Maps", mapNames.join(", "));
|
|
9699
|
-
|
|
10454
|
+
printNextSteps2([
|
|
9700
10455
|
`Retry with ${command(`tileflow deploy ${options.mapId ? `--map-id ${options.mapId} ` : ""}--map ${mapNames[0]}`)}.`
|
|
9701
10456
|
]);
|
|
9702
10457
|
process.exitCode = 1;
|
|
@@ -9705,7 +10460,7 @@ function selectManagedDeployMaps(mapNames, options) {
|
|
|
9705
10460
|
function validateMapSetupTarget(options, source) {
|
|
9706
10461
|
const fail = (message, steps = []) => {
|
|
9707
10462
|
logError(message);
|
|
9708
|
-
|
|
10463
|
+
printNextSteps2(steps);
|
|
9709
10464
|
process.exitCode = 1;
|
|
9710
10465
|
return false;
|
|
9711
10466
|
};
|
|
@@ -9745,7 +10500,7 @@ function validateDeployManifestMapNames(mapNames) {
|
|
|
9745
10500
|
async function loadExistingDeployManifest(manifestPath, options) {
|
|
9746
10501
|
let source;
|
|
9747
10502
|
try {
|
|
9748
|
-
source = await readFile6(
|
|
10503
|
+
source = await readFile6(resolve12(process.cwd(), manifestPath), "utf8");
|
|
9749
10504
|
} catch (error) {
|
|
9750
10505
|
if (error.code === "ENOENT") return null;
|
|
9751
10506
|
throw new Error("Existing Tileflow manifest could not be read.");
|
|
@@ -9777,64 +10532,59 @@ async function loadExistingDeployManifest(manifestPath, options) {
|
|
|
9777
10532
|
return manifest;
|
|
9778
10533
|
}
|
|
9779
10534
|
async function requireApiOptions(options, behavior = {}) {
|
|
10535
|
+
const refuse = (code, message, steps = []) => {
|
|
10536
|
+
if (behavior.onFailure) behavior.onFailure(code, message);
|
|
10537
|
+
else if (!behavior.silent) {
|
|
10538
|
+
logError(message);
|
|
10539
|
+
if (steps.length) printNextSteps2(steps);
|
|
10540
|
+
}
|
|
10541
|
+
process.exitCode = 1;
|
|
10542
|
+
return null;
|
|
10543
|
+
};
|
|
9780
10544
|
const apiUrl = normalizeApiOrigin(options.apiUrl ?? defaultApiUrl);
|
|
9781
10545
|
const requestedMapId = options.mapId;
|
|
9782
10546
|
if (!requestedMapId || !/^map_[A-Za-z0-9_-]{16}$/u.test(requestedMapId)) {
|
|
9783
|
-
|
|
9784
|
-
process.exitCode = 1;
|
|
9785
|
-
return null;
|
|
10547
|
+
return refuse("invalid_map_id", "Managed Map ID is invalid.");
|
|
9786
10548
|
}
|
|
9787
10549
|
if (options.apiKey !== void 0) {
|
|
9788
10550
|
if (!isHostedApiKey(options.apiKey)) {
|
|
9789
|
-
|
|
9790
|
-
process.exitCode = 1;
|
|
9791
|
-
return null;
|
|
10551
|
+
return refuse("invalid_api_key", "Tileflow API key is invalid.");
|
|
9792
10552
|
}
|
|
9793
10553
|
if (behavior.assertExplicitKeyMap) {
|
|
9794
10554
|
const profile = await validateApiKey(apiUrl, options.apiKey);
|
|
9795
10555
|
if (!profile.ok) {
|
|
9796
|
-
|
|
9797
|
-
process.exitCode = 1;
|
|
9798
|
-
return null;
|
|
10556
|
+
return refuse("api_key_validation_failed", profile.error);
|
|
9799
10557
|
}
|
|
9800
10558
|
if (!apiProfileCanTargetMap(profile.value, requestedMapId)) {
|
|
9801
|
-
|
|
9802
|
-
process.exitCode = 1;
|
|
9803
|
-
return null;
|
|
10559
|
+
return refuse("api_key_map_mismatch", "This API key belongs to another Map.");
|
|
9804
10560
|
}
|
|
9805
10561
|
}
|
|
9806
10562
|
return { apiKey: options.apiKey, apiUrl, mapId: requestedMapId };
|
|
9807
10563
|
}
|
|
9808
10564
|
const allowStoredCredential = behavior.allowStoredCredential !== false && allowsStoredDeployCredential(resolveDeploySource(process.env));
|
|
9809
10565
|
if (!allowStoredCredential) {
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
10566
|
+
return refuse(
|
|
10567
|
+
"api_key_required",
|
|
10568
|
+
"CI requires an explicit Tileflow credential with access to the target Map.",
|
|
10569
|
+
[
|
|
10570
|
+
`Set ${pc8.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
|
|
9814
10571
|
"The saved personal account session is never used in CI."
|
|
9815
|
-
]
|
|
9816
|
-
|
|
9817
|
-
process.exitCode = 1;
|
|
9818
|
-
return null;
|
|
10572
|
+
]
|
|
10573
|
+
);
|
|
9819
10574
|
}
|
|
9820
10575
|
let config;
|
|
9821
10576
|
try {
|
|
9822
10577
|
config = await loadAuthConfig();
|
|
9823
10578
|
} catch (error) {
|
|
9824
|
-
|
|
9825
|
-
process.exitCode = 1;
|
|
9826
|
-
return null;
|
|
10579
|
+
return refuse("auth_state_unavailable", safeAuthError(error));
|
|
9827
10580
|
}
|
|
9828
10581
|
const resolvedSession = resolveAccountSession(config, apiUrl);
|
|
9829
10582
|
if (resolvedSession.kind !== "selected") {
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
)
|
|
9834
|
-
|
|
9835
|
-
}
|
|
9836
|
-
process.exitCode = 1;
|
|
9837
|
-
return null;
|
|
10583
|
+
return refuse(
|
|
10584
|
+
`account_session_${resolvedSession.kind}`,
|
|
10585
|
+
resolvedSession.kind === "expired" ? "The Tileflow account session has expired." : "No Tileflow account session is saved.",
|
|
10586
|
+
[`Run ${command("tileflow login")} to authorize this machine.`]
|
|
10587
|
+
);
|
|
9838
10588
|
}
|
|
9839
10589
|
const capability = await requestMapCapability(
|
|
9840
10590
|
resolvedSession.session,
|
|
@@ -9842,9 +10592,7 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
9842
10592
|
behavior.capabilityScopes ?? ["status:read"]
|
|
9843
10593
|
);
|
|
9844
10594
|
if (!capability.ok) {
|
|
9845
|
-
|
|
9846
|
-
process.exitCode = 1;
|
|
9847
|
-
return null;
|
|
10595
|
+
return refuse("map_capability_failed", capability.error);
|
|
9848
10596
|
}
|
|
9849
10597
|
return { apiKey: capability.capability, apiUrl, mapId: capability.mapId };
|
|
9850
10598
|
}
|
|
@@ -9879,72 +10627,72 @@ function printCliError(error) {
|
|
|
9879
10627
|
}
|
|
9880
10628
|
const message = error instanceof Error ? error.message : "Command failed.";
|
|
9881
10629
|
if (message.includes("Cannot find module '@tileflow/core'")) {
|
|
9882
|
-
logError(`Cannot load ${
|
|
9883
|
-
|
|
9884
|
-
`Install ${
|
|
10630
|
+
logError(`Cannot load ${pc8.cyan("@tileflow/core")} from this project.`);
|
|
10631
|
+
printNextSteps2([
|
|
10632
|
+
`Install ${pc8.cyan("@tileflow/core")} in this project.`,
|
|
9885
10633
|
`Run the command again from the project root.`
|
|
9886
10634
|
]);
|
|
9887
10635
|
return;
|
|
9888
10636
|
}
|
|
9889
10637
|
if (message.includes("Cannot find module '@tileflow/maps'")) {
|
|
9890
|
-
logError(`Cannot load ${
|
|
9891
|
-
|
|
9892
|
-
`Install ${
|
|
10638
|
+
logError(`Cannot load ${pc8.cyan("@tileflow/maps")} from this project.`);
|
|
10639
|
+
printNextSteps2([
|
|
10640
|
+
`Install ${pc8.cyan("@tileflow/maps")} in this project.`,
|
|
9893
10641
|
`Run the command again from the project root.`
|
|
9894
10642
|
]);
|
|
9895
10643
|
return;
|
|
9896
10644
|
}
|
|
9897
10645
|
logError(message.split("\n")[0] ?? message);
|
|
9898
10646
|
if (message.includes(defaultConfigPath) || message.includes("config")) {
|
|
9899
|
-
|
|
10647
|
+
printNextSteps2([`Run ${command("tileflow validate")} to check the config.`]);
|
|
9900
10648
|
}
|
|
9901
10649
|
}
|
|
9902
10650
|
function logSuccess(message) {
|
|
9903
|
-
console.log(`${
|
|
10651
|
+
console.log(`${pc8.green("\u2713")} ${message}`);
|
|
9904
10652
|
}
|
|
9905
10653
|
function logError(message) {
|
|
9906
|
-
console.log(`${
|
|
10654
|
+
console.log(`${pc8.red("\u2715")} ${message}`);
|
|
9907
10655
|
}
|
|
9908
10656
|
function logWarning(message) {
|
|
9909
|
-
console.log(`${
|
|
10657
|
+
console.log(`${pc8.yellow("!")} ${message}`);
|
|
9910
10658
|
}
|
|
9911
10659
|
function logInfo(message) {
|
|
9912
|
-
console.log(`${
|
|
10660
|
+
console.log(`${pc8.cyan("i")} ${message}`);
|
|
9913
10661
|
}
|
|
9914
10662
|
function logMuted(message) {
|
|
9915
|
-
console.log(
|
|
10663
|
+
console.log(pc8.gray(message));
|
|
9916
10664
|
}
|
|
9917
10665
|
function printTitle(title, subtitle) {
|
|
9918
|
-
console.log(
|
|
10666
|
+
console.log(pc8.bold(title));
|
|
9919
10667
|
if (subtitle) {
|
|
9920
|
-
console.log(
|
|
10668
|
+
console.log(pc8.gray(subtitle));
|
|
9921
10669
|
}
|
|
9922
10670
|
}
|
|
9923
10671
|
function printKeyValue(label, value) {
|
|
9924
|
-
console.log(`${
|
|
10672
|
+
console.log(`${pc8.gray(`${label}:`.padEnd(13))} ${value}`);
|
|
9925
10673
|
}
|
|
9926
10674
|
function printChecks(items) {
|
|
9927
10675
|
for (const item of items) {
|
|
9928
|
-
console.log(` ${
|
|
10676
|
+
console.log(` ${pc8.green("\u2713")} ${item}`);
|
|
9929
10677
|
}
|
|
9930
10678
|
}
|
|
9931
|
-
function
|
|
10679
|
+
function printNextSteps2(steps) {
|
|
9932
10680
|
if (steps.length === 0) return;
|
|
9933
10681
|
console.log(`
|
|
9934
|
-
${
|
|
10682
|
+
${pc8.bold("Next steps")}`);
|
|
9935
10683
|
for (const step of steps) {
|
|
9936
|
-
console.log(` ${
|
|
10684
|
+
console.log(` ${pc8.cyan("\u2022")} ${step}`);
|
|
9937
10685
|
}
|
|
9938
10686
|
}
|
|
9939
10687
|
function printValidationErrors(messages) {
|
|
9940
10688
|
logError("Tileflow config has errors.");
|
|
9941
10689
|
for (const message of messages) {
|
|
9942
|
-
const path = message.path ?
|
|
9943
|
-
console.log(` ${
|
|
10690
|
+
const path = message.path ? pc8.cyan(message.path) : pc8.cyan("(root)");
|
|
10691
|
+
console.log(` ${pc8.red("\u2715")} ${path} ${pc8.gray("-")} ${message.message}`);
|
|
9944
10692
|
}
|
|
9945
10693
|
}
|
|
9946
10694
|
function printAccountDetails(session) {
|
|
9947
|
-
printKeyValue("Account",
|
|
10695
|
+
printKeyValue("Account", pc8.bold(session.account.name));
|
|
9948
10696
|
printKeyValue("Email", session.account.email);
|
|
9949
10697
|
printKeyValue("API", link(session.apiOrigin));
|
|
9950
10698
|
printKeyValue("Expires", formatDate(session.expiresAt));
|
|
@@ -9973,11 +10721,11 @@ function printDeployedMaps(maps) {
|
|
|
9973
10721
|
const entries = Object.entries(maps);
|
|
9974
10722
|
if (entries.length === 0) return;
|
|
9975
10723
|
console.log(`
|
|
9976
|
-
${
|
|
10724
|
+
${pc8.bold("Maps")}`);
|
|
9977
10725
|
for (const [name, map] of entries) {
|
|
9978
10726
|
for (const [themeName, theme] of Object.entries(map.themes)) {
|
|
9979
10727
|
const label = `${name}/${themeName}`;
|
|
9980
|
-
console.log(` ${
|
|
10728
|
+
console.log(` ${pc8.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
|
|
9981
10729
|
}
|
|
9982
10730
|
}
|
|
9983
10731
|
}
|
|
@@ -9995,13 +10743,13 @@ function plural(count, singular, pluralForm = `${singular}s`) {
|
|
|
9995
10743
|
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
9996
10744
|
}
|
|
9997
10745
|
function command(value) {
|
|
9998
|
-
return
|
|
10746
|
+
return pc8.cyan(value);
|
|
9999
10747
|
}
|
|
10000
10748
|
function link(value) {
|
|
10001
|
-
return
|
|
10749
|
+
return pc8.cyan(value);
|
|
10002
10750
|
}
|
|
10003
10751
|
function pathLabel(value) {
|
|
10004
|
-
return
|
|
10752
|
+
return pc8.cyan(value);
|
|
10005
10753
|
}
|
|
10006
10754
|
function starterConfig() {
|
|
10007
10755
|
return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
|
|
@@ -10047,15 +10795,15 @@ function printHostedCompatibilityIssues(issues) {
|
|
|
10047
10795
|
}
|
|
10048
10796
|
function printMapStatus(status, apiUrl) {
|
|
10049
10797
|
printTitle("Tileflow status");
|
|
10050
|
-
printKeyValue("Map",
|
|
10798
|
+
printKeyValue("Map", pc8.bold(status.mapId));
|
|
10051
10799
|
console.log(`
|
|
10052
|
-
${
|
|
10800
|
+
${pc8.bold("Styles")}`);
|
|
10053
10801
|
if (status.styles.length === 0) {
|
|
10054
10802
|
logMuted(" No styles deployed.");
|
|
10055
10803
|
}
|
|
10056
10804
|
for (const style of status.styles) {
|
|
10057
10805
|
console.log(
|
|
10058
|
-
` ${
|
|
10806
|
+
` ${pc8.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes4(style.size).padStart(10)} ${pc8.gray(formatDate(style.uploaded))}`
|
|
10059
10807
|
);
|
|
10060
10808
|
}
|
|
10061
10809
|
}
|