tileflow 0.1.0-alpha.14 → 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/dist/index.js +671 -302
- package/docs/command-families.md +4 -0
- package/docs/hosted-authentication-and-deploy.md +11 -9
- 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();
|
|
@@ -4128,14 +4128,11 @@ function printFeatureInspectError(error) {
|
|
|
4128
4128
|
}
|
|
4129
4129
|
|
|
4130
4130
|
// src/hosted-client.ts
|
|
4131
|
-
import {
|
|
4132
|
-
serializeTileflowFontBundleManifest,
|
|
4133
|
-
serializeTileflowModelBundleManifest
|
|
4134
|
-
} from "@tileflow/core";
|
|
4131
|
+
import { serializeTileflowFontBundleManifest } from "@tileflow/core";
|
|
4135
4132
|
|
|
4136
4133
|
// src/hosted-response.ts
|
|
4137
4134
|
import { z as z2 } from "zod";
|
|
4138
|
-
import { tileflowIconPackageLimits
|
|
4135
|
+
import { tileflowIconPackageLimits } from "@tileflow/core";
|
|
4139
4136
|
var maximumHostedResponseBytes = 1024 * 1024;
|
|
4140
4137
|
var safeTextSchema = z2.string().min(1).max(300).refine((value) => !/[\p{Cc}]/u.test(value));
|
|
4141
4138
|
var safeIdentifierSchema = z2.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u);
|
|
@@ -4155,7 +4152,18 @@ var hostedStyleDeploymentResponseSchema = z2.object({
|
|
|
4155
4152
|
styleUrl: publicHttpUrlSchema
|
|
4156
4153
|
}).strict()
|
|
4157
4154
|
).refine((themes) => Object.keys(themes).length > 0),
|
|
4158
|
-
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()
|
|
4159
4167
|
}).strict();
|
|
4160
4168
|
var hostedIconPackageResponseSchema = z2.object({
|
|
4161
4169
|
changed: z2.boolean().optional(),
|
|
@@ -4173,14 +4181,6 @@ var hostedFontBundleResponseSchema = z2.object({
|
|
|
4173
4181
|
id: z2.string().regex(/^fnb_[A-Za-z0-9_-]{16,64}$/u),
|
|
4174
4182
|
totalBytes: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER)
|
|
4175
4183
|
});
|
|
4176
|
-
var hostedModelBundleResponseSchema = z2.object({
|
|
4177
|
-
baseUrl: publicHttpUrlSchema,
|
|
4178
|
-
changed: z2.boolean().optional(),
|
|
4179
|
-
contentHash: z2.string().regex(/^[a-f0-9]{64}$/u),
|
|
4180
|
-
id: z2.string().regex(/^mdb_[A-Za-z0-9_-]{16,64}$/u),
|
|
4181
|
-
modelCount: z2.number().int().positive().max(tileflowModelBundleLimits.maxModelCount),
|
|
4182
|
-
totalBytes: z2.number().int().positive().max(tileflowModelBundleLimits.maxPackageBytes)
|
|
4183
|
-
});
|
|
4184
4184
|
var hostedStatusStyleSchema = z2.object({
|
|
4185
4185
|
environment: safeTextSchema,
|
|
4186
4186
|
key: safeStorageKeySchema,
|
|
@@ -4274,7 +4274,7 @@ async function startDeviceAuthorization(appUrl, body, options = {}) {
|
|
|
4274
4274
|
async function pollDeviceToken(appUrl, authorization, input, options = {}) {
|
|
4275
4275
|
const expiresAt = Date.now() + authorization.expiresIn * 1e3;
|
|
4276
4276
|
const intervalMs = Math.max(authorization.interval, 1) * 1e3;
|
|
4277
|
-
const wait = input.sleep ?? ((milliseconds) => new Promise((
|
|
4277
|
+
const wait = input.sleep ?? ((milliseconds) => new Promise((resolve13) => setTimeout(resolve13, milliseconds)));
|
|
4278
4278
|
while (Date.now() < expiresAt) {
|
|
4279
4279
|
await wait(intervalMs);
|
|
4280
4280
|
const response = await requestHostedJson(
|
|
@@ -4533,42 +4533,6 @@ async function uploadHostedFontBundle(api, bundle, options = {}) {
|
|
|
4533
4533
|
}
|
|
4534
4534
|
return { ok: true, value };
|
|
4535
4535
|
}
|
|
4536
|
-
async function uploadHostedModelBundle(api, bundle, options = {}) {
|
|
4537
|
-
const formData = new FormData();
|
|
4538
|
-
formData.append(
|
|
4539
|
-
"manifest",
|
|
4540
|
-
new Blob([serializeTileflowModelBundleManifest(bundle.manifest)], {
|
|
4541
|
-
type: "application/json"
|
|
4542
|
-
}),
|
|
4543
|
-
"manifest.json"
|
|
4544
|
-
);
|
|
4545
|
-
for (const file of bundle.files) {
|
|
4546
|
-
const bytes = new Uint8Array(file.source.byteLength);
|
|
4547
|
-
bytes.set(file.source);
|
|
4548
|
-
formData.append(
|
|
4549
|
-
`file:${encodeURIComponent(file.fileName)}`,
|
|
4550
|
-
new Blob([bytes.buffer], { type: file.contentType }),
|
|
4551
|
-
file.fileName
|
|
4552
|
-
);
|
|
4553
|
-
}
|
|
4554
|
-
const response = await requestHostedJson(
|
|
4555
|
-
api.apiUrl,
|
|
4556
|
-
`/v1/model-bundles/${encodeURIComponent(bundle.contentHash)}`,
|
|
4557
|
-
{ body: formData, headers: authorizationHeaders(api.apiKey, api.mapId), method: "PUT" },
|
|
4558
|
-
options
|
|
4559
|
-
);
|
|
4560
|
-
if (!response.ok) return { ok: false, status: response.status };
|
|
4561
|
-
const value = parseResponse(
|
|
4562
|
-
response,
|
|
4563
|
-
hostedModelBundleResponseSchema,
|
|
4564
|
-
"Model bundle upload response"
|
|
4565
|
-
);
|
|
4566
|
-
const publicBaseUrl = new URL(value.baseUrl);
|
|
4567
|
-
if (value.contentHash !== bundle.contentHash || value.modelCount !== bundle.manifest.models.length || value.totalBytes !== bundle.manifest.models.reduce((total, model) => total + model.byteLength, 0) || publicBaseUrl.pathname !== `/model-bundles/${value.id}` || publicBaseUrl.search !== "") {
|
|
4568
|
-
throw new Error("Model bundle upload response did not confirm the submitted bundle.");
|
|
4569
|
-
}
|
|
4570
|
-
return { ok: true, value };
|
|
4571
|
-
}
|
|
4572
4536
|
async function fetchHostedMapStatus(api, options = {}) {
|
|
4573
4537
|
const response = await requestHostedJson(
|
|
4574
4538
|
api.apiUrl,
|
|
@@ -4610,7 +4574,7 @@ async function withHostedResponse(apiUrl, path, init, options, consume) {
|
|
|
4610
4574
|
}
|
|
4611
4575
|
function raceWithAbort(operation, signal) {
|
|
4612
4576
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
4613
|
-
return new Promise((
|
|
4577
|
+
return new Promise((resolve13, reject) => {
|
|
4614
4578
|
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
4615
4579
|
const onAbort = () => {
|
|
4616
4580
|
cleanup();
|
|
@@ -4620,7 +4584,7 @@ function raceWithAbort(operation, signal) {
|
|
|
4620
4584
|
operation.then(
|
|
4621
4585
|
(value) => {
|
|
4622
4586
|
cleanup();
|
|
4623
|
-
|
|
4587
|
+
resolve13(value);
|
|
4624
4588
|
},
|
|
4625
4589
|
(error) => {
|
|
4626
4590
|
cleanup();
|
|
@@ -4830,12 +4794,12 @@ function cloneStyle(style) {
|
|
|
4830
4794
|
}
|
|
4831
4795
|
|
|
4832
4796
|
// src/model-assets.ts
|
|
4797
|
+
import { tileflowTeamModelUrl } from "@tileflow/core";
|
|
4833
4798
|
import {
|
|
4834
|
-
createTileflowModelBundle,
|
|
4835
4799
|
prepareTileflowMapTreeModels
|
|
4836
4800
|
} from "@tileflow/dev/icons";
|
|
4837
4801
|
async function prepareModelAssets(project, options) {
|
|
4838
|
-
const prepared = { assets: {},
|
|
4802
|
+
const prepared = { assets: {}, buildings: {}, identities: {} };
|
|
4839
4803
|
for (const [name, map] of Object.entries(project.maps)) {
|
|
4840
4804
|
if (!map.models?.length) continue;
|
|
4841
4805
|
const models = await prepareTileflowMapTreeModels(map.models, {
|
|
@@ -4844,25 +4808,36 @@ async function prepareModelAssets(project, options) {
|
|
|
4844
4808
|
configPath: `maps.${name}.models`
|
|
4845
4809
|
});
|
|
4846
4810
|
prepared.assets[name] = { models: models.models };
|
|
4847
|
-
|
|
4848
|
-
if (bundle) prepared.bundles[name] = bundle;
|
|
4811
|
+
if (Object.keys(models.buildings).length > 0) prepared.buildings[name] = models.buildings;
|
|
4849
4812
|
if (models.identities.length > 0) prepared.identities[name] = models.identities;
|
|
4850
4813
|
}
|
|
4851
4814
|
return prepared;
|
|
4852
4815
|
}
|
|
4853
|
-
function mapAssetsWithBuildingModels(prepared,
|
|
4816
|
+
function mapAssetsWithBuildingModels(prepared, target) {
|
|
4854
4817
|
const assets = { ...prepared.assets };
|
|
4855
|
-
for (const [mapName,
|
|
4856
|
-
const base =
|
|
4818
|
+
for (const [mapName, buildings] of Object.entries(prepared.buildings)) {
|
|
4819
|
+
const base = target.hosted ? "" : target.localBase(mapName).replace(/\/+$/u, "");
|
|
4857
4820
|
assets[mapName] = {
|
|
4858
4821
|
...assets[mapName],
|
|
4859
4822
|
buildingModels: Object.fromEntries(
|
|
4860
|
-
|
|
4823
|
+
Object.entries(buildings).map(([id, model]) => [
|
|
4824
|
+
id,
|
|
4825
|
+
target.hosted ? tileflowTeamModelUrl(id) : `${base}/${model.fileName}`
|
|
4826
|
+
])
|
|
4861
4827
|
)
|
|
4862
4828
|
};
|
|
4863
4829
|
}
|
|
4864
4830
|
return assets;
|
|
4865
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
|
+
}
|
|
4866
4841
|
|
|
4867
4842
|
// src/hosted-native-deploy.ts
|
|
4868
4843
|
function failure(message) {
|
|
@@ -4902,7 +4877,7 @@ async function prepareNativeHostedDeploy(options) {
|
|
|
4902
4877
|
const iconPackage = binding ? compiledIcons.packages.find((entry) => entry.contentHash === binding.packageHash) : void 0;
|
|
4903
4878
|
const sprite = binding ? `${apiOrigin}/sprites/preflight/${binding.packageHash}/sprite` : void 0;
|
|
4904
4879
|
const modelAssets = await prepareModelAssets(project, { baseDirectory, target: "hosted" });
|
|
4905
|
-
if (modelAssets.
|
|
4880
|
+
if (modelAssets.buildings[mapName])
|
|
4906
4881
|
throw failure(
|
|
4907
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."
|
|
4908
4883
|
);
|
|
@@ -6942,6 +6917,421 @@ async function readCurrentPins(contents) {
|
|
|
6942
6917
|
return (await parseTileflowIconsLockfile(contents)).sets;
|
|
6943
6918
|
}
|
|
6944
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
|
+
|
|
6945
7335
|
// src/native-preview-command.ts
|
|
6946
7336
|
import { tileflowNativeProfile } from "@tileflow/core/native-profile";
|
|
6947
7337
|
import {
|
|
@@ -7030,7 +7420,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
|
|
|
7030
7420
|
}
|
|
7031
7421
|
|
|
7032
7422
|
// src/project-commands.ts
|
|
7033
|
-
import
|
|
7423
|
+
import pc5 from "picocolors";
|
|
7034
7424
|
function registerProjectCommands(program2, dependencies) {
|
|
7035
7425
|
const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
|
|
7036
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) => {
|
|
@@ -7046,12 +7436,12 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7046
7436
|
};
|
|
7047
7437
|
if (options.json) return emitJson2(document);
|
|
7048
7438
|
if (!result.items.length) {
|
|
7049
|
-
console.log(
|
|
7439
|
+
console.log(pc5.gray("No managed destinations found."));
|
|
7050
7440
|
return;
|
|
7051
7441
|
}
|
|
7052
7442
|
for (const target of result.items) {
|
|
7053
7443
|
console.log(
|
|
7054
|
-
` ${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)") : ""}`
|
|
7055
7445
|
);
|
|
7056
7446
|
}
|
|
7057
7447
|
});
|
|
@@ -7104,8 +7494,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7104
7494
|
...asRecord2(result.body)
|
|
7105
7495
|
};
|
|
7106
7496
|
if (options.json) return emitJson2(document);
|
|
7107
|
-
console.log(`${
|
|
7108
|
-
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}.`));
|
|
7109
7499
|
}
|
|
7110
7500
|
);
|
|
7111
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) => {
|
|
@@ -7127,8 +7517,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7127
7517
|
if (!isProjectItem(project)) {
|
|
7128
7518
|
return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
|
|
7129
7519
|
}
|
|
7130
|
-
console.log(`${
|
|
7131
|
-
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"));
|
|
7132
7522
|
});
|
|
7133
7523
|
for (const archived of [true, false]) {
|
|
7134
7524
|
const verb = archived ? "archive" : "unarchive";
|
|
@@ -7155,7 +7545,7 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
7155
7545
|
...asRecord2(result.body)
|
|
7156
7546
|
};
|
|
7157
7547
|
if (options.json) return emitJson2(document);
|
|
7158
|
-
console.log(`${
|
|
7548
|
+
console.log(`${pc5.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
|
|
7159
7549
|
});
|
|
7160
7550
|
}
|
|
7161
7551
|
}
|
|
@@ -7266,7 +7656,7 @@ function emitLocalFailure(json2, code, message, details = {}) {
|
|
|
7266
7656
|
console.error(message);
|
|
7267
7657
|
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
7268
7658
|
} else {
|
|
7269
|
-
console.error(`${
|
|
7659
|
+
console.error(`${pc5.red("\u2715")} ${message}`);
|
|
7270
7660
|
}
|
|
7271
7661
|
process.exitCode = 1;
|
|
7272
7662
|
}
|
|
@@ -7301,7 +7691,7 @@ function safeMessage2(error) {
|
|
|
7301
7691
|
}
|
|
7302
7692
|
|
|
7303
7693
|
// src/renderer-artifact-command.ts
|
|
7304
|
-
import { resolve as
|
|
7694
|
+
import { resolve as resolve9 } from "path";
|
|
7305
7695
|
import { auditTileflowMapThemeValues } from "@tileflow/core";
|
|
7306
7696
|
import {
|
|
7307
7697
|
createTileflowNativeDiagnostic,
|
|
@@ -7389,7 +7779,7 @@ async function runRendererArtifactCommand(command2, options) {
|
|
|
7389
7779
|
if (artifacts.nativeBuild)
|
|
7390
7780
|
console.log("Profile: native-v1 (static artifacts; no device qualification).");
|
|
7391
7781
|
if (command2 === "build")
|
|
7392
|
-
console.log(`Output: ${
|
|
7782
|
+
console.log(`Output: ${resolve9(outDir, ...renderer === "native" ? ["native"] : [])}`);
|
|
7393
7783
|
}
|
|
7394
7784
|
} catch (error) {
|
|
7395
7785
|
const document = createTileflowCommandFailureDocument6(command2, error, process.cwd(), {
|
|
@@ -7410,15 +7800,15 @@ async function runRendererArtifactCommand(command2, options) {
|
|
|
7410
7800
|
}
|
|
7411
7801
|
|
|
7412
7802
|
// src/tileset-command.ts
|
|
7413
|
-
import
|
|
7414
|
-
import { z as
|
|
7803
|
+
import pc6 from "picocolors";
|
|
7804
|
+
import { z as z7 } from "zod";
|
|
7415
7805
|
import { serializeCanonicalJson as serializeCanonicalJson6 } from "@tileflow/core";
|
|
7416
7806
|
import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
|
|
7417
7807
|
|
|
7418
7808
|
// src/tileset-upload.ts
|
|
7419
7809
|
import { createHash as createHash3 } from "crypto";
|
|
7420
7810
|
import { open as open5, stat } from "fs/promises";
|
|
7421
|
-
import { z as
|
|
7811
|
+
import { z as z6 } from "zod";
|
|
7422
7812
|
import { serializeCanonicalJson as serializeCanonicalJson5 } from "@tileflow/core";
|
|
7423
7813
|
var teamTilesetPartSize = 16777216;
|
|
7424
7814
|
var maximumTeamTilesetBytes = 4e9;
|
|
@@ -7979,36 +8369,36 @@ function asRecord3(value) {
|
|
|
7979
8369
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7980
8370
|
}
|
|
7981
8371
|
function defaultSleep(milliseconds) {
|
|
7982
|
-
return new Promise((
|
|
7983
|
-
}
|
|
7984
|
-
var identifierSchema =
|
|
7985
|
-
var uploadPartStatusSchema =
|
|
7986
|
-
byteCount:
|
|
7987
|
-
partNumber:
|
|
7988
|
-
uploaded:
|
|
7989
|
-
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"])
|
|
7990
8380
|
}).strict();
|
|
7991
|
-
var publishedVersionSchema =
|
|
7992
|
-
byteCount:
|
|
7993
|
-
contentHash:
|
|
7994
|
-
contentHashAlgorithm:
|
|
7995
|
-
publishedVersion:
|
|
7996
|
-
schemaVersion:
|
|
7997
|
-
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"),
|
|
7998
8388
|
versionId: identifierSchema.regex(/^tsv_[A-Za-z0-9_-]+$/u)
|
|
7999
8389
|
}).strict();
|
|
8000
|
-
var uploadControlSchema =
|
|
8001
|
-
byteCount:
|
|
8002
|
-
contentHash:
|
|
8003
|
-
contentHashAlgorithm:
|
|
8004
|
-
expiresAt:
|
|
8005
|
-
failure:
|
|
8006
|
-
partCount:
|
|
8007
|
-
parts:
|
|
8008
|
-
partSize:
|
|
8009
|
-
publishedVersion:
|
|
8010
|
-
schemaVersion:
|
|
8011
|
-
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([
|
|
8012
8402
|
"aborted",
|
|
8013
8403
|
"aborting",
|
|
8014
8404
|
"expired",
|
|
@@ -8021,32 +8411,32 @@ var uploadControlSchema = z5.object({
|
|
|
8021
8411
|
"uploading",
|
|
8022
8412
|
"validating"
|
|
8023
8413
|
]),
|
|
8024
|
-
uploadId:
|
|
8025
|
-
versionId:
|
|
8414
|
+
uploadId: z6.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
|
|
8415
|
+
versionId: z6.null()
|
|
8026
8416
|
}).strict();
|
|
8027
|
-
var uploadSessionSchema =
|
|
8028
|
-
var authorizationSchema =
|
|
8029
|
-
expiresAt:
|
|
8030
|
-
headers:
|
|
8031
|
-
"Content-Length":
|
|
8032
|
-
"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)
|
|
8033
8423
|
}).strict(),
|
|
8034
|
-
method:
|
|
8035
|
-
partNumber:
|
|
8036
|
-
url:
|
|
8424
|
+
method: z6.literal("PUT"),
|
|
8425
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
8426
|
+
url: z6.url().max(8192).refine((value) => {
|
|
8037
8427
|
const url = new URL(value);
|
|
8038
8428
|
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
8039
8429
|
})
|
|
8040
8430
|
}).strict();
|
|
8041
|
-
var authorizationResponseSchema =
|
|
8042
|
-
authorizations:
|
|
8043
|
-
schemaVersion:
|
|
8431
|
+
var authorizationResponseSchema = z6.object({
|
|
8432
|
+
authorizations: z6.array(authorizationSchema).min(1).max(16),
|
|
8433
|
+
schemaVersion: z6.literal(1)
|
|
8044
8434
|
}).strict();
|
|
8045
|
-
var partReceiptResponseSchema =
|
|
8046
|
-
changed:
|
|
8047
|
-
etag:
|
|
8048
|
-
partNumber:
|
|
8049
|
-
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)
|
|
8050
8440
|
}).strict();
|
|
8051
8441
|
|
|
8052
8442
|
// src/tileset-command.ts
|
|
@@ -8069,7 +8459,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8069
8459
|
`);
|
|
8070
8460
|
return;
|
|
8071
8461
|
}
|
|
8072
|
-
console.log(`${
|
|
8462
|
+
console.log(`${pc6.green("\u2713")} PMTiles archive is valid.`);
|
|
8073
8463
|
console.log(` Type: ${inspection.contract.tileType}`);
|
|
8074
8464
|
console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
|
|
8075
8465
|
console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
|
|
@@ -8079,7 +8469,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8079
8469
|
);
|
|
8080
8470
|
}
|
|
8081
8471
|
for (const warning of inspection.warnings) {
|
|
8082
|
-
console.log(`${
|
|
8472
|
+
console.log(`${pc6.yellow("!")} ${warning.message}`);
|
|
8083
8473
|
}
|
|
8084
8474
|
} catch (error) {
|
|
8085
8475
|
const record = asRecord4(error);
|
|
@@ -8092,7 +8482,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
|
|
|
8092
8482
|
`
|
|
8093
8483
|
);
|
|
8094
8484
|
} else {
|
|
8095
|
-
console.error(`${
|
|
8485
|
+
console.error(`${pc6.red("Error:")} ${message}`);
|
|
8096
8486
|
}
|
|
8097
8487
|
process.exitCode = 1;
|
|
8098
8488
|
}
|
|
@@ -8180,7 +8570,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8180
8570
|
};
|
|
8181
8571
|
if (options.json) return emitJson(document);
|
|
8182
8572
|
console.log(
|
|
8183
|
-
`${
|
|
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"}.`
|
|
8184
8574
|
);
|
|
8185
8575
|
} catch (error) {
|
|
8186
8576
|
if (error instanceof TeamTilesetUploadError) {
|
|
@@ -8200,9 +8590,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8200
8590
|
const items = await fetchAllTeamTilesets(authority);
|
|
8201
8591
|
const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
|
|
8202
8592
|
if (options.json) return emitJson(document);
|
|
8203
|
-
if (items.length === 0) return console.log(
|
|
8593
|
+
if (items.length === 0) return console.log(pc6.gray("No Team tilesets."));
|
|
8204
8594
|
for (const item of items) {
|
|
8205
|
-
console.log(` ${
|
|
8595
|
+
console.log(` ${pc6.bold(item.logicalName)} ${pc6.gray(item.status)}`);
|
|
8206
8596
|
}
|
|
8207
8597
|
} catch {
|
|
8208
8598
|
emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
|
|
@@ -8230,7 +8620,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8230
8620
|
const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
|
|
8231
8621
|
if (options.json) return emitJson(document);
|
|
8232
8622
|
const item = parsed.tileset;
|
|
8233
|
-
console.log(`${
|
|
8623
|
+
console.log(`${pc6.bold(item.name)} ${pc6.gray(item.state)}`);
|
|
8234
8624
|
console.log(` ID: ${item.id}`);
|
|
8235
8625
|
console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
|
|
8236
8626
|
} catch {
|
|
@@ -8281,7 +8671,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
8281
8671
|
const parsed = tilesetPurgeResponseSchema.parse(response.body);
|
|
8282
8672
|
const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
|
|
8283
8673
|
if (options.json) return emitJson(document);
|
|
8284
|
-
console.log(`${
|
|
8674
|
+
console.log(`${pc6.green("\u2713")} Purge ${parsed.reclamation}.`);
|
|
8285
8675
|
} catch {
|
|
8286
8676
|
emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
|
|
8287
8677
|
}
|
|
@@ -8343,69 +8733,69 @@ function inspectionOptionError(message) {
|
|
|
8343
8733
|
function asRecord4(value) {
|
|
8344
8734
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
8345
8735
|
}
|
|
8346
|
-
var safeIdentifierSchema2 =
|
|
8347
|
-
var physicalTilesetIdSchema =
|
|
8348
|
-
var cursorSchema2 =
|
|
8349
|
-
var tilesetInventoryItemSchema =
|
|
8350
|
-
currentVersion:
|
|
8351
|
-
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(),
|
|
8352
8742
|
id: physicalTilesetIdSchema,
|
|
8353
|
-
logicalName:
|
|
8354
|
-
name:
|
|
8355
|
-
retainedBytes:
|
|
8356
|
-
retainedDeploymentCount:
|
|
8357
|
-
status:
|
|
8358
|
-
updatedAt:
|
|
8359
|
-
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)
|
|
8360
8750
|
}).strict();
|
|
8361
|
-
var tilesetListResponseSchema =
|
|
8751
|
+
var tilesetListResponseSchema = z7.object({
|
|
8362
8752
|
nextCursor: cursorSchema2.optional(),
|
|
8363
|
-
schemaVersion:
|
|
8364
|
-
tilesets:
|
|
8753
|
+
schemaVersion: z7.literal(1),
|
|
8754
|
+
tilesets: z7.array(tilesetInventoryItemSchema).max(100)
|
|
8365
8755
|
}).strict();
|
|
8366
|
-
var tilesetDependencySchema =
|
|
8367
|
-
active:
|
|
8756
|
+
var tilesetDependencySchema = z7.object({
|
|
8757
|
+
active: z7.boolean(),
|
|
8368
8758
|
deploymentId: safeIdentifierSchema2,
|
|
8369
|
-
deploymentVersion:
|
|
8370
|
-
environment:
|
|
8759
|
+
deploymentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
8760
|
+
environment: z7.string().min(1).max(64),
|
|
8371
8761
|
mapId: safeIdentifierSchema2,
|
|
8372
8762
|
sourceId: safeIdentifierSchema2
|
|
8373
8763
|
}).strict();
|
|
8374
|
-
var tilesetDetailResponseSchema =
|
|
8375
|
-
schemaVersion:
|
|
8376
|
-
tileset:
|
|
8377
|
-
attribution:
|
|
8378
|
-
currentVersion:
|
|
8379
|
-
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),
|
|
8380
8770
|
dependencyNextCursor: cursorSchema2,
|
|
8381
8771
|
id: physicalTilesetIdSchema,
|
|
8382
|
-
name:
|
|
8383
|
-
slug:
|
|
8384
|
-
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"])
|
|
8385
8775
|
}).strict()
|
|
8386
8776
|
}).strict();
|
|
8387
|
-
var purgeDependencySchema =
|
|
8777
|
+
var purgeDependencySchema = z7.object({
|
|
8388
8778
|
deploymentId: safeIdentifierSchema2,
|
|
8389
8779
|
mapId: safeIdentifierSchema2,
|
|
8390
8780
|
sourceId: safeIdentifierSchema2
|
|
8391
8781
|
}).strict();
|
|
8392
|
-
var tilesetInUseResponseSchema =
|
|
8393
|
-
code:
|
|
8394
|
-
dependencies:
|
|
8395
|
-
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"),
|
|
8396
8786
|
nextCursor: cursorSchema2
|
|
8397
8787
|
}).strict();
|
|
8398
|
-
var tilesetPurgeResponseSchema =
|
|
8399
|
-
deliveryFence:
|
|
8400
|
-
pendingVersions:
|
|
8401
|
-
reclamation:
|
|
8402
|
-
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"]),
|
|
8403
8793
|
tilesetId: physicalTilesetIdSchema
|
|
8404
8794
|
}).strict();
|
|
8405
8795
|
|
|
8406
8796
|
// src/visual-command.ts
|
|
8407
|
-
import { lstat as
|
|
8408
|
-
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";
|
|
8409
8799
|
import {
|
|
8410
8800
|
analyzeTileflowCaptureReference,
|
|
8411
8801
|
compareTileflowCaptureToBaseline,
|
|
@@ -8490,14 +8880,14 @@ async function runVisualAnalyze(scene, options) {
|
|
|
8490
8880
|
validateVisualSelection([scene]);
|
|
8491
8881
|
const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
|
|
8492
8882
|
const cwd = process.cwd();
|
|
8493
|
-
const referencePath =
|
|
8883
|
+
const referencePath = resolve10(cwd, options.reference);
|
|
8494
8884
|
const referencePng = await readVisualReference(referencePath);
|
|
8495
8885
|
const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
|
|
8496
8886
|
if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
|
|
8497
8887
|
const outputDirectory = await resolveVisualDirectory(cwd, options.outputDir, "output");
|
|
8498
|
-
const actualPath =
|
|
8499
|
-
const managedDiffPath =
|
|
8500
|
-
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`);
|
|
8501
8891
|
assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
|
|
8502
8892
|
const controller = installSignalAbortController();
|
|
8503
8893
|
const session = createTileflowCaptureSession3({
|
|
@@ -8586,15 +8976,15 @@ async function runVisualDiff(scenes, options) {
|
|
|
8586
8976
|
capture,
|
|
8587
8977
|
files.kind === "complete" ? files.baseline : void 0
|
|
8588
8978
|
);
|
|
8589
|
-
const actualPath =
|
|
8590
|
-
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;
|
|
8591
8981
|
prepared.push({
|
|
8592
8982
|
capture,
|
|
8593
8983
|
comparison,
|
|
8594
8984
|
baselinePath: files.pngPath,
|
|
8595
8985
|
actualPath,
|
|
8596
8986
|
diffPath,
|
|
8597
|
-
reportPath:
|
|
8987
|
+
reportPath: resolve10(outputDirectory.path, `${capture.scene}.visual.json`)
|
|
8598
8988
|
});
|
|
8599
8989
|
}
|
|
8600
8990
|
const document = createTileflowVisualDiffJson(prepared, cwd);
|
|
@@ -8614,7 +9004,7 @@ async function runVisualDiff(scenes, options) {
|
|
|
8614
9004
|
}
|
|
8615
9005
|
]),
|
|
8616
9006
|
removePaths: prepared.flatMap(
|
|
8617
|
-
(item) => item.diffPath ? [] : [
|
|
9007
|
+
(item) => item.diffPath ? [] : [resolve10(outputDirectory.path, `${item.capture.scene}.diff.png`)]
|
|
8618
9008
|
)
|
|
8619
9009
|
});
|
|
8620
9010
|
if (options.json) {
|
|
@@ -8735,7 +9125,7 @@ async function resolvePreviousBaseline(capture, files) {
|
|
|
8735
9125
|
}
|
|
8736
9126
|
}
|
|
8737
9127
|
async function readBaselineFiles(directory, scene) {
|
|
8738
|
-
const pngPath =
|
|
9128
|
+
const pngPath = resolve10(directory.path, `${scene}.png`);
|
|
8739
9129
|
const receiptPath = captureReceiptPath(pngPath);
|
|
8740
9130
|
const [png, receipt] = await Promise.all([
|
|
8741
9131
|
readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits2.maximumPngBytes),
|
|
@@ -8750,10 +9140,10 @@ async function readBaselineFiles(directory, scene) {
|
|
|
8750
9140
|
return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
|
|
8751
9141
|
}
|
|
8752
9142
|
async function readOptionalArtifact(path, boundary, maximumBytes) {
|
|
8753
|
-
await assertNoSymlinkComponents(
|
|
9143
|
+
await assertNoSymlinkComponents(dirname12(path), boundary);
|
|
8754
9144
|
let info;
|
|
8755
9145
|
try {
|
|
8756
|
-
info = await
|
|
9146
|
+
info = await lstat6(path);
|
|
8757
9147
|
} catch (error) {
|
|
8758
9148
|
if (isMissingFile5(error)) return void 0;
|
|
8759
9149
|
throw error;
|
|
@@ -8773,7 +9163,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
|
|
|
8773
9163
|
async function readVisualReference(path) {
|
|
8774
9164
|
let info;
|
|
8775
9165
|
try {
|
|
8776
|
-
info = await
|
|
9166
|
+
info = await lstat6(path);
|
|
8777
9167
|
} catch (error) {
|
|
8778
9168
|
throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
|
|
8779
9169
|
cause: error
|
|
@@ -8795,12 +9185,12 @@ async function readVisualReference(path) {
|
|
|
8795
9185
|
return new Uint8Array(await readFile6(path));
|
|
8796
9186
|
}
|
|
8797
9187
|
async function resolveVisualDirectory(cwd, value, kind) {
|
|
8798
|
-
const path =
|
|
9188
|
+
const path = resolve10(cwd, value);
|
|
8799
9189
|
if (path === cwd || path === parse2(path).root) {
|
|
8800
9190
|
throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
|
|
8801
9191
|
}
|
|
8802
9192
|
if (kind === "baseline") {
|
|
8803
|
-
for (const managed of [
|
|
9193
|
+
for (const managed of [resolve10(cwd, ".tileflow/captures"), resolve10(cwd, ".tileflow/diffs")]) {
|
|
8804
9194
|
if (pathsOverlap2(path, managed)) {
|
|
8805
9195
|
throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
|
|
8806
9196
|
}
|
|
@@ -8809,7 +9199,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
|
|
|
8809
9199
|
const boundary = isInside2(cwd, path) ? cwd : path;
|
|
8810
9200
|
await assertNoSymlinkComponents(path, boundary);
|
|
8811
9201
|
try {
|
|
8812
|
-
const info = await
|
|
9202
|
+
const info = await lstat6(path);
|
|
8813
9203
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
8814
9204
|
throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
|
|
8815
9205
|
}
|
|
@@ -8836,10 +9226,10 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
|
|
|
8836
9226
|
}
|
|
8837
9227
|
}
|
|
8838
9228
|
function portablePathIdentity2(path) {
|
|
8839
|
-
return
|
|
9229
|
+
return resolve10(path).normalize("NFC").toLowerCase();
|
|
8840
9230
|
}
|
|
8841
9231
|
function isInside2(parent, child) {
|
|
8842
|
-
const path =
|
|
9232
|
+
const path = relative5(parent, child);
|
|
8843
9233
|
return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
|
|
8844
9234
|
}
|
|
8845
9235
|
function isMissingFile5(error) {
|
|
@@ -8848,8 +9238,8 @@ function isMissingFile5(error) {
|
|
|
8848
9238
|
|
|
8849
9239
|
// src/workbench-service.ts
|
|
8850
9240
|
import { opendir, stat as stat2 } from "fs/promises";
|
|
8851
|
-
import { resolve as
|
|
8852
|
-
import { z as
|
|
9241
|
+
import { resolve as resolve11 } from "path";
|
|
9242
|
+
import { z as z8 } from "zod";
|
|
8853
9243
|
import {
|
|
8854
9244
|
compareTileflowCapturesForReview as compareTileflowCapturesForReview3,
|
|
8855
9245
|
createTileflowCaptureSession as createTileflowCaptureSession4,
|
|
@@ -8862,22 +9252,22 @@ import {
|
|
|
8862
9252
|
} from "@tileflow/core";
|
|
8863
9253
|
import { resolveTileflowPreview } from "@tileflow/dev/preview";
|
|
8864
9254
|
var prefix = "/__workbench";
|
|
8865
|
-
var captureInput =
|
|
8866
|
-
side:
|
|
8867
|
-
generation:
|
|
9255
|
+
var captureInput = z8.object({
|
|
9256
|
+
side: z8.enum(["left", "right"]).default("left"),
|
|
9257
|
+
generation: z8.number().int().nonnegative(),
|
|
8868
9258
|
name: tileflowCaptureSceneNameSchema2,
|
|
8869
9259
|
definition: tileflowCaptureSceneSchema3
|
|
8870
9260
|
}).strict();
|
|
8871
|
-
var compareInput =
|
|
8872
|
-
var deleteInput =
|
|
8873
|
-
var contextInput =
|
|
8874
|
-
client:
|
|
8875
|
-
side:
|
|
8876
|
-
generation:
|
|
8877
|
-
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())
|
|
8878
9268
|
}).strict();
|
|
8879
9269
|
function createWorkbenchService(options) {
|
|
8880
|
-
const directory =
|
|
9270
|
+
const directory = resolve11(options.cwd, captureRecordDirectory);
|
|
8881
9271
|
const contexts = /* @__PURE__ */ new Map();
|
|
8882
9272
|
let captureSession = options.captureSession;
|
|
8883
9273
|
let pending;
|
|
@@ -8949,7 +9339,7 @@ function createWorkbenchService(options) {
|
|
|
8949
9339
|
break;
|
|
8950
9340
|
}
|
|
8951
9341
|
if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) continue;
|
|
8952
|
-
const info = await stat2(
|
|
9342
|
+
const info = await stat2(resolve11(directory, entry.name));
|
|
8953
9343
|
entries.push({ id: entry.name.slice(0, -5), modified: info.mtimeMs });
|
|
8954
9344
|
}
|
|
8955
9345
|
} catch (error) {
|
|
@@ -9088,7 +9478,7 @@ function createWorkbenchService(options) {
|
|
|
9088
9478
|
});
|
|
9089
9479
|
}
|
|
9090
9480
|
if (path === "/captures") {
|
|
9091
|
-
const offset =
|
|
9481
|
+
const offset = z8.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
|
|
9092
9482
|
return json(await history(offset));
|
|
9093
9483
|
}
|
|
9094
9484
|
const image = /^\/captures\/([a-f0-9]{64})\.png$/u.exec(path);
|
|
@@ -9219,17 +9609,18 @@ var packageJson = JSON.parse(
|
|
|
9219
9609
|
var program = new Command();
|
|
9220
9610
|
var defaultApiUrl = defaultTileflowApiUrl;
|
|
9221
9611
|
var defaultAppUrl = "https://tileflow.dev";
|
|
9222
|
-
var defaultConfigPath =
|
|
9612
|
+
var defaultConfigPath = defaultTileflowConfigPath2;
|
|
9223
9613
|
var defaultManifestPath = defaultTileflowManifestPath;
|
|
9224
|
-
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();
|
|
9225
9615
|
program.addHelpText("after", "\n -V, --version output the version number\n");
|
|
9226
9616
|
registerDescribeCommand(program);
|
|
9227
9617
|
registerLanguageCommand(program);
|
|
9228
9618
|
registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
9229
9619
|
registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
9620
|
+
registerModelCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
9230
9621
|
program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
|
|
9231
|
-
const configPath =
|
|
9232
|
-
const ignorePath =
|
|
9622
|
+
const configPath = resolve12(process.cwd(), defaultConfigPath);
|
|
9623
|
+
const ignorePath = resolve12(process.cwd(), ".gitignore");
|
|
9233
9624
|
if (existsSync(configPath) && !options.force) {
|
|
9234
9625
|
logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
|
|
9235
9626
|
printNextSteps([`Run ${command("tileflow init --force")} to replace it.`]);
|
|
@@ -9245,9 +9636,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
9245
9636
|
}
|
|
9246
9637
|
logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
|
|
9247
9638
|
printKeyValue("Path", pathLabel(configPath));
|
|
9248
|
-
printKeyValue("Map",
|
|
9639
|
+
printKeyValue("Map", pc7.bold("madrid"));
|
|
9249
9640
|
printNextSteps([
|
|
9250
|
-
`Install ${
|
|
9641
|
+
`Install ${pc7.cyan("@tileflow/core")} and ${pc7.cyan("@tileflow/maps")} in this project if needed.`,
|
|
9251
9642
|
`Preview locally with ${command("tileflow preview")}.`,
|
|
9252
9643
|
`Validate the config with ${command("tileflow validate")}.`
|
|
9253
9644
|
]);
|
|
@@ -9383,12 +9774,12 @@ program.command("validate").description("Validate a Tileflow config").option("-c
|
|
|
9383
9774
|
try {
|
|
9384
9775
|
if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
|
|
9385
9776
|
const loaded = await withTileflowConfigSecretsHidden(
|
|
9386
|
-
() =>
|
|
9777
|
+
() => loadTileflowConfigWithInputs5(options.config)
|
|
9387
9778
|
);
|
|
9388
9779
|
const project = loaded.project;
|
|
9389
|
-
const baseDirectory =
|
|
9780
|
+
const baseDirectory = dirname13(loaded.configFile);
|
|
9390
9781
|
failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
|
|
9391
|
-
|
|
9782
|
+
assertValidTileflowConfig4(project);
|
|
9392
9783
|
const mapNames = getTileflowMapNames5(project).sort();
|
|
9393
9784
|
failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
|
|
9394
9785
|
const compiledIcons = await compileTileflowIconPackages3(project, {
|
|
@@ -9406,7 +9797,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
|
|
|
9406
9797
|
(binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`,
|
|
9407
9798
|
mapAssetsWithBuildingModels(
|
|
9408
9799
|
modelAssets,
|
|
9409
|
-
|
|
9800
|
+
options.target === "hosted" ? { hosted: true } : { localBase: (mapName) => `/tileflow/models/${mapName}` }
|
|
9410
9801
|
)
|
|
9411
9802
|
);
|
|
9412
9803
|
failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
|
|
@@ -9526,7 +9917,7 @@ program.command("build").description("Generate static Tileflow styles").option("
|
|
|
9526
9917
|
})
|
|
9527
9918
|
);
|
|
9528
9919
|
logSuccess("Built Tileflow artifacts.");
|
|
9529
|
-
printKeyValue("Output", pathLabel(
|
|
9920
|
+
printKeyValue("Output", pathLabel(resolve12(process.cwd(), options.out)));
|
|
9530
9921
|
}
|
|
9531
9922
|
);
|
|
9532
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(
|
|
@@ -9839,7 +10230,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9839
10230
|
);
|
|
9840
10231
|
if (result) {
|
|
9841
10232
|
logSuccess(
|
|
9842
|
-
`${result.changed ? "Published" : "Unchanged"} ${
|
|
10233
|
+
`${result.changed ? "Published" : "Unchanged"} ${pc7.bold(result.mapName)} web + native-v1 (v${result.version}).`
|
|
9843
10234
|
);
|
|
9844
10235
|
printKeyValue("Manifest", pathLabel(result.manifestPath));
|
|
9845
10236
|
printKeyValue("Native", link(result.nativeManifestUrl));
|
|
@@ -9848,11 +10239,11 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9848
10239
|
}
|
|
9849
10240
|
logInfo(`Deploying ${pathLabel(options.config)}.`);
|
|
9850
10241
|
const loaded = await withTileflowConfigSecretsHidden(
|
|
9851
|
-
() =>
|
|
10242
|
+
() => loadTileflowConfigWithInputs5(options.config)
|
|
9852
10243
|
);
|
|
9853
10244
|
const project = loaded.project;
|
|
9854
|
-
const baseDirectory =
|
|
9855
|
-
|
|
10245
|
+
const baseDirectory = dirname13(loaded.configFile);
|
|
10246
|
+
assertValidTileflowConfig4(project);
|
|
9856
10247
|
const configuredMapNames = getTileflowMapNames5(project);
|
|
9857
10248
|
const mapNames = selectManagedDeployMaps(configuredMapNames, options);
|
|
9858
10249
|
if (!mapNames) return;
|
|
@@ -9883,7 +10274,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9883
10274
|
const preflightMapAssets = createCompiledMapAssets(
|
|
9884
10275
|
compiledIcons,
|
|
9885
10276
|
(binding) => `${apiUrl}/sprites/preflight/${binding.packageHash}/sprite`,
|
|
9886
|
-
mapAssetsWithBuildingModels(modelAssets,
|
|
10277
|
+
mapAssetsWithBuildingModels(modelAssets, { hosted: true })
|
|
9887
10278
|
);
|
|
9888
10279
|
const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
|
|
9889
10280
|
apiBaseUrl: apiUrl,
|
|
@@ -9953,29 +10344,10 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9953
10344
|
);
|
|
9954
10345
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
9955
10346
|
logSuccess(
|
|
9956
|
-
`${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)}).`
|
|
9957
10348
|
);
|
|
9958
10349
|
hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
|
|
9959
10350
|
}
|
|
9960
|
-
const hostedModelBaseByHash = /* @__PURE__ */ new Map();
|
|
9961
|
-
const uniqueModelBundles = [
|
|
9962
|
-
...new Map(
|
|
9963
|
-
Object.values(modelAssets.bundles).map((bundle) => [bundle.contentHash, bundle])
|
|
9964
|
-
).values()
|
|
9965
|
-
].sort((left, right) => left.contentHash.localeCompare(right.contentHash));
|
|
9966
|
-
for (const bundle of uniqueModelBundles) {
|
|
9967
|
-
const uploaded = await uploadHostedModelBundle(api, bundle);
|
|
9968
|
-
if (!uploaded.ok) {
|
|
9969
|
-
logError(`Model bundle upload failed: ${uploaded.status}.`);
|
|
9970
|
-
process.exitCode = 1;
|
|
9971
|
-
return;
|
|
9972
|
-
}
|
|
9973
|
-
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
9974
|
-
logSuccess(
|
|
9975
|
-
`${action} 3D model bundle (${plural(bundle.manifest.models.length, "model")}, ${formatBytes3(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
|
|
9976
|
-
);
|
|
9977
|
-
hostedModelBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
|
|
9978
|
-
}
|
|
9979
10351
|
const hostedMapAssets = createCompiledMapAssets(
|
|
9980
10352
|
compiledIcons,
|
|
9981
10353
|
(binding) => {
|
|
@@ -9983,11 +10355,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
9983
10355
|
if (!sprite) throw new Error(`Missing hosted sprite URL for map ${binding.mapName}`);
|
|
9984
10356
|
return sprite;
|
|
9985
10357
|
},
|
|
9986
|
-
mapAssetsWithBuildingModels(modelAssets,
|
|
9987
|
-
const base = hostedModelBaseByHash.get(bundle.contentHash);
|
|
9988
|
-
if (!base) throw new Error(`Missing hosted model bundle URL for map ${mapName}.`);
|
|
9989
|
-
return base;
|
|
9990
|
-
})
|
|
10358
|
+
mapAssetsWithBuildingModels(modelAssets, { hosted: true })
|
|
9991
10359
|
);
|
|
9992
10360
|
const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
|
|
9993
10361
|
apiBaseUrl: api.apiUrl,
|
|
@@ -10028,7 +10396,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10028
10396
|
}
|
|
10029
10397
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
10030
10398
|
logSuccess(
|
|
10031
|
-
`${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)}).`
|
|
10032
10400
|
);
|
|
10033
10401
|
hostedFontBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
|
|
10034
10402
|
}
|
|
@@ -10078,12 +10446,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10078
10446
|
fileName: `icons/${mapName}/${file.fileName}`,
|
|
10079
10447
|
source: file.source
|
|
10080
10448
|
})),
|
|
10081
|
-
...hostedFonts.bundles[mapName]?.files ?? []
|
|
10082
|
-
...(modelAssets.bundles[mapName]?.files ?? []).map((file) => ({
|
|
10083
|
-
contentType: file.contentType,
|
|
10084
|
-
fileName: `models/${mapName}/${file.fileName}`,
|
|
10085
|
-
source: file.source
|
|
10086
|
-
}))
|
|
10449
|
+
...hostedFonts.bundles[mapName]?.files ?? []
|
|
10087
10450
|
],
|
|
10088
10451
|
lineage: deploymentProject.mapMetadata?.[mapName]?.lineage ?? [
|
|
10089
10452
|
{ id: map.id, mapVersion: map.version }
|
|
@@ -10093,7 +10456,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10093
10456
|
fonts: hostedFonts.sourceIdentities[mapName] ?? [],
|
|
10094
10457
|
icons: compiledIcons.sourceIdentities[mapName] ?? [],
|
|
10095
10458
|
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
|
|
10096
|
-
...modelAssets
|
|
10459
|
+
...hostedModelSourceIdentities(modelAssets, mapName) ? { models: hostedModelSourceIdentities(modelAssets, mapName) } : {}
|
|
10097
10460
|
},
|
|
10098
10461
|
styles: styles[mapName]
|
|
10099
10462
|
}
|
|
@@ -10106,13 +10469,13 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10106
10469
|
const iconBinding = bindingsByMap.get(mapName);
|
|
10107
10470
|
const iconPackage = iconBinding ? packagesByHash.get(iconBinding.packageHash) : void 0;
|
|
10108
10471
|
const fontBundle = hostedFonts.bundles[mapName];
|
|
10109
|
-
const
|
|
10472
|
+
const modelSources = teamModelSources(modelAssets, mapName);
|
|
10110
10473
|
return {
|
|
10111
10474
|
iconBinding,
|
|
10112
10475
|
iconComposition: compiledIcons.compositions[mapName],
|
|
10113
10476
|
iconPackage,
|
|
10114
10477
|
fontBundle,
|
|
10115
|
-
|
|
10478
|
+
modelSources,
|
|
10116
10479
|
mapName,
|
|
10117
10480
|
teamSources: hostedThemeFamilies[mapName].teamSources,
|
|
10118
10481
|
buildManifest: {
|
|
@@ -10132,7 +10495,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10132
10495
|
iconComposition,
|
|
10133
10496
|
iconPackage,
|
|
10134
10497
|
mapName,
|
|
10135
|
-
|
|
10498
|
+
modelSources,
|
|
10136
10499
|
teamSources,
|
|
10137
10500
|
styles: themeStyles
|
|
10138
10501
|
} = deployment;
|
|
@@ -10143,7 +10506,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10143
10506
|
}
|
|
10144
10507
|
const themeNames = Object.keys(themeStyles).sort();
|
|
10145
10508
|
logInfo(
|
|
10146
|
-
`Deploying compiled map ${
|
|
10509
|
+
`Deploying compiled map ${pc7.bold(mapName)} (${plural(themeNames.length, "theme")}).`
|
|
10147
10510
|
);
|
|
10148
10511
|
const response = await publishHostedStyle(
|
|
10149
10512
|
api,
|
|
@@ -10167,7 +10530,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10167
10530
|
} : {},
|
|
10168
10531
|
...iconComposition ? { iconComposition } : {},
|
|
10169
10532
|
...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
|
|
10170
|
-
...
|
|
10533
|
+
...modelSources ? { teamModelSources: modelSources } : {},
|
|
10171
10534
|
source
|
|
10172
10535
|
},
|
|
10173
10536
|
`Deploy response for ${mapName}`
|
|
@@ -10225,9 +10588,15 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
10225
10588
|
};
|
|
10226
10589
|
const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
|
|
10227
10590
|
if (body.changed === false) {
|
|
10228
|
-
logSuccess(`Unchanged ${
|
|
10591
|
+
logSuccess(`Unchanged ${pc7.bold(mapName)}${versionLabel}.`);
|
|
10229
10592
|
} else {
|
|
10230
|
-
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
|
+
);
|
|
10231
10600
|
}
|
|
10232
10601
|
}
|
|
10233
10602
|
const manifestPath = await writeDeployManifest(options.manifest, {
|
|
@@ -10589,12 +10958,12 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
10589
10958
|
}
|
|
10590
10959
|
function comparisonCaptureConfigArgument(configPath) {
|
|
10591
10960
|
const cwd = realpathSync(process.cwd());
|
|
10592
|
-
const requestedConfigPath =
|
|
10593
|
-
const defaultRequestedPath =
|
|
10961
|
+
const requestedConfigPath = resolve12(process.cwd(), configPath);
|
|
10962
|
+
const defaultRequestedPath = resolve12(process.cwd(), defaultConfigPath);
|
|
10594
10963
|
const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
|
|
10595
10964
|
const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
|
|
10596
10965
|
if (absoluteConfigPath === absoluteDefaultPath) return void 0;
|
|
10597
|
-
const relativeConfigPath =
|
|
10966
|
+
const relativeConfigPath = relative6(cwd, absoluteConfigPath);
|
|
10598
10967
|
if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
|
|
10599
10968
|
throw new Error(
|
|
10600
10969
|
"Comparison configs must be inside the current working directory so Copy command can remain reproducible."
|
|
@@ -10634,7 +11003,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
10634
11003
|
codeChallenge,
|
|
10635
11004
|
deviceName: hostname()
|
|
10636
11005
|
});
|
|
10637
|
-
printKeyValue("Code",
|
|
11006
|
+
printKeyValue("Code", pc7.bold(authorization.userCode));
|
|
10638
11007
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
10639
11008
|
if (options.noBrowser) {
|
|
10640
11009
|
logMuted("Open the URL above to continue.");
|
|
@@ -10644,7 +11013,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
10644
11013
|
output: process.stdout
|
|
10645
11014
|
});
|
|
10646
11015
|
try {
|
|
10647
|
-
await readline.question(`${
|
|
11016
|
+
await readline.question(`${pc7.cyan("?")} Press Enter to open Tileflow Dashboard...`);
|
|
10648
11017
|
openBrowser(authorization.verificationUriComplete);
|
|
10649
11018
|
} finally {
|
|
10650
11019
|
readline.close();
|
|
@@ -10695,9 +11064,9 @@ function openBrowser(url, errorsToStderr = false) {
|
|
|
10695
11064
|
});
|
|
10696
11065
|
}
|
|
10697
11066
|
async function writeDeployManifest(manifestPath, manifest) {
|
|
10698
|
-
const outputPath =
|
|
11067
|
+
const outputPath = resolve12(process.cwd(), manifestPath);
|
|
10699
11068
|
await writeAtomicFile({
|
|
10700
|
-
boundaryPath:
|
|
11069
|
+
boundaryPath: dirname13(outputPath),
|
|
10701
11070
|
force: true,
|
|
10702
11071
|
label: "Deploy manifest",
|
|
10703
11072
|
managed: true,
|
|
@@ -10785,7 +11154,7 @@ function validateDeployManifestMapNames(mapNames) {
|
|
|
10785
11154
|
async function loadExistingDeployManifest(manifestPath, options) {
|
|
10786
11155
|
let source;
|
|
10787
11156
|
try {
|
|
10788
|
-
source = await readFile7(
|
|
11157
|
+
source = await readFile7(resolve12(process.cwd(), manifestPath), "utf8");
|
|
10789
11158
|
} catch (error) {
|
|
10790
11159
|
if (error.code === "ENOENT") return null;
|
|
10791
11160
|
throw new Error("Existing Tileflow manifest could not be read.");
|
|
@@ -10845,7 +11214,7 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
10845
11214
|
if (!behavior.silent) {
|
|
10846
11215
|
logError("CI requires an explicit Tileflow credential with access to the target Map.");
|
|
10847
11216
|
printNextSteps([
|
|
10848
|
-
`Set ${
|
|
11217
|
+
`Set ${pc7.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
|
|
10849
11218
|
"The saved personal account session is never used in CI."
|
|
10850
11219
|
]);
|
|
10851
11220
|
}
|
|
@@ -10914,17 +11283,17 @@ function printCliError(error) {
|
|
|
10914
11283
|
}
|
|
10915
11284
|
const message = error instanceof Error ? error.message : "Command failed.";
|
|
10916
11285
|
if (message.includes("Cannot find module '@tileflow/core'")) {
|
|
10917
|
-
logError(`Cannot load ${
|
|
11286
|
+
logError(`Cannot load ${pc7.cyan("@tileflow/core")} from this project.`);
|
|
10918
11287
|
printNextSteps([
|
|
10919
|
-
`Install ${
|
|
11288
|
+
`Install ${pc7.cyan("@tileflow/core")} in this project.`,
|
|
10920
11289
|
`Run the command again from the project root.`
|
|
10921
11290
|
]);
|
|
10922
11291
|
return;
|
|
10923
11292
|
}
|
|
10924
11293
|
if (message.includes("Cannot find module '@tileflow/maps'")) {
|
|
10925
|
-
logError(`Cannot load ${
|
|
11294
|
+
logError(`Cannot load ${pc7.cyan("@tileflow/maps")} from this project.`);
|
|
10926
11295
|
printNextSteps([
|
|
10927
|
-
`Install ${
|
|
11296
|
+
`Install ${pc7.cyan("@tileflow/maps")} in this project.`,
|
|
10928
11297
|
`Run the command again from the project root.`
|
|
10929
11298
|
]);
|
|
10930
11299
|
return;
|
|
@@ -10935,51 +11304,51 @@ function printCliError(error) {
|
|
|
10935
11304
|
}
|
|
10936
11305
|
}
|
|
10937
11306
|
function logSuccess(message) {
|
|
10938
|
-
console.log(`${
|
|
11307
|
+
console.log(`${pc7.green("\u2713")} ${message}`);
|
|
10939
11308
|
}
|
|
10940
11309
|
function logError(message) {
|
|
10941
|
-
console.log(`${
|
|
11310
|
+
console.log(`${pc7.red("\u2715")} ${message}`);
|
|
10942
11311
|
}
|
|
10943
11312
|
function logWarning(message) {
|
|
10944
|
-
console.log(`${
|
|
11313
|
+
console.log(`${pc7.yellow("!")} ${message}`);
|
|
10945
11314
|
}
|
|
10946
11315
|
function logInfo(message) {
|
|
10947
|
-
console.log(`${
|
|
11316
|
+
console.log(`${pc7.cyan("i")} ${message}`);
|
|
10948
11317
|
}
|
|
10949
11318
|
function logMuted(message) {
|
|
10950
|
-
console.log(
|
|
11319
|
+
console.log(pc7.gray(message));
|
|
10951
11320
|
}
|
|
10952
11321
|
function printTitle(title, subtitle) {
|
|
10953
|
-
console.log(
|
|
11322
|
+
console.log(pc7.bold(title));
|
|
10954
11323
|
if (subtitle) {
|
|
10955
|
-
console.log(
|
|
11324
|
+
console.log(pc7.gray(subtitle));
|
|
10956
11325
|
}
|
|
10957
11326
|
}
|
|
10958
11327
|
function printKeyValue(label, value) {
|
|
10959
|
-
console.log(`${
|
|
11328
|
+
console.log(`${pc7.gray(`${label}:`.padEnd(13))} ${value}`);
|
|
10960
11329
|
}
|
|
10961
11330
|
function printChecks(items) {
|
|
10962
11331
|
for (const item of items) {
|
|
10963
|
-
console.log(` ${
|
|
11332
|
+
console.log(` ${pc7.green("\u2713")} ${item}`);
|
|
10964
11333
|
}
|
|
10965
11334
|
}
|
|
10966
11335
|
function printNextSteps(steps) {
|
|
10967
11336
|
if (steps.length === 0) return;
|
|
10968
11337
|
console.log(`
|
|
10969
|
-
${
|
|
11338
|
+
${pc7.bold("Next steps")}`);
|
|
10970
11339
|
for (const step of steps) {
|
|
10971
|
-
console.log(` ${
|
|
11340
|
+
console.log(` ${pc7.cyan("\u2022")} ${step}`);
|
|
10972
11341
|
}
|
|
10973
11342
|
}
|
|
10974
11343
|
function printValidationErrors(messages) {
|
|
10975
11344
|
logError("Tileflow config has errors.");
|
|
10976
11345
|
for (const message of messages) {
|
|
10977
|
-
const path = message.path ?
|
|
10978
|
-
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}`);
|
|
10979
11348
|
}
|
|
10980
11349
|
}
|
|
10981
11350
|
function printAccountDetails(session) {
|
|
10982
|
-
printKeyValue("Account",
|
|
11351
|
+
printKeyValue("Account", pc7.bold(session.account.name));
|
|
10983
11352
|
printKeyValue("Email", session.account.email);
|
|
10984
11353
|
printKeyValue("API", link(session.apiOrigin));
|
|
10985
11354
|
printKeyValue("Expires", formatDate(session.expiresAt));
|
|
@@ -11008,11 +11377,11 @@ function printDeployedMaps(maps) {
|
|
|
11008
11377
|
const entries = Object.entries(maps);
|
|
11009
11378
|
if (entries.length === 0) return;
|
|
11010
11379
|
console.log(`
|
|
11011
|
-
${
|
|
11380
|
+
${pc7.bold("Maps")}`);
|
|
11012
11381
|
for (const [name, map] of entries) {
|
|
11013
11382
|
for (const [themeName, theme] of Object.entries(map.themes)) {
|
|
11014
11383
|
const label = `${name}/${themeName}`;
|
|
11015
|
-
console.log(` ${
|
|
11384
|
+
console.log(` ${pc7.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
|
|
11016
11385
|
}
|
|
11017
11386
|
}
|
|
11018
11387
|
}
|
|
@@ -11030,13 +11399,13 @@ function plural(count, singular, pluralForm = `${singular}s`) {
|
|
|
11030
11399
|
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
11031
11400
|
}
|
|
11032
11401
|
function command(value) {
|
|
11033
|
-
return
|
|
11402
|
+
return pc7.cyan(value);
|
|
11034
11403
|
}
|
|
11035
11404
|
function link(value) {
|
|
11036
|
-
return
|
|
11405
|
+
return pc7.cyan(value);
|
|
11037
11406
|
}
|
|
11038
11407
|
function pathLabel(value) {
|
|
11039
|
-
return
|
|
11408
|
+
return pc7.cyan(value);
|
|
11040
11409
|
}
|
|
11041
11410
|
function starterConfig() {
|
|
11042
11411
|
return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
|
|
@@ -11082,19 +11451,19 @@ function printHostedCompatibilityIssues(issues) {
|
|
|
11082
11451
|
}
|
|
11083
11452
|
function printMapStatus(status, apiUrl) {
|
|
11084
11453
|
printTitle("Tileflow status");
|
|
11085
|
-
printKeyValue("Map",
|
|
11454
|
+
printKeyValue("Map", pc7.bold(status.mapId));
|
|
11086
11455
|
console.log(`
|
|
11087
|
-
${
|
|
11456
|
+
${pc7.bold("Styles")}`);
|
|
11088
11457
|
if (status.styles.length === 0) {
|
|
11089
11458
|
logMuted(" No styles deployed.");
|
|
11090
11459
|
}
|
|
11091
11460
|
for (const style of status.styles) {
|
|
11092
11461
|
console.log(
|
|
11093
|
-
` ${
|
|
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))}`
|
|
11094
11463
|
);
|
|
11095
11464
|
}
|
|
11096
11465
|
}
|
|
11097
|
-
function
|
|
11466
|
+
function formatBytes4(bytes) {
|
|
11098
11467
|
if (bytes < 1024) return `${bytes} B`;
|
|
11099
11468
|
const units = ["KB", "MB", "GB", "TB"];
|
|
11100
11469
|
let value = bytes;
|