tileflow 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +2 -1
- package/dist/index.js +697 -236
- package/docs/command-families.md +4 -0
- package/docs/hosted-authentication-and-deploy.md +15 -2
- package/docs/local-visual-feedback.md +2 -2
- package/docs/map-authoring.md +7 -0
- package/docs/team-3d-models.md +36 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -7,9 +7,9 @@ import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
|
|
|
7
7
|
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
8
8
|
import { readFile as readFile7, writeFile as writeFile2 } from "fs/promises";
|
|
9
9
|
import { hostname } from "os";
|
|
10
|
-
import { dirname as
|
|
10
|
+
import { dirname as dirname13, isAbsolute as isAbsolute5, relative as relative6, resolve as resolve12, sep as sep5 } from "path";
|
|
11
11
|
import { createInterface } from "readline/promises";
|
|
12
|
-
import
|
|
12
|
+
import pc7 from "picocolors";
|
|
13
13
|
import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
|
|
14
14
|
import {
|
|
15
15
|
auditTileflowMapThemeValues as auditTileflowMapThemeValues2,
|
|
@@ -33,12 +33,12 @@ import {
|
|
|
33
33
|
writeTileflowBuildArtifacts
|
|
34
34
|
} from "@tileflow/dev/artifacts";
|
|
35
35
|
import {
|
|
36
|
-
assertValidTileflowConfig as
|
|
36
|
+
assertValidTileflowConfig as assertValidTileflowConfig4,
|
|
37
37
|
defaultTileflowApiUrl,
|
|
38
|
-
defaultTileflowConfigPath,
|
|
38
|
+
defaultTileflowConfigPath as defaultTileflowConfigPath2,
|
|
39
39
|
defaultTileflowManifestPath,
|
|
40
40
|
getTileflowMapNames as getTileflowMapNames5,
|
|
41
|
-
loadTileflowConfigWithInputs as
|
|
41
|
+
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs5,
|
|
42
42
|
TileflowValidationError as TileflowValidationError4
|
|
43
43
|
} from "@tileflow/dev/config";
|
|
44
44
|
import {
|
|
@@ -3572,7 +3572,7 @@ async function readCoordinatesInput(input, signal) {
|
|
|
3572
3572
|
async function readStandardInput(signal) {
|
|
3573
3573
|
const chunks = [];
|
|
3574
3574
|
let length = 0;
|
|
3575
|
-
return new Promise((
|
|
3575
|
+
return new Promise((resolve13, reject) => {
|
|
3576
3576
|
const finish = (result) => {
|
|
3577
3577
|
process.stdin.removeListener("data", onData);
|
|
3578
3578
|
process.stdin.removeListener("end", onEnd);
|
|
@@ -3594,7 +3594,7 @@ async function readStandardInput(signal) {
|
|
|
3594
3594
|
}
|
|
3595
3595
|
chunks.push(bytes);
|
|
3596
3596
|
};
|
|
3597
|
-
const onEnd = () => finish(() =>
|
|
3597
|
+
const onEnd = () => finish(() => resolve13(decodeInput(Buffer.concat(chunks))));
|
|
3598
3598
|
const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
|
|
3599
3599
|
if (signal.aborted) {
|
|
3600
3600
|
onAbort();
|
|
@@ -3922,7 +3922,7 @@ function isRecord(value) {
|
|
|
3922
3922
|
|
|
3923
3923
|
// src/describe-command.ts
|
|
3924
3924
|
function registerDescribeCommand(program2) {
|
|
3925
|
-
program2.command("describe").description("Discover domains and query the generated authoring schema without loading a map").argument("[topic]", "domain name, expressions, or schema").option(
|
|
3925
|
+
program2.command("describe").description("Discover domains and query the generated authoring schema without loading a map").argument("[topic]", "domain name, themes, expressions, or schema").option(
|
|
3926
3926
|
"--ref <pointer>",
|
|
3927
3927
|
"exact local schema reference, such as #/$defs/TileflowRoadsModuleOptions"
|
|
3928
3928
|
).option("--path <pointer>", "JSON Pointer within the selected topic or schema", "").option("--limit <count>", "maximum returned tree entries (1\u2013500)", "80").option("--depth <count>", "maximum returned tree depth (1\u201312)", "4").option("--json", "print deterministic bounded JSON").action(
|
|
@@ -3933,7 +3933,7 @@ function registerDescribeCommand(program2) {
|
|
|
3933
3933
|
const manifest = tileflowAuthoringManifest2;
|
|
3934
3934
|
let value;
|
|
3935
3935
|
let schemaRef;
|
|
3936
|
-
if (options.ref || topic && topic !== "expressions") {
|
|
3936
|
+
if (options.ref || topic && topic !== "expressions" && topic !== "themes") {
|
|
3937
3937
|
const schema = await loadPackagedSchema();
|
|
3938
3938
|
const domain = manifest.domains.find((entry) => entry.name === topic);
|
|
3939
3939
|
if (topic && topic !== "schema" && !domain)
|
|
@@ -3946,6 +3946,8 @@ function registerDescribeCommand(program2) {
|
|
|
3946
3946
|
value = selectJsonPointer(schema, schemaRef.replace(/^#/u, ""));
|
|
3947
3947
|
} else if (topic === "expressions") {
|
|
3948
3948
|
value = manifest.expressions;
|
|
3949
|
+
} else if (topic === "themes") {
|
|
3950
|
+
value = manifest.themes;
|
|
3949
3951
|
} else {
|
|
3950
3952
|
value = {
|
|
3951
3953
|
domains: manifest.domains.map(({ name, api, optionsSchemaRef }) => ({
|
|
@@ -3953,7 +3955,7 @@ function registerDescribeCommand(program2) {
|
|
|
3953
3955
|
api,
|
|
3954
3956
|
schemaRef: optionsSchemaRef
|
|
3955
3957
|
})),
|
|
3956
|
-
topics: ["expressions", "schema"],
|
|
3958
|
+
topics: ["expressions", "schema", "themes"],
|
|
3957
3959
|
commands: ["describe", "inspect", "capture", "compare", "preview"]
|
|
3958
3960
|
};
|
|
3959
3961
|
}
|
|
@@ -4150,7 +4152,18 @@ var hostedStyleDeploymentResponseSchema = z2.object({
|
|
|
4150
4152
|
styleUrl: publicHttpUrlSchema
|
|
4151
4153
|
}).strict()
|
|
4152
4154
|
).refine((themes) => Object.keys(themes).length > 0),
|
|
4153
|
-
version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional()
|
|
4155
|
+
version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
4156
|
+
/**
|
|
4157
|
+
* The Team 3D models the map places, each at its published current version, and whether that
|
|
4158
|
+
* version was prepared from the same glTF binaries as the map's own files.
|
|
4159
|
+
*/
|
|
4160
|
+
teamModels: z2.array(
|
|
4161
|
+
z2.object({
|
|
4162
|
+
model: z2.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u).max(64),
|
|
4163
|
+
version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
4164
|
+
sources: z2.enum(["match", "differ", "unknown"])
|
|
4165
|
+
}).strict()
|
|
4166
|
+
).max(256).optional()
|
|
4154
4167
|
}).strict();
|
|
4155
4168
|
var hostedIconPackageResponseSchema = z2.object({
|
|
4156
4169
|
changed: z2.boolean().optional(),
|
|
@@ -4261,7 +4274,7 @@ async function startDeviceAuthorization(appUrl, body, options = {}) {
|
|
|
4261
4274
|
async function pollDeviceToken(appUrl, authorization, input, options = {}) {
|
|
4262
4275
|
const expiresAt = Date.now() + authorization.expiresIn * 1e3;
|
|
4263
4276
|
const intervalMs = Math.max(authorization.interval, 1) * 1e3;
|
|
4264
|
-
const wait = input.sleep ?? ((milliseconds) => new Promise((
|
|
4277
|
+
const wait = input.sleep ?? ((milliseconds) => new Promise((resolve13) => setTimeout(resolve13, milliseconds)));
|
|
4265
4278
|
while (Date.now() < expiresAt) {
|
|
4266
4279
|
await wait(intervalMs);
|
|
4267
4280
|
const response = await requestHostedJson(
|
|
@@ -4561,7 +4574,7 @@ async function withHostedResponse(apiUrl, path, init, options, consume) {
|
|
|
4561
4574
|
}
|
|
4562
4575
|
function raceWithAbort(operation, signal) {
|
|
4563
4576
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
4564
|
-
return new Promise((
|
|
4577
|
+
return new Promise((resolve13, reject) => {
|
|
4565
4578
|
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
4566
4579
|
const onAbort = () => {
|
|
4567
4580
|
cleanup();
|
|
@@ -4571,7 +4584,7 @@ function raceWithAbort(operation, signal) {
|
|
|
4571
4584
|
operation.then(
|
|
4572
4585
|
(value) => {
|
|
4573
4586
|
cleanup();
|
|
4574
|
-
|
|
4587
|
+
resolve13(value);
|
|
4575
4588
|
},
|
|
4576
4589
|
(error) => {
|
|
4577
4590
|
cleanup();
|
|
@@ -4773,22 +4786,6 @@ function inspectTileflowHostedCompatibility(project, styles) {
|
|
|
4773
4786
|
});
|
|
4774
4787
|
}
|
|
4775
4788
|
}
|
|
4776
|
-
const own = /* @__PURE__ */ new Set();
|
|
4777
|
-
for (const style of Object.values(themes)) {
|
|
4778
|
-
const models = style?.metadata?.["tileflow:buildings"]?.models;
|
|
4779
|
-
if (!Array.isArray(models)) continue;
|
|
4780
|
-
for (const entry of models) {
|
|
4781
|
-
const { model, url } = entry ?? {};
|
|
4782
|
-
if (typeof url === "string" && url.includes(`models/${mapName}/`) && typeof model === "string")
|
|
4783
|
-
own.add(model);
|
|
4784
|
-
}
|
|
4785
|
-
}
|
|
4786
|
-
if (own.size > 0)
|
|
4787
|
-
issues.push({
|
|
4788
|
-
map: mapName,
|
|
4789
|
-
message: `Hosted deploy does not take a map's own 3D building models yet. Map ${mapName} places ${[...own].sort(compareCodeUnits5).join(", ")}; keep the map local or remove them from buildings.threeDimensional.models.`,
|
|
4790
|
-
path: `maps.${mapName}.modules.buildings.threeDimensional.models`
|
|
4791
|
-
});
|
|
4792
4789
|
}
|
|
4793
4790
|
return issues;
|
|
4794
4791
|
}
|
|
@@ -4797,20 +4794,50 @@ function cloneStyle(style) {
|
|
|
4797
4794
|
}
|
|
4798
4795
|
|
|
4799
4796
|
// src/model-assets.ts
|
|
4800
|
-
import {
|
|
4797
|
+
import { tileflowTeamModelUrl } from "@tileflow/core";
|
|
4798
|
+
import {
|
|
4799
|
+
prepareTileflowMapTreeModels
|
|
4800
|
+
} from "@tileflow/dev/icons";
|
|
4801
4801
|
async function prepareModelAssets(project, options) {
|
|
4802
|
-
const
|
|
4802
|
+
const prepared = { assets: {}, buildings: {}, identities: {} };
|
|
4803
4803
|
for (const [name, map] of Object.entries(project.maps)) {
|
|
4804
4804
|
if (!map.models?.length) continue;
|
|
4805
|
-
const
|
|
4805
|
+
const models = await prepareTileflowMapTreeModels(map.models, {
|
|
4806
4806
|
...options,
|
|
4807
4807
|
cwd: process.cwd(),
|
|
4808
4808
|
configPath: `maps.${name}.models`
|
|
4809
4809
|
});
|
|
4810
|
-
assets[name] = { models:
|
|
4810
|
+
prepared.assets[name] = { models: models.models };
|
|
4811
|
+
if (Object.keys(models.buildings).length > 0) prepared.buildings[name] = models.buildings;
|
|
4812
|
+
if (models.identities.length > 0) prepared.identities[name] = models.identities;
|
|
4813
|
+
}
|
|
4814
|
+
return prepared;
|
|
4815
|
+
}
|
|
4816
|
+
function mapAssetsWithBuildingModels(prepared, target) {
|
|
4817
|
+
const assets = { ...prepared.assets };
|
|
4818
|
+
for (const [mapName, buildings] of Object.entries(prepared.buildings)) {
|
|
4819
|
+
const base = target.hosted ? "" : target.localBase(mapName).replace(/\/+$/u, "");
|
|
4820
|
+
assets[mapName] = {
|
|
4821
|
+
...assets[mapName],
|
|
4822
|
+
buildingModels: Object.fromEntries(
|
|
4823
|
+
Object.entries(buildings).map(([id, model]) => [
|
|
4824
|
+
id,
|
|
4825
|
+
target.hosted ? tileflowTeamModelUrl(id) : `${base}/${model.fileName}`
|
|
4826
|
+
])
|
|
4827
|
+
)
|
|
4828
|
+
};
|
|
4811
4829
|
}
|
|
4812
4830
|
return assets;
|
|
4813
4831
|
}
|
|
4832
|
+
function hostedModelSourceIdentities(prepared, mapName) {
|
|
4833
|
+
const buildings = prepared.buildings[mapName] ?? {};
|
|
4834
|
+
const identities = (prepared.identities[mapName] ?? []).filter(({ id }) => !(id in buildings));
|
|
4835
|
+
return identities.length > 0 ? identities : void 0;
|
|
4836
|
+
}
|
|
4837
|
+
function teamModelSources(prepared, mapName) {
|
|
4838
|
+
const buildings = Object.entries(prepared.buildings[mapName] ?? {});
|
|
4839
|
+
return buildings.length > 0 ? Object.fromEntries(buildings.map(([id, model]) => [id, model.sources])) : void 0;
|
|
4840
|
+
}
|
|
4814
4841
|
|
|
4815
4842
|
// src/hosted-native-deploy.ts
|
|
4816
4843
|
function failure(message) {
|
|
@@ -4850,13 +4877,17 @@ async function prepareNativeHostedDeploy(options) {
|
|
|
4850
4877
|
const iconPackage = binding ? compiledIcons.packages.find((entry) => entry.contentHash === binding.packageHash) : void 0;
|
|
4851
4878
|
const sprite = binding ? `${apiOrigin}/sprites/preflight/${binding.packageHash}/sprite` : void 0;
|
|
4852
4879
|
const modelAssets = await prepareModelAssets(project, { baseDirectory, target: "hosted" });
|
|
4880
|
+
if (modelAssets.buildings[mapName])
|
|
4881
|
+
throw failure(
|
|
4882
|
+
"A deployment with --with-native does not take a map's own 3D building models yet. Deploy without --with-native, or remove them from the map's models directories."
|
|
4883
|
+
);
|
|
4853
4884
|
const mapAssets = {
|
|
4854
|
-
...modelAssets,
|
|
4885
|
+
...modelAssets.assets,
|
|
4855
4886
|
...Object.fromEntries(
|
|
4856
4887
|
compiledIcons.bindings.map((entry) => [
|
|
4857
4888
|
entry.mapName,
|
|
4858
4889
|
{
|
|
4859
|
-
...modelAssets[entry.mapName],
|
|
4890
|
+
...modelAssets.assets[entry.mapName],
|
|
4860
4891
|
icons: {
|
|
4861
4892
|
ids: entry.iconIds,
|
|
4862
4893
|
sprite: `${apiOrigin}/sprites/preflight/${entry.packageHash}/sprite`
|
|
@@ -6886,6 +6917,421 @@ async function readCurrentPins(contents) {
|
|
|
6886
6917
|
return (await parseTileflowIconsLockfile(contents)).sets;
|
|
6887
6918
|
}
|
|
6888
6919
|
|
|
6920
|
+
// src/model-command.ts
|
|
6921
|
+
import { lstat as lstat5 } from "fs/promises";
|
|
6922
|
+
import { basename as basename4, dirname as dirname11, relative as relative4, resolve as resolve8 } from "path";
|
|
6923
|
+
import pc4 from "picocolors";
|
|
6924
|
+
import { z as z5 } from "zod";
|
|
6925
|
+
import { tileflowModelContentType } from "@tileflow/core";
|
|
6926
|
+
import {
|
|
6927
|
+
assertValidTileflowConfig as assertValidTileflowConfig3,
|
|
6928
|
+
defaultTileflowConfigPath,
|
|
6929
|
+
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs4
|
|
6930
|
+
} from "@tileflow/dev/config";
|
|
6931
|
+
import {
|
|
6932
|
+
prepareTileflowMapTreeModels as prepareTileflowMapTreeModels2
|
|
6933
|
+
} from "@tileflow/dev/icons";
|
|
6934
|
+
var modelName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
6935
|
+
var sha256 = /^[a-f0-9]{64}$/u;
|
|
6936
|
+
var maximumListPages = 20;
|
|
6937
|
+
function registerModelCommands(program2, dependencies) {
|
|
6938
|
+
const models = program2.command("models").description("Publish and manage the Team 3D models that maps place");
|
|
6939
|
+
withTeamOptions2(
|
|
6940
|
+
models.command("publish").description(
|
|
6941
|
+
"Prepare building models and publish each as its Team 3D model's current version"
|
|
6942
|
+
).argument(
|
|
6943
|
+
"[paths...]",
|
|
6944
|
+
"model files (<name>.glb) or directories; defaults to the maps' models directories"
|
|
6945
|
+
).option(
|
|
6946
|
+
"-c, --config <path>",
|
|
6947
|
+
"config path, read when no path is given",
|
|
6948
|
+
defaultTileflowConfigPath
|
|
6949
|
+
)
|
|
6950
|
+
).action(async (paths, options) => {
|
|
6951
|
+
let prepared;
|
|
6952
|
+
try {
|
|
6953
|
+
prepared = paths.length > 0 ? await prepareFromPaths(paths) : await prepareFromConfig(options.config);
|
|
6954
|
+
} catch (error) {
|
|
6955
|
+
return emitFailure(options.json, "model_preparation_failed", safeMessage(error));
|
|
6956
|
+
}
|
|
6957
|
+
if (prepared.size === 0) {
|
|
6958
|
+
return emitFailure(
|
|
6959
|
+
options.json,
|
|
6960
|
+
"no_building_models",
|
|
6961
|
+
"No building models were found. A building model is a .glb whose materials name its parts: surface, secondary, glass, accent and roof."
|
|
6962
|
+
);
|
|
6963
|
+
}
|
|
6964
|
+
const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
|
|
6965
|
+
if (!authority) return;
|
|
6966
|
+
const results = [];
|
|
6967
|
+
for (const [name, model] of [...prepared].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
6968
|
+
const body = new FormData();
|
|
6969
|
+
body.append(
|
|
6970
|
+
"metadata",
|
|
6971
|
+
new Blob([JSON.stringify({ sources: model.sources })], { type: "application/json" }),
|
|
6972
|
+
"metadata.json"
|
|
6973
|
+
);
|
|
6974
|
+
const bytes = new Uint8Array(model.bytes.byteLength);
|
|
6975
|
+
bytes.set(model.bytes);
|
|
6976
|
+
body.append(
|
|
6977
|
+
"file",
|
|
6978
|
+
new Blob([bytes.buffer], { type: tileflowModelContentType }),
|
|
6979
|
+
`${name}.glb`
|
|
6980
|
+
);
|
|
6981
|
+
const response = await authorizedTeamRequest(
|
|
6982
|
+
authority,
|
|
6983
|
+
`/v1/models/${encodeURIComponent(name)}/versions/${model.sha256}`,
|
|
6984
|
+
"PUT",
|
|
6985
|
+
{ body }
|
|
6986
|
+
);
|
|
6987
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
|
|
6988
|
+
const parsed = publishResponseSchema2.safeParse(response.body);
|
|
6989
|
+
if (!parsed.success || parsed.data.model.current?.contentHash !== model.sha256) {
|
|
6990
|
+
return emitFailure(
|
|
6991
|
+
options.json,
|
|
6992
|
+
"model_publish_unconfirmed",
|
|
6993
|
+
`The publication of ${name} did not confirm the submitted model file.`
|
|
6994
|
+
);
|
|
6995
|
+
}
|
|
6996
|
+
results.push(parsed.data);
|
|
6997
|
+
if (!options.json) {
|
|
6998
|
+
const current = parsed.data.model.current;
|
|
6999
|
+
const action = parsed.data.changed ? "Published" : "Unchanged";
|
|
7000
|
+
console.log(
|
|
7001
|
+
`${pc4.green("\u2713")} ${action} 3D model ${pc4.bold(name)} version ${current.version} (${formatBytes3(current.byteLength)}, ${formatCount(current.summary.levels[0]?.triangles ?? 0)} triangles).`
|
|
7002
|
+
);
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
7005
|
+
if (options.json) {
|
|
7006
|
+
emitJson({
|
|
7007
|
+
command: "models publish",
|
|
7008
|
+
ok: true,
|
|
7009
|
+
schemaVersion: 1,
|
|
7010
|
+
models: results.map(({ changed, model }) => ({
|
|
7011
|
+
changed,
|
|
7012
|
+
contentHash: model.current.contentHash,
|
|
7013
|
+
name: model.name,
|
|
7014
|
+
version: model.current.version
|
|
7015
|
+
}))
|
|
7016
|
+
});
|
|
7017
|
+
} else if (results.some(({ changed }) => changed)) {
|
|
7018
|
+
console.log("Maps that place them draw the new versions on their next load.");
|
|
7019
|
+
}
|
|
7020
|
+
});
|
|
7021
|
+
withTeamOptions2(models.command("list").description("List the Team's 3D models")).action(
|
|
7022
|
+
async (options) => {
|
|
7023
|
+
const authority = await resolveTeamAuthority(options, ["models:read"], dependencies);
|
|
7024
|
+
if (!authority) return;
|
|
7025
|
+
const listed = [];
|
|
7026
|
+
let cursor;
|
|
7027
|
+
for (let page = 0; page < maximumListPages; page += 1) {
|
|
7028
|
+
const response = await authorizedTeamRequest(
|
|
7029
|
+
authority,
|
|
7030
|
+
`/v1/models${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
|
|
7031
|
+
"GET"
|
|
7032
|
+
);
|
|
7033
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body);
|
|
7034
|
+
const parsed = listResponseSchema.safeParse(response.body);
|
|
7035
|
+
if (!parsed.success)
|
|
7036
|
+
return emitFailure(options.json, "invalid_response", "The model list was not readable.");
|
|
7037
|
+
listed.push(...parsed.data.models);
|
|
7038
|
+
cursor = parsed.data.nextCursor;
|
|
7039
|
+
if (!cursor) break;
|
|
7040
|
+
}
|
|
7041
|
+
if (options.json)
|
|
7042
|
+
return emitJson({ command: "models list", ok: true, schemaVersion: 1, models: listed });
|
|
7043
|
+
if (listed.length === 0) {
|
|
7044
|
+
console.log("The Team has no 3D models. Publish one with tileflow models publish.");
|
|
7045
|
+
return;
|
|
7046
|
+
}
|
|
7047
|
+
for (const model of listed) {
|
|
7048
|
+
const current = model.current;
|
|
7049
|
+
console.log(
|
|
7050
|
+
`${pc4.bold(model.name)} ${current ? `version ${current.version}, ${formatBytes3(current.byteLength)}` : "no current version"} ${model.usedByMaps === 1 ? "1 map" : `${model.usedByMaps} maps`}${model.state === "purging" ? pc4.yellow(" purging") : ""}`
|
|
7051
|
+
);
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
7054
|
+
);
|
|
7055
|
+
withTeamOptions2(
|
|
7056
|
+
models.command("status").description("Show a 3D model's versions and the maps that place it").argument("<name>", "model name")
|
|
7057
|
+
).action(async (name, options) => {
|
|
7058
|
+
if (!modelName.test(name))
|
|
7059
|
+
return emitFailure(options.json, "invalid_model", "Model name is invalid.");
|
|
7060
|
+
const authority = await resolveTeamAuthority(options, ["models:read"], dependencies);
|
|
7061
|
+
if (!authority) return;
|
|
7062
|
+
const detail = await readDetail(authority, name, options.json);
|
|
7063
|
+
if (!detail) return;
|
|
7064
|
+
if (options.json) return emitJson({ ...detail, command: "models status", ok: true });
|
|
7065
|
+
const { model, versions, uses } = detail;
|
|
7066
|
+
console.log(
|
|
7067
|
+
`${pc4.bold(model.name)}${model.state === "purging" ? pc4.yellow(" (purging)") : ""}`
|
|
7068
|
+
);
|
|
7069
|
+
for (const version of versions) {
|
|
7070
|
+
const role = version.state === "current" ? "current" : version.state === "previous" ? "previous" : version.state;
|
|
7071
|
+
console.log(
|
|
7072
|
+
` version ${version.version} ${role} ${formatBytes3(version.byteLength)} ${formatCount(version.summary.levels[0]?.triangles ?? 0)} triangles ${version.publishedAt}`
|
|
7073
|
+
);
|
|
7074
|
+
}
|
|
7075
|
+
console.log(
|
|
7076
|
+
uses.length === 0 ? " No map places it." : ` Placed by ${uses.map(({ mapName, mapId }) => mapName ?? mapId).join(", ")}.`
|
|
7077
|
+
);
|
|
7078
|
+
});
|
|
7079
|
+
withTeamOptions2(
|
|
7080
|
+
models.command("restore").description("Make a 3D model's previous version current again, in every map that places it").argument("<name>", "model name")
|
|
7081
|
+
).action(async (name, options) => {
|
|
7082
|
+
if (!modelName.test(name))
|
|
7083
|
+
return emitFailure(options.json, "invalid_model", "Model name is invalid.");
|
|
7084
|
+
const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
|
|
7085
|
+
if (!authority) return;
|
|
7086
|
+
const detail = await readDetail(authority, name, options.json);
|
|
7087
|
+
if (!detail) return;
|
|
7088
|
+
const { current, previous } = detail.model;
|
|
7089
|
+
if (!current || !previous) {
|
|
7090
|
+
return emitFailure(
|
|
7091
|
+
options.json,
|
|
7092
|
+
"no_previous_version",
|
|
7093
|
+
`${name} has no previous version to restore.`
|
|
7094
|
+
);
|
|
7095
|
+
}
|
|
7096
|
+
const response = await authorizedTeamRequest(
|
|
7097
|
+
authority,
|
|
7098
|
+
`/v1/models/${encodeURIComponent(name)}/restore`,
|
|
7099
|
+
"POST",
|
|
7100
|
+
{
|
|
7101
|
+
body: JSON.stringify({ expectedCurrentVersion: current.version }),
|
|
7102
|
+
headers: { "Content-Type": "application/json" }
|
|
7103
|
+
}
|
|
7104
|
+
);
|
|
7105
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
|
|
7106
|
+
const parsed = restoreResponseSchema.safeParse(response.body);
|
|
7107
|
+
if (!parsed.success || parsed.data.model.current?.version !== previous.version) {
|
|
7108
|
+
return emitFailure(
|
|
7109
|
+
options.json,
|
|
7110
|
+
"model_restore_unconfirmed",
|
|
7111
|
+
`The restore of ${name} was not confirmed.`
|
|
7112
|
+
);
|
|
7113
|
+
}
|
|
7114
|
+
if (options.json)
|
|
7115
|
+
return emitJson({
|
|
7116
|
+
command: "models restore",
|
|
7117
|
+
ok: true,
|
|
7118
|
+
schemaVersion: 1,
|
|
7119
|
+
model: parsed.data.model
|
|
7120
|
+
});
|
|
7121
|
+
console.log(
|
|
7122
|
+
`${pc4.green("\u2713")} ${pc4.bold(name)} is at version ${previous.version} again; version ${current.version} is its previous one. Maps draw it on their next load.`
|
|
7123
|
+
);
|
|
7124
|
+
});
|
|
7125
|
+
withTeamOptions2(
|
|
7126
|
+
models.command("purge").description("Delete a 3D model and its versions; refused while a map places it").argument("<name>", "model name").option("--confirm <name>", "repeat the model name to confirm")
|
|
7127
|
+
).action(async (name, options) => {
|
|
7128
|
+
if (!modelName.test(name))
|
|
7129
|
+
return emitFailure(options.json, "invalid_model", "Model name is invalid.");
|
|
7130
|
+
if (options.confirm !== name) {
|
|
7131
|
+
return emitFailure(
|
|
7132
|
+
options.json,
|
|
7133
|
+
"confirmation_required",
|
|
7134
|
+
`Repeat the model name to purge it: --confirm ${name}`
|
|
7135
|
+
);
|
|
7136
|
+
}
|
|
7137
|
+
const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
|
|
7138
|
+
if (!authority) return;
|
|
7139
|
+
const response = await authorizedTeamRequest(
|
|
7140
|
+
authority,
|
|
7141
|
+
`/v1/models/${encodeURIComponent(name)}`,
|
|
7142
|
+
"DELETE",
|
|
7143
|
+
{
|
|
7144
|
+
headers: { "X-Tileflow-Confirm-Model-Purge": name }
|
|
7145
|
+
}
|
|
7146
|
+
);
|
|
7147
|
+
if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
|
|
7148
|
+
const parsed = purgeResponseSchema2.safeParse(response.body);
|
|
7149
|
+
if (!parsed.success)
|
|
7150
|
+
return emitFailure(options.json, "invalid_response", "The purge was not confirmed.");
|
|
7151
|
+
if (options.json)
|
|
7152
|
+
return emitJson({
|
|
7153
|
+
command: "models purge",
|
|
7154
|
+
ok: true,
|
|
7155
|
+
schemaVersion: 1,
|
|
7156
|
+
purge: parsed.data.purge
|
|
7157
|
+
});
|
|
7158
|
+
console.log(
|
|
7159
|
+
`${pc4.green("\u2713")} ${pc4.bold(name)} is being purged; its name is free once its files are deleted.`
|
|
7160
|
+
);
|
|
7161
|
+
});
|
|
7162
|
+
}
|
|
7163
|
+
function withTeamOptions2(command2) {
|
|
7164
|
+
return command2.option("--team <team>", "Team, as @team, when the account has several").option("--api-key <key>", "Team data API key with the 3D models permission").option("--api-url <url>", "Tileflow API origin").option("--json", "machine-readable output");
|
|
7165
|
+
}
|
|
7166
|
+
async function readDetail(authority, name, json2) {
|
|
7167
|
+
const response = await authorizedTeamRequest(
|
|
7168
|
+
authority,
|
|
7169
|
+
`/v1/models/${encodeURIComponent(name)}`,
|
|
7170
|
+
"GET"
|
|
7171
|
+
);
|
|
7172
|
+
if (!response.ok) {
|
|
7173
|
+
emitHttpFailure2(json2, response.status, response.body, name);
|
|
7174
|
+
return void 0;
|
|
7175
|
+
}
|
|
7176
|
+
const parsed = detailResponseSchema.safeParse(response.body);
|
|
7177
|
+
if (!parsed.success) {
|
|
7178
|
+
emitFailure(json2, "invalid_response", `The details of ${name} were not readable.`);
|
|
7179
|
+
return void 0;
|
|
7180
|
+
}
|
|
7181
|
+
return parsed.data;
|
|
7182
|
+
}
|
|
7183
|
+
async function prepareFromPaths(paths) {
|
|
7184
|
+
const cwd = process.cwd();
|
|
7185
|
+
const directories = /* @__PURE__ */ new Map();
|
|
7186
|
+
for (const path of paths) {
|
|
7187
|
+
const absolute = resolve8(cwd, path);
|
|
7188
|
+
const info = await lstat5(absolute).catch(() => void 0);
|
|
7189
|
+
if (!info) throw new Error(`${path} does not exist.`);
|
|
7190
|
+
const directory = info.isDirectory() ? absolute : dirname11(absolute);
|
|
7191
|
+
if (relative4(cwd, directory).startsWith(".."))
|
|
7192
|
+
throw new Error(`${path} is outside the current directory.`);
|
|
7193
|
+
if (info.isDirectory()) {
|
|
7194
|
+
directories.set(directory, "all");
|
|
7195
|
+
continue;
|
|
7196
|
+
}
|
|
7197
|
+
const file = basename4(absolute);
|
|
7198
|
+
if (!file.toLowerCase().endsWith(".glb"))
|
|
7199
|
+
throw new Error(`${path} is not a glTF binary (.glb).`);
|
|
7200
|
+
const name = file.replace(/(\.mid|\.far)?\.glb$/iu, "");
|
|
7201
|
+
const selected = directories.get(directory);
|
|
7202
|
+
if (selected === "all") continue;
|
|
7203
|
+
directories.set(directory, /* @__PURE__ */ new Set([...selected ?? [], name]));
|
|
7204
|
+
}
|
|
7205
|
+
const models = /* @__PURE__ */ new Map();
|
|
7206
|
+
for (const [directory, selected] of directories) {
|
|
7207
|
+
const parent = dirname11(directory);
|
|
7208
|
+
const local = `./${basename4(directory)}`;
|
|
7209
|
+
const display = relative4(cwd, directory) || ".";
|
|
7210
|
+
const prepared = await prepareTileflowMapTreeModels2([local], {
|
|
7211
|
+
baseDirectory: parent,
|
|
7212
|
+
configPath: display,
|
|
7213
|
+
cwd: parent,
|
|
7214
|
+
target: "hosted"
|
|
7215
|
+
});
|
|
7216
|
+
for (const [name, model] of Object.entries(prepared.buildings)) {
|
|
7217
|
+
if (selected !== "all" && !selected.has(name)) continue;
|
|
7218
|
+
addModel(models, name, model);
|
|
7219
|
+
}
|
|
7220
|
+
if (selected !== "all") {
|
|
7221
|
+
for (const name of selected)
|
|
7222
|
+
if (!prepared.buildings[name])
|
|
7223
|
+
throw new Error(
|
|
7224
|
+
`${display}/${name}.glb is not a building model: its materials must name its parts (surface, secondary, glass, accent, roof).`
|
|
7225
|
+
);
|
|
7226
|
+
}
|
|
7227
|
+
}
|
|
7228
|
+
return models;
|
|
7229
|
+
}
|
|
7230
|
+
async function prepareFromConfig(configPath) {
|
|
7231
|
+
const loaded = await withTileflowConfigSecretsHidden(
|
|
7232
|
+
() => loadTileflowConfigWithInputs4(configPath)
|
|
7233
|
+
);
|
|
7234
|
+
const project = loaded.project;
|
|
7235
|
+
assertValidTileflowConfig3(project);
|
|
7236
|
+
const prepared = await prepareModelAssets(project, {
|
|
7237
|
+
baseDirectory: dirname11(loaded.configFile),
|
|
7238
|
+
target: "hosted"
|
|
7239
|
+
});
|
|
7240
|
+
const models = /* @__PURE__ */ new Map();
|
|
7241
|
+
for (const buildings of Object.values(prepared.buildings))
|
|
7242
|
+
for (const [name, model] of Object.entries(buildings)) addModel(models, name, model);
|
|
7243
|
+
return models;
|
|
7244
|
+
}
|
|
7245
|
+
function addModel(models, name, model) {
|
|
7246
|
+
const known = models.get(name);
|
|
7247
|
+
if (known && known.sha256 !== model.sha256)
|
|
7248
|
+
throw new Error(`Two different models are named ${name}; a Team has one model of each name.`);
|
|
7249
|
+
models.set(name, model);
|
|
7250
|
+
}
|
|
7251
|
+
function emitHttpFailure2(json2, status, body, name) {
|
|
7252
|
+
const error = z5.object({ error: z5.object({ code: z5.string().max(80), message: z5.string().max(400) }).partial() }).partial().safeParse(body);
|
|
7253
|
+
const message = error.success ? error.data.error?.message : void 0;
|
|
7254
|
+
const code = error.success ? error.data.error?.code : void 0;
|
|
7255
|
+
if (status === 404)
|
|
7256
|
+
return emitFailure(
|
|
7257
|
+
json2,
|
|
7258
|
+
"model_not_found",
|
|
7259
|
+
name ? `The Team has no 3D model named ${name}.` : "Not found."
|
|
7260
|
+
);
|
|
7261
|
+
return emitFailure(
|
|
7262
|
+
json2,
|
|
7263
|
+
code ?? `http_${status}`,
|
|
7264
|
+
message ?? `The request failed with ${status}.`
|
|
7265
|
+
);
|
|
7266
|
+
}
|
|
7267
|
+
function formatBytes3(bytes) {
|
|
7268
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
7269
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
7270
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
|
|
7271
|
+
}
|
|
7272
|
+
function formatCount(count) {
|
|
7273
|
+
return new Intl.NumberFormat("en-US").format(count);
|
|
7274
|
+
}
|
|
7275
|
+
var summarySchema = z5.object({
|
|
7276
|
+
levels: z5.array(
|
|
7277
|
+
z5.object({
|
|
7278
|
+
level: z5.enum(["near", "mid", "far"]),
|
|
7279
|
+
vertices: z5.number().int().nonnegative(),
|
|
7280
|
+
triangles: z5.number().int().nonnegative()
|
|
7281
|
+
}).strict()
|
|
7282
|
+
).min(1).max(3),
|
|
7283
|
+
width: z5.number().nonnegative(),
|
|
7284
|
+
depth: z5.number().nonnegative(),
|
|
7285
|
+
height: z5.number().nonnegative(),
|
|
7286
|
+
footprint: z5.object({
|
|
7287
|
+
rings: z5.number().int().nonnegative(),
|
|
7288
|
+
points: z5.number().int().nonnegative(),
|
|
7289
|
+
area: z5.number().nonnegative()
|
|
7290
|
+
}).strict()
|
|
7291
|
+
}).strict();
|
|
7292
|
+
var versionSchema = z5.object({
|
|
7293
|
+
id: z5.string().regex(/^tmv_[A-Za-z0-9_-]{16,64}$/u),
|
|
7294
|
+
version: z5.number().int().positive(),
|
|
7295
|
+
state: z5.enum(["current", "previous", "gc_pending", "deleting", "deleted"]),
|
|
7296
|
+
contentHash: z5.string().regex(sha256),
|
|
7297
|
+
byteLength: z5.number().int().positive(),
|
|
7298
|
+
publishedAt: z5.iso.datetime({ offset: true }),
|
|
7299
|
+
summary: summarySchema
|
|
7300
|
+
}).strict();
|
|
7301
|
+
var modelSchema = z5.object({
|
|
7302
|
+
id: z5.string().regex(/^tmd_[A-Za-z0-9_-]{16,64}$/u),
|
|
7303
|
+
name: z5.string().regex(modelName).max(64),
|
|
7304
|
+
state: z5.enum(["active", "purging"]),
|
|
7305
|
+
current: versionSchema.nullable(),
|
|
7306
|
+
previous: versionSchema.nullable(),
|
|
7307
|
+
url: z5.url(),
|
|
7308
|
+
usedByMaps: z5.number().int().nonnegative(),
|
|
7309
|
+
updatedAt: z5.iso.datetime({ offset: true })
|
|
7310
|
+
}).strict();
|
|
7311
|
+
var listResponseSchema = z5.object({
|
|
7312
|
+
schemaVersion: z5.literal(1),
|
|
7313
|
+
models: z5.array(modelSchema).max(100),
|
|
7314
|
+
nextCursor: z5.string().min(1).max(512).optional()
|
|
7315
|
+
}).strict();
|
|
7316
|
+
var detailResponseSchema = z5.object({
|
|
7317
|
+
schemaVersion: z5.literal(1),
|
|
7318
|
+
model: modelSchema,
|
|
7319
|
+
versions: z5.array(versionSchema).max(100),
|
|
7320
|
+
uses: z5.array(
|
|
7321
|
+
z5.object({
|
|
7322
|
+
mapId: z5.string().min(1).max(160),
|
|
7323
|
+
mapName: z5.string().max(200).nullable(),
|
|
7324
|
+
relationship: z5.enum(["active", "retained"])
|
|
7325
|
+
}).strict()
|
|
7326
|
+
).max(100)
|
|
7327
|
+
}).strict();
|
|
7328
|
+
var publishResponseSchema2 = z5.object({ schemaVersion: z5.literal(1), changed: z5.boolean(), model: modelSchema }).strict();
|
|
7329
|
+
var restoreResponseSchema = z5.object({ schemaVersion: z5.literal(1), model: modelSchema }).strict();
|
|
7330
|
+
var purgeResponseSchema2 = z5.object({
|
|
7331
|
+
schemaVersion: z5.literal(1),
|
|
7332
|
+
purge: z5.object({ name: z5.string().regex(modelName), state: z5.enum(["purging", "purged"]) }).strict()
|
|
7333
|
+
}).strict();
|
|
7334
|
+
|
|
6889
7335
|
// src/native-preview-command.ts
|
|
6890
7336
|
import { tileflowNativeProfile } from "@tileflow/core/native-profile";
|
|
6891
7337
|
import {
|
|
@@ -6974,7 +7420,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
|
|
|
6974
7420
|
}
|
|
6975
7421
|
|
|
6976
7422
|
// src/project-commands.ts
|
|
6977
|
-
import
|
|
7423
|
+
import pc5 from "picocolors";
|
|
6978
7424
|
function registerProjectCommands(program2, dependencies) {
|
|
6979
7425
|
const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
|
|
6980
7426
|
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) => {
|
|
@@ -6990,12 +7436,12 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
6990
7436
|
};
|
|
6991
7437
|
if (options.json) return emitJson2(document);
|
|
6992
7438
|
if (!result.items.length) {
|
|
6993
|
-
console.log(
|
|
7439
|
+
console.log(pc5.gray("No managed destinations found."));
|
|
6994
7440
|
return;
|
|
6995
7441
|
}
|
|
6996
7442
|
for (const target of result.items) {
|
|
6997
7443
|
console.log(
|
|
6998
|
-
` ${target.project.archivedAt ?
|
|
7444
|
+
` ${target.project.archivedAt ? pc5.yellow("\u25CB") : pc5.green("\u25CF")} ${pc5.bold(target.project.name)} ${pc5.gray(target.reference)}${target.project.archivedAt ? pc5.gray(" (archived)") : ""}`
|
|
6999
7445
|
);
|
|
7000
7446
|
}
|
|
7001
7447
|
});
|
|
@@ -7048,8 +7494,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7048
7494
|
...asRecord2(result.body)
|
|
7049
7495
|
};
|
|
7050
7496
|
if (options.json) return emitJson2(document);
|
|
7051
|
-
console.log(`${
|
|
7052
|
-
console.log(
|
|
7497
|
+
console.log(`${pc5.green("\u2713")} Application ${pc5.bold(options.name.trim())} is ready.`);
|
|
7498
|
+
console.log(pc5.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
|
|
7053
7499
|
}
|
|
7054
7500
|
);
|
|
7055
7501
|
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) => {
|
|
@@ -7071,8 +7517,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7071
7517
|
if (!isProjectItem(project)) {
|
|
7072
7518
|
return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
|
|
7073
7519
|
}
|
|
7074
|
-
console.log(`${
|
|
7075
|
-
console.log(project.archivedAt ?
|
|
7520
|
+
console.log(`${pc5.bold(project.name)} ${pc5.gray(target)}`);
|
|
7521
|
+
console.log(project.archivedAt ? pc5.yellow("Archived") : pc5.green("Active"));
|
|
7076
7522
|
});
|
|
7077
7523
|
for (const archived of [true, false]) {
|
|
7078
7524
|
const verb = archived ? "archive" : "unarchive";
|
|
@@ -7099,7 +7545,7 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7099
7545
|
...asRecord2(result.body)
|
|
7100
7546
|
};
|
|
7101
7547
|
if (options.json) return emitJson2(document);
|
|
7102
|
-
console.log(`${
|
|
7548
|
+
console.log(`${pc5.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
|
|
7103
7549
|
});
|
|
7104
7550
|
}
|
|
7105
7551
|
}
|
|
@@ -7210,7 +7656,7 @@ function emitLocalFailure(json2, code, message, details = {}) {
|
|
|
7210
7656
|
console.error(message);
|
|
7211
7657
|
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
7212
7658
|
} else {
|
|
7213
|
-
console.error(`${
|
|
7659
|
+
console.error(`${pc5.red("\u2715")} ${message}`);
|
|
7214
7660
|
}
|
|
7215
7661
|
process.exitCode = 1;
|
|
7216
7662
|
}
|
|
@@ -7245,7 +7691,7 @@ function safeMessage2(error) {
|
|
|
7245
7691
|
}
|
|
7246
7692
|
|
|
7247
7693
|
// src/renderer-artifact-command.ts
|
|
7248
|
-
import { resolve as
|
|
7694
|
+
import { resolve as resolve9 } from "path";
|
|
7249
7695
|
import { auditTileflowMapThemeValues } from "@tileflow/core";
|
|
7250
7696
|
import {
|
|
7251
7697
|
createTileflowNativeDiagnostic,
|
|
@@ -7333,7 +7779,7 @@ async function runRendererArtifactCommand(command2, options) {
|
|
|
7333
7779
|
if (artifacts.nativeBuild)
|
|
7334
7780
|
console.log("Profile: native-v1 (static artifacts; no device qualification).");
|
|
7335
7781
|
if (command2 === "build")
|
|
7336
|
-
console.log(`Output: ${
|
|
7782
|
+
console.log(`Output: ${resolve9(outDir, ...renderer === "native" ? ["native"] : [])}`);
|
|
7337
7783
|
}
|
|
7338
7784
|
} catch (error) {
|
|
7339
7785
|
const document = createTileflowCommandFailureDocument6(command2, error, process.cwd(), {
|
|
@@ -7354,15 +7800,15 @@ async function runRendererArtifactCommand(command2, options) {
|
|
|
7354
7800
|
}
|
|
7355
7801
|
|
|
7356
7802
|
// src/tileset-command.ts
|
|
7357
|
-
import
|
|
7358
|
-
import { z as
|
|
7803
|
+
import pc6 from "picocolors";
|
|
7804
|
+
import { z as z7 } from "zod";
|
|
7359
7805
|
import { serializeCanonicalJson as serializeCanonicalJson6 } from "@tileflow/core";
|
|
7360
7806
|
import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
|
|
7361
7807
|
|
|
7362
7808
|
// src/tileset-upload.ts
|
|
7363
7809
|
import { createHash as createHash3 } from "crypto";
|
|
7364
7810
|
import { open as open5, stat } from "fs/promises";
|
|
7365
|
-
import { z as
|
|
7811
|
+
import { z as z6 } from "zod";
|
|
7366
7812
|
import { serializeCanonicalJson as serializeCanonicalJson5 } from "@tileflow/core";
|
|
7367
7813
|
var teamTilesetPartSize = 16777216;
|
|
7368
7814
|
var maximumTeamTilesetBytes = 4e9;
|
|
@@ -7923,36 +8369,36 @@ function asRecord3(value) {
|
|
|
7923
8369
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7924
8370
|
}
|
|
7925
8371
|
function defaultSleep(milliseconds) {
|
|
7926
|
-
return new Promise((
|
|
7927
|
-
}
|
|
7928
|
-
var identifierSchema =
|
|
7929
|
-
var uploadPartStatusSchema =
|
|
7930
|
-
byteCount:
|
|
7931
|
-
partNumber:
|
|
7932
|
-
uploaded:
|
|
7933
|
-
validationState:
|
|
8372
|
+
return new Promise((resolve13) => setTimeout(resolve13, milliseconds));
|
|
8373
|
+
}
|
|
8374
|
+
var identifierSchema = z6.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
|
|
8375
|
+
var uploadPartStatusSchema = z6.object({
|
|
8376
|
+
byteCount: z6.number().int().min(1).max(teamTilesetPartSize),
|
|
8377
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
8378
|
+
uploaded: z6.boolean(),
|
|
8379
|
+
validationState: z6.enum(["mismatch", "pending", "processing", "valid"])
|
|
7934
8380
|
}).strict();
|
|
7935
|
-
var publishedVersionSchema =
|
|
7936
|
-
byteCount:
|
|
7937
|
-
contentHash:
|
|
7938
|
-
contentHashAlgorithm:
|
|
7939
|
-
publishedVersion:
|
|
7940
|
-
schemaVersion:
|
|
7941
|
-
state:
|
|
8381
|
+
var publishedVersionSchema = z6.object({
|
|
8382
|
+
byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
|
|
8383
|
+
contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
|
|
8384
|
+
contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
|
|
8385
|
+
publishedVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
8386
|
+
schemaVersion: z6.literal(1),
|
|
8387
|
+
state: z6.literal("published"),
|
|
7942
8388
|
versionId: identifierSchema.regex(/^tsv_[A-Za-z0-9_-]+$/u)
|
|
7943
8389
|
}).strict();
|
|
7944
|
-
var uploadControlSchema =
|
|
7945
|
-
byteCount:
|
|
7946
|
-
contentHash:
|
|
7947
|
-
contentHashAlgorithm:
|
|
7948
|
-
expiresAt:
|
|
7949
|
-
failure:
|
|
7950
|
-
partCount:
|
|
7951
|
-
parts:
|
|
7952
|
-
partSize:
|
|
7953
|
-
publishedVersion:
|
|
7954
|
-
schemaVersion:
|
|
7955
|
-
state:
|
|
8390
|
+
var uploadControlSchema = z6.object({
|
|
8391
|
+
byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
|
|
8392
|
+
contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
|
|
8393
|
+
contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
|
|
8394
|
+
expiresAt: z6.iso.datetime({ offset: true }),
|
|
8395
|
+
failure: z6.object({ code: z6.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
|
|
8396
|
+
partCount: z6.number().int().min(1).max(239),
|
|
8397
|
+
parts: z6.array(uploadPartStatusSchema).max(239),
|
|
8398
|
+
partSize: z6.literal(teamTilesetPartSize),
|
|
8399
|
+
publishedVersion: z6.null(),
|
|
8400
|
+
schemaVersion: z6.literal(1),
|
|
8401
|
+
state: z6.enum([
|
|
7956
8402
|
"aborted",
|
|
7957
8403
|
"aborting",
|
|
7958
8404
|
"expired",
|
|
@@ -7965,32 +8411,32 @@ var uploadControlSchema = z5.object({
|
|
|
7965
8411
|
"uploading",
|
|
7966
8412
|
"validating"
|
|
7967
8413
|
]),
|
|
7968
|
-
uploadId:
|
|
7969
|
-
versionId:
|
|
8414
|
+
uploadId: z6.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
|
|
8415
|
+
versionId: z6.null()
|
|
7970
8416
|
}).strict();
|
|
7971
|
-
var uploadSessionSchema =
|
|
7972
|
-
var authorizationSchema =
|
|
7973
|
-
expiresAt:
|
|
7974
|
-
headers:
|
|
7975
|
-
"Content-Length":
|
|
7976
|
-
"Content-MD5":
|
|
8417
|
+
var uploadSessionSchema = z6.union([publishedVersionSchema, uploadControlSchema]);
|
|
8418
|
+
var authorizationSchema = z6.object({
|
|
8419
|
+
expiresAt: z6.iso.datetime({ offset: true }),
|
|
8420
|
+
headers: z6.object({
|
|
8421
|
+
"Content-Length": z6.string().regex(/^[1-9][0-9]{0,9}$/u),
|
|
8422
|
+
"Content-MD5": z6.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
|
|
7977
8423
|
}).strict(),
|
|
7978
|
-
method:
|
|
7979
|
-
partNumber:
|
|
7980
|
-
url:
|
|
8424
|
+
method: z6.literal("PUT"),
|
|
8425
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
8426
|
+
url: z6.url().max(8192).refine((value) => {
|
|
7981
8427
|
const url = new URL(value);
|
|
7982
8428
|
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
7983
8429
|
})
|
|
7984
8430
|
}).strict();
|
|
7985
|
-
var authorizationResponseSchema =
|
|
7986
|
-
authorizations:
|
|
7987
|
-
schemaVersion:
|
|
8431
|
+
var authorizationResponseSchema = z6.object({
|
|
8432
|
+
authorizations: z6.array(authorizationSchema).min(1).max(16),
|
|
8433
|
+
schemaVersion: z6.literal(1)
|
|
7988
8434
|
}).strict();
|
|
7989
|
-
var partReceiptResponseSchema =
|
|
7990
|
-
changed:
|
|
7991
|
-
etag:
|
|
7992
|
-
partNumber:
|
|
7993
|
-
schemaVersion:
|
|
8435
|
+
var partReceiptResponseSchema = z6.object({
|
|
8436
|
+
changed: z6.boolean(),
|
|
8437
|
+
etag: z6.string().regex(/^[0-9a-f]{32}$/u),
|
|
8438
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
8439
|
+
schemaVersion: z6.literal(1)
|
|
7994
8440
|
}).strict();
|
|
7995
8441
|
|
|
7996
8442
|
// src/tileset-command.ts
|
|
@@ -8013,7 +8459,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8013
8459
|
`);
|
|
8014
8460
|
return;
|
|
8015
8461
|
}
|
|
8016
|
-
console.log(`${
|
|
8462
|
+
console.log(`${pc6.green("\u2713")} PMTiles archive is valid.`);
|
|
8017
8463
|
console.log(` Type: ${inspection.contract.tileType}`);
|
|
8018
8464
|
console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
|
|
8019
8465
|
console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
|
|
@@ -8023,7 +8469,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8023
8469
|
);
|
|
8024
8470
|
}
|
|
8025
8471
|
for (const warning of inspection.warnings) {
|
|
8026
|
-
console.log(`${
|
|
8472
|
+
console.log(`${pc6.yellow("!")} ${warning.message}`);
|
|
8027
8473
|
}
|
|
8028
8474
|
} catch (error) {
|
|
8029
8475
|
const record = asRecord4(error);
|
|
@@ -8036,7 +8482,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8036
8482
|
`
|
|
8037
8483
|
);
|
|
8038
8484
|
} else {
|
|
8039
|
-
console.error(`${
|
|
8485
|
+
console.error(`${pc6.red("Error:")} ${message}`);
|
|
8040
8486
|
}
|
|
8041
8487
|
process.exitCode = 1;
|
|
8042
8488
|
}
|
|
@@ -8124,7 +8570,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8124
8570
|
};
|
|
8125
8571
|
if (options.json) return emitJson(document);
|
|
8126
8572
|
console.log(
|
|
8127
|
-
`${
|
|
8573
|
+
`${pc6.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc6.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
|
|
8128
8574
|
);
|
|
8129
8575
|
} catch (error) {
|
|
8130
8576
|
if (error instanceof TeamTilesetUploadError) {
|
|
@@ -8144,9 +8590,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8144
8590
|
const items = await fetchAllTeamTilesets(authority);
|
|
8145
8591
|
const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
|
|
8146
8592
|
if (options.json) return emitJson(document);
|
|
8147
|
-
if (items.length === 0) return console.log(
|
|
8593
|
+
if (items.length === 0) return console.log(pc6.gray("No Team tilesets."));
|
|
8148
8594
|
for (const item of items) {
|
|
8149
|
-
console.log(` ${
|
|
8595
|
+
console.log(` ${pc6.bold(item.logicalName)} ${pc6.gray(item.status)}`);
|
|
8150
8596
|
}
|
|
8151
8597
|
} catch {
|
|
8152
8598
|
emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
|
|
@@ -8174,7 +8620,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8174
8620
|
const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
|
|
8175
8621
|
if (options.json) return emitJson(document);
|
|
8176
8622
|
const item = parsed.tileset;
|
|
8177
|
-
console.log(`${
|
|
8623
|
+
console.log(`${pc6.bold(item.name)} ${pc6.gray(item.state)}`);
|
|
8178
8624
|
console.log(` ID: ${item.id}`);
|
|
8179
8625
|
console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
|
|
8180
8626
|
} catch {
|
|
@@ -8225,7 +8671,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8225
8671
|
const parsed = tilesetPurgeResponseSchema.parse(response.body);
|
|
8226
8672
|
const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
|
|
8227
8673
|
if (options.json) return emitJson(document);
|
|
8228
|
-
console.log(`${
|
|
8674
|
+
console.log(`${pc6.green("\u2713")} Purge ${parsed.reclamation}.`);
|
|
8229
8675
|
} catch {
|
|
8230
8676
|
emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
|
|
8231
8677
|
}
|
|
@@ -8287,69 +8733,69 @@ function inspectionOptionError(message) {
|
|
|
8287
8733
|
function asRecord4(value) {
|
|
8288
8734
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
8289
8735
|
}
|
|
8290
|
-
var safeIdentifierSchema2 =
|
|
8291
|
-
var physicalTilesetIdSchema =
|
|
8292
|
-
var cursorSchema2 =
|
|
8293
|
-
var tilesetInventoryItemSchema =
|
|
8294
|
-
currentVersion:
|
|
8295
|
-
hasCurrent:
|
|
8736
|
+
var safeIdentifierSchema2 = z7.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
|
|
8737
|
+
var physicalTilesetIdSchema = z7.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
|
|
8738
|
+
var cursorSchema2 = z7.string().min(1).max(2048).nullable();
|
|
8739
|
+
var tilesetInventoryItemSchema = z7.object({
|
|
8740
|
+
currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
8741
|
+
hasCurrent: z7.boolean(),
|
|
8296
8742
|
id: physicalTilesetIdSchema,
|
|
8297
|
-
logicalName:
|
|
8298
|
-
name:
|
|
8299
|
-
retainedBytes:
|
|
8300
|
-
retainedDeploymentCount:
|
|
8301
|
-
status:
|
|
8302
|
-
updatedAt:
|
|
8303
|
-
usedByMapCount:
|
|
8743
|
+
logicalName: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
8744
|
+
name: z7.string().min(1).max(200),
|
|
8745
|
+
retainedBytes: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
8746
|
+
retainedDeploymentCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
8747
|
+
status: z7.enum(["active", "purging", "retired"]),
|
|
8748
|
+
updatedAt: z7.iso.datetime({ offset: true }),
|
|
8749
|
+
usedByMapCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
|
|
8304
8750
|
}).strict();
|
|
8305
|
-
var tilesetListResponseSchema =
|
|
8751
|
+
var tilesetListResponseSchema = z7.object({
|
|
8306
8752
|
nextCursor: cursorSchema2.optional(),
|
|
8307
|
-
schemaVersion:
|
|
8308
|
-
tilesets:
|
|
8753
|
+
schemaVersion: z7.literal(1),
|
|
8754
|
+
tilesets: z7.array(tilesetInventoryItemSchema).max(100)
|
|
8309
8755
|
}).strict();
|
|
8310
|
-
var tilesetDependencySchema =
|
|
8311
|
-
active:
|
|
8756
|
+
var tilesetDependencySchema = z7.object({
|
|
8757
|
+
active: z7.boolean(),
|
|
8312
8758
|
deploymentId: safeIdentifierSchema2,
|
|
8313
|
-
deploymentVersion:
|
|
8314
|
-
environment:
|
|
8759
|
+
deploymentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
8760
|
+
environment: z7.string().min(1).max(64),
|
|
8315
8761
|
mapId: safeIdentifierSchema2,
|
|
8316
8762
|
sourceId: safeIdentifierSchema2
|
|
8317
8763
|
}).strict();
|
|
8318
|
-
var tilesetDetailResponseSchema =
|
|
8319
|
-
schemaVersion:
|
|
8320
|
-
tileset:
|
|
8321
|
-
attribution:
|
|
8322
|
-
currentVersion:
|
|
8323
|
-
dependencies:
|
|
8764
|
+
var tilesetDetailResponseSchema = z7.object({
|
|
8765
|
+
schemaVersion: z7.literal(1),
|
|
8766
|
+
tileset: z7.object({
|
|
8767
|
+
attribution: z7.string().max(4096).nullable(),
|
|
8768
|
+
currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
8769
|
+
dependencies: z7.array(tilesetDependencySchema).max(25),
|
|
8324
8770
|
dependencyNextCursor: cursorSchema2,
|
|
8325
8771
|
id: physicalTilesetIdSchema,
|
|
8326
|
-
name:
|
|
8327
|
-
slug:
|
|
8328
|
-
state:
|
|
8772
|
+
name: z7.string().min(1).max(200),
|
|
8773
|
+
slug: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
8774
|
+
state: z7.enum(["active", "purging", "purged"])
|
|
8329
8775
|
}).strict()
|
|
8330
8776
|
}).strict();
|
|
8331
|
-
var purgeDependencySchema =
|
|
8777
|
+
var purgeDependencySchema = z7.object({
|
|
8332
8778
|
deploymentId: safeIdentifierSchema2,
|
|
8333
8779
|
mapId: safeIdentifierSchema2,
|
|
8334
8780
|
sourceId: safeIdentifierSchema2
|
|
8335
8781
|
}).strict();
|
|
8336
|
-
var tilesetInUseResponseSchema =
|
|
8337
|
-
code:
|
|
8338
|
-
dependencies:
|
|
8339
|
-
error:
|
|
8782
|
+
var tilesetInUseResponseSchema = z7.object({
|
|
8783
|
+
code: z7.literal("TF_TILESET_IN_USE"),
|
|
8784
|
+
dependencies: z7.array(purgeDependencySchema).max(32),
|
|
8785
|
+
error: z7.literal("Tileset is used by retained Map deployments"),
|
|
8340
8786
|
nextCursor: cursorSchema2
|
|
8341
8787
|
}).strict();
|
|
8342
|
-
var tilesetPurgeResponseSchema =
|
|
8343
|
-
deliveryFence:
|
|
8344
|
-
pendingVersions:
|
|
8345
|
-
reclamation:
|
|
8346
|
-
state:
|
|
8788
|
+
var tilesetPurgeResponseSchema = z7.object({
|
|
8789
|
+
deliveryFence: z7.enum(["complete", "pending"]),
|
|
8790
|
+
pendingVersions: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
8791
|
+
reclamation: z7.enum(["complete", "pending"]),
|
|
8792
|
+
state: z7.enum(["purged", "purging"]),
|
|
8347
8793
|
tilesetId: physicalTilesetIdSchema
|
|
8348
8794
|
}).strict();
|
|
8349
8795
|
|
|
8350
8796
|
// src/visual-command.ts
|
|
8351
|
-
import { lstat as
|
|
8352
|
-
import { dirname as
|
|
8797
|
+
import { lstat as lstat6, readFile as readFile6 } from "fs/promises";
|
|
8798
|
+
import { dirname as dirname12, isAbsolute as isAbsolute4, parse as parse2, relative as relative5, resolve as resolve10, sep as sep4 } from "path";
|
|
8353
8799
|
import {
|
|
8354
8800
|
analyzeTileflowCaptureReference,
|
|
8355
8801
|
compareTileflowCaptureToBaseline,
|
|
@@ -8434,14 +8880,14 @@ async function runVisualAnalyze(scene, options) {
|
|
|
8434
8880
|
validateVisualSelection([scene]);
|
|
8435
8881
|
const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
|
|
8436
8882
|
const cwd = process.cwd();
|
|
8437
|
-
const referencePath =
|
|
8883
|
+
const referencePath = resolve10(cwd, options.reference);
|
|
8438
8884
|
const referencePng = await readVisualReference(referencePath);
|
|
8439
8885
|
const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
|
|
8440
8886
|
if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
|
|
8441
8887
|
const outputDirectory = await resolveVisualDirectory(cwd, options.outputDir, "output");
|
|
8442
|
-
const actualPath =
|
|
8443
|
-
const managedDiffPath =
|
|
8444
|
-
const reportPath =
|
|
8888
|
+
const actualPath = resolve10(outputDirectory.path, `${scene}.actual.png`);
|
|
8889
|
+
const managedDiffPath = resolve10(outputDirectory.path, `${scene}.diff.png`);
|
|
8890
|
+
const reportPath = resolve10(outputDirectory.path, `${scene}.analysis.json`);
|
|
8445
8891
|
assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
|
|
8446
8892
|
const controller = installSignalAbortController();
|
|
8447
8893
|
const session = createTileflowCaptureSession3({
|
|
@@ -8530,15 +8976,15 @@ async function runVisualDiff(scenes, options) {
|
|
|
8530
8976
|
capture,
|
|
8531
8977
|
files.kind === "complete" ? files.baseline : void 0
|
|
8532
8978
|
);
|
|
8533
|
-
const actualPath =
|
|
8534
|
-
const diffPath = comparison.diffPng ?
|
|
8979
|
+
const actualPath = resolve10(outputDirectory.path, `${capture.scene}.actual.png`);
|
|
8980
|
+
const diffPath = comparison.diffPng ? resolve10(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
|
|
8535
8981
|
prepared.push({
|
|
8536
8982
|
capture,
|
|
8537
8983
|
comparison,
|
|
8538
8984
|
baselinePath: files.pngPath,
|
|
8539
8985
|
actualPath,
|
|
8540
8986
|
diffPath,
|
|
8541
|
-
reportPath:
|
|
8987
|
+
reportPath: resolve10(outputDirectory.path, `${capture.scene}.visual.json`)
|
|
8542
8988
|
});
|
|
8543
8989
|
}
|
|
8544
8990
|
const document = createTileflowVisualDiffJson(prepared, cwd);
|
|
@@ -8558,7 +9004,7 @@ async function runVisualDiff(scenes, options) {
|
|
|
8558
9004
|
}
|
|
8559
9005
|
]),
|
|
8560
9006
|
removePaths: prepared.flatMap(
|
|
8561
|
-
(item) => item.diffPath ? [] : [
|
|
9007
|
+
(item) => item.diffPath ? [] : [resolve10(outputDirectory.path, `${item.capture.scene}.diff.png`)]
|
|
8562
9008
|
)
|
|
8563
9009
|
});
|
|
8564
9010
|
if (options.json) {
|
|
@@ -8679,7 +9125,7 @@ async function resolvePreviousBaseline(capture, files) {
|
|
|
8679
9125
|
}
|
|
8680
9126
|
}
|
|
8681
9127
|
async function readBaselineFiles(directory, scene) {
|
|
8682
|
-
const pngPath =
|
|
9128
|
+
const pngPath = resolve10(directory.path, `${scene}.png`);
|
|
8683
9129
|
const receiptPath = captureReceiptPath(pngPath);
|
|
8684
9130
|
const [png, receipt] = await Promise.all([
|
|
8685
9131
|
readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits2.maximumPngBytes),
|
|
@@ -8694,10 +9140,10 @@ async function readBaselineFiles(directory, scene) {
|
|
|
8694
9140
|
return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
|
|
8695
9141
|
}
|
|
8696
9142
|
async function readOptionalArtifact(path, boundary, maximumBytes) {
|
|
8697
|
-
await assertNoSymlinkComponents(
|
|
9143
|
+
await assertNoSymlinkComponents(dirname12(path), boundary);
|
|
8698
9144
|
let info;
|
|
8699
9145
|
try {
|
|
8700
|
-
info = await
|
|
9146
|
+
info = await lstat6(path);
|
|
8701
9147
|
} catch (error) {
|
|
8702
9148
|
if (isMissingFile5(error)) return void 0;
|
|
8703
9149
|
throw error;
|
|
@@ -8717,7 +9163,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
|
|
|
8717
9163
|
async function readVisualReference(path) {
|
|
8718
9164
|
let info;
|
|
8719
9165
|
try {
|
|
8720
|
-
info = await
|
|
9166
|
+
info = await lstat6(path);
|
|
8721
9167
|
} catch (error) {
|
|
8722
9168
|
throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
|
|
8723
9169
|
cause: error
|
|
@@ -8739,12 +9185,12 @@ async function readVisualReference(path) {
|
|
|
8739
9185
|
return new Uint8Array(await readFile6(path));
|
|
8740
9186
|
}
|
|
8741
9187
|
async function resolveVisualDirectory(cwd, value, kind) {
|
|
8742
|
-
const path =
|
|
9188
|
+
const path = resolve10(cwd, value);
|
|
8743
9189
|
if (path === cwd || path === parse2(path).root) {
|
|
8744
9190
|
throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
|
|
8745
9191
|
}
|
|
8746
9192
|
if (kind === "baseline") {
|
|
8747
|
-
for (const managed of [
|
|
9193
|
+
for (const managed of [resolve10(cwd, ".tileflow/captures"), resolve10(cwd, ".tileflow/diffs")]) {
|
|
8748
9194
|
if (pathsOverlap2(path, managed)) {
|
|
8749
9195
|
throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
|
|
8750
9196
|
}
|
|
@@ -8753,7 +9199,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
|
|
|
8753
9199
|
const boundary = isInside2(cwd, path) ? cwd : path;
|
|
8754
9200
|
await assertNoSymlinkComponents(path, boundary);
|
|
8755
9201
|
try {
|
|
8756
|
-
const info = await
|
|
9202
|
+
const info = await lstat6(path);
|
|
8757
9203
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
8758
9204
|
throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
|
|
8759
9205
|
}
|
|
@@ -8780,10 +9226,10 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
|
|
|
8780
9226
|
}
|
|
8781
9227
|
}
|
|
8782
9228
|
function portablePathIdentity2(path) {
|
|
8783
|
-
return
|
|
9229
|
+
return resolve10(path).normalize("NFC").toLowerCase();
|
|
8784
9230
|
}
|
|
8785
9231
|
function isInside2(parent, child) {
|
|
8786
|
-
const path =
|
|
9232
|
+
const path = relative5(parent, child);
|
|
8787
9233
|
return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
|
|
8788
9234
|
}
|
|
8789
9235
|
function isMissingFile5(error) {
|
|
@@ -8792,8 +9238,8 @@ function isMissingFile5(error) {
|
|
|
8792
9238
|
|
|
8793
9239
|
// src/workbench-service.ts
|
|
8794
9240
|
import { opendir, stat as stat2 } from "fs/promises";
|
|
8795
|
-
import { resolve as
|
|
8796
|
-
import { z as
|
|
9241
|
+
import { resolve as resolve11 } from "path";
|
|
9242
|
+
import { z as z8 } from "zod";
|
|
8797
9243
|
import {
|
|
8798
9244
|
compareTileflowCapturesForReview as compareTileflowCapturesForReview3,
|
|
8799
9245
|
createTileflowCaptureSession as createTileflowCaptureSession4,
|
|
@@ -8806,22 +9252,22 @@ import {
|
|
|
8806
9252
|
} from "@tileflow/core";
|
|
8807
9253
|
import { resolveTileflowPreview } from "@tileflow/dev/preview";
|
|
8808
9254
|
var prefix = "/__workbench";
|
|
8809
|
-
var captureInput =
|
|
8810
|
-
side:
|
|
8811
|
-
generation:
|
|
9255
|
+
var captureInput = z8.object({
|
|
9256
|
+
side: z8.enum(["left", "right"]).default("left"),
|
|
9257
|
+
generation: z8.number().int().nonnegative(),
|
|
8812
9258
|
name: tileflowCaptureSceneNameSchema2,
|
|
8813
9259
|
definition: tileflowCaptureSceneSchema3
|
|
8814
9260
|
}).strict();
|
|
8815
|
-
var compareInput =
|
|
8816
|
-
var deleteInput =
|
|
8817
|
-
var contextInput =
|
|
8818
|
-
client:
|
|
8819
|
-
side:
|
|
8820
|
-
generation:
|
|
8821
|
-
context:
|
|
9261
|
+
var compareInput = z8.object({ before: z8.string().regex(/^[a-f0-9]{64}$/u), after: z8.string().regex(/^[a-f0-9]{64}$/u) }).strict();
|
|
9262
|
+
var deleteInput = z8.object({ id: z8.string().regex(/^[a-f0-9]{64}$/u) }).strict();
|
|
9263
|
+
var contextInput = z8.object({
|
|
9264
|
+
client: z8.string().regex(/^[a-zA-Z0-9-]{1,80}$/u),
|
|
9265
|
+
side: z8.enum(["left", "right"]),
|
|
9266
|
+
generation: z8.number().int(),
|
|
9267
|
+
context: z8.record(z8.string(), z8.unknown())
|
|
8822
9268
|
}).strict();
|
|
8823
9269
|
function createWorkbenchService(options) {
|
|
8824
|
-
const directory =
|
|
9270
|
+
const directory = resolve11(options.cwd, captureRecordDirectory);
|
|
8825
9271
|
const contexts = /* @__PURE__ */ new Map();
|
|
8826
9272
|
let captureSession = options.captureSession;
|
|
8827
9273
|
let pending;
|
|
@@ -8893,7 +9339,7 @@ function createWorkbenchService(options) {
|
|
|
8893
9339
|
break;
|
|
8894
9340
|
}
|
|
8895
9341
|
if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) continue;
|
|
8896
|
-
const info = await stat2(
|
|
9342
|
+
const info = await stat2(resolve11(directory, entry.name));
|
|
8897
9343
|
entries.push({ id: entry.name.slice(0, -5), modified: info.mtimeMs });
|
|
8898
9344
|
}
|
|
8899
9345
|
} catch (error) {
|
|
@@ -9032,7 +9478,7 @@ function createWorkbenchService(options) {
|
|
|
9032
9478
|
});
|
|
9033
9479
|
}
|
|
9034
9480
|
if (path === "/captures") {
|
|
9035
|
-
const offset =
|
|
9481
|
+
const offset = z8.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
|
|
9036
9482
|
return json(await history(offset));
|
|
9037
9483
|
}
|
|
9038
9484
|
const image = /^\/captures\/([a-f0-9]{64})\.png$/u.exec(path);
|
|
@@ -9163,17 +9609,18 @@ var packageJson = JSON.parse(
|
|
|
9163
9609
|
var program = new Command();
|
|
9164
9610
|
var defaultApiUrl = defaultTileflowApiUrl;
|
|
9165
9611
|
var defaultAppUrl = "https://tileflow.dev";
|
|
9166
|
-
var defaultConfigPath =
|
|
9612
|
+
var defaultConfigPath = defaultTileflowConfigPath2;
|
|
9167
9613
|
var defaultManifestPath = defaultTileflowManifestPath;
|
|
9168
|
-
program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(
|
|
9614
|
+
program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(pc7.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
|
|
9169
9615
|
program.addHelpText("after", "\n -V, --version output the version number\n");
|
|
9170
9616
|
registerDescribeCommand(program);
|
|
9171
9617
|
registerLanguageCommand(program);
|
|
9172
9618
|
registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
9173
9619
|
registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
9620
|
+
registerModelCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
9174
9621
|
program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
|
|
9175
|
-
const configPath =
|
|
9176
|
-
const ignorePath =
|
|
9622
|
+
const configPath = resolve12(process.cwd(), defaultConfigPath);
|
|
9623
|
+
const ignorePath = resolve12(process.cwd(), ".gitignore");
|
|
9177
9624
|
if (existsSync(configPath) && !options.force) {
|
|
9178
9625
|
logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
|
|
9179
9626
|
printNextSteps([`Run ${command("tileflow init --force")} to replace it.`]);
|
|
@@ -9189,9 +9636,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
9189
9636
|
}
|
|
9190
9637
|
logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
|
|
9191
9638
|
printKeyValue("Path", pathLabel(configPath));
|
|
9192
|
-
printKeyValue("Map",
|
|
9639
|
+
printKeyValue("Map", pc7.bold("madrid"));
|
|
9193
9640
|
printNextSteps([
|
|
9194
|
-
`Install ${
|
|
9641
|
+
`Install ${pc7.cyan("@tileflow/core")} and ${pc7.cyan("@tileflow/maps")} in this project if needed.`,
|
|
9195
9642
|
`Preview locally with ${command("tileflow preview")}.`,
|
|
9196
9643
|
`Validate the config with ${command("tileflow validate")}.`
|
|
9197
9644
|
]);
|
|
@@ -9327,12 +9774,12 @@ program.command("validate").description("Validate a Tileflow config").option("-c
|
|
|
9327
9774
|
try {
|
|
9328
9775
|
if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
|
|
9329
9776
|
const loaded = await withTileflowConfigSecretsHidden(
|
|
9330
|
-
() =>
|
|
9777
|
+
() => loadTileflowConfigWithInputs5(options.config)
|
|
9331
9778
|
);
|
|
9332
9779
|
const project = loaded.project;
|
|
9333
|
-
const baseDirectory =
|
|
9780
|
+
const baseDirectory = dirname13(loaded.configFile);
|
|
9334
9781
|
failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
|
|
9335
|
-
|
|
9782
|
+
assertValidTileflowConfig4(project);
|
|
9336
9783
|
const mapNames = getTileflowMapNames5(project).sort();
|
|
9337
9784
|
failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
|
|
9338
9785
|
const compiledIcons = await compileTileflowIconPackages3(project, {
|
|
@@ -9348,7 +9795,10 @@ program.command("validate").description("Validate a Tileflow config").option("-c
|
|
|
9348
9795
|
const mapAssets = createCompiledMapAssets(
|
|
9349
9796
|
compiledIcons,
|
|
9350
9797
|
(binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`,
|
|
9351
|
-
|
|
9798
|
+
mapAssetsWithBuildingModels(
|
|
9799
|
+
modelAssets,
|
|
9800
|
+
options.target === "hosted" ? { hosted: true } : { localBase: (mapName) => `/tileflow/models/${mapName}` }
|
|
9801
|
+
)
|
|
9352
9802
|
);
|
|
9353
9803
|
failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
|
|
9354
9804
|
const compiledStyles = createTileflowStyles2(project, {
|
|
@@ -9467,7 +9917,7 @@ program.command("build").description("Generate static Tileflow styles").option("
|
|
|
9467
9917
|
})
|
|
9468
9918
|
);
|
|
9469
9919
|
logSuccess("Built Tileflow artifacts.");
|
|
9470
|
-
printKeyValue("Output", pathLabel(
|
|
9920
|
+
printKeyValue("Output", pathLabel(resolve12(process.cwd(), options.out)));
|
|
9471
9921
|
}
|
|
9472
9922
|
);
|
|
9473
9923
|
program.command("preview").description("Open Map DevTools with inspection and optional comparison").option("-c, --config <path>", "config path", defaultConfigPath).option("--against-config <path>", "second config for live comparison in Map DevTools").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 Map DevTools without opening the browser").option("--scene <name>", "preview one committed standalone map scene").option(
|
|
@@ -9780,7 +10230,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9780
10230
|
);
|
|
9781
10231
|
if (result) {
|
|
9782
10232
|
logSuccess(
|
|
9783
|
-
`${result.changed ? "Published" : "Unchanged"} ${
|
|
10233
|
+
`${result.changed ? "Published" : "Unchanged"} ${pc7.bold(result.mapName)} web + native-v1 (v${result.version}).`
|
|
9784
10234
|
);
|
|
9785
10235
|
printKeyValue("Manifest", pathLabel(result.manifestPath));
|
|
9786
10236
|
printKeyValue("Native", link(result.nativeManifestUrl));
|
|
@@ -9789,11 +10239,11 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9789
10239
|
}
|
|
9790
10240
|
logInfo(`Deploying ${pathLabel(options.config)}.`);
|
|
9791
10241
|
const loaded = await withTileflowConfigSecretsHidden(
|
|
9792
|
-
() =>
|
|
10242
|
+
() => loadTileflowConfigWithInputs5(options.config)
|
|
9793
10243
|
);
|
|
9794
10244
|
const project = loaded.project;
|
|
9795
|
-
const baseDirectory =
|
|
9796
|
-
|
|
10245
|
+
const baseDirectory = dirname13(loaded.configFile);
|
|
10246
|
+
assertValidTileflowConfig4(project);
|
|
9797
10247
|
const configuredMapNames = getTileflowMapNames5(project);
|
|
9798
10248
|
const mapNames = selectManagedDeployMaps(configuredMapNames, options);
|
|
9799
10249
|
if (!mapNames) return;
|
|
@@ -9824,7 +10274,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9824
10274
|
const preflightMapAssets = createCompiledMapAssets(
|
|
9825
10275
|
compiledIcons,
|
|
9826
10276
|
(binding) => `${apiUrl}/sprites/preflight/${binding.packageHash}/sprite`,
|
|
9827
|
-
modelAssets
|
|
10277
|
+
mapAssetsWithBuildingModels(modelAssets, { hosted: true })
|
|
9828
10278
|
);
|
|
9829
10279
|
const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
|
|
9830
10280
|
apiBaseUrl: apiUrl,
|
|
@@ -9894,7 +10344,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9894
10344
|
);
|
|
9895
10345
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
9896
10346
|
logSuccess(
|
|
9897
|
-
`${action} icon package ${
|
|
10347
|
+
`${action} icon package ${pc7.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes4(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
|
|
9898
10348
|
);
|
|
9899
10349
|
hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
|
|
9900
10350
|
}
|
|
@@ -9905,7 +10355,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9905
10355
|
if (!sprite) throw new Error(`Missing hosted sprite URL for map ${binding.mapName}`);
|
|
9906
10356
|
return sprite;
|
|
9907
10357
|
},
|
|
9908
|
-
modelAssets
|
|
10358
|
+
mapAssetsWithBuildingModels(modelAssets, { hosted: true })
|
|
9909
10359
|
);
|
|
9910
10360
|
const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
|
|
9911
10361
|
apiBaseUrl: api.apiUrl,
|
|
@@ -9946,7 +10396,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9946
10396
|
}
|
|
9947
10397
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
9948
10398
|
logSuccess(
|
|
9949
|
-
`${action} font bundle (${plural(bundle.manifest.fontFaces.length, "face")}, ${
|
|
10399
|
+
`${action} font bundle (${plural(bundle.manifest.fontFaces.length, "face")}, ${formatBytes4(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
|
|
9950
10400
|
);
|
|
9951
10401
|
hostedFontBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
|
|
9952
10402
|
}
|
|
@@ -10005,7 +10455,8 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10005
10455
|
sourceAssets: {
|
|
10006
10456
|
fonts: hostedFonts.sourceIdentities[mapName] ?? [],
|
|
10007
10457
|
icons: compiledIcons.sourceIdentities[mapName] ?? [],
|
|
10008
|
-
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {}
|
|
10458
|
+
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
|
|
10459
|
+
...hostedModelSourceIdentities(modelAssets, mapName) ? { models: hostedModelSourceIdentities(modelAssets, mapName) } : {}
|
|
10009
10460
|
},
|
|
10010
10461
|
styles: styles[mapName]
|
|
10011
10462
|
}
|
|
@@ -10018,11 +10469,13 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10018
10469
|
const iconBinding = bindingsByMap.get(mapName);
|
|
10019
10470
|
const iconPackage = iconBinding ? packagesByHash.get(iconBinding.packageHash) : void 0;
|
|
10020
10471
|
const fontBundle = hostedFonts.bundles[mapName];
|
|
10472
|
+
const modelSources = teamModelSources(modelAssets, mapName);
|
|
10021
10473
|
return {
|
|
10022
10474
|
iconBinding,
|
|
10023
10475
|
iconComposition: compiledIcons.compositions[mapName],
|
|
10024
10476
|
iconPackage,
|
|
10025
10477
|
fontBundle,
|
|
10478
|
+
modelSources,
|
|
10026
10479
|
mapName,
|
|
10027
10480
|
teamSources: hostedThemeFamilies[mapName].teamSources,
|
|
10028
10481
|
buildManifest: {
|
|
@@ -10042,6 +10495,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10042
10495
|
iconComposition,
|
|
10043
10496
|
iconPackage,
|
|
10044
10497
|
mapName,
|
|
10498
|
+
modelSources,
|
|
10045
10499
|
teamSources,
|
|
10046
10500
|
styles: themeStyles
|
|
10047
10501
|
} = deployment;
|
|
@@ -10052,7 +10506,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10052
10506
|
}
|
|
10053
10507
|
const themeNames = Object.keys(themeStyles).sort();
|
|
10054
10508
|
logInfo(
|
|
10055
|
-
`Deploying compiled map ${
|
|
10509
|
+
`Deploying compiled map ${pc7.bold(mapName)} (${plural(themeNames.length, "theme")}).`
|
|
10056
10510
|
);
|
|
10057
10511
|
const response = await publishHostedStyle(
|
|
10058
10512
|
api,
|
|
@@ -10076,6 +10530,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10076
10530
|
} : {},
|
|
10077
10531
|
...iconComposition ? { iconComposition } : {},
|
|
10078
10532
|
...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
|
|
10533
|
+
...modelSources ? { teamModelSources: modelSources } : {},
|
|
10079
10534
|
source
|
|
10080
10535
|
},
|
|
10081
10536
|
`Deploy response for ${mapName}`
|
|
@@ -10133,9 +10588,15 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10133
10588
|
};
|
|
10134
10589
|
const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
|
|
10135
10590
|
if (body.changed === false) {
|
|
10136
|
-
logSuccess(`Unchanged ${
|
|
10591
|
+
logSuccess(`Unchanged ${pc7.bold(mapName)}${versionLabel}.`);
|
|
10137
10592
|
} else {
|
|
10138
|
-
logSuccess(`Published ${
|
|
10593
|
+
logSuccess(`Published ${pc7.bold(mapName)}${versionLabel}.`);
|
|
10594
|
+
}
|
|
10595
|
+
for (const teamModel of body.teamModels ?? []) {
|
|
10596
|
+
if (teamModel.sources !== "differ") continue;
|
|
10597
|
+
logWarning(
|
|
10598
|
+
`3D model ${pc7.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.`
|
|
10599
|
+
);
|
|
10139
10600
|
}
|
|
10140
10601
|
}
|
|
10141
10602
|
const manifestPath = await writeDeployManifest(options.manifest, {
|
|
@@ -10497,12 +10958,12 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
10497
10958
|
}
|
|
10498
10959
|
function comparisonCaptureConfigArgument(configPath) {
|
|
10499
10960
|
const cwd = realpathSync(process.cwd());
|
|
10500
|
-
const requestedConfigPath =
|
|
10501
|
-
const defaultRequestedPath =
|
|
10961
|
+
const requestedConfigPath = resolve12(process.cwd(), configPath);
|
|
10962
|
+
const defaultRequestedPath = resolve12(process.cwd(), defaultConfigPath);
|
|
10502
10963
|
const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
|
|
10503
10964
|
const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
|
|
10504
10965
|
if (absoluteConfigPath === absoluteDefaultPath) return void 0;
|
|
10505
|
-
const relativeConfigPath =
|
|
10966
|
+
const relativeConfigPath = relative6(cwd, absoluteConfigPath);
|
|
10506
10967
|
if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
|
|
10507
10968
|
throw new Error(
|
|
10508
10969
|
"Comparison configs must be inside the current working directory so Copy command can remain reproducible."
|
|
@@ -10542,7 +11003,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
10542
11003
|
codeChallenge,
|
|
10543
11004
|
deviceName: hostname()
|
|
10544
11005
|
});
|
|
10545
|
-
printKeyValue("Code",
|
|
11006
|
+
printKeyValue("Code", pc7.bold(authorization.userCode));
|
|
10546
11007
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
10547
11008
|
if (options.noBrowser) {
|
|
10548
11009
|
logMuted("Open the URL above to continue.");
|
|
@@ -10552,7 +11013,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
10552
11013
|
output: process.stdout
|
|
10553
11014
|
});
|
|
10554
11015
|
try {
|
|
10555
|
-
await readline.question(`${
|
|
11016
|
+
await readline.question(`${pc7.cyan("?")} Press Enter to open Tileflow Dashboard...`);
|
|
10556
11017
|
openBrowser(authorization.verificationUriComplete);
|
|
10557
11018
|
} finally {
|
|
10558
11019
|
readline.close();
|
|
@@ -10603,9 +11064,9 @@ function openBrowser(url, errorsToStderr = false) {
|
|
|
10603
11064
|
});
|
|
10604
11065
|
}
|
|
10605
11066
|
async function writeDeployManifest(manifestPath, manifest) {
|
|
10606
|
-
const outputPath =
|
|
11067
|
+
const outputPath = resolve12(process.cwd(), manifestPath);
|
|
10607
11068
|
await writeAtomicFile({
|
|
10608
|
-
boundaryPath:
|
|
11069
|
+
boundaryPath: dirname13(outputPath),
|
|
10609
11070
|
force: true,
|
|
10610
11071
|
label: "Deploy manifest",
|
|
10611
11072
|
managed: true,
|
|
@@ -10693,7 +11154,7 @@ function validateDeployManifestMapNames(mapNames) {
|
|
|
10693
11154
|
async function loadExistingDeployManifest(manifestPath, options) {
|
|
10694
11155
|
let source;
|
|
10695
11156
|
try {
|
|
10696
|
-
source = await readFile7(
|
|
11157
|
+
source = await readFile7(resolve12(process.cwd(), manifestPath), "utf8");
|
|
10697
11158
|
} catch (error) {
|
|
10698
11159
|
if (error.code === "ENOENT") return null;
|
|
10699
11160
|
throw new Error("Existing Tileflow manifest could not be read.");
|
|
@@ -10753,7 +11214,7 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
10753
11214
|
if (!behavior.silent) {
|
|
10754
11215
|
logError("CI requires an explicit Tileflow credential with access to the target Map.");
|
|
10755
11216
|
printNextSteps([
|
|
10756
|
-
`Set ${
|
|
11217
|
+
`Set ${pc7.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
|
|
10757
11218
|
"The saved personal account session is never used in CI."
|
|
10758
11219
|
]);
|
|
10759
11220
|
}
|
|
@@ -10822,17 +11283,17 @@ function printCliError(error) {
|
|
|
10822
11283
|
}
|
|
10823
11284
|
const message = error instanceof Error ? error.message : "Command failed.";
|
|
10824
11285
|
if (message.includes("Cannot find module '@tileflow/core'")) {
|
|
10825
|
-
logError(`Cannot load ${
|
|
11286
|
+
logError(`Cannot load ${pc7.cyan("@tileflow/core")} from this project.`);
|
|
10826
11287
|
printNextSteps([
|
|
10827
|
-
`Install ${
|
|
11288
|
+
`Install ${pc7.cyan("@tileflow/core")} in this project.`,
|
|
10828
11289
|
`Run the command again from the project root.`
|
|
10829
11290
|
]);
|
|
10830
11291
|
return;
|
|
10831
11292
|
}
|
|
10832
11293
|
if (message.includes("Cannot find module '@tileflow/maps'")) {
|
|
10833
|
-
logError(`Cannot load ${
|
|
11294
|
+
logError(`Cannot load ${pc7.cyan("@tileflow/maps")} from this project.`);
|
|
10834
11295
|
printNextSteps([
|
|
10835
|
-
`Install ${
|
|
11296
|
+
`Install ${pc7.cyan("@tileflow/maps")} in this project.`,
|
|
10836
11297
|
`Run the command again from the project root.`
|
|
10837
11298
|
]);
|
|
10838
11299
|
return;
|
|
@@ -10843,51 +11304,51 @@ function printCliError(error) {
|
|
|
10843
11304
|
}
|
|
10844
11305
|
}
|
|
10845
11306
|
function logSuccess(message) {
|
|
10846
|
-
console.log(`${
|
|
11307
|
+
console.log(`${pc7.green("\u2713")} ${message}`);
|
|
10847
11308
|
}
|
|
10848
11309
|
function logError(message) {
|
|
10849
|
-
console.log(`${
|
|
11310
|
+
console.log(`${pc7.red("\u2715")} ${message}`);
|
|
10850
11311
|
}
|
|
10851
11312
|
function logWarning(message) {
|
|
10852
|
-
console.log(`${
|
|
11313
|
+
console.log(`${pc7.yellow("!")} ${message}`);
|
|
10853
11314
|
}
|
|
10854
11315
|
function logInfo(message) {
|
|
10855
|
-
console.log(`${
|
|
11316
|
+
console.log(`${pc7.cyan("i")} ${message}`);
|
|
10856
11317
|
}
|
|
10857
11318
|
function logMuted(message) {
|
|
10858
|
-
console.log(
|
|
11319
|
+
console.log(pc7.gray(message));
|
|
10859
11320
|
}
|
|
10860
11321
|
function printTitle(title, subtitle) {
|
|
10861
|
-
console.log(
|
|
11322
|
+
console.log(pc7.bold(title));
|
|
10862
11323
|
if (subtitle) {
|
|
10863
|
-
console.log(
|
|
11324
|
+
console.log(pc7.gray(subtitle));
|
|
10864
11325
|
}
|
|
10865
11326
|
}
|
|
10866
11327
|
function printKeyValue(label, value) {
|
|
10867
|
-
console.log(`${
|
|
11328
|
+
console.log(`${pc7.gray(`${label}:`.padEnd(13))} ${value}`);
|
|
10868
11329
|
}
|
|
10869
11330
|
function printChecks(items) {
|
|
10870
11331
|
for (const item of items) {
|
|
10871
|
-
console.log(` ${
|
|
11332
|
+
console.log(` ${pc7.green("\u2713")} ${item}`);
|
|
10872
11333
|
}
|
|
10873
11334
|
}
|
|
10874
11335
|
function printNextSteps(steps) {
|
|
10875
11336
|
if (steps.length === 0) return;
|
|
10876
11337
|
console.log(`
|
|
10877
|
-
${
|
|
11338
|
+
${pc7.bold("Next steps")}`);
|
|
10878
11339
|
for (const step of steps) {
|
|
10879
|
-
console.log(` ${
|
|
11340
|
+
console.log(` ${pc7.cyan("\u2022")} ${step}`);
|
|
10880
11341
|
}
|
|
10881
11342
|
}
|
|
10882
11343
|
function printValidationErrors(messages) {
|
|
10883
11344
|
logError("Tileflow config has errors.");
|
|
10884
11345
|
for (const message of messages) {
|
|
10885
|
-
const path = message.path ?
|
|
10886
|
-
console.log(` ${
|
|
11346
|
+
const path = message.path ? pc7.cyan(message.path) : pc7.cyan("(root)");
|
|
11347
|
+
console.log(` ${pc7.red("\u2715")} ${path} ${pc7.gray("-")} ${message.message}`);
|
|
10887
11348
|
}
|
|
10888
11349
|
}
|
|
10889
11350
|
function printAccountDetails(session) {
|
|
10890
|
-
printKeyValue("Account",
|
|
11351
|
+
printKeyValue("Account", pc7.bold(session.account.name));
|
|
10891
11352
|
printKeyValue("Email", session.account.email);
|
|
10892
11353
|
printKeyValue("API", link(session.apiOrigin));
|
|
10893
11354
|
printKeyValue("Expires", formatDate(session.expiresAt));
|
|
@@ -10916,11 +11377,11 @@ function printDeployedMaps(maps) {
|
|
|
10916
11377
|
const entries = Object.entries(maps);
|
|
10917
11378
|
if (entries.length === 0) return;
|
|
10918
11379
|
console.log(`
|
|
10919
|
-
${
|
|
11380
|
+
${pc7.bold("Maps")}`);
|
|
10920
11381
|
for (const [name, map] of entries) {
|
|
10921
11382
|
for (const [themeName, theme] of Object.entries(map.themes)) {
|
|
10922
11383
|
const label = `${name}/${themeName}`;
|
|
10923
|
-
console.log(` ${
|
|
11384
|
+
console.log(` ${pc7.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
|
|
10924
11385
|
}
|
|
10925
11386
|
}
|
|
10926
11387
|
}
|
|
@@ -10938,13 +11399,13 @@ function plural(count, singular, pluralForm = `${singular}s`) {
|
|
|
10938
11399
|
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
10939
11400
|
}
|
|
10940
11401
|
function command(value) {
|
|
10941
|
-
return
|
|
11402
|
+
return pc7.cyan(value);
|
|
10942
11403
|
}
|
|
10943
11404
|
function link(value) {
|
|
10944
|
-
return
|
|
11405
|
+
return pc7.cyan(value);
|
|
10945
11406
|
}
|
|
10946
11407
|
function pathLabel(value) {
|
|
10947
|
-
return
|
|
11408
|
+
return pc7.cyan(value);
|
|
10948
11409
|
}
|
|
10949
11410
|
function starterConfig() {
|
|
10950
11411
|
return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
|
|
@@ -10990,19 +11451,19 @@ function printHostedCompatibilityIssues(issues) {
|
|
|
10990
11451
|
}
|
|
10991
11452
|
function printMapStatus(status, apiUrl) {
|
|
10992
11453
|
printTitle("Tileflow status");
|
|
10993
|
-
printKeyValue("Map",
|
|
11454
|
+
printKeyValue("Map", pc7.bold(status.mapId));
|
|
10994
11455
|
console.log(`
|
|
10995
|
-
${
|
|
11456
|
+
${pc7.bold("Styles")}`);
|
|
10996
11457
|
if (status.styles.length === 0) {
|
|
10997
11458
|
logMuted(" No styles deployed.");
|
|
10998
11459
|
}
|
|
10999
11460
|
for (const style of status.styles) {
|
|
11000
11461
|
console.log(
|
|
11001
|
-
` ${
|
|
11462
|
+
` ${pc7.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes4(style.size).padStart(10)} ${pc7.gray(formatDate(style.uploaded))}`
|
|
11002
11463
|
);
|
|
11003
11464
|
}
|
|
11004
11465
|
}
|
|
11005
|
-
function
|
|
11466
|
+
function formatBytes4(bytes) {
|
|
11006
11467
|
if (bytes < 1024) return `${bytes} B`;
|
|
11007
11468
|
const units = ["KB", "MB", "GB", "TB"];
|
|
11008
11469
|
let value = bytes;
|