tileflow 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +38 -0
- package/dist/index.js +1346 -475
- package/docs/agent-icon-composition.md +27 -12
- package/docs/command-families.md +8 -0
- package/docs/team-icon-sets.md +121 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -7,14 +7,14 @@ 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 dirname11, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
|
|
11
11
|
import { createInterface } from "readline/promises";
|
|
12
|
-
import
|
|
12
|
+
import pc6 from "picocolors";
|
|
13
13
|
import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
|
|
14
14
|
import {
|
|
15
15
|
auditTileflowMapThemeValues,
|
|
16
16
|
getTileflowStyleFontFaces,
|
|
17
|
-
parseResolvedTileflowMap as
|
|
17
|
+
parseResolvedTileflowMap as parseResolvedTileflowMap4,
|
|
18
18
|
tileflowMapIdSchema as tileflowMapIdSchema2
|
|
19
19
|
} from "@tileflow/core";
|
|
20
20
|
import {
|
|
@@ -36,16 +36,19 @@ import {
|
|
|
36
36
|
defaultTileflowApiUrl,
|
|
37
37
|
defaultTileflowConfigPath,
|
|
38
38
|
defaultTileflowManifestPath,
|
|
39
|
-
getTileflowMapNames as
|
|
39
|
+
getTileflowMapNames as getTileflowMapNames4,
|
|
40
40
|
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3,
|
|
41
|
-
TileflowValidationError as
|
|
41
|
+
TileflowValidationError as TileflowValidationError5
|
|
42
42
|
} from "@tileflow/dev/config";
|
|
43
43
|
import {
|
|
44
44
|
bindTileflowStyleFontBundle,
|
|
45
45
|
prepareTileflowStyleFonts,
|
|
46
46
|
TileflowFontCompilationError
|
|
47
47
|
} from "@tileflow/dev/fonts";
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
compileTileflowIconPackages as compileTileflowIconPackages2,
|
|
50
|
+
TileflowIconCompilationError as TileflowIconCompilationError3
|
|
51
|
+
} from "@tileflow/dev/icons";
|
|
49
52
|
import { resolveTileflowPreview } from "@tileflow/dev/preview";
|
|
50
53
|
import {
|
|
51
54
|
createTileflowComparisonRequestHandler,
|
|
@@ -2882,7 +2885,9 @@ import {
|
|
|
2882
2885
|
getTileflowMapNames,
|
|
2883
2886
|
loadTileflowConfigWithInputs as loadTileflowConfigWithInputs2
|
|
2884
2887
|
} from "@tileflow/dev/config";
|
|
2885
|
-
import {
|
|
2888
|
+
import {
|
|
2889
|
+
compileTileflowIconPackages
|
|
2890
|
+
} from "@tileflow/dev/icons";
|
|
2886
2891
|
|
|
2887
2892
|
// src/icon-diff-report.ts
|
|
2888
2893
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -3441,7 +3446,7 @@ var tileflowIconDiffDocumentSchema = z4.object({
|
|
|
3441
3446
|
hasChanges: z4.boolean()
|
|
3442
3447
|
}).strict();
|
|
3443
3448
|
function registerIconDiffCommand(icons, dependencies) {
|
|
3444
|
-
icons.command("diff").description("Preview a local icon package against an active hosted revision").requiredOption("--against <environment>", "named map environment to compare").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).requiredOption("--map-id <id>", "managed Map destination").option("--json", "print deterministic schema-version-1 JSON").option("--report <path>", "write a self-contained HTML visual report").option("--open", "open an explicitly requested report").option("--force", "replace a different explicitly requested report").action(async (options) => {
|
|
3449
|
+
icons.command("diff").description("Preview a local icon package against an active hosted revision").requiredOption("--against <environment>", "named map environment to compare").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).requiredOption("--map-id <id>", "managed Map destination").option("--json", "print deterministic schema-version-1 JSON").option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--report <path>", "write a self-contained HTML visual report").option("--open", "open an explicitly requested report").option("--force", "replace a different explicitly requested report").action(async (options) => {
|
|
3445
3450
|
try {
|
|
3446
3451
|
await runIconDiff(options, dependencies);
|
|
3447
3452
|
} catch (error) {
|
|
@@ -3477,9 +3482,14 @@ async function runIconDiff(options, dependencies) {
|
|
|
3477
3482
|
throw new Error("Missing Tileflow API key. Run tileflow login or set TILEFLOW_API_KEY.");
|
|
3478
3483
|
}
|
|
3479
3484
|
const selectedProject = { ...project, maps: { [environment]: project.maps[environment] } };
|
|
3485
|
+
const icons = {
|
|
3486
|
+
...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
|
|
3487
|
+
...options.offline ? { offline: true } : {}
|
|
3488
|
+
};
|
|
3480
3489
|
const compiled = await compileTileflowIconPackages(selectedProject, {
|
|
3481
3490
|
baseDirectory: dirname6(loaded.configFile),
|
|
3482
3491
|
cwd: process.cwd(),
|
|
3492
|
+
...Object.keys(icons).length > 0 ? { icons } : {},
|
|
3483
3493
|
target: "hosted"
|
|
3484
3494
|
});
|
|
3485
3495
|
const binding = compiled.bindings.find((candidate) => candidate.mapName === environment);
|
|
@@ -3633,7 +3643,7 @@ import {
|
|
|
3633
3643
|
TileflowIconCompilationError as TileflowIconCompilationError2
|
|
3634
3644
|
} from "@tileflow/dev/icons";
|
|
3635
3645
|
function registerIconListCommand(icons, dependencies) {
|
|
3636
|
-
icons.command("list").description("List each map icon directory composition as deterministic agent JSON").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--map <id>", "inspect one exact configured map").option("--json", "print deterministic schema-version-
|
|
3646
|
+
icons.command("list").description("List each map icon directory composition as deterministic agent JSON").option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--map <id>", "inspect one exact configured map").option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--json", "print deterministic schema-version-3 JSON").action(async (options) => {
|
|
3637
3647
|
if (!options.json) {
|
|
3638
3648
|
console.error(
|
|
3639
3649
|
"tileflow icons list currently requires --json. Run tileflow icons list --json."
|
|
@@ -3651,7 +3661,7 @@ function registerIconListCommand(icons, dependencies) {
|
|
|
3651
3661
|
}
|
|
3652
3662
|
function createTileflowIconListJson(inspection) {
|
|
3653
3663
|
return {
|
|
3654
|
-
schemaVersion:
|
|
3664
|
+
schemaVersion: 3,
|
|
3655
3665
|
pathBase: "cwd",
|
|
3656
3666
|
maps: [...inspection.maps].sort((left, right) => compareCodeUnits6(left.name, right.name)).map((map) => createMapJson(map, inspection.catalogs))
|
|
3657
3667
|
};
|
|
@@ -3671,9 +3681,14 @@ async function runIconList(options) {
|
|
|
3671
3681
|
`Unknown map "${options.map}". Available maps: ${mapIds.join(", ") || "(none)"}`
|
|
3672
3682
|
);
|
|
3673
3683
|
}
|
|
3684
|
+
const icons = {
|
|
3685
|
+
...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
|
|
3686
|
+
...options.offline ? { offline: true } : {}
|
|
3687
|
+
};
|
|
3674
3688
|
const inspection = await inspectTileflowIconCatalogs(project, {
|
|
3675
3689
|
baseDirectory: dirname7(loaded.configFile),
|
|
3676
3690
|
cwd: process.cwd(),
|
|
3691
|
+
...Object.keys(icons).length > 0 ? { icons } : {},
|
|
3677
3692
|
...options.map ? { mapNames: [options.map] } : {}
|
|
3678
3693
|
});
|
|
3679
3694
|
process.stdout.write(serializeTileflowIconListJson(createTileflowIconListJson(inspection)));
|
|
@@ -3684,7 +3699,7 @@ function createMapJson(map, catalogs) {
|
|
|
3684
3699
|
}
|
|
3685
3700
|
const mapIcons = map.icons;
|
|
3686
3701
|
const catalog = catalogs.find(
|
|
3687
|
-
(candidate) => candidate.compiledPackage.contentHash === mapIcons.packageHash &&
|
|
3702
|
+
(candidate) => candidate.compiledPackage.contentHash === mapIcons.packageHash && sameContributors(candidate.contributors, mapIcons.contributors)
|
|
3688
3703
|
);
|
|
3689
3704
|
if (!catalog) {
|
|
3690
3705
|
throw new Error(`Missing inspected icon catalog for map ${map.name}`);
|
|
@@ -3692,8 +3707,8 @@ function createMapJson(map, catalogs) {
|
|
|
3692
3707
|
return {
|
|
3693
3708
|
id: map.name,
|
|
3694
3709
|
icons: {
|
|
3695
|
-
kind: "
|
|
3696
|
-
|
|
3710
|
+
kind: "sources",
|
|
3711
|
+
contributors: mapIcons.contributors.map(createContributorJson),
|
|
3697
3712
|
finalIds: [...mapIcons.iconIds],
|
|
3698
3713
|
insideWorkingTree: catalog.insideWorkingTree,
|
|
3699
3714
|
replacements: catalog.replacements.map((replacement) => ({
|
|
@@ -3702,41 +3717,959 @@ function createMapJson(map, catalogs) {
|
|
|
3702
3717
|
winner: replacement.winner
|
|
3703
3718
|
})),
|
|
3704
3719
|
packageHash: mapIcons.packageHash,
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3720
|
+
composition: mapIcons.composition,
|
|
3721
|
+
sources: [...catalog.icons].sort((left, right) => compareCodeUnits6(left.id, right.id)).map(
|
|
3722
|
+
(icon) => icon.source.kind === "icon-set" ? {
|
|
3723
|
+
kind: "icon-set",
|
|
3724
|
+
id: icon.id,
|
|
3725
|
+
contributor: icon.source.contributor,
|
|
3726
|
+
reference: icon.source.reference,
|
|
3727
|
+
version: icon.source.version
|
|
3728
|
+
} : {
|
|
3729
|
+
kind: "file",
|
|
3730
|
+
id: icon.id,
|
|
3731
|
+
contributor: icon.source.contributor,
|
|
3732
|
+
path: icon.source.path,
|
|
3733
|
+
format: icon.source.format,
|
|
3734
|
+
byteLength: icon.source.byteLength,
|
|
3735
|
+
dimensions: icon.source.dimensions ? { width: icon.source.dimensions.width, height: icon.source.dimensions.height } : null
|
|
3736
|
+
}
|
|
3737
|
+
)
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
function createContributorJson(contributor) {
|
|
3742
|
+
return contributor.kind === "icon-set" ? {
|
|
3743
|
+
kind: "icon-set",
|
|
3744
|
+
label: contributor.label,
|
|
3745
|
+
iconIds: [...contributor.iconIds],
|
|
3746
|
+
reference: contributor.reference,
|
|
3747
|
+
version: contributor.version
|
|
3748
|
+
} : {
|
|
3749
|
+
kind: contributor.kind,
|
|
3750
|
+
label: contributor.label,
|
|
3751
|
+
iconIds: [...contributor.iconIds],
|
|
3752
|
+
insideWorkingTree: contributor.insideWorkingTree
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
function sameContributors(left, right) {
|
|
3756
|
+
return left.length === right.length && left.every((contributor, index) => contributor.label === right[index]?.label);
|
|
3757
|
+
}
|
|
3758
|
+
function printIconListError(error) {
|
|
3759
|
+
if (error instanceof TileflowValidationError3) {
|
|
3760
|
+
printIssues("Tileflow config has errors.", error.messages);
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
if (error instanceof TileflowIconCompilationError2) {
|
|
3764
|
+
printIssues("Tileflow icon catalog has errors.", error.issues);
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
console.error(error instanceof Error ? error.message : "Icon catalog listing failed.");
|
|
3768
|
+
}
|
|
3769
|
+
function printIssues(heading, issues) {
|
|
3770
|
+
console.error(
|
|
3771
|
+
[heading, ...issues.map((issue) => `- ${issue.path || "(root)"}: ${issue.message}`)].join("\n")
|
|
3772
|
+
);
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
// src/icon-lock-command.ts
|
|
3776
|
+
import { dirname as dirname8 } from "path";
|
|
3777
|
+
import pc3 from "picocolors";
|
|
3778
|
+
import {
|
|
3779
|
+
collectTileflowIconSetReferences,
|
|
3780
|
+
compareCodeUnits as compareCodeUnits7,
|
|
3781
|
+
parseResolvedTileflowMap as parseResolvedTileflowMap2,
|
|
3782
|
+
parseTileflowIconsLockfile,
|
|
3783
|
+
tileflowIconSetReferenceSchema as tileflowIconSetReferenceSchema2,
|
|
3784
|
+
tileflowIconsLockfileName
|
|
3785
|
+
} from "@tileflow/core";
|
|
3786
|
+
import {
|
|
3787
|
+
getTileflowMapNames as getTileflowMapNames3,
|
|
3788
|
+
loadValidTileflowConfigWithInputs as loadValidTileflowConfigWithInputs3,
|
|
3789
|
+
TileflowValidationError as TileflowValidationError4
|
|
3790
|
+
} from "@tileflow/dev/config";
|
|
3791
|
+
import { readTileflowIconsLockfileText, writeTileflowIconsLockfile } from "@tileflow/dev/icons";
|
|
3792
|
+
|
|
3793
|
+
// src/hosted-team.ts
|
|
3794
|
+
import pc from "picocolors";
|
|
3795
|
+
import { serializeCanonicalJson } from "@tileflow/core";
|
|
3796
|
+
async function resolveTeamAuthority(options, scopes, dependencies) {
|
|
3797
|
+
let apiOrigin;
|
|
3798
|
+
try {
|
|
3799
|
+
apiOrigin = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
|
|
3800
|
+
} catch (error) {
|
|
3801
|
+
emitFailure(options.json, "invalid_api_url", safeMessage(error));
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3804
|
+
if (options.apiKey) {
|
|
3805
|
+
if (!/^tf_live_[0-9a-f]{48}$/u.test(options.apiKey)) {
|
|
3806
|
+
emitFailure(options.json, "invalid_team_data_key", "Team data API key is invalid.");
|
|
3807
|
+
return null;
|
|
3808
|
+
}
|
|
3809
|
+
if (options.team) {
|
|
3810
|
+
emitFailure(
|
|
3811
|
+
options.json,
|
|
3812
|
+
"team_selector_with_api_key",
|
|
3813
|
+
"A Team data key already selects its Team; omit --team."
|
|
3814
|
+
);
|
|
3815
|
+
return null;
|
|
3816
|
+
}
|
|
3817
|
+
return { apiOrigin, credential: options.apiKey, team: null };
|
|
3818
|
+
}
|
|
3819
|
+
let config;
|
|
3820
|
+
try {
|
|
3821
|
+
config = await dependencies.loadAuthConfig();
|
|
3822
|
+
} catch (error) {
|
|
3823
|
+
emitFailure(options.json, "auth_state_unavailable", safeMessage(error));
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3826
|
+
const selected = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
|
|
3827
|
+
if (selected.kind !== "selected") {
|
|
3828
|
+
emitFailure(
|
|
3829
|
+
options.json,
|
|
3830
|
+
`account_session_${selected.kind}`,
|
|
3831
|
+
selected.kind === "expired" ? "Account session expired. Run tileflow login." : "Run tileflow login."
|
|
3832
|
+
);
|
|
3833
|
+
return null;
|
|
3834
|
+
}
|
|
3835
|
+
const discovered = await listAccountTeams(selected.session);
|
|
3836
|
+
if (!discovered.ok) {
|
|
3837
|
+
emitFailure(options.json, "team_discovery_failed", discovered.error);
|
|
3838
|
+
return null;
|
|
3839
|
+
}
|
|
3840
|
+
const requested = options.team ? /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(options.team) : null;
|
|
3841
|
+
if (options.team && !requested) {
|
|
3842
|
+
emitFailure(options.json, "invalid_team", "Team must use @team syntax.");
|
|
3843
|
+
return null;
|
|
3844
|
+
}
|
|
3845
|
+
const team = requested ? discovered.teams.find(({ slug }) => slug === requested[1]) : discovered.teams.length === 1 ? discovered.teams[0] : null;
|
|
3846
|
+
if (!team) {
|
|
3847
|
+
const options_ = discovered.teams.map(({ slug }) => `@${slug}`);
|
|
3848
|
+
emitFailure(
|
|
3849
|
+
options.json,
|
|
3850
|
+
options_.length > 1 ? "team_ambiguous" : "team_not_found",
|
|
3851
|
+
options_.length > 1 ? `Choose one Team with --team: ${options_.join(", ")}.` : "No matching Team is available.",
|
|
3852
|
+
{ options: options_ }
|
|
3853
|
+
);
|
|
3854
|
+
return null;
|
|
3855
|
+
}
|
|
3856
|
+
const capability = await requestTeamCapability(selected.session, `@${team.slug}`, scopes);
|
|
3857
|
+
if (!capability.ok) {
|
|
3858
|
+
emitFailure(options.json, "team_capability_failed", capability.error);
|
|
3859
|
+
return null;
|
|
3860
|
+
}
|
|
3861
|
+
return {
|
|
3862
|
+
apiOrigin: selected.session.apiOrigin,
|
|
3863
|
+
credential: capability.capability,
|
|
3864
|
+
team
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
function authorizedTeamRequest(authority, path, method, init = {}) {
|
|
3868
|
+
return requestHostedJson(authority.apiOrigin, path, {
|
|
3869
|
+
...init.body === void 0 ? {} : { body: init.body },
|
|
3870
|
+
headers: { Authorization: `Bearer ${authority.credential}`, ...init.headers },
|
|
3871
|
+
method
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
function emitJson(value) {
|
|
3875
|
+
process.stdout.write(`${serializeCanonicalJson(value)}
|
|
3876
|
+
`);
|
|
3877
|
+
}
|
|
3878
|
+
function emitFailure(json, code, message, context = {}) {
|
|
3879
|
+
if (json) {
|
|
3880
|
+
process.stderr.write(
|
|
3881
|
+
`${serializeCanonicalJson({ error: { code, ...context, message }, ok: false, schemaVersion: 1 })}
|
|
3882
|
+
`
|
|
3883
|
+
);
|
|
3884
|
+
} else {
|
|
3885
|
+
console.error(`${pc.red("Error:")} ${message}`);
|
|
3886
|
+
}
|
|
3887
|
+
process.exitCode = 1;
|
|
3888
|
+
}
|
|
3889
|
+
function safeMessage(error) {
|
|
3890
|
+
return error instanceof Error ? error.message : "Team command failed.";
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3893
|
+
// src/icon-set-command.ts
|
|
3894
|
+
import { isAbsolute as isAbsolute2 } from "path";
|
|
3895
|
+
import pc2 from "picocolors";
|
|
3896
|
+
import { z as z5 } from "zod";
|
|
3897
|
+
import {
|
|
3898
|
+
tileflowIconSetPackageIdSchema,
|
|
3899
|
+
tileflowIconSetPinSchema,
|
|
3900
|
+
tileflowIconSetReferenceSchema
|
|
3901
|
+
} from "@tileflow/core";
|
|
3902
|
+
import { composeTileflowIconSources } from "@tileflow/dev/icons";
|
|
3903
|
+
var maximumCatalogPages = 100;
|
|
3904
|
+
var pageSize = 100;
|
|
3905
|
+
function registerIconSetCommands(program2, dependencies) {
|
|
3906
|
+
const iconSet = program2.command("icon-set").description("Publish and manage versioned Team Icon Sets");
|
|
3907
|
+
withTeamOptions(
|
|
3908
|
+
iconSet.command("publish").description("Compile one local directory and publish it as an immutable Team revision").argument("<directory>", "repository-local icon directory").requiredOption("--id <id>", "Team-local Icon Set slug").requiredOption("--idempotency-key <key>", "durable retry key for this exact publication").option("--name <name>", "display name").option("--description <text>", "display description")
|
|
3909
|
+
).action(
|
|
3910
|
+
async (directory, options) => {
|
|
3911
|
+
try {
|
|
3912
|
+
if (!isIconSetSlug(options.id)) {
|
|
3913
|
+
return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
|
|
3914
|
+
}
|
|
3915
|
+
if (!isIdempotencyKey(options.idempotencyKey)) {
|
|
3916
|
+
return emitFailure(
|
|
3917
|
+
options.json,
|
|
3918
|
+
"invalid_idempotency_key",
|
|
3919
|
+
"Idempotency key must be 8 to 128 characters from A-Z a-z 0-9 . _ : -"
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
const source = normalizeLocalDirectory(directory);
|
|
3923
|
+
if (!source) {
|
|
3924
|
+
return emitFailure(
|
|
3925
|
+
options.json,
|
|
3926
|
+
"invalid_icon_directory",
|
|
3927
|
+
"Publish one repository-relative icon directory."
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
let iconPackage;
|
|
3931
|
+
try {
|
|
3932
|
+
const composed = await composeTileflowIconSources([source], {
|
|
3933
|
+
cwd: process.cwd(),
|
|
3934
|
+
target: "hosted"
|
|
3935
|
+
});
|
|
3936
|
+
if (!composed.package) throw new Error("The selected directory has no icons.");
|
|
3937
|
+
iconPackage = composed.package;
|
|
3938
|
+
} catch (error) {
|
|
3939
|
+
return emitFailure(options.json, "icon_compilation_failed", safeMessage(error));
|
|
3940
|
+
}
|
|
3941
|
+
const authority = await resolveTeamAuthority(options, ["icons:write"], dependencies);
|
|
3942
|
+
if (!authority) return;
|
|
3943
|
+
const body = new FormData();
|
|
3944
|
+
for (const file of iconPackage.files) {
|
|
3945
|
+
const bytes = new Uint8Array(file.source.byteLength);
|
|
3946
|
+
bytes.set(file.source);
|
|
3947
|
+
body.append(
|
|
3948
|
+
file.fileName,
|
|
3949
|
+
new Blob([bytes.buffer], { type: file.contentType }),
|
|
3950
|
+
file.fileName
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
const response = await authorizedTeamRequest(
|
|
3954
|
+
authority,
|
|
3955
|
+
`/v1/icon-sets/${encodeURIComponent(options.id)}/versions/${encodeURIComponent(iconPackage.contentHash)}`,
|
|
3956
|
+
"PUT",
|
|
3957
|
+
{ body, headers: { "Idempotency-Key": options.idempotencyKey } }
|
|
3958
|
+
);
|
|
3959
|
+
if (!response.ok) return emitHttpFailure(options.json, response.status, "publish");
|
|
3960
|
+
const parsed = publishResponseSchema.parse(response.body);
|
|
3961
|
+
if (parsed.revision.pin && parsed.revision.pin.contentHash !== iconPackage.contentHash) {
|
|
3962
|
+
return emitFailure(
|
|
3963
|
+
options.json,
|
|
3964
|
+
"icon_set_publish_unconfirmed",
|
|
3965
|
+
"Publication response did not confirm the submitted generated artifact."
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3968
|
+
const metadata = {
|
|
3969
|
+
...options.description === void 0 ? {} : { description: options.description },
|
|
3970
|
+
...options.name === void 0 ? {} : { name: options.name }
|
|
3971
|
+
};
|
|
3972
|
+
if (Object.keys(metadata).length > 0) {
|
|
3973
|
+
try {
|
|
3974
|
+
const metadataResponse = await authorizedTeamRequest(
|
|
3975
|
+
authority,
|
|
3976
|
+
`/v1/icon-sets/${encodeURIComponent(options.id)}`,
|
|
3977
|
+
"PATCH",
|
|
3978
|
+
{
|
|
3979
|
+
body: JSON.stringify(metadata),
|
|
3980
|
+
headers: { "Content-Type": "application/json" }
|
|
3981
|
+
}
|
|
3982
|
+
);
|
|
3983
|
+
if (!metadataResponse.ok || !catalogDetailResponseSchema.safeParse(metadataResponse.body).success) {
|
|
3984
|
+
return emitFailure(
|
|
3985
|
+
options.json,
|
|
3986
|
+
"icon_set_metadata_update_failed",
|
|
3987
|
+
"Icon Set publication succeeded, but display metadata could not be updated; rerun the same command."
|
|
3988
|
+
);
|
|
3989
|
+
}
|
|
3990
|
+
} catch {
|
|
3991
|
+
return emitFailure(
|
|
3992
|
+
options.json,
|
|
3993
|
+
"icon_set_metadata_update_failed",
|
|
3994
|
+
"Icon Set publication succeeded, but display metadata could not be updated; rerun the same command."
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
const totalBytes = iconPackage.manifest.files.reduce(
|
|
3999
|
+
(total, file) => total + file.byteLength,
|
|
4000
|
+
0
|
|
4001
|
+
);
|
|
4002
|
+
const document = {
|
|
4003
|
+
command: "icon-set publish",
|
|
4004
|
+
iconSet: {
|
|
4005
|
+
contentHash: iconPackage.contentHash,
|
|
4006
|
+
iconCount: iconPackage.manifest.iconNames.length,
|
|
4007
|
+
logicalId: options.id,
|
|
4008
|
+
packageId: parsed.revision.pin?.packageId ?? null,
|
|
4009
|
+
reference: parsed.revision.reference,
|
|
4010
|
+
totalBytes,
|
|
4011
|
+
version: parsed.revision.version,
|
|
4012
|
+
versionId: parsed.revision.id
|
|
4013
|
+
},
|
|
4014
|
+
publication: parsed.changed ? "changed" : "unchanged",
|
|
4015
|
+
schemaVersion: 1,
|
|
4016
|
+
team: describeTeam(authority)
|
|
4017
|
+
};
|
|
4018
|
+
if (options.json) return emitJson(document);
|
|
4019
|
+
console.log(
|
|
4020
|
+
`${pc2.green("\u2713")} ${parsed.changed ? "Published" : "Unchanged"} ${pc2.bold(parsed.revision.reference)} revision ${parsed.revision.version}.`
|
|
4021
|
+
);
|
|
4022
|
+
} catch (error) {
|
|
4023
|
+
emitFailure(options.json, "icon_set_publish_failed", boundedMessage(error, "publish"));
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
);
|
|
4027
|
+
withTeamOptions(
|
|
4028
|
+
iconSet.command("list").description("List Icon Sets owned by one Team").option("--archived", "list archived Icon Sets instead of active ones")
|
|
4029
|
+
).action(async (options) => {
|
|
4030
|
+
try {
|
|
4031
|
+
const authority = await resolveTeamAuthority(options, ["icons:read"], dependencies);
|
|
4032
|
+
if (!authority) return;
|
|
4033
|
+
const sets = await readAllPages(
|
|
4034
|
+
authority,
|
|
4035
|
+
"/v1/icon-sets",
|
|
4036
|
+
options.archived ? { archived: "true" } : {},
|
|
4037
|
+
catalogListResponseSchema,
|
|
4038
|
+
(page) => page.sets,
|
|
4039
|
+
(set) => set.id
|
|
4040
|
+
);
|
|
4041
|
+
const document = {
|
|
4042
|
+
command: "icon-set list",
|
|
4043
|
+
schemaVersion: 1,
|
|
4044
|
+
sets,
|
|
4045
|
+
team: describeTeam(authority)
|
|
4046
|
+
};
|
|
4047
|
+
if (options.json) return emitJson(document);
|
|
4048
|
+
if (sets.length === 0) return console.log(pc2.gray("No Team Icon Sets."));
|
|
4049
|
+
for (const set of sets) {
|
|
4050
|
+
console.log(
|
|
4051
|
+
` ${pc2.bold(set.reference)} ${pc2.gray(set.archivedAt ? "archived" : `v${String(set.latestVersion ?? 0)}`)}`
|
|
4052
|
+
);
|
|
4053
|
+
}
|
|
4054
|
+
} catch (error) {
|
|
4055
|
+
emitFailure(options.json, "icon_set_list_failed", boundedMessage(error, "list"));
|
|
4056
|
+
}
|
|
4057
|
+
});
|
|
4058
|
+
withTeamOptions(
|
|
4059
|
+
iconSet.command("status").description("Show one Team Icon Set").argument("<slug>", "Team-local Icon Set slug")
|
|
4060
|
+
).action(async (slug, options) => {
|
|
4061
|
+
await readCommand(slug, options, "icon-set status", async (authority) => {
|
|
4062
|
+
const response = await authorizedTeamRequest(
|
|
4063
|
+
authority,
|
|
4064
|
+
`/v1/icon-sets/${encodeURIComponent(slug)}`,
|
|
4065
|
+
"GET"
|
|
4066
|
+
);
|
|
4067
|
+
if (!response.ok) return { status: response.status };
|
|
4068
|
+
const parsed = catalogDetailResponseSchema.parse(response.body);
|
|
4069
|
+
return { value: { set: parsed.set } };
|
|
4070
|
+
});
|
|
4071
|
+
});
|
|
4072
|
+
withTeamOptions(
|
|
4073
|
+
iconSet.command("versions").description("List the retained revisions of one Team Icon Set").argument("<slug>", "Team-local Icon Set slug")
|
|
4074
|
+
).action(async (slug, options) => {
|
|
4075
|
+
await readCommand(slug, options, "icon-set versions", async (authority) => {
|
|
4076
|
+
const versions = await readAllPages(
|
|
4077
|
+
authority,
|
|
4078
|
+
`/v1/icon-sets/${encodeURIComponent(slug)}/versions`,
|
|
4079
|
+
{},
|
|
4080
|
+
versionsResponseSchema,
|
|
4081
|
+
(page) => page.versions,
|
|
4082
|
+
(revision) => revision.id
|
|
4083
|
+
);
|
|
4084
|
+
return { value: { versions: versions.map(describeRevision) } };
|
|
4085
|
+
});
|
|
4086
|
+
});
|
|
4087
|
+
withTeamOptions(
|
|
4088
|
+
iconSet.command("uses").description("List the known hosted deployments that retain one Team Icon Set").argument("<slug>", "Team-local Icon Set slug")
|
|
4089
|
+
).action(async (slug, options) => {
|
|
4090
|
+
await readCommand(slug, options, "icon-set uses", async (authority) => {
|
|
4091
|
+
const uses = await readAllPages(
|
|
4092
|
+
authority,
|
|
4093
|
+
`/v1/icon-sets/${encodeURIComponent(slug)}/uses`,
|
|
4094
|
+
{},
|
|
4095
|
+
usesResponseSchema,
|
|
4096
|
+
(page) => page.uses,
|
|
4097
|
+
(use) => `${use.deploymentId}\0${use.versionId}\0${String(use.ordinal)}`
|
|
4098
|
+
);
|
|
4099
|
+
return {
|
|
4100
|
+
value: {
|
|
4101
|
+
// Repository locks that were never deployed are unknowable to the catalog.
|
|
4102
|
+
coverage: "known-hosted-deployments",
|
|
4103
|
+
includesUndeployedRepositoryLocks: false,
|
|
4104
|
+
uses
|
|
4105
|
+
}
|
|
4106
|
+
};
|
|
4107
|
+
});
|
|
4108
|
+
});
|
|
4109
|
+
for (const [name, archived] of [
|
|
4110
|
+
["archive", true],
|
|
4111
|
+
["unarchive", false]
|
|
4112
|
+
]) {
|
|
4113
|
+
withTeamOptions(
|
|
4114
|
+
iconSet.command(name).description(
|
|
4115
|
+
archived ? "Archive one Team Icon Set without removing its retained revisions" : "Return one archived Team Icon Set to the active catalog"
|
|
4116
|
+
).argument("<slug>", "Team-local Icon Set slug")
|
|
4117
|
+
).action(async (slug, options) => {
|
|
4118
|
+
try {
|
|
4119
|
+
if (!isIconSetSlug(slug)) {
|
|
4120
|
+
return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
|
|
4121
|
+
}
|
|
4122
|
+
const authority = await resolveTeamAuthority(options, ["icons:write"], dependencies);
|
|
4123
|
+
if (!authority) return;
|
|
4124
|
+
const response = await authorizedTeamRequest(
|
|
4125
|
+
authority,
|
|
4126
|
+
`/v1/icon-sets/${encodeURIComponent(slug)}`,
|
|
4127
|
+
"PATCH",
|
|
4128
|
+
{
|
|
4129
|
+
body: JSON.stringify({ archived }),
|
|
4130
|
+
headers: { "Content-Type": "application/json" }
|
|
4131
|
+
}
|
|
4132
|
+
);
|
|
4133
|
+
if (!response.ok) return emitHttpFailure(options.json, response.status, name);
|
|
4134
|
+
const parsed = catalogDetailResponseSchema.parse(response.body);
|
|
4135
|
+
const document = {
|
|
4136
|
+
command: `icon-set ${name}`,
|
|
4137
|
+
schemaVersion: 1,
|
|
4138
|
+
set: parsed.set,
|
|
4139
|
+
team: describeTeam(authority)
|
|
4140
|
+
};
|
|
4141
|
+
if (options.json) return emitJson(document);
|
|
4142
|
+
console.log(
|
|
4143
|
+
`${pc2.green("\u2713")} ${archived ? "Archived" : "Restored"} ${pc2.bold(parsed.set.reference)}.`
|
|
4144
|
+
);
|
|
4145
|
+
} catch (error) {
|
|
4146
|
+
emitFailure(options.json, `icon_set_${name}_failed`, boundedMessage(error, name));
|
|
4147
|
+
}
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
withTeamOptions(
|
|
4151
|
+
iconSet.command("purge").description("Permanently purge one exact Team Icon Set revision").argument("<slug>", "Team-local Icon Set slug").requiredOption("--version <version>", "exact positive revision to purge").requiredOption("--idempotency-key <key>", "durable retry key for this exact purge").requiredOption("--confirm <reference>", "repeat the exact @team/set@version being purged").option(
|
|
4152
|
+
"--acknowledge-unknown-locks",
|
|
4153
|
+
"acknowledge that undeployed repository locks are not discoverable"
|
|
4154
|
+
)
|
|
4155
|
+
).action(
|
|
4156
|
+
async (slug, options) => {
|
|
4157
|
+
try {
|
|
4158
|
+
if (!isIconSetSlug(slug)) {
|
|
4159
|
+
return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
|
|
4160
|
+
}
|
|
4161
|
+
if (!/^[1-9][0-9]*$/u.test(options.version) || !Number.isSafeInteger(+options.version)) {
|
|
4162
|
+
return emitFailure(
|
|
4163
|
+
options.json,
|
|
4164
|
+
"invalid_icon_set_version",
|
|
4165
|
+
"Purge requires one exact positive revision."
|
|
4166
|
+
);
|
|
4167
|
+
}
|
|
4168
|
+
if (!isIdempotencyKey(options.idempotencyKey)) {
|
|
4169
|
+
return emitFailure(
|
|
4170
|
+
options.json,
|
|
4171
|
+
"invalid_idempotency_key",
|
|
4172
|
+
"Idempotency key must be 8 to 128 characters from A-Z a-z 0-9 . _ : -"
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4175
|
+
const confirmation = parsePurgeConfirmation(options.confirm);
|
|
4176
|
+
if (!confirmation || confirmation.slug !== slug || confirmation.version !== Number(options.version)) {
|
|
4177
|
+
return emitFailure(
|
|
4178
|
+
options.json,
|
|
4179
|
+
"purge_confirmation_mismatch",
|
|
4180
|
+
"Purge confirmation must repeat the exact @team/set@version being purged."
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
if (options.acknowledgeUnknownLocks !== true) {
|
|
4184
|
+
return emitFailure(
|
|
4185
|
+
options.json,
|
|
4186
|
+
"purge_unknown_locks_unacknowledged",
|
|
4187
|
+
"Purge requires --acknowledge-unknown-locks; undeployed repository locks are not discoverable."
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
const authority = await resolveTeamAuthority(options, ["icons:write"], dependencies);
|
|
4191
|
+
if (!authority) return;
|
|
4192
|
+
if (authority.team && `@${authority.team.slug}` !== confirmation.team) {
|
|
4193
|
+
return emitFailure(
|
|
4194
|
+
options.json,
|
|
4195
|
+
"purge_confirmation_mismatch",
|
|
4196
|
+
"Purge confirmation must name the selected Team."
|
|
4197
|
+
);
|
|
4198
|
+
}
|
|
4199
|
+
const response = await authorizedTeamRequest(
|
|
4200
|
+
authority,
|
|
4201
|
+
`/v1/icon-sets/${encodeURIComponent(slug)}/versions/${encodeURIComponent(options.version)}/purge`,
|
|
4202
|
+
"POST",
|
|
4203
|
+
{
|
|
4204
|
+
body: JSON.stringify({
|
|
4205
|
+
acknowledgeUnknownLocks: true,
|
|
4206
|
+
confirmation: options.confirm
|
|
4207
|
+
}),
|
|
4208
|
+
headers: {
|
|
4209
|
+
"Content-Type": "application/json",
|
|
4210
|
+
"Idempotency-Key": options.idempotencyKey
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
);
|
|
4214
|
+
if (!response.ok) return emitHttpFailure(options.json, response.status, "purge");
|
|
4215
|
+
const parsed = purgeResponseSchema.parse(response.body);
|
|
4216
|
+
const document = {
|
|
4217
|
+
command: "icon-set purge",
|
|
4218
|
+
result: parsed.result,
|
|
4219
|
+
schemaVersion: 1,
|
|
4220
|
+
team: describeTeam(authority)
|
|
4221
|
+
};
|
|
4222
|
+
if (options.json) return emitJson(document);
|
|
4223
|
+
console.log(`${pc2.green("\u2713")} Purged ${pc2.bold(options.confirm)}.`);
|
|
4224
|
+
console.log(pc2.yellow(`! ${parsed.result.warning}`));
|
|
4225
|
+
} catch (error) {
|
|
4226
|
+
emitFailure(options.json, "icon_set_purge_failed", boundedMessage(error, "purge"));
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
);
|
|
4230
|
+
async function readCommand(slug, options, command2, run) {
|
|
4231
|
+
try {
|
|
4232
|
+
if (!isIconSetSlug(slug)) {
|
|
4233
|
+
return emitFailure(options.json, "invalid_icon_set_id", "Icon Set ID is invalid.");
|
|
4234
|
+
}
|
|
4235
|
+
const authority = await resolveTeamAuthority(options, ["icons:read"], dependencies);
|
|
4236
|
+
if (!authority) return;
|
|
4237
|
+
const result = await run(authority);
|
|
4238
|
+
if ("status" in result) return emitHttpFailure(options.json, result.status, command2);
|
|
4239
|
+
const document = {
|
|
4240
|
+
command: command2,
|
|
4241
|
+
schemaVersion: 1,
|
|
4242
|
+
team: describeTeam(authority),
|
|
4243
|
+
...result.value
|
|
4244
|
+
};
|
|
4245
|
+
if (options.json) return emitJson(document);
|
|
4246
|
+
process.stdout.write(`${JSON.stringify(document, null, 2)}
|
|
4247
|
+
`);
|
|
4248
|
+
} catch (error) {
|
|
4249
|
+
emitFailure(
|
|
4250
|
+
options.json,
|
|
4251
|
+
`${command2.replaceAll(/[^a-z]+/gu, "_")}_failed`,
|
|
4252
|
+
boundedMessage(error, command2)
|
|
4253
|
+
);
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
function withTeamOptions(command2) {
|
|
4258
|
+
return command2.option("--team <team>", "target Team as @team").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Team data API key", process.env.TILEFLOW_API_KEY).option("--json", "print deterministic schema-version-1 JSON");
|
|
4259
|
+
}
|
|
4260
|
+
async function readAllPages(authority, path, query, schema, select, identify) {
|
|
4261
|
+
const items = [];
|
|
4262
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
4263
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4264
|
+
let cursor = null;
|
|
4265
|
+
for (let page = 0; page < maximumCatalogPages; page += 1) {
|
|
4266
|
+
const parameters = new URLSearchParams({ limit: String(pageSize), ...query });
|
|
4267
|
+
if (cursor) parameters.set("cursor", cursor);
|
|
4268
|
+
const response = await authorizedTeamRequest(
|
|
4269
|
+
authority,
|
|
4270
|
+
`${path}?${parameters.toString()}`,
|
|
4271
|
+
"GET"
|
|
4272
|
+
);
|
|
4273
|
+
if (!response.ok) throw new IconSetTransportError(response.status);
|
|
4274
|
+
const parsed = schema.parse(response.body);
|
|
4275
|
+
for (const item of select(parsed)) {
|
|
4276
|
+
const id = identify(item);
|
|
4277
|
+
if (seenIds.has(id)) throw new Error("Icon Set list returned a duplicate row.");
|
|
4278
|
+
seenIds.add(id);
|
|
4279
|
+
items.push(item);
|
|
4280
|
+
}
|
|
4281
|
+
cursor = parsed.nextCursor ?? null;
|
|
4282
|
+
if (!cursor) return items;
|
|
4283
|
+
if (seenCursors.has(cursor)) throw new Error("Icon Set list repeated a cursor.");
|
|
4284
|
+
seenCursors.add(cursor);
|
|
4285
|
+
}
|
|
4286
|
+
throw new Error("Icon Set list exceeded its safe page limit.");
|
|
4287
|
+
}
|
|
4288
|
+
var IconSetTransportError = class extends Error {
|
|
4289
|
+
constructor(status) {
|
|
4290
|
+
super(`Icon Set request failed (${status}).`);
|
|
4291
|
+
this.status = status;
|
|
4292
|
+
this.name = "IconSetTransportError";
|
|
4293
|
+
}
|
|
4294
|
+
status;
|
|
4295
|
+
};
|
|
4296
|
+
function emitHttpFailure(json, status, command2) {
|
|
4297
|
+
emitFailure(json, `http_${status}`, `Icon Set ${command2} failed (${status}).`);
|
|
4298
|
+
}
|
|
4299
|
+
function boundedMessage(error, command2) {
|
|
4300
|
+
if (error instanceof IconSetTransportError) return error.message;
|
|
4301
|
+
if (error instanceof Error && /limit|cursor|duplicate row/u.test(error.message)) {
|
|
4302
|
+
return error.message;
|
|
4303
|
+
}
|
|
4304
|
+
return `Icon Set ${command2} failed.`;
|
|
4305
|
+
}
|
|
4306
|
+
function describeTeam(authority) {
|
|
4307
|
+
return authority.team ? { id: authority.team.id, slug: authority.team.slug } : null;
|
|
4308
|
+
}
|
|
4309
|
+
function describeRevision(revision) {
|
|
4310
|
+
return {
|
|
4311
|
+
iconCount: revision.iconCount,
|
|
4312
|
+
id: revision.id,
|
|
4313
|
+
packageId: revision.packageId,
|
|
4314
|
+
publishedAt: revision.publishedAt,
|
|
4315
|
+
purgedAt: revision.purgedAt,
|
|
4316
|
+
reference: revision.reference,
|
|
4317
|
+
totalBytes: revision.totalBytes,
|
|
4318
|
+
version: revision.version
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
function normalizeLocalDirectory(value) {
|
|
4322
|
+
if (!value || isAbsolute2(value) || value.includes("\\") || /[\p{Cc}]/u.test(value)) return null;
|
|
4323
|
+
const normalized = value.startsWith("./") || value.startsWith("../") ? value : `./${value}`;
|
|
4324
|
+
return normalized.replace(/\/+$/u, "");
|
|
4325
|
+
}
|
|
4326
|
+
function parsePurgeConfirmation(value) {
|
|
4327
|
+
const separator = value.lastIndexOf("@");
|
|
4328
|
+
if (separator <= 0 || value.length > 160) return null;
|
|
4329
|
+
const reference = value.slice(0, separator);
|
|
4330
|
+
const version = value.slice(separator + 1);
|
|
4331
|
+
if (!tileflowIconSetReferenceSchema.safeParse(reference).success) return null;
|
|
4332
|
+
if (!/^[1-9][0-9]*$/u.test(version) || !Number.isSafeInteger(Number(version))) return null;
|
|
4333
|
+
return { slug: reference.split("/")[1], team: reference.split("/")[0], version: Number(version) };
|
|
4334
|
+
}
|
|
4335
|
+
function isIconSetSlug(value) {
|
|
4336
|
+
return value.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value);
|
|
4337
|
+
}
|
|
4338
|
+
function isIdempotencyKey(value) {
|
|
4339
|
+
return value.length >= 8 && value.length <= 128 && /^[A-Za-z0-9._:-]+$/u.test(value);
|
|
4340
|
+
}
|
|
4341
|
+
var cursorSchema = z5.string().min(1).max(2048).nullable();
|
|
4342
|
+
var isoDateSchema = z5.iso.datetime({ offset: true });
|
|
4343
|
+
var setSchema = z5.object({
|
|
4344
|
+
id: z5.string().regex(/^ics_[A-Za-z0-9_-]{16}$/u),
|
|
4345
|
+
reference: tileflowIconSetReferenceSchema,
|
|
4346
|
+
name: z5.string().max(200),
|
|
4347
|
+
description: z5.string().max(2e3),
|
|
4348
|
+
archivedAt: isoDateSchema.nullable(),
|
|
4349
|
+
latestVersion: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
4350
|
+
createdAt: isoDateSchema,
|
|
4351
|
+
updatedAt: isoDateSchema
|
|
4352
|
+
}).strict();
|
|
4353
|
+
var revisionBaseSchema = z5.object({
|
|
4354
|
+
id: z5.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
|
|
4355
|
+
reference: tileflowIconSetReferenceSchema,
|
|
4356
|
+
version: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
4357
|
+
publishedAt: isoDateSchema,
|
|
4358
|
+
purgedAt: isoDateSchema.nullable(),
|
|
4359
|
+
iconCount: z5.number().int().min(0).max(256),
|
|
4360
|
+
totalBytes: z5.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
4361
|
+
publisher: z5.object({ kind: z5.enum(["credential", "membership"]), id: z5.string().max(200).nullable() }).strict()
|
|
4362
|
+
}).strict();
|
|
4363
|
+
var revisionSchema = revisionBaseSchema.extend({ pin: tileflowIconSetPinSchema.nullable() }).transform(({ id, reference, version, publishedAt, purgedAt, iconCount, totalBytes, pin }) => ({
|
|
4364
|
+
iconCount,
|
|
4365
|
+
id,
|
|
4366
|
+
pin,
|
|
4367
|
+
publishedAt,
|
|
4368
|
+
purgedAt,
|
|
4369
|
+
reference,
|
|
4370
|
+
totalBytes,
|
|
4371
|
+
version
|
|
4372
|
+
}));
|
|
4373
|
+
var revisionSummarySchema = revisionBaseSchema.extend({ packageId: tileflowIconSetPackageIdSchema.nullable() }).transform(
|
|
4374
|
+
({ id, reference, version, publishedAt, purgedAt, iconCount, totalBytes, packageId }) => ({
|
|
4375
|
+
iconCount,
|
|
4376
|
+
id,
|
|
4377
|
+
packageId,
|
|
4378
|
+
publishedAt,
|
|
4379
|
+
purgedAt,
|
|
4380
|
+
reference,
|
|
4381
|
+
totalBytes,
|
|
4382
|
+
version
|
|
4383
|
+
})
|
|
4384
|
+
);
|
|
4385
|
+
var catalogListResponseSchema = z5.object({
|
|
4386
|
+
schemaVersion: z5.literal(1),
|
|
4387
|
+
sets: z5.array(setSchema).max(pageSize),
|
|
4388
|
+
nextCursor: cursorSchema.optional()
|
|
4389
|
+
}).strict();
|
|
4390
|
+
var catalogDetailResponseSchema = z5.object({ schemaVersion: z5.literal(1), set: setSchema }).strict();
|
|
4391
|
+
var versionsResponseSchema = z5.object({
|
|
4392
|
+
schemaVersion: z5.literal(1),
|
|
4393
|
+
versions: z5.array(revisionSummarySchema).max(pageSize),
|
|
4394
|
+
nextCursor: cursorSchema.optional()
|
|
4395
|
+
}).strict();
|
|
4396
|
+
var revisionResponseSchema = z5.object({
|
|
4397
|
+
schemaVersion: z5.literal(1),
|
|
4398
|
+
archived: z5.boolean(),
|
|
4399
|
+
revision: revisionSchema
|
|
4400
|
+
}).strict();
|
|
4401
|
+
var publishResponseSchema = z5.object({ schemaVersion: z5.literal(1), changed: z5.boolean(), revision: revisionSchema }).strict();
|
|
4402
|
+
var usesResponseSchema = z5.object({
|
|
4403
|
+
schemaVersion: z5.literal(1),
|
|
4404
|
+
uses: z5.array(
|
|
4405
|
+
z5.object({
|
|
4406
|
+
deploymentId: z5.string().min(1).max(160),
|
|
4407
|
+
deploymentVersion: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
4408
|
+
mapId: z5.string().min(1).max(160),
|
|
4409
|
+
mapName: z5.string().max(200).nullable(),
|
|
4410
|
+
mapState: z5.string().max(64),
|
|
4411
|
+
versionId: z5.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
|
|
4412
|
+
version: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
4413
|
+
ordinal: z5.number().int().min(0).max(31),
|
|
4414
|
+
relationship: z5.enum(["active", "retained"])
|
|
4415
|
+
}).strict()
|
|
4416
|
+
).max(pageSize),
|
|
4417
|
+
nextCursor: cursorSchema.optional(),
|
|
4418
|
+
coverage: z5.literal("known-hosted-deployments"),
|
|
4419
|
+
includesUndeployedRepositoryLocks: z5.literal(false)
|
|
4420
|
+
}).strict();
|
|
4421
|
+
var purgeResponseSchema = z5.object({
|
|
4422
|
+
schemaVersion: z5.literal(1),
|
|
4423
|
+
result: z5.object({
|
|
4424
|
+
schemaVersion: z5.literal(1),
|
|
4425
|
+
versionId: z5.string().regex(/^icv_[A-Za-z0-9_-]{16}$/u),
|
|
4426
|
+
version: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
4427
|
+
purged: z5.boolean(),
|
|
4428
|
+
knownRetainedDeployments: z5.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
4429
|
+
physicalBytesReleased: z5.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
4430
|
+
unknownRepositoryLocks: z5.boolean(),
|
|
4431
|
+
warning: z5.string().max(400)
|
|
4432
|
+
}).strict()
|
|
4433
|
+
}).strict();
|
|
4434
|
+
|
|
4435
|
+
// src/icon-lock-command.ts
|
|
4436
|
+
function registerIconLockCommands(program2, dependencies) {
|
|
4437
|
+
const icons = program2.commands.find((command2) => command2.name() === "icons") ?? program2.command("icons").description("Inspect managed Tileflow icons");
|
|
4438
|
+
withLockOptions(
|
|
4439
|
+
icons.command("install").description("Lock every declared Icon Set reference that has no exact pin yet").argument("[references...]", "declared @team/set references; omit for every declared set"),
|
|
4440
|
+
dependencies
|
|
4441
|
+
).action(async (references, options) => {
|
|
4442
|
+
await maintainLock("install", references, options);
|
|
4443
|
+
});
|
|
4444
|
+
withLockOptions(
|
|
4445
|
+
icons.command("update").description("Move selected declared Icon Set references to their current latest revision").argument("[references...]", "declared @team/set references; omit for every declared set"),
|
|
4446
|
+
dependencies
|
|
4447
|
+
).action(async (references, options) => {
|
|
4448
|
+
await maintainLock("update", references, options);
|
|
4449
|
+
});
|
|
4450
|
+
withLockOptions(
|
|
4451
|
+
icons.command("pin").description("Lock one declared Icon Set reference to one exact revision").argument("<reference>", "declared @team/set reference").requiredOption("--version <version>", "exact positive revision"),
|
|
4452
|
+
dependencies
|
|
4453
|
+
).action(async (reference, options) => {
|
|
4454
|
+
if (!/^[1-9][0-9]*$/u.test(options.version) || !Number.isSafeInteger(+options.version)) {
|
|
4455
|
+
return emitFailure(
|
|
4456
|
+
options.json,
|
|
4457
|
+
"invalid_icon_set_version",
|
|
4458
|
+
"Pin requires one exact positive revision."
|
|
4459
|
+
);
|
|
4460
|
+
}
|
|
4461
|
+
await maintainLock("pin", [reference], options, Number(options.version));
|
|
4462
|
+
});
|
|
4463
|
+
async function maintainLock(operation, requested, options, version) {
|
|
4464
|
+
let declared;
|
|
4465
|
+
let baseDirectory;
|
|
4466
|
+
try {
|
|
4467
|
+
const loaded = await withTileflowConfigSecretsHidden(
|
|
4468
|
+
() => loadValidTileflowConfigWithInputs3(options.config)
|
|
4469
|
+
);
|
|
4470
|
+
baseDirectory = dirname8(loaded.configFile);
|
|
4471
|
+
declared = collectDeclaredReferences(loaded.project);
|
|
4472
|
+
} catch (error) {
|
|
4473
|
+
return emitFailure(
|
|
4474
|
+
options.json,
|
|
4475
|
+
error instanceof TileflowValidationError4 ? "config_invalid" : "config_load_failed",
|
|
4476
|
+
error instanceof TileflowValidationError4 ? "Tileflow config has errors." : "Tileflow config could not be loaded."
|
|
4477
|
+
);
|
|
4478
|
+
}
|
|
4479
|
+
const selection = selectReferences(operation, requested, declared);
|
|
4480
|
+
if ("error" in selection) {
|
|
4481
|
+
return emitFailure(options.json, selection.error.code, selection.error.message);
|
|
4482
|
+
}
|
|
4483
|
+
let expected;
|
|
4484
|
+
let current;
|
|
4485
|
+
try {
|
|
4486
|
+
expected = await readTileflowIconsLockfileText(baseDirectory);
|
|
4487
|
+
current = expected === null ? {} : await readCurrentPins(expected);
|
|
4488
|
+
} catch {
|
|
4489
|
+
return emitFailure(
|
|
4490
|
+
options.json,
|
|
4491
|
+
"icon_lock_unreadable",
|
|
4492
|
+
`Existing ${tileflowIconsLockfileName} is unreadable; repair or remove it explicitly.`
|
|
4493
|
+
);
|
|
4494
|
+
}
|
|
4495
|
+
const targets = operation === "install" ? selection.selected.filter((reference) => !current[reference]) : selection.selected;
|
|
4496
|
+
if (targets.length === 0 && operation !== "install") {
|
|
4497
|
+
return emitFailure(
|
|
4498
|
+
options.json,
|
|
4499
|
+
"icon_set_not_declared",
|
|
4500
|
+
"Select at least one declared @team/set reference."
|
|
4501
|
+
);
|
|
4502
|
+
}
|
|
4503
|
+
const authority = targets.length === 0 ? null : await resolveTeamAuthority(options, ["icons:read"], dependencies);
|
|
4504
|
+
if (targets.length > 0 && !authority) return;
|
|
4505
|
+
const resolved = {};
|
|
4506
|
+
for (const reference of targets) {
|
|
4507
|
+
const result = await resolvePin(authority, reference, version);
|
|
4508
|
+
if ("error" in result) {
|
|
4509
|
+
return emitFailure(options.json, result.error.code, result.error.message);
|
|
4510
|
+
}
|
|
4511
|
+
resolved[reference] = result.pin;
|
|
4512
|
+
}
|
|
4513
|
+
const sets = {};
|
|
4514
|
+
for (const reference of declared.references) {
|
|
4515
|
+
const pin = resolved[reference] ?? current[reference];
|
|
4516
|
+
if (pin) sets[reference] = pin;
|
|
4517
|
+
}
|
|
4518
|
+
if (expected !== null || Object.keys(sets).length > 0) {
|
|
4519
|
+
try {
|
|
4520
|
+
await writeTileflowIconsLockfile(baseDirectory, { lockfileVersion: 1, sets }, expected);
|
|
4521
|
+
} catch (error) {
|
|
4522
|
+
const code = error !== null && typeof error === "object" && "code" in error ? String(error.code) : "icon_lock_write_failed";
|
|
4523
|
+
return emitFailure(
|
|
4524
|
+
options.json,
|
|
4525
|
+
code,
|
|
4526
|
+
code === "ICON_LOCK_CONFLICT" ? "The icon lock changed while this command ran; rerun it without discarding other pins." : "The icon lock could not be written."
|
|
4527
|
+
);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
const missing = declared.references.filter((reference) => !sets[reference]);
|
|
4531
|
+
const document = {
|
|
4532
|
+
command: `icons ${operation}`,
|
|
4533
|
+
lockfile: tileflowIconsLockfileName,
|
|
4534
|
+
missing,
|
|
4535
|
+
schemaVersion: 1,
|
|
4536
|
+
sets: declared.references.filter((reference) => sets[reference]).map((reference) => ({
|
|
4537
|
+
changed: Boolean(resolved[reference]),
|
|
4538
|
+
contentHash: sets[reference].contentHash,
|
|
4539
|
+
packageId: sets[reference].packageId,
|
|
4540
|
+
reference,
|
|
4541
|
+
version: sets[reference].version,
|
|
4542
|
+
versionId: sets[reference].versionId
|
|
3711
4543
|
}))
|
|
4544
|
+
};
|
|
4545
|
+
if (options.json) return emitJson(document);
|
|
4546
|
+
if (document.sets.length === 0) return console.log(pc3.gray("No Icon Set references declared."));
|
|
4547
|
+
for (const entry of document.sets) {
|
|
4548
|
+
console.log(
|
|
4549
|
+
` ${pc3.bold(entry.reference)} ${pc3.gray(`v${String(entry.version)}`)}${entry.changed ? "" : pc3.gray(" (unchanged)")}`
|
|
4550
|
+
);
|
|
4551
|
+
}
|
|
4552
|
+
for (const reference of missing) {
|
|
4553
|
+
console.log(pc3.yellow(`! ${reference} is declared but not locked.`));
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
async function resolvePin(authority, reference, version) {
|
|
4557
|
+
const slug = reference.split("/")[1];
|
|
4558
|
+
const selector = version === void 0 ? "latest" : String(version);
|
|
4559
|
+
const response = await authorizedTeamRequest(
|
|
4560
|
+
authority,
|
|
4561
|
+
`/v1/icon-sets/${encodeURIComponent(slug)}/versions/${encodeURIComponent(selector)}`,
|
|
4562
|
+
"GET"
|
|
4563
|
+
);
|
|
4564
|
+
if (!response.ok) {
|
|
4565
|
+
return {
|
|
4566
|
+
error: {
|
|
4567
|
+
code: `http_${response.status}`,
|
|
4568
|
+
message: `Resolving ${reference}@${selector} failed (${response.status}).`
|
|
4569
|
+
}
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
const parsed = revisionResponseSchema.safeParse(response.body);
|
|
4573
|
+
if (!parsed.success) {
|
|
4574
|
+
return {
|
|
4575
|
+
error: {
|
|
4576
|
+
code: "icon_set_response_invalid",
|
|
4577
|
+
message: `Resolving ${reference}@${selector} returned an invalid response.`
|
|
4578
|
+
}
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4581
|
+
const revision = parsed.data.revision;
|
|
4582
|
+
if (revision.reference !== reference) {
|
|
4583
|
+
return {
|
|
4584
|
+
error: {
|
|
4585
|
+
code: "icon_set_reference_mismatch",
|
|
4586
|
+
message: `Resolving ${reference} returned a different Icon Set.`
|
|
4587
|
+
}
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
if (!revision.pin || revision.purgedAt !== null) {
|
|
4591
|
+
return {
|
|
4592
|
+
error: {
|
|
4593
|
+
code: "icon_set_revision_unavailable",
|
|
4594
|
+
message: `${reference}@${selector} has no retained delivery artifact.`
|
|
4595
|
+
}
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
if (version !== void 0 && revision.version !== version) {
|
|
4599
|
+
return {
|
|
4600
|
+
error: {
|
|
4601
|
+
code: "icon_set_revision_mismatch",
|
|
4602
|
+
message: `${reference}@${selector} returned revision ${revision.version}.`
|
|
4603
|
+
}
|
|
4604
|
+
};
|
|
4605
|
+
}
|
|
4606
|
+
if (authority.team && revision.pin.teamId !== authority.team.id) {
|
|
4607
|
+
return {
|
|
4608
|
+
error: {
|
|
4609
|
+
code: "icon_set_team_mismatch",
|
|
4610
|
+
message: `${reference} is owned by a different Team.`
|
|
4611
|
+
}
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
return { pin: revision.pin };
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
function withLockOptions(command2, dependencies) {
|
|
4618
|
+
return command2.option("-c, --config <path>", "config path", dependencies.defaultConfigPath).option("--team <team>", "target Team as @team").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Team data API key", process.env.TILEFLOW_API_KEY).option("--json", "print deterministic schema-version-1 JSON");
|
|
4619
|
+
}
|
|
4620
|
+
function collectDeclaredReferences(project) {
|
|
4621
|
+
const references = [];
|
|
4622
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4623
|
+
for (const mapName of getTileflowMapNames3(project).sort(compareCodeUnits7)) {
|
|
4624
|
+
const sources = parseResolvedTileflowMap2(project.maps[mapName]).icons ?? [];
|
|
4625
|
+
for (const reference of collectTileflowIconSetReferences(sources)) {
|
|
4626
|
+
if (seen.has(reference)) continue;
|
|
4627
|
+
seen.add(reference);
|
|
4628
|
+
references.push(reference);
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
return { references: references.sort(compareCodeUnits7) };
|
|
4632
|
+
}
|
|
4633
|
+
function selectReferences(operation, requested, declared) {
|
|
4634
|
+
if (requested.length === 0) {
|
|
4635
|
+
if (operation === "pin") {
|
|
4636
|
+
return { error: { code: "icon_set_not_selected", message: "Pin one @team/set reference." } };
|
|
4637
|
+
}
|
|
4638
|
+
return { selected: [...declared.references] };
|
|
4639
|
+
}
|
|
4640
|
+
const selected = [];
|
|
4641
|
+
for (const reference of requested) {
|
|
4642
|
+
if (!tileflowIconSetReferenceSchema2.safeParse(reference).success) {
|
|
4643
|
+
return {
|
|
4644
|
+
error: {
|
|
4645
|
+
code: "invalid_icon_set_reference",
|
|
4646
|
+
message: `${reference} is not a canonical @team/set reference.`
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
if (!declared.references.includes(reference)) {
|
|
4651
|
+
return {
|
|
4652
|
+
error: {
|
|
4653
|
+
code: "icon_set_not_declared",
|
|
4654
|
+
message: `${reference} is not declared by any map; add iconSet('${reference}') first.`
|
|
4655
|
+
}
|
|
4656
|
+
};
|
|
4657
|
+
}
|
|
4658
|
+
if (!selected.includes(reference)) {
|
|
4659
|
+
selected.push(reference);
|
|
3712
4660
|
}
|
|
3713
|
-
};
|
|
3714
|
-
}
|
|
3715
|
-
function sameStrings(left, right) {
|
|
3716
|
-
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3717
|
-
}
|
|
3718
|
-
function printIconListError(error) {
|
|
3719
|
-
if (error instanceof TileflowValidationError3) {
|
|
3720
|
-
printIssues("Tileflow config has errors.", error.messages);
|
|
3721
|
-
return;
|
|
3722
|
-
}
|
|
3723
|
-
if (error instanceof TileflowIconCompilationError2) {
|
|
3724
|
-
printIssues("Tileflow icon catalog has errors.", error.issues);
|
|
3725
|
-
return;
|
|
3726
4661
|
}
|
|
3727
|
-
|
|
4662
|
+
return { selected: selected.sort(compareCodeUnits7) };
|
|
3728
4663
|
}
|
|
3729
|
-
function
|
|
3730
|
-
|
|
3731
|
-
[heading, ...issues.map((issue) => `- ${issue.path || "(root)"}: ${issue.message}`)].join("\n")
|
|
3732
|
-
);
|
|
4664
|
+
async function readCurrentPins(contents) {
|
|
4665
|
+
return (await parseTileflowIconsLockfile(contents)).sets;
|
|
3733
4666
|
}
|
|
3734
4667
|
|
|
3735
4668
|
// src/language-command.ts
|
|
3736
4669
|
import { readFile as readFile4 } from "fs/promises";
|
|
3737
4670
|
import { fileURLToPath } from "url";
|
|
3738
4671
|
import {
|
|
3739
|
-
serializeCanonicalJson,
|
|
4672
|
+
serializeCanonicalJson as serializeCanonicalJson2,
|
|
3740
4673
|
tileflowAuthoringManifest
|
|
3741
4674
|
} from "@tileflow/core";
|
|
3742
4675
|
import {
|
|
@@ -3778,7 +4711,7 @@ async function runLanguageCommand(command2, options, operation) {
|
|
|
3778
4711
|
}
|
|
3779
4712
|
function printManifest(manifest, options) {
|
|
3780
4713
|
if (options.json) {
|
|
3781
|
-
process.stdout.write(`${
|
|
4714
|
+
process.stdout.write(`${serializeCanonicalJson2(manifest)}
|
|
3782
4715
|
`);
|
|
3783
4716
|
return;
|
|
3784
4717
|
}
|
|
@@ -3789,7 +4722,7 @@ function printManifest(manifest, options) {
|
|
|
3789
4722
|
}
|
|
3790
4723
|
function printSchema(schema, options) {
|
|
3791
4724
|
if (options.json) {
|
|
3792
|
-
process.stdout.write(`${
|
|
4725
|
+
process.stdout.write(`${serializeCanonicalJson2(schema)}
|
|
3793
4726
|
`);
|
|
3794
4727
|
return;
|
|
3795
4728
|
}
|
|
@@ -3841,7 +4774,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
|
|
|
3841
4774
|
}
|
|
3842
4775
|
|
|
3843
4776
|
// src/project-commands.ts
|
|
3844
|
-
import
|
|
4777
|
+
import pc4 from "picocolors";
|
|
3845
4778
|
function registerProjectCommands(program2, dependencies) {
|
|
3846
4779
|
const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
|
|
3847
4780
|
projects.command("list").description("List application destinations accessible to the authenticated Tileflow account").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--include-archived", "include archived application destinations").option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
|
|
@@ -3855,14 +4788,14 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3855
4788
|
command: "projects list",
|
|
3856
4789
|
projects: result.items
|
|
3857
4790
|
};
|
|
3858
|
-
if (options.json) return
|
|
4791
|
+
if (options.json) return emitJson2(document);
|
|
3859
4792
|
if (!result.items.length) {
|
|
3860
|
-
console.log(
|
|
4793
|
+
console.log(pc4.gray("No managed destinations found."));
|
|
3861
4794
|
return;
|
|
3862
4795
|
}
|
|
3863
4796
|
for (const target of result.items) {
|
|
3864
4797
|
console.log(
|
|
3865
|
-
` ${target.project.archivedAt ?
|
|
4798
|
+
` ${target.project.archivedAt ? pc4.yellow("\u25CB") : pc4.green("\u25CF")} ${pc4.bold(target.project.name)} ${pc4.gray(target.reference)}${target.project.archivedAt ? pc4.gray(" (archived)") : ""}`
|
|
3866
4799
|
);
|
|
3867
4800
|
}
|
|
3868
4801
|
});
|
|
@@ -3914,9 +4847,9 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3914
4847
|
command: "projects create",
|
|
3915
4848
|
...asRecord2(result.body)
|
|
3916
4849
|
};
|
|
3917
|
-
if (options.json) return
|
|
3918
|
-
console.log(`${
|
|
3919
|
-
console.log(
|
|
4850
|
+
if (options.json) return emitJson2(document);
|
|
4851
|
+
console.log(`${pc4.green("\u2713")} Application ${pc4.bold(options.name.trim())} is ready.`);
|
|
4852
|
+
console.log(pc4.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
|
|
3920
4853
|
}
|
|
3921
4854
|
);
|
|
3922
4855
|
projects.command("show <target>").description("Show one application destination").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--json", "print deterministic schema-version-1 JSON").action(async (target, options) => {
|
|
@@ -3933,13 +4866,13 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3933
4866
|
);
|
|
3934
4867
|
if (!result.ok) return emitRemoteFailure(options.json, result);
|
|
3935
4868
|
const document = { schemaVersion: 1, command: "projects show", ...asRecord2(result.body) };
|
|
3936
|
-
if (options.json) return
|
|
4869
|
+
if (options.json) return emitJson2(document);
|
|
3937
4870
|
const project = asRecord2(result.body).project;
|
|
3938
4871
|
if (!isProjectItem(project)) {
|
|
3939
4872
|
return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
|
|
3940
4873
|
}
|
|
3941
|
-
console.log(`${
|
|
3942
|
-
console.log(project.archivedAt ?
|
|
4874
|
+
console.log(`${pc4.bold(project.name)} ${pc4.gray(target)}`);
|
|
4875
|
+
console.log(project.archivedAt ? pc4.yellow("Archived") : pc4.green("Active"));
|
|
3943
4876
|
});
|
|
3944
4877
|
for (const archived of [true, false]) {
|
|
3945
4878
|
const verb = archived ? "archive" : "unarchive";
|
|
@@ -3965,8 +4898,8 @@ function registerProjectCommands(program2, dependencies) {
|
|
|
3965
4898
|
command: `projects ${verb}`,
|
|
3966
4899
|
...asRecord2(result.body)
|
|
3967
4900
|
};
|
|
3968
|
-
if (options.json) return
|
|
3969
|
-
console.log(`${
|
|
4901
|
+
if (options.json) return emitJson2(document);
|
|
4902
|
+
console.log(`${pc4.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
|
|
3970
4903
|
});
|
|
3971
4904
|
}
|
|
3972
4905
|
}
|
|
@@ -4008,7 +4941,7 @@ async function requireCommandSession(options, dependencies) {
|
|
|
4008
4941
|
try {
|
|
4009
4942
|
config = await dependencies.loadAuthConfig();
|
|
4010
4943
|
} catch (error) {
|
|
4011
|
-
emitLocalFailure(options.json, "auth_state_unavailable",
|
|
4944
|
+
emitLocalFailure(options.json, "auth_state_unavailable", safeMessage2(error));
|
|
4012
4945
|
return null;
|
|
4013
4946
|
}
|
|
4014
4947
|
const resolved = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
|
|
@@ -4087,13 +5020,13 @@ function emitRemoteFailure(json, result) {
|
|
|
4087
5020
|
function emitLocalFailure(json, code, message, details = {}) {
|
|
4088
5021
|
if (json) {
|
|
4089
5022
|
console.error(message);
|
|
4090
|
-
|
|
5023
|
+
emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
|
|
4091
5024
|
} else {
|
|
4092
|
-
console.error(`${
|
|
5025
|
+
console.error(`${pc4.red("\u2715")} ${message}`);
|
|
4093
5026
|
}
|
|
4094
5027
|
process.exitCode = 1;
|
|
4095
5028
|
}
|
|
4096
|
-
function
|
|
5029
|
+
function emitJson2(value) {
|
|
4097
5030
|
process.stdout.write(`${JSON.stringify(value)}
|
|
4098
5031
|
`);
|
|
4099
5032
|
}
|
|
@@ -4119,21 +5052,21 @@ function validName(value) {
|
|
|
4119
5052
|
const normalized = value.trim();
|
|
4120
5053
|
return normalized.length > 0 && normalized.length <= 200 && !/[\p{Cc}]/u.test(normalized);
|
|
4121
5054
|
}
|
|
4122
|
-
function
|
|
5055
|
+
function safeMessage2(error) {
|
|
4123
5056
|
return error instanceof Error && error.message.length <= 300 ? error.message : "Tileflow auth state is unavailable.";
|
|
4124
5057
|
}
|
|
4125
5058
|
|
|
4126
5059
|
// src/tileset-command.ts
|
|
4127
|
-
import
|
|
4128
|
-
import { serializeCanonicalJson as
|
|
5060
|
+
import pc5 from "picocolors";
|
|
5061
|
+
import { serializeCanonicalJson as serializeCanonicalJson4 } from "@tileflow/core";
|
|
4129
5062
|
import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
|
|
4130
|
-
import { z as
|
|
5063
|
+
import { z as z7 } from "zod";
|
|
4131
5064
|
|
|
4132
5065
|
// src/tileset-upload.ts
|
|
4133
5066
|
import { createHash } from "crypto";
|
|
4134
5067
|
import { open as open4, stat } from "fs/promises";
|
|
4135
|
-
import { z as
|
|
4136
|
-
import { serializeCanonicalJson as
|
|
5068
|
+
import { z as z6 } from "zod";
|
|
5069
|
+
import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
|
|
4137
5070
|
var teamTilesetPartSize = 16777216;
|
|
4138
5071
|
var maximumTeamTilesetBytes = 4e9;
|
|
4139
5072
|
var authorizationBatchSize = 16;
|
|
@@ -4273,7 +5206,7 @@ async function prepareTeamTilesetUpload(file) {
|
|
|
4273
5206
|
};
|
|
4274
5207
|
return Object.freeze({
|
|
4275
5208
|
byteCount: identity.size,
|
|
4276
|
-
contentHash: createHash("sha256").update(
|
|
5209
|
+
contentHash: createHash("sha256").update(serializeCanonicalJson3(contentIdentity)).digest("hex"),
|
|
4277
5210
|
contentHashAlgorithm: "sha256-fixed-parts-v1",
|
|
4278
5211
|
identity,
|
|
4279
5212
|
partSize: teamTilesetPartSize,
|
|
@@ -4289,7 +5222,7 @@ function createTeamTilesetUploadIdempotencyKey(tilesetId, upload) {
|
|
|
4289
5222
|
);
|
|
4290
5223
|
}
|
|
4291
5224
|
return createHash("sha256").update(
|
|
4292
|
-
|
|
5225
|
+
serializeCanonicalJson3({
|
|
4293
5226
|
byteCount: upload.byteCount,
|
|
4294
5227
|
contentHash: upload.contentHash,
|
|
4295
5228
|
contentHashAlgorithm: upload.contentHashAlgorithm,
|
|
@@ -4695,34 +5628,34 @@ function asRecord3(value) {
|
|
|
4695
5628
|
function defaultSleep(milliseconds) {
|
|
4696
5629
|
return new Promise((resolve8) => setTimeout(resolve8, milliseconds));
|
|
4697
5630
|
}
|
|
4698
|
-
var identifierSchema =
|
|
4699
|
-
var uploadPartStatusSchema =
|
|
4700
|
-
byteCount:
|
|
4701
|
-
partNumber:
|
|
4702
|
-
uploaded:
|
|
4703
|
-
validationState:
|
|
5631
|
+
var identifierSchema = z6.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
|
|
5632
|
+
var uploadPartStatusSchema = z6.object({
|
|
5633
|
+
byteCount: z6.number().int().min(1).max(teamTilesetPartSize),
|
|
5634
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
5635
|
+
uploaded: z6.boolean(),
|
|
5636
|
+
validationState: z6.enum(["mismatch", "pending", "processing", "valid"])
|
|
4704
5637
|
}).strict();
|
|
4705
|
-
var publishedVersionSchema =
|
|
4706
|
-
byteCount:
|
|
4707
|
-
contentHash:
|
|
4708
|
-
contentHashAlgorithm:
|
|
4709
|
-
publishedVersion:
|
|
4710
|
-
schemaVersion:
|
|
4711
|
-
state:
|
|
5638
|
+
var publishedVersionSchema = z6.object({
|
|
5639
|
+
byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
|
|
5640
|
+
contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
|
|
5641
|
+
contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
|
|
5642
|
+
publishedVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
5643
|
+
schemaVersion: z6.literal(1),
|
|
5644
|
+
state: z6.literal("published"),
|
|
4712
5645
|
versionId: identifierSchema.regex(/^tsv_[A-Za-z0-9_-]+$/u)
|
|
4713
5646
|
}).strict();
|
|
4714
|
-
var uploadControlSchema =
|
|
4715
|
-
byteCount:
|
|
4716
|
-
contentHash:
|
|
4717
|
-
contentHashAlgorithm:
|
|
4718
|
-
expiresAt:
|
|
4719
|
-
failure:
|
|
4720
|
-
partCount:
|
|
4721
|
-
parts:
|
|
4722
|
-
partSize:
|
|
4723
|
-
publishedVersion:
|
|
4724
|
-
schemaVersion:
|
|
4725
|
-
state:
|
|
5647
|
+
var uploadControlSchema = z6.object({
|
|
5648
|
+
byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
|
|
5649
|
+
contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
|
|
5650
|
+
contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
|
|
5651
|
+
expiresAt: z6.iso.datetime({ offset: true }),
|
|
5652
|
+
failure: z6.object({ code: z6.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
|
|
5653
|
+
partCount: z6.number().int().min(1).max(239),
|
|
5654
|
+
parts: z6.array(uploadPartStatusSchema).max(239),
|
|
5655
|
+
partSize: z6.literal(teamTilesetPartSize),
|
|
5656
|
+
publishedVersion: z6.null(),
|
|
5657
|
+
schemaVersion: z6.literal(1),
|
|
5658
|
+
state: z6.enum([
|
|
4726
5659
|
"aborted",
|
|
4727
5660
|
"aborting",
|
|
4728
5661
|
"expired",
|
|
@@ -4735,32 +5668,32 @@ var uploadControlSchema = z5.object({
|
|
|
4735
5668
|
"uploading",
|
|
4736
5669
|
"validating"
|
|
4737
5670
|
]),
|
|
4738
|
-
uploadId:
|
|
4739
|
-
versionId:
|
|
5671
|
+
uploadId: z6.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
|
|
5672
|
+
versionId: z6.null()
|
|
4740
5673
|
}).strict();
|
|
4741
|
-
var uploadSessionSchema =
|
|
4742
|
-
var authorizationSchema =
|
|
4743
|
-
expiresAt:
|
|
4744
|
-
headers:
|
|
4745
|
-
"Content-Length":
|
|
4746
|
-
"Content-MD5":
|
|
5674
|
+
var uploadSessionSchema = z6.union([publishedVersionSchema, uploadControlSchema]);
|
|
5675
|
+
var authorizationSchema = z6.object({
|
|
5676
|
+
expiresAt: z6.iso.datetime({ offset: true }),
|
|
5677
|
+
headers: z6.object({
|
|
5678
|
+
"Content-Length": z6.string().regex(/^[1-9][0-9]{0,9}$/u),
|
|
5679
|
+
"Content-MD5": z6.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
|
|
4747
5680
|
}).strict(),
|
|
4748
|
-
method:
|
|
4749
|
-
partNumber:
|
|
4750
|
-
url:
|
|
5681
|
+
method: z6.literal("PUT"),
|
|
5682
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
5683
|
+
url: z6.url().max(8192).refine((value) => {
|
|
4751
5684
|
const url = new URL(value);
|
|
4752
5685
|
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
4753
5686
|
})
|
|
4754
5687
|
}).strict();
|
|
4755
|
-
var authorizationResponseSchema =
|
|
4756
|
-
authorizations:
|
|
4757
|
-
schemaVersion:
|
|
5688
|
+
var authorizationResponseSchema = z6.object({
|
|
5689
|
+
authorizations: z6.array(authorizationSchema).min(1).max(16),
|
|
5690
|
+
schemaVersion: z6.literal(1)
|
|
4758
5691
|
}).strict();
|
|
4759
|
-
var partReceiptResponseSchema =
|
|
4760
|
-
changed:
|
|
4761
|
-
etag:
|
|
4762
|
-
partNumber:
|
|
4763
|
-
schemaVersion:
|
|
5692
|
+
var partReceiptResponseSchema = z6.object({
|
|
5693
|
+
changed: z6.boolean(),
|
|
5694
|
+
etag: z6.string().regex(/^[0-9a-f]{32}$/u),
|
|
5695
|
+
partNumber: z6.number().int().min(1).max(239),
|
|
5696
|
+
schemaVersion: z6.literal(1)
|
|
4764
5697
|
}).strict();
|
|
4765
5698
|
|
|
4766
5699
|
// src/tileset-command.ts
|
|
@@ -4780,11 +5713,11 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4780
5713
|
sample: options.sample
|
|
4781
5714
|
});
|
|
4782
5715
|
if (options.json) {
|
|
4783
|
-
process.stdout.write(`${
|
|
5716
|
+
process.stdout.write(`${serializeCanonicalJson4(inspection)}
|
|
4784
5717
|
`);
|
|
4785
5718
|
return;
|
|
4786
5719
|
}
|
|
4787
|
-
console.log(`${
|
|
5720
|
+
console.log(`${pc5.green("\u2713")} PMTiles archive is valid.`);
|
|
4788
5721
|
console.log(` Type: ${inspection.contract.tileType}`);
|
|
4789
5722
|
console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
|
|
4790
5723
|
console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
|
|
@@ -4794,7 +5727,7 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4794
5727
|
);
|
|
4795
5728
|
}
|
|
4796
5729
|
for (const warning of inspection.warnings) {
|
|
4797
|
-
console.log(`${
|
|
5730
|
+
console.log(`${pc5.yellow("!")} ${warning.message}`);
|
|
4798
5731
|
}
|
|
4799
5732
|
} catch (error) {
|
|
4800
5733
|
const record = asRecord4(error);
|
|
@@ -4803,11 +5736,11 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4803
5736
|
const message = error instanceof Error ? error.message : "Tileset inspection failed.";
|
|
4804
5737
|
if (options.json) {
|
|
4805
5738
|
process.stderr.write(
|
|
4806
|
-
`${
|
|
5739
|
+
`${serializeCanonicalJson4({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
|
|
4807
5740
|
`
|
|
4808
5741
|
);
|
|
4809
5742
|
} else {
|
|
4810
|
-
console.error(`${
|
|
5743
|
+
console.error(`${pc5.red("Error:")} ${message}`);
|
|
4811
5744
|
}
|
|
4812
5745
|
process.exitCode = 1;
|
|
4813
5746
|
}
|
|
@@ -4889,9 +5822,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4889
5822
|
version: { id: publication.versionId, number: publication.version }
|
|
4890
5823
|
}
|
|
4891
5824
|
};
|
|
4892
|
-
if (options.json) return
|
|
5825
|
+
if (options.json) return emitJson(document);
|
|
4893
5826
|
console.log(
|
|
4894
|
-
`${
|
|
5827
|
+
`${pc5.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc5.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
|
|
4895
5828
|
);
|
|
4896
5829
|
} catch (error) {
|
|
4897
5830
|
if (error instanceof TeamTilesetUploadError) {
|
|
@@ -4910,10 +5843,10 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4910
5843
|
if (!authority) return;
|
|
4911
5844
|
const items = await fetchAllTeamTilesets(authority);
|
|
4912
5845
|
const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
|
|
4913
|
-
if (options.json) return
|
|
4914
|
-
if (items.length === 0) return console.log(
|
|
5846
|
+
if (options.json) return emitJson(document);
|
|
5847
|
+
if (items.length === 0) return console.log(pc5.gray("No Team tilesets."));
|
|
4915
5848
|
for (const item of items) {
|
|
4916
|
-
console.log(` ${
|
|
5849
|
+
console.log(` ${pc5.bold(item.logicalName)} ${pc5.gray(item.status)}`);
|
|
4917
5850
|
}
|
|
4918
5851
|
} catch {
|
|
4919
5852
|
emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
|
|
@@ -4939,9 +5872,9 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4939
5872
|
}
|
|
4940
5873
|
const parsed = tilesetDetailResponseSchema.parse(response.body);
|
|
4941
5874
|
const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
|
|
4942
|
-
if (options.json) return
|
|
5875
|
+
if (options.json) return emitJson(document);
|
|
4943
5876
|
const item = parsed.tileset;
|
|
4944
|
-
console.log(`${
|
|
5877
|
+
console.log(`${pc5.bold(item.name)} ${pc5.gray(item.state)}`);
|
|
4945
5878
|
console.log(` ID: ${item.id}`);
|
|
4946
5879
|
console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
|
|
4947
5880
|
} catch {
|
|
@@ -4991,90 +5924,13 @@ function registerTilesetCommands(program2, dependencies) {
|
|
|
4991
5924
|
}
|
|
4992
5925
|
const parsed = tilesetPurgeResponseSchema.parse(response.body);
|
|
4993
5926
|
const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
|
|
4994
|
-
if (options.json) return
|
|
4995
|
-
console.log(`${
|
|
5927
|
+
if (options.json) return emitJson(document);
|
|
5928
|
+
console.log(`${pc5.green("\u2713")} Purge ${parsed.reclamation}.`);
|
|
4996
5929
|
} catch {
|
|
4997
5930
|
emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
|
|
4998
5931
|
}
|
|
4999
5932
|
});
|
|
5000
5933
|
}
|
|
5001
|
-
async function resolveTeamAuthority(options, scopes, dependencies) {
|
|
5002
|
-
let apiOrigin;
|
|
5003
|
-
try {
|
|
5004
|
-
apiOrigin = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
|
|
5005
|
-
} catch (error) {
|
|
5006
|
-
emitFailure(options.json, "invalid_api_url", safeMessage2(error));
|
|
5007
|
-
return null;
|
|
5008
|
-
}
|
|
5009
|
-
if (options.apiKey) {
|
|
5010
|
-
if (!/^tf_live_[0-9a-f]{48}$/u.test(options.apiKey)) {
|
|
5011
|
-
emitFailure(options.json, "invalid_team_data_key", "Team data API key is invalid.");
|
|
5012
|
-
return null;
|
|
5013
|
-
}
|
|
5014
|
-
if (options.team) {
|
|
5015
|
-
emitFailure(
|
|
5016
|
-
options.json,
|
|
5017
|
-
"team_selector_with_api_key",
|
|
5018
|
-
"A Team data key already selects its Team; omit --team."
|
|
5019
|
-
);
|
|
5020
|
-
return null;
|
|
5021
|
-
}
|
|
5022
|
-
return { apiOrigin, credential: options.apiKey, team: null };
|
|
5023
|
-
}
|
|
5024
|
-
let config;
|
|
5025
|
-
try {
|
|
5026
|
-
config = await dependencies.loadAuthConfig();
|
|
5027
|
-
} catch (error) {
|
|
5028
|
-
emitFailure(options.json, "auth_state_unavailable", safeMessage2(error));
|
|
5029
|
-
return null;
|
|
5030
|
-
}
|
|
5031
|
-
const selected = resolveAccountSession(config, options.apiUrl ?? dependencies.defaultApiUrl);
|
|
5032
|
-
if (selected.kind !== "selected") {
|
|
5033
|
-
emitFailure(
|
|
5034
|
-
options.json,
|
|
5035
|
-
`account_session_${selected.kind}`,
|
|
5036
|
-
selected.kind === "expired" ? "Account session expired. Run tileflow login." : "Run tileflow login."
|
|
5037
|
-
);
|
|
5038
|
-
return null;
|
|
5039
|
-
}
|
|
5040
|
-
const discovered = await listAccountTeams(selected.session);
|
|
5041
|
-
if (!discovered.ok) {
|
|
5042
|
-
emitFailure(options.json, "team_discovery_failed", discovered.error);
|
|
5043
|
-
return null;
|
|
5044
|
-
}
|
|
5045
|
-
const requested = options.team ? /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u.exec(options.team) : null;
|
|
5046
|
-
if (options.team && !requested) {
|
|
5047
|
-
emitFailure(options.json, "invalid_team", "Team must use @team syntax.");
|
|
5048
|
-
return null;
|
|
5049
|
-
}
|
|
5050
|
-
const team = requested ? discovered.teams.find(({ slug }) => slug === requested[1]) : discovered.teams.length === 1 ? discovered.teams[0] : null;
|
|
5051
|
-
if (!team) {
|
|
5052
|
-
const options_ = discovered.teams.map(({ slug }) => `@${slug}`);
|
|
5053
|
-
emitFailure(
|
|
5054
|
-
options.json,
|
|
5055
|
-
options_.length > 1 ? "team_ambiguous" : "team_not_found",
|
|
5056
|
-
options_.length > 1 ? `Choose one Team with --team: ${options_.join(", ")}.` : "No matching Team is available.",
|
|
5057
|
-
{ options: options_ }
|
|
5058
|
-
);
|
|
5059
|
-
return null;
|
|
5060
|
-
}
|
|
5061
|
-
const capability = await requestTeamCapability(selected.session, `@${team.slug}`, scopes);
|
|
5062
|
-
if (!capability.ok) {
|
|
5063
|
-
emitFailure(options.json, "team_capability_failed", capability.error);
|
|
5064
|
-
return null;
|
|
5065
|
-
}
|
|
5066
|
-
return {
|
|
5067
|
-
apiOrigin: selected.session.apiOrigin,
|
|
5068
|
-
credential: capability.capability,
|
|
5069
|
-
team
|
|
5070
|
-
};
|
|
5071
|
-
}
|
|
5072
|
-
function authorizedTeamRequest(authority, path, method) {
|
|
5073
|
-
return requestHostedJson(authority.apiOrigin, path, {
|
|
5074
|
-
headers: { Authorization: `Bearer ${authority.credential}` },
|
|
5075
|
-
method
|
|
5076
|
-
});
|
|
5077
|
-
}
|
|
5078
5934
|
async function fetchAllTeamTilesets(authority) {
|
|
5079
5935
|
const items = [];
|
|
5080
5936
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
@@ -5111,21 +5967,6 @@ async function fetchAllTeamTilesets(authority) {
|
|
|
5111
5967
|
}
|
|
5112
5968
|
throw new Error("Tileset list exceeded its safe page limit.");
|
|
5113
5969
|
}
|
|
5114
|
-
function emitJson2(value) {
|
|
5115
|
-
process.stdout.write(`${serializeCanonicalJson3(value)}
|
|
5116
|
-
`);
|
|
5117
|
-
}
|
|
5118
|
-
function emitFailure(json, code, message, context = {}) {
|
|
5119
|
-
if (json) {
|
|
5120
|
-
process.stderr.write(
|
|
5121
|
-
`${serializeCanonicalJson3({ error: { code, ...context, message }, ok: false, schemaVersion: 1 })}
|
|
5122
|
-
`
|
|
5123
|
-
);
|
|
5124
|
-
} else {
|
|
5125
|
-
console.error(`${pc2.red("Error:")} ${message}`);
|
|
5126
|
-
}
|
|
5127
|
-
process.exitCode = 1;
|
|
5128
|
-
}
|
|
5129
5970
|
function isTilesetId(value) {
|
|
5130
5971
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value) && !["terrain", "world"].includes(value);
|
|
5131
5972
|
}
|
|
@@ -5143,75 +5984,72 @@ function inspectionOptionError(message) {
|
|
|
5143
5984
|
path: "includeValues"
|
|
5144
5985
|
});
|
|
5145
5986
|
}
|
|
5146
|
-
function safeMessage2(error) {
|
|
5147
|
-
return error instanceof Error ? error.message : "Tileset command failed.";
|
|
5148
|
-
}
|
|
5149
5987
|
function asRecord4(value) {
|
|
5150
5988
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5151
5989
|
}
|
|
5152
|
-
var safeIdentifierSchema2 =
|
|
5153
|
-
var physicalTilesetIdSchema =
|
|
5154
|
-
var
|
|
5155
|
-
var tilesetInventoryItemSchema =
|
|
5156
|
-
currentVersion:
|
|
5157
|
-
hasCurrent:
|
|
5990
|
+
var safeIdentifierSchema2 = z7.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
|
|
5991
|
+
var physicalTilesetIdSchema = z7.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
|
|
5992
|
+
var cursorSchema2 = z7.string().min(1).max(2048).nullable();
|
|
5993
|
+
var tilesetInventoryItemSchema = z7.object({
|
|
5994
|
+
currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
5995
|
+
hasCurrent: z7.boolean(),
|
|
5158
5996
|
id: physicalTilesetIdSchema,
|
|
5159
|
-
logicalName:
|
|
5160
|
-
name:
|
|
5161
|
-
retainedBytes:
|
|
5162
|
-
retainedDeploymentCount:
|
|
5163
|
-
status:
|
|
5164
|
-
updatedAt:
|
|
5165
|
-
usedByMapCount:
|
|
5997
|
+
logicalName: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
5998
|
+
name: z7.string().min(1).max(200),
|
|
5999
|
+
retainedBytes: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
6000
|
+
retainedDeploymentCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
6001
|
+
status: z7.enum(["active", "purging", "retired"]),
|
|
6002
|
+
updatedAt: z7.iso.datetime({ offset: true }),
|
|
6003
|
+
usedByMapCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
|
|
5166
6004
|
}).strict();
|
|
5167
|
-
var tilesetListResponseSchema =
|
|
5168
|
-
nextCursor:
|
|
5169
|
-
schemaVersion:
|
|
5170
|
-
tilesets:
|
|
6005
|
+
var tilesetListResponseSchema = z7.object({
|
|
6006
|
+
nextCursor: cursorSchema2.optional(),
|
|
6007
|
+
schemaVersion: z7.literal(1),
|
|
6008
|
+
tilesets: z7.array(tilesetInventoryItemSchema).max(100)
|
|
5171
6009
|
}).strict();
|
|
5172
|
-
var tilesetDependencySchema =
|
|
5173
|
-
active:
|
|
6010
|
+
var tilesetDependencySchema = z7.object({
|
|
6011
|
+
active: z7.boolean(),
|
|
5174
6012
|
deploymentId: safeIdentifierSchema2,
|
|
5175
|
-
deploymentVersion:
|
|
5176
|
-
environment:
|
|
6013
|
+
deploymentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
6014
|
+
environment: z7.string().min(1).max(64),
|
|
5177
6015
|
mapId: safeIdentifierSchema2,
|
|
5178
6016
|
sourceId: safeIdentifierSchema2
|
|
5179
6017
|
}).strict();
|
|
5180
|
-
var tilesetDetailResponseSchema =
|
|
5181
|
-
schemaVersion:
|
|
5182
|
-
tileset:
|
|
5183
|
-
attribution:
|
|
5184
|
-
currentVersion:
|
|
5185
|
-
dependencies:
|
|
5186
|
-
dependencyNextCursor:
|
|
6018
|
+
var tilesetDetailResponseSchema = z7.object({
|
|
6019
|
+
schemaVersion: z7.literal(1),
|
|
6020
|
+
tileset: z7.object({
|
|
6021
|
+
attribution: z7.string().max(4096).nullable(),
|
|
6022
|
+
currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
6023
|
+
dependencies: z7.array(tilesetDependencySchema).max(25),
|
|
6024
|
+
dependencyNextCursor: cursorSchema2,
|
|
5187
6025
|
id: physicalTilesetIdSchema,
|
|
5188
|
-
name:
|
|
5189
|
-
slug:
|
|
5190
|
-
state:
|
|
6026
|
+
name: z7.string().min(1).max(200),
|
|
6027
|
+
slug: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
6028
|
+
state: z7.enum(["active", "purging", "purged"])
|
|
5191
6029
|
}).strict()
|
|
5192
6030
|
}).strict();
|
|
5193
|
-
var purgeDependencySchema =
|
|
6031
|
+
var purgeDependencySchema = z7.object({
|
|
5194
6032
|
deploymentId: safeIdentifierSchema2,
|
|
5195
6033
|
mapId: safeIdentifierSchema2,
|
|
5196
6034
|
sourceId: safeIdentifierSchema2
|
|
5197
6035
|
}).strict();
|
|
5198
|
-
var tilesetInUseResponseSchema =
|
|
5199
|
-
code:
|
|
5200
|
-
dependencies:
|
|
5201
|
-
error:
|
|
5202
|
-
nextCursor:
|
|
6036
|
+
var tilesetInUseResponseSchema = z7.object({
|
|
6037
|
+
code: z7.literal("TF_TILESET_IN_USE"),
|
|
6038
|
+
dependencies: z7.array(purgeDependencySchema).max(32),
|
|
6039
|
+
error: z7.literal("Tileset is used by retained Map deployments"),
|
|
6040
|
+
nextCursor: cursorSchema2
|
|
5203
6041
|
}).strict();
|
|
5204
|
-
var tilesetPurgeResponseSchema =
|
|
5205
|
-
deliveryFence:
|
|
5206
|
-
pendingVersions:
|
|
5207
|
-
reclamation:
|
|
5208
|
-
state:
|
|
6042
|
+
var tilesetPurgeResponseSchema = z7.object({
|
|
6043
|
+
deliveryFence: z7.enum(["complete", "pending"]),
|
|
6044
|
+
pendingVersions: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
6045
|
+
reclamation: z7.enum(["complete", "pending"]),
|
|
6046
|
+
state: z7.enum(["purged", "purging"]),
|
|
5209
6047
|
tilesetId: physicalTilesetIdSchema
|
|
5210
6048
|
}).strict();
|
|
5211
6049
|
|
|
5212
6050
|
// src/visual-command.ts
|
|
5213
6051
|
import { lstat as lstat4, readFile as readFile6 } from "fs/promises";
|
|
5214
|
-
import { dirname as
|
|
6052
|
+
import { dirname as dirname10, isAbsolute as isAbsolute4, parse as parse2, relative as relative4, resolve as resolve6, sep as sep4 } from "path";
|
|
5215
6053
|
import {
|
|
5216
6054
|
analyzeTileflowCaptureReference,
|
|
5217
6055
|
compareTileflowCaptureToBaseline,
|
|
@@ -5225,12 +6063,12 @@ import {
|
|
|
5225
6063
|
tileflowVisualArtifactLimits,
|
|
5226
6064
|
validateTileflowVisualReferencePng
|
|
5227
6065
|
} from "@tileflow/capture";
|
|
5228
|
-
import { compareCodeUnits as
|
|
6066
|
+
import { compareCodeUnits as compareCodeUnits9 } from "@tileflow/core";
|
|
5229
6067
|
|
|
5230
6068
|
// src/visual-compare-command.ts
|
|
5231
6069
|
import { createHash as createHash3 } from "crypto";
|
|
5232
6070
|
import { lstat as lstat3, readFile as readFile5, realpath } from "fs/promises";
|
|
5233
|
-
import { basename as basename3, dirname as
|
|
6071
|
+
import { basename as basename3, dirname as dirname9, extname, isAbsolute as isAbsolute3, parse, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
|
|
5234
6072
|
import {
|
|
5235
6073
|
compareTileflowCapturesForReview,
|
|
5236
6074
|
createTileflowCaptureSession as createTileflowCaptureSession2,
|
|
@@ -5239,9 +6077,9 @@ import {
|
|
|
5239
6077
|
tileflowSyntheticAssetOrigin as tileflowSyntheticAssetOrigin2
|
|
5240
6078
|
} from "@tileflow/capture";
|
|
5241
6079
|
import {
|
|
5242
|
-
compareCodeUnits as
|
|
6080
|
+
compareCodeUnits as compareCodeUnits8,
|
|
5243
6081
|
normalizeTileflowCaptureScene as normalizeTileflowCaptureScene2,
|
|
5244
|
-
parseResolvedTileflowMap as
|
|
6082
|
+
parseResolvedTileflowMap as parseResolvedTileflowMap3,
|
|
5245
6083
|
resolveThemeSelection,
|
|
5246
6084
|
tileflowCaptureSceneLimits
|
|
5247
6085
|
} from "@tileflow/core";
|
|
@@ -5671,7 +6509,7 @@ async function prepareVisualCompareOutput(plan, leftArtifacts, rightArtifacts, c
|
|
|
5671
6509
|
documentPath: relativePath(cwd, plan.output.documentPath),
|
|
5672
6510
|
reportPath: relativePath(cwd, plan.output.reportPath)
|
|
5673
6511
|
},
|
|
5674
|
-
warnings: [...warnings].sort(
|
|
6512
|
+
warnings: [...warnings].sort(compareCodeUnits8)
|
|
5675
6513
|
};
|
|
5676
6514
|
const report = renderTileflowVisualCompareReport({
|
|
5677
6515
|
generatedBy: "Tileflow CLI",
|
|
@@ -5959,7 +6797,7 @@ async function runVisualCompareWatch(plan, openReport) {
|
|
|
5959
6797
|
}
|
|
5960
6798
|
}
|
|
5961
6799
|
function resolveVisualCompareSide(artifacts, config, requestedMap, requestedTheme, role) {
|
|
5962
|
-
const availableMaps = Object.keys(artifacts.project.maps).sort(
|
|
6800
|
+
const availableMaps = Object.keys(artifacts.project.maps).sort(compareCodeUnits8);
|
|
5963
6801
|
const map = requestedMap ?? (availableMaps.length === 1 ? availableMaps[0] : void 0);
|
|
5964
6802
|
if (!map) {
|
|
5965
6803
|
throw new Error(
|
|
@@ -5972,7 +6810,7 @@ function resolveVisualCompareSide(artifacts, config, requestedMap, requestedThem
|
|
|
5972
6810
|
`Unknown ${role} map "${map}". Available maps: ${availableMaps.join(", ") || "(none)"}.`
|
|
5973
6811
|
);
|
|
5974
6812
|
}
|
|
5975
|
-
const resolvedMap =
|
|
6813
|
+
const resolvedMap = parseResolvedTileflowMap3(candidate);
|
|
5976
6814
|
const theme = resolveThemeSelection(resolvedMap, requestedTheme).name;
|
|
5977
6815
|
if (!artifacts.styles[map]?.[theme]) {
|
|
5978
6816
|
throw new Error(`The ${role} map theme "${map}/${theme}" was not compiled.`);
|
|
@@ -6028,7 +6866,7 @@ async function resolveVisualCompareOutput(cwd, value, managed, force, watch, zoo
|
|
|
6028
6866
|
if (extname(reportPath).toLowerCase() !== ".html") {
|
|
6029
6867
|
throw new Error("--report must name an .html file.");
|
|
6030
6868
|
}
|
|
6031
|
-
const reportDirectory =
|
|
6869
|
+
const reportDirectory = dirname9(reportPath);
|
|
6032
6870
|
if (reportDirectory === cwd || reportDirectory === parse(reportPath).root) {
|
|
6033
6871
|
throw new Error("Visual compare output must use a dedicated subdirectory.");
|
|
6034
6872
|
}
|
|
@@ -6168,7 +7006,7 @@ function createVisualCompareInventory(assetDirectory, assets) {
|
|
|
6168
7006
|
throw new Error("Visual compare produced an invalid managed asset path.");
|
|
6169
7007
|
}
|
|
6170
7008
|
return { path: relativeAssetPath, sha256: sha256Source(source) };
|
|
6171
|
-
}).sort((left, right) =>
|
|
7009
|
+
}).sort((left, right) => compareCodeUnits8(left.path, right.path))
|
|
6172
7010
|
};
|
|
6173
7011
|
}
|
|
6174
7012
|
function serializeVisualCompareInventory(inventory) {
|
|
@@ -6176,7 +7014,7 @@ function serializeVisualCompareInventory(inventory) {
|
|
|
6176
7014
|
`;
|
|
6177
7015
|
}
|
|
6178
7016
|
async function readVisualCompareInventory(assetDirectory, inventoryPath, boundary) {
|
|
6179
|
-
await assertNoSymlinkComponents(
|
|
7017
|
+
await assertNoSymlinkComponents(dirname9(inventoryPath), boundary);
|
|
6180
7018
|
let info;
|
|
6181
7019
|
try {
|
|
6182
7020
|
info = await lstat3(inventoryPath);
|
|
@@ -6200,7 +7038,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
|
|
|
6200
7038
|
const assets = [];
|
|
6201
7039
|
for (const asset of inventory.assets) {
|
|
6202
7040
|
const path = resolve5(assetDirectory, asset.path);
|
|
6203
|
-
await assertNoSymlinkComponents(
|
|
7041
|
+
await assertNoSymlinkComponents(dirname9(path), boundary);
|
|
6204
7042
|
let assetInfo;
|
|
6205
7043
|
try {
|
|
6206
7044
|
assetInfo = await lstat3(path);
|
|
@@ -6243,7 +7081,7 @@ function validateVisualCompareInventory(input) {
|
|
|
6243
7081
|
return { path: asset.path, sha256: asset.sha256 };
|
|
6244
7082
|
});
|
|
6245
7083
|
const paths = assets.map((asset) => asset.path);
|
|
6246
|
-
const sortedPaths = [...paths].sort(
|
|
7084
|
+
const sortedPaths = [...paths].sort(compareCodeUnits8);
|
|
6247
7085
|
if (new Set(paths).size !== paths.length || paths.some((path, index) => path !== sortedPaths[index])) {
|
|
6248
7086
|
throw new Error("Visual compare inventory asset paths must be unique and sorted.");
|
|
6249
7087
|
}
|
|
@@ -6263,8 +7101,8 @@ function isVisualCompareOwnedAssetName(value) {
|
|
|
6263
7101
|
return Number.isInteger(row) && row >= 1 && row <= tileflowVisualCompareLimits.maximumRows;
|
|
6264
7102
|
}
|
|
6265
7103
|
function hasExactKeys(record, keys) {
|
|
6266
|
-
const actual = Object.keys(record).sort(
|
|
6267
|
-
const expected = [...keys].sort(
|
|
7104
|
+
const actual = Object.keys(record).sort(compareCodeUnits8);
|
|
7105
|
+
const expected = [...keys].sort(compareCodeUnits8);
|
|
6268
7106
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
6269
7107
|
}
|
|
6270
7108
|
function sha256Source(source) {
|
|
@@ -6281,7 +7119,7 @@ async function canonicalPathIdentity(path) {
|
|
|
6281
7119
|
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
6282
7120
|
} catch (error) {
|
|
6283
7121
|
if (!isMissingPathComponent(error)) throw error;
|
|
6284
|
-
const parent =
|
|
7122
|
+
const parent = dirname9(current);
|
|
6285
7123
|
if (parent === current) {
|
|
6286
7124
|
const canonical = absolute.normalize("NFC");
|
|
6287
7125
|
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
@@ -6358,7 +7196,7 @@ function portablePathIdentity(path) {
|
|
|
6358
7196
|
}
|
|
6359
7197
|
function isInside(parent, child) {
|
|
6360
7198
|
const path = relative3(parent, child);
|
|
6361
|
-
return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !
|
|
7199
|
+
return path === "" || path !== ".." && !path.startsWith(`..${sep3}`) && !isAbsolute3(path);
|
|
6362
7200
|
}
|
|
6363
7201
|
function pathsOverlap(left, right) {
|
|
6364
7202
|
return isInside(left, right) || isInside(right, left);
|
|
@@ -6438,7 +7276,7 @@ function createTileflowVisualDiffJson(comparisons, cwd) {
|
|
|
6438
7276
|
return {
|
|
6439
7277
|
schemaVersion: 1,
|
|
6440
7278
|
command: "visual.diff",
|
|
6441
|
-
comparisons: [...comparisons].sort((left, right) =>
|
|
7279
|
+
comparisons: [...comparisons].sort((left, right) => compareCodeUnits9(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
|
|
6442
7280
|
...createTileflowVisualComparisonDocument(comparison),
|
|
6443
7281
|
baselinePath: relativePath(cwd, baselinePath),
|
|
6444
7282
|
actualPath: relativePath(cwd, actualPath),
|
|
@@ -6653,7 +7491,7 @@ async function runVisualUpdate(scenes, options) {
|
|
|
6653
7491
|
renderer: capture.renderer,
|
|
6654
7492
|
networkDependent: capture.networkDependent
|
|
6655
7493
|
},
|
|
6656
|
-
warnings: [...capture.warnings].sort(
|
|
7494
|
+
warnings: [...capture.warnings].sort(compareCodeUnits9)
|
|
6657
7495
|
});
|
|
6658
7496
|
outputFiles.push(
|
|
6659
7497
|
{ path: files.pngPath, source: capture.png },
|
|
@@ -6670,7 +7508,7 @@ async function runVisualUpdate(scenes, options) {
|
|
|
6670
7508
|
const document = {
|
|
6671
7509
|
schemaVersion: 1,
|
|
6672
7510
|
command: "visual.update",
|
|
6673
|
-
updates: updates.sort((left, right) =>
|
|
7511
|
+
updates: updates.sort((left, right) => compareCodeUnits9(left.scene, right.scene))
|
|
6674
7512
|
};
|
|
6675
7513
|
if (options.json) {
|
|
6676
7514
|
process.stdout.write(serializeTileflowVisualCommandJson(document));
|
|
@@ -6726,7 +7564,7 @@ async function readBaselineFiles(directory, scene) {
|
|
|
6726
7564
|
return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
|
|
6727
7565
|
}
|
|
6728
7566
|
async function readOptionalArtifact(path, boundary, maximumBytes) {
|
|
6729
|
-
await assertNoSymlinkComponents(
|
|
7567
|
+
await assertNoSymlinkComponents(dirname10(path), boundary);
|
|
6730
7568
|
let info;
|
|
6731
7569
|
try {
|
|
6732
7570
|
info = await lstat4(path);
|
|
@@ -6816,7 +7654,7 @@ function portablePathIdentity2(path) {
|
|
|
6816
7654
|
}
|
|
6817
7655
|
function isInside2(parent, child) {
|
|
6818
7656
|
const path = relative4(parent, child);
|
|
6819
|
-
return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !
|
|
7657
|
+
return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
|
|
6820
7658
|
}
|
|
6821
7659
|
function isMissingFile5(error) {
|
|
6822
7660
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
@@ -6831,9 +7669,10 @@ var defaultApiUrl = defaultTileflowApiUrl;
|
|
|
6831
7669
|
var defaultAppUrl = "https://tileflow.dev";
|
|
6832
7670
|
var defaultConfigPath = defaultTileflowConfigPath;
|
|
6833
7671
|
var defaultManifestPath = defaultTileflowManifestPath;
|
|
6834
|
-
program.name("tileflow").description("Beautiful maps from config.").version(packageJson.version).showHelpAfterError(
|
|
7672
|
+
program.name("tileflow").description("Beautiful maps from config.").version(packageJson.version).showHelpAfterError(pc6.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
|
|
6835
7673
|
registerLanguageCommand(program);
|
|
6836
7674
|
registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
7675
|
+
registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
|
|
6837
7676
|
program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
|
|
6838
7677
|
const configPath = resolve7(process.cwd(), defaultConfigPath);
|
|
6839
7678
|
const ignorePath = resolve7(process.cwd(), ".gitignore");
|
|
@@ -6852,9 +7691,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
|
|
|
6852
7691
|
}
|
|
6853
7692
|
logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
|
|
6854
7693
|
printKeyValue("Path", pathLabel(configPath));
|
|
6855
|
-
printKeyValue("Map",
|
|
7694
|
+
printKeyValue("Map", pc6.bold("madrid"));
|
|
6856
7695
|
printNextSteps([
|
|
6857
|
-
`Install ${
|
|
7696
|
+
`Install ${pc6.cyan("@tileflow/core")} and ${pc6.cyan("@tileflow/maps")} in this project if needed.`,
|
|
6858
7697
|
`Preview locally with ${command("tileflow preview")}.`,
|
|
6859
7698
|
`Validate the config with ${command("tileflow validate")}.`
|
|
6860
7699
|
]);
|
|
@@ -6947,172 +7786,181 @@ program.command("validate").description("Validate a Tileflow config").option("-c
|
|
|
6947
7786
|
"--api-base-url <url>",
|
|
6948
7787
|
"Tileflow API base URL used to resolve official map assets",
|
|
6949
7788
|
process.env.TILEFLOW_API_URL ?? defaultApiUrl
|
|
6950
|
-
).option("--json", "print deterministic schema-version-1 JSON").action(
|
|
6951
|
-
|
|
6952
|
-
if (options.
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
7789
|
+
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--json", "print deterministic schema-version-1 JSON").action(
|
|
7790
|
+
async (options) => {
|
|
7791
|
+
if (options.target !== "local" && options.target !== "hosted") {
|
|
7792
|
+
if (options.json) {
|
|
7793
|
+
const failure = createTileflowCommandFailureDocument4(
|
|
7794
|
+
"validate",
|
|
7795
|
+
{
|
|
7796
|
+
code: "INVALID_TARGET",
|
|
7797
|
+
issues: [
|
|
7798
|
+
{
|
|
7799
|
+
code: "INVALID_TARGET",
|
|
7800
|
+
message: `Invalid validation target: ${options.target}`,
|
|
7801
|
+
path: "target",
|
|
7802
|
+
phase: "command-validation"
|
|
7803
|
+
}
|
|
7804
|
+
],
|
|
7805
|
+
phase: "command-validation"
|
|
7806
|
+
},
|
|
7807
|
+
process.cwd(),
|
|
7808
|
+
{ code: "INVALID_TARGET", phase: "command-validation" }
|
|
7809
|
+
);
|
|
7810
|
+
process.stderr.write(serializeTileflowCommandDocument4(failure));
|
|
7811
|
+
process.exitCode = 1;
|
|
7812
|
+
return;
|
|
7813
|
+
}
|
|
7814
|
+
logError(`Invalid validation target: ${options.target}`);
|
|
7815
|
+
printNextSteps([
|
|
7816
|
+
`Use ${command("tileflow validate --target local")} or ${command("tileflow validate --target hosted")}.`
|
|
7817
|
+
]);
|
|
6971
7818
|
process.exitCode = 1;
|
|
6972
7819
|
return;
|
|
6973
7820
|
}
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
if (hostedIssues.length > 0) {
|
|
7023
|
-
if (!options.json) {
|
|
7024
|
-
printHostedCompatibilityIssues(hostedIssues);
|
|
7025
|
-
return;
|
|
7026
|
-
}
|
|
7027
|
-
throw Object.assign(new Error("Tileflow config is not Hosted-compatible."), {
|
|
7028
|
-
code: "HOSTED_INCOMPATIBLE",
|
|
7029
|
-
issues: hostedIssues.map((issue) => ({
|
|
7821
|
+
let failureDefaults = { code: "CONFIG_LOAD_FAILED", phase: "config-load" };
|
|
7822
|
+
try {
|
|
7823
|
+
if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
|
|
7824
|
+
const loaded = await withTileflowConfigSecretsHidden(
|
|
7825
|
+
() => loadTileflowConfigWithInputs3(options.config)
|
|
7826
|
+
);
|
|
7827
|
+
const project = loaded.project;
|
|
7828
|
+
const baseDirectory = dirname11(loaded.configFile);
|
|
7829
|
+
failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
|
|
7830
|
+
assertValidTileflowConfig2(project);
|
|
7831
|
+
const mapNames = getTileflowMapNames4(project).sort();
|
|
7832
|
+
failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
|
|
7833
|
+
const compiledIcons = await compileTileflowIconPackages2(project, {
|
|
7834
|
+
baseDirectory,
|
|
7835
|
+
cwd: process.cwd(),
|
|
7836
|
+
icons: resolveIconOptions(options),
|
|
7837
|
+
target: options.target
|
|
7838
|
+
});
|
|
7839
|
+
const mapAssets = createCompiledMapAssets(
|
|
7840
|
+
compiledIcons,
|
|
7841
|
+
(binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`
|
|
7842
|
+
);
|
|
7843
|
+
failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
|
|
7844
|
+
const compiledStyles = createTileflowStyles(project, {
|
|
7845
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
7846
|
+
mapAssets
|
|
7847
|
+
});
|
|
7848
|
+
failureDefaults = { code: "TF_LOCAL_TILESET_INVALID", phase: "local-tileset-resolution" };
|
|
7849
|
+
const localTilesets = await prepareTileflowLocalTilesets(project, compiledStyles, {
|
|
7850
|
+
assetBaseUrl: "/tileflow",
|
|
7851
|
+
baseDirectory,
|
|
7852
|
+
cwd: process.cwd()
|
|
7853
|
+
});
|
|
7854
|
+
failureDefaults = { code: "FONT_COMPILATION_FAILED", phase: "font-compilation" };
|
|
7855
|
+
const { styles } = await prepareTileflowStyleFonts(project, localTilesets.styles, {
|
|
7856
|
+
assetBaseUrl: "/tileflow",
|
|
7857
|
+
baseDirectory,
|
|
7858
|
+
cwd: process.cwd(),
|
|
7859
|
+
target: options.target
|
|
7860
|
+
});
|
|
7861
|
+
failureDefaults = { code: "HOSTED_INCOMPATIBLE", phase: "hosted-validation" };
|
|
7862
|
+
const hostedIssues = options.target === "hosted" ? inspectTileflowHostedCompatibility(project, styles) : [];
|
|
7863
|
+
if (hostedIssues.length > 0) {
|
|
7864
|
+
if (!options.json) {
|
|
7865
|
+
printHostedCompatibilityIssues(hostedIssues);
|
|
7866
|
+
return;
|
|
7867
|
+
}
|
|
7868
|
+
throw Object.assign(new Error("Tileflow config is not Hosted-compatible."), {
|
|
7030
7869
|
code: "HOSTED_INCOMPATIBLE",
|
|
7031
|
-
|
|
7032
|
-
|
|
7870
|
+
issues: hostedIssues.map((issue) => ({
|
|
7871
|
+
code: "HOSTED_INCOMPATIBLE",
|
|
7872
|
+
message: issue.message,
|
|
7873
|
+
path: issue.path,
|
|
7874
|
+
phase: "hosted-validation"
|
|
7875
|
+
})),
|
|
7033
7876
|
phase: "hosted-validation"
|
|
7034
|
-
})
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
)
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
code: "VALIDATION_WARNING",
|
|
7045
|
-
phase: "theme-audit"
|
|
7046
|
-
});
|
|
7047
|
-
const checks = [
|
|
7048
|
-
"Config schema",
|
|
7049
|
-
"Icon asset closure",
|
|
7050
|
-
"Text provider closure",
|
|
7051
|
-
"Named map styles",
|
|
7052
|
-
"MapLibre style semantics",
|
|
7053
|
-
...options.target === "hosted" ? ["Hosted compatibility"] : []
|
|
7054
|
-
];
|
|
7055
|
-
if (options.json) {
|
|
7056
|
-
const summary = createTileflowCommandSummary2({
|
|
7057
|
-
code: "VALIDATION_OK",
|
|
7058
|
-
command: "validate",
|
|
7059
|
-
message: `Tileflow config is valid (${plural(mapNames.length, "map")}).`,
|
|
7060
|
-
ok: true,
|
|
7061
|
-
path: "",
|
|
7062
|
-
phase: "validation",
|
|
7063
|
-
severity: "info",
|
|
7064
|
-
suggestion: "No changes are required."
|
|
7877
|
+
});
|
|
7878
|
+
}
|
|
7879
|
+
const themeWarnings = mapNames.flatMap(
|
|
7880
|
+
(mapName) => auditTileflowMapThemeValues(project.maps[mapName]).filter(
|
|
7881
|
+
({ severity }) => severity === "warning"
|
|
7882
|
+
)
|
|
7883
|
+
);
|
|
7884
|
+
const diagnostics = themeWarnings.length === 0 ? [] : createTileflowStructuredDiagnostics2({ diagnostics: themeWarnings }, process.cwd(), {
|
|
7885
|
+
code: "VALIDATION_WARNING",
|
|
7886
|
+
phase: "theme-audit"
|
|
7065
7887
|
});
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7888
|
+
const sharedIconMaps = Object.keys(compiledIcons.compositions).sort();
|
|
7889
|
+
const checks = [
|
|
7890
|
+
"Config schema",
|
|
7891
|
+
"Icon asset closure",
|
|
7892
|
+
...sharedIconMaps.length > 0 ? ["Locked Icon Set composition"] : [],
|
|
7893
|
+
"Text provider closure",
|
|
7894
|
+
"Named map styles",
|
|
7895
|
+
"MapLibre style semantics",
|
|
7896
|
+
...options.target === "hosted" ? ["Hosted compatibility"] : []
|
|
7897
|
+
];
|
|
7898
|
+
if (options.json) {
|
|
7899
|
+
const summary = createTileflowCommandSummary2({
|
|
7900
|
+
code: "VALIDATION_OK",
|
|
7901
|
+
command: "validate",
|
|
7902
|
+
message: `Tileflow config is valid (${plural(mapNames.length, "map")}).`,
|
|
7903
|
+
ok: true,
|
|
7904
|
+
path: "",
|
|
7905
|
+
phase: "validation",
|
|
7906
|
+
severity: "info",
|
|
7907
|
+
suggestion: "No changes are required."
|
|
7908
|
+
});
|
|
7909
|
+
process.stdout.write(
|
|
7910
|
+
serializeTileflowCommandDocument4({
|
|
7911
|
+
...summary,
|
|
7912
|
+
target: options.target,
|
|
7913
|
+
maps: mapNames,
|
|
7914
|
+
checks,
|
|
7915
|
+
diagnostics
|
|
7916
|
+
})
|
|
7917
|
+
);
|
|
7918
|
+
return;
|
|
7919
|
+
}
|
|
7920
|
+
logSuccess(`Config is valid (${plural(mapNames.length, "map")}).`);
|
|
7921
|
+
printChecks(checks);
|
|
7922
|
+
for (const diagnostic of diagnostics) {
|
|
7923
|
+
logWarning(`${diagnostic.path || "(root)"}: ${diagnostic.message}`);
|
|
7924
|
+
}
|
|
7925
|
+
} catch (error) {
|
|
7926
|
+
if (!options.json) throw error;
|
|
7927
|
+
const failure = createTileflowCommandFailureDocument4(
|
|
7928
|
+
"validate",
|
|
7929
|
+
error,
|
|
7930
|
+
process.cwd(),
|
|
7931
|
+
failureDefaults
|
|
7074
7932
|
);
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
logSuccess(`Config is valid (${plural(mapNames.length, "map")}).`);
|
|
7078
|
-
printChecks(checks);
|
|
7079
|
-
for (const diagnostic of diagnostics) {
|
|
7080
|
-
logWarning(`${diagnostic.path || "(root)"}: ${diagnostic.message}`);
|
|
7933
|
+
process.stderr.write(serializeTileflowCommandDocument4(failure));
|
|
7934
|
+
process.exitCode = 1;
|
|
7081
7935
|
}
|
|
7082
|
-
} catch (error) {
|
|
7083
|
-
if (!options.json) throw error;
|
|
7084
|
-
const failure = createTileflowCommandFailureDocument4(
|
|
7085
|
-
"validate",
|
|
7086
|
-
error,
|
|
7087
|
-
process.cwd(),
|
|
7088
|
-
failureDefaults
|
|
7089
|
-
);
|
|
7090
|
-
process.stderr.write(serializeTileflowCommandDocument4(failure));
|
|
7091
|
-
process.exitCode = 1;
|
|
7092
7936
|
}
|
|
7093
|
-
|
|
7937
|
+
);
|
|
7094
7938
|
program.command("build").description("Generate static Tileflow styles").option("-c, --config <path>", "config path", defaultConfigPath).option("-o, --out <path>", "output directory", "dist/tileflow").option(
|
|
7095
7939
|
"--api-base-url <url>",
|
|
7096
7940
|
"Tileflow API base URL used to resolve official map assets",
|
|
7097
7941
|
process.env.TILEFLOW_API_URL ?? defaultApiUrl
|
|
7098
|
-
).
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7942
|
+
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
|
|
7943
|
+
async (options) => {
|
|
7944
|
+
logInfo(`Building ${pathLabel(options.config)}.`);
|
|
7945
|
+
const icons = resolveIconOptions(options);
|
|
7946
|
+
await withTileflowConfigSecretsHidden(
|
|
7947
|
+
() => writeTileflowBuildArtifacts({
|
|
7948
|
+
config: options.config,
|
|
7949
|
+
outDir: options.out,
|
|
7950
|
+
styleBaseUrl: ".",
|
|
7951
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
7952
|
+
...icons ? { icons } : {}
|
|
7953
|
+
})
|
|
7954
|
+
);
|
|
7955
|
+
logSuccess("Built Tileflow artifacts.");
|
|
7956
|
+
printKeyValue("Output", pathLabel(resolve7(process.cwd(), options.out)));
|
|
7957
|
+
}
|
|
7958
|
+
);
|
|
7111
7959
|
program.command("preview").alias("dev").description("Run a local map preview").option("-c, --config <path>", "config path", defaultConfigPath).option("--against-config <path>", "second config for the comparison workbench").option("--against-map <name>", "map on the right; defaults to --map").option("--against-scene <name>", "committed standalone scene on the right").option("--against-theme <name>", "concrete theme on the right; defaults to --theme").option("--host <host>", "bind host: an explicit IP address or localhost", defaultTileflowDevHost).option("--map <name>", "preview one configured map").option("--theme <name>", "preview one concrete theme; defaults to the map default").option("-p, --port <port>", "preview port", "3333").option("--scene <name>", "preview one committed standalone map scene").option(
|
|
7112
7960
|
"--api-base-url <url>",
|
|
7113
7961
|
"Tileflow API base URL used to resolve official map assets",
|
|
7114
7962
|
process.env.TILEFLOW_API_URL ?? defaultApiUrl
|
|
7115
|
-
).option("--json", "emit schema-version-1 NDJSON lifecycle events").action(
|
|
7963
|
+
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").option("--json", "emit schema-version-1 NDJSON lifecycle events").action(
|
|
7116
7964
|
async (options) => {
|
|
7117
7965
|
const port = parsePort(options.port);
|
|
7118
7966
|
if (port === null) {
|
|
@@ -7165,10 +8013,12 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7165
8013
|
}
|
|
7166
8014
|
await withTileflowConfigSecretsHidden(async () => {
|
|
7167
8015
|
const origin = tileflowDevOrigin(host, port);
|
|
8016
|
+
const icons = resolveIconOptions(options);
|
|
7168
8017
|
const session = await createTileflowArtifactSession3({
|
|
7169
8018
|
assetBaseUrl: origin,
|
|
7170
8019
|
apiBaseUrl: options.apiBaseUrl,
|
|
7171
8020
|
config: options.config,
|
|
8021
|
+
...icons ? { icons } : {},
|
|
7172
8022
|
styleBaseUrl: origin,
|
|
7173
8023
|
watch: true
|
|
7174
8024
|
});
|
|
@@ -7268,7 +8118,7 @@ program.command("preview").alias("dev").description("Run a local map preview").o
|
|
|
7268
8118
|
program.command("deploy").description("Deploy maps to Tileflow and write the frontend manifest").option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--map-id <id>", "managed Map destination").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
|
|
7269
8119
|
"--overwrite-self-hosted-manifest",
|
|
7270
8120
|
"explicitly replace an existing self-hosted manifest at --manifest"
|
|
7271
|
-
).action(
|
|
8121
|
+
).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
|
|
7272
8122
|
async (options) => {
|
|
7273
8123
|
const source = resolveDeploySource(process.env);
|
|
7274
8124
|
const resolveApi = (selectedMap) => requireApiOptions(options, {
|
|
@@ -7296,9 +8146,9 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7296
8146
|
() => loadTileflowConfigWithInputs3(options.config)
|
|
7297
8147
|
);
|
|
7298
8148
|
const project = loaded.project;
|
|
7299
|
-
const baseDirectory =
|
|
8149
|
+
const baseDirectory = dirname11(loaded.configFile);
|
|
7300
8150
|
assertValidTileflowConfig2(project);
|
|
7301
|
-
const configuredMapNames =
|
|
8151
|
+
const configuredMapNames = getTileflowMapNames4(project);
|
|
7302
8152
|
const mapNames = selectManagedDeployMaps(configuredMapNames, options);
|
|
7303
8153
|
if (!mapNames) return;
|
|
7304
8154
|
if (!validateDeployTarget(options)) return;
|
|
@@ -7312,6 +8162,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7312
8162
|
const compiledIcons = await compileTileflowIconPackages2(deploymentProject, {
|
|
7313
8163
|
baseDirectory,
|
|
7314
8164
|
cwd: process.cwd(),
|
|
8165
|
+
icons: resolveIconOptions(options),
|
|
7315
8166
|
target: "hosted"
|
|
7316
8167
|
});
|
|
7317
8168
|
const bindingsByMap = new Map(
|
|
@@ -7392,7 +8243,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7392
8243
|
);
|
|
7393
8244
|
const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
|
|
7394
8245
|
logSuccess(
|
|
7395
|
-
`${action} icon package ${
|
|
8246
|
+
`${action} icon package ${pc6.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
|
|
7396
8247
|
);
|
|
7397
8248
|
hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
|
|
7398
8249
|
}
|
|
@@ -7498,7 +8349,8 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7498
8349
|
map,
|
|
7499
8350
|
sourceAssets: {
|
|
7500
8351
|
fonts: hostedFonts.sourceIdentities[mapName] ?? [],
|
|
7501
|
-
icons: compiledIcons.sourceIdentities[mapName] ?? []
|
|
8352
|
+
icons: compiledIcons.sourceIdentities[mapName] ?? [],
|
|
8353
|
+
...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {}
|
|
7502
8354
|
},
|
|
7503
8355
|
styles: styles[mapName]
|
|
7504
8356
|
}
|
|
@@ -7513,6 +8365,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7513
8365
|
const fontBundle = hostedFonts.bundles[mapName];
|
|
7514
8366
|
return {
|
|
7515
8367
|
iconBinding,
|
|
8368
|
+
iconComposition: compiledIcons.compositions[mapName],
|
|
7516
8369
|
iconPackage,
|
|
7517
8370
|
fontBundle,
|
|
7518
8371
|
mapName,
|
|
@@ -7531,14 +8384,20 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7531
8384
|
buildManifest: buildManifest2,
|
|
7532
8385
|
fontBundle,
|
|
7533
8386
|
iconBinding,
|
|
8387
|
+
iconComposition,
|
|
7534
8388
|
iconPackage,
|
|
7535
8389
|
mapName,
|
|
7536
8390
|
teamSources,
|
|
7537
8391
|
styles: themeStyles
|
|
7538
8392
|
} = deployment;
|
|
8393
|
+
if (iconComposition && iconComposition.packageHash !== iconPackage?.contentHash) {
|
|
8394
|
+
logError(`Icon composition does not match the effective package for ${mapName}.`);
|
|
8395
|
+
process.exitCode = 1;
|
|
8396
|
+
return;
|
|
8397
|
+
}
|
|
7539
8398
|
const themeNames = Object.keys(themeStyles).sort();
|
|
7540
8399
|
logInfo(
|
|
7541
|
-
`Deploying compiled map ${
|
|
8400
|
+
`Deploying compiled map ${pc6.bold(mapName)} (${plural(themeNames.length, "theme")}).`
|
|
7542
8401
|
);
|
|
7543
8402
|
const response = await publishHostedStyle(
|
|
7544
8403
|
api,
|
|
@@ -7560,6 +8419,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7560
8419
|
label: iconBinding.label
|
|
7561
8420
|
}
|
|
7562
8421
|
} : {},
|
|
8422
|
+
...iconComposition ? { iconComposition } : {},
|
|
7563
8423
|
...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
|
|
7564
8424
|
source
|
|
7565
8425
|
},
|
|
@@ -7588,7 +8448,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7588
8448
|
process.exitCode = 1;
|
|
7589
8449
|
return;
|
|
7590
8450
|
}
|
|
7591
|
-
const resolvedMap =
|
|
8451
|
+
const resolvedMap = parseResolvedTileflowMap4(deploymentProject.maps[mapName]);
|
|
7592
8452
|
const mapBuild = buildManifest2.maps[mapName];
|
|
7593
8453
|
deployedMaps[mapName] = {
|
|
7594
8454
|
defaultTheme: resolvedMap.defaultTheme,
|
|
@@ -7617,9 +8477,9 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
|
|
|
7617
8477
|
};
|
|
7618
8478
|
const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
|
|
7619
8479
|
if (body.changed === false) {
|
|
7620
|
-
logSuccess(`Unchanged ${
|
|
8480
|
+
logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
|
|
7621
8481
|
} else {
|
|
7622
|
-
logSuccess(`Published ${
|
|
8482
|
+
logSuccess(`Published ${pc6.bold(mapName)}${versionLabel}.`);
|
|
7623
8483
|
}
|
|
7624
8484
|
}
|
|
7625
8485
|
const manifestPath = await writeDeployManifest(options.manifest, {
|
|
@@ -7693,6 +8553,7 @@ program.command("status").description("Show deployed compiled styles").option("-
|
|
|
7693
8553
|
printMapStatus(status, api.apiUrl);
|
|
7694
8554
|
});
|
|
7695
8555
|
var iconsCommand = program.command("icons").description("Inspect managed Tileflow icons");
|
|
8556
|
+
registerIconLockCommands(program, { defaultApiUrl, defaultConfigPath, loadAuthConfig });
|
|
7696
8557
|
registerIconListCommand(iconsCommand, { defaultConfigPath });
|
|
7697
8558
|
registerIconDiffCommand(iconsCommand, {
|
|
7698
8559
|
defaultApiUrl,
|
|
@@ -7782,10 +8643,12 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7782
8643
|
try {
|
|
7783
8644
|
const leftCaptureConfig = comparisonCaptureConfigArgument(options.config);
|
|
7784
8645
|
const rightCaptureConfig = comparisonCaptureConfigArgument(rightConfig);
|
|
8646
|
+
const comparisonIcons = resolveIconOptions(options);
|
|
7785
8647
|
leftSession = await createTileflowArtifactSession3({
|
|
7786
8648
|
apiBaseUrl: options.apiBaseUrl,
|
|
7787
8649
|
assetBaseUrl: `${origin}${leftBasePath}`,
|
|
7788
8650
|
config: options.config,
|
|
8651
|
+
...comparisonIcons ? { icons: comparisonIcons } : {},
|
|
7789
8652
|
inspection: true,
|
|
7790
8653
|
styleBaseUrl: `${origin}${leftBasePath}`,
|
|
7791
8654
|
watch: true
|
|
@@ -7794,6 +8657,7 @@ async function runTileflowComparisonPreview(options, address) {
|
|
|
7794
8657
|
apiBaseUrl: options.apiBaseUrl,
|
|
7795
8658
|
assetBaseUrl: `${origin}${rightBasePath}`,
|
|
7796
8659
|
config: rightConfig,
|
|
8660
|
+
...comparisonIcons ? { icons: comparisonIcons } : {},
|
|
7797
8661
|
inspection: true,
|
|
7798
8662
|
styleBaseUrl: `${origin}${rightBasePath}`,
|
|
7799
8663
|
watch: true
|
|
@@ -7925,7 +8789,7 @@ function comparisonCaptureConfigArgument(configPath) {
|
|
|
7925
8789
|
const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
|
|
7926
8790
|
if (absoluteConfigPath === absoluteDefaultPath) return void 0;
|
|
7927
8791
|
const relativeConfigPath = relative5(cwd, absoluteConfigPath);
|
|
7928
|
-
if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) ||
|
|
8792
|
+
if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
|
|
7929
8793
|
throw new Error(
|
|
7930
8794
|
"Comparison configs must be inside the current working directory so Copy command can remain reproducible."
|
|
7931
8795
|
);
|
|
@@ -7964,7 +8828,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
7964
8828
|
codeChallenge,
|
|
7965
8829
|
deviceName: hostname()
|
|
7966
8830
|
});
|
|
7967
|
-
printKeyValue("Code",
|
|
8831
|
+
printKeyValue("Code", pc6.bold(authorization.userCode));
|
|
7968
8832
|
printKeyValue("URL", link(authorization.verificationUriComplete));
|
|
7969
8833
|
if (options.noBrowser) {
|
|
7970
8834
|
logMuted("Open the URL above to continue.");
|
|
@@ -7974,7 +8838,7 @@ async function loginWithDeviceFlow(options) {
|
|
|
7974
8838
|
output: process.stdout
|
|
7975
8839
|
});
|
|
7976
8840
|
try {
|
|
7977
|
-
await readline.question(`${
|
|
8841
|
+
await readline.question(`${pc6.cyan("?")} Press Enter to open Tileflow Dashboard...`);
|
|
7978
8842
|
openBrowser(authorization.verificationUriComplete);
|
|
7979
8843
|
} finally {
|
|
7980
8844
|
readline.close();
|
|
@@ -8027,7 +8891,7 @@ function openBrowser(url, errorsToStderr = false) {
|
|
|
8027
8891
|
async function writeDeployManifest(manifestPath, manifest) {
|
|
8028
8892
|
const outputPath = resolve7(process.cwd(), manifestPath);
|
|
8029
8893
|
await writeAtomicFile({
|
|
8030
|
-
boundaryPath:
|
|
8894
|
+
boundaryPath: dirname11(outputPath),
|
|
8031
8895
|
force: true,
|
|
8032
8896
|
label: "Deploy manifest",
|
|
8033
8897
|
managed: true,
|
|
@@ -8037,6 +8901,13 @@ async function writeDeployManifest(manifestPath, manifest) {
|
|
|
8037
8901
|
});
|
|
8038
8902
|
return manifestPath;
|
|
8039
8903
|
}
|
|
8904
|
+
function resolveIconOptions(options) {
|
|
8905
|
+
const icons = {
|
|
8906
|
+
...options.cacheDir ? { cacheRoot: options.cacheDir } : {},
|
|
8907
|
+
...options.offline ? { offline: true } : {}
|
|
8908
|
+
};
|
|
8909
|
+
return Object.keys(icons).length > 0 ? icons : void 0;
|
|
8910
|
+
}
|
|
8040
8911
|
function createCompiledMapAssets(compiled, resolveSprite) {
|
|
8041
8912
|
return Object.fromEntries(
|
|
8042
8913
|
compiled.bindings.map((binding) => [
|
|
@@ -8190,7 +9061,7 @@ async function requireApiOptions(options, behavior = {}) {
|
|
|
8190
9061
|
if (!behavior.silent) {
|
|
8191
9062
|
logError("CI requires an explicit Tileflow credential with access to the target Map.");
|
|
8192
9063
|
printNextSteps([
|
|
8193
|
-
`Set ${
|
|
9064
|
+
`Set ${pc6.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
|
|
8194
9065
|
"The saved personal account session is never used in CI."
|
|
8195
9066
|
]);
|
|
8196
9067
|
}
|
|
@@ -8270,7 +9141,7 @@ function printTileflowPreviewError(error) {
|
|
|
8270
9141
|
printValidationErrors(error.issues);
|
|
8271
9142
|
return;
|
|
8272
9143
|
}
|
|
8273
|
-
if (error instanceof
|
|
9144
|
+
if (error instanceof TileflowValidationError5) {
|
|
8274
9145
|
printValidationErrors(error.messages);
|
|
8275
9146
|
return;
|
|
8276
9147
|
}
|
|
@@ -8282,7 +9153,7 @@ function printCliError(error) {
|
|
|
8282
9153
|
printValidationErrors(error.issues);
|
|
8283
9154
|
return;
|
|
8284
9155
|
}
|
|
8285
|
-
if (error instanceof
|
|
9156
|
+
if (error instanceof TileflowValidationError5) {
|
|
8286
9157
|
printValidationErrors(error.messages);
|
|
8287
9158
|
return;
|
|
8288
9159
|
}
|
|
@@ -8296,17 +9167,17 @@ function printCliError(error) {
|
|
|
8296
9167
|
}
|
|
8297
9168
|
const message = error instanceof Error ? error.message : "Command failed.";
|
|
8298
9169
|
if (message.includes("Cannot find module '@tileflow/core'")) {
|
|
8299
|
-
logError(`Cannot load ${
|
|
9170
|
+
logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
|
|
8300
9171
|
printNextSteps([
|
|
8301
|
-
`Install ${
|
|
9172
|
+
`Install ${pc6.cyan("@tileflow/core")} in this project.`,
|
|
8302
9173
|
`Run the command again from the project root.`
|
|
8303
9174
|
]);
|
|
8304
9175
|
return;
|
|
8305
9176
|
}
|
|
8306
9177
|
if (message.includes("Cannot find module '@tileflow/maps'")) {
|
|
8307
|
-
logError(`Cannot load ${
|
|
9178
|
+
logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
|
|
8308
9179
|
printNextSteps([
|
|
8309
|
-
`Install ${
|
|
9180
|
+
`Install ${pc6.cyan("@tileflow/maps")} in this project.`,
|
|
8310
9181
|
`Run the command again from the project root.`
|
|
8311
9182
|
]);
|
|
8312
9183
|
return;
|
|
@@ -8317,51 +9188,51 @@ function printCliError(error) {
|
|
|
8317
9188
|
}
|
|
8318
9189
|
}
|
|
8319
9190
|
function logSuccess(message) {
|
|
8320
|
-
console.log(`${
|
|
9191
|
+
console.log(`${pc6.green("\u2713")} ${message}`);
|
|
8321
9192
|
}
|
|
8322
9193
|
function logError(message) {
|
|
8323
|
-
console.log(`${
|
|
9194
|
+
console.log(`${pc6.red("\u2715")} ${message}`);
|
|
8324
9195
|
}
|
|
8325
9196
|
function logWarning(message) {
|
|
8326
|
-
console.log(`${
|
|
9197
|
+
console.log(`${pc6.yellow("!")} ${message}`);
|
|
8327
9198
|
}
|
|
8328
9199
|
function logInfo(message) {
|
|
8329
|
-
console.log(`${
|
|
9200
|
+
console.log(`${pc6.cyan("i")} ${message}`);
|
|
8330
9201
|
}
|
|
8331
9202
|
function logMuted(message) {
|
|
8332
|
-
console.log(
|
|
9203
|
+
console.log(pc6.gray(message));
|
|
8333
9204
|
}
|
|
8334
9205
|
function printTitle(title, subtitle) {
|
|
8335
|
-
console.log(
|
|
9206
|
+
console.log(pc6.bold(title));
|
|
8336
9207
|
if (subtitle) {
|
|
8337
|
-
console.log(
|
|
9208
|
+
console.log(pc6.gray(subtitle));
|
|
8338
9209
|
}
|
|
8339
9210
|
}
|
|
8340
9211
|
function printKeyValue(label, value) {
|
|
8341
|
-
console.log(`${
|
|
9212
|
+
console.log(`${pc6.gray(`${label}:`.padEnd(13))} ${value}`);
|
|
8342
9213
|
}
|
|
8343
9214
|
function printChecks(items) {
|
|
8344
9215
|
for (const item of items) {
|
|
8345
|
-
console.log(` ${
|
|
9216
|
+
console.log(` ${pc6.green("\u2713")} ${item}`);
|
|
8346
9217
|
}
|
|
8347
9218
|
}
|
|
8348
9219
|
function printNextSteps(steps) {
|
|
8349
9220
|
if (steps.length === 0) return;
|
|
8350
9221
|
console.log(`
|
|
8351
|
-
${
|
|
9222
|
+
${pc6.bold("Next steps")}`);
|
|
8352
9223
|
for (const step of steps) {
|
|
8353
|
-
console.log(` ${
|
|
9224
|
+
console.log(` ${pc6.cyan("\u2022")} ${step}`);
|
|
8354
9225
|
}
|
|
8355
9226
|
}
|
|
8356
9227
|
function printValidationErrors(messages) {
|
|
8357
9228
|
logError("Tileflow config has errors.");
|
|
8358
9229
|
for (const message of messages) {
|
|
8359
|
-
const path = message.path ?
|
|
8360
|
-
console.log(` ${
|
|
9230
|
+
const path = message.path ? pc6.cyan(message.path) : pc6.cyan("(root)");
|
|
9231
|
+
console.log(` ${pc6.red("\u2715")} ${path} ${pc6.gray("-")} ${message.message}`);
|
|
8361
9232
|
}
|
|
8362
9233
|
}
|
|
8363
9234
|
function printAccountDetails(session) {
|
|
8364
|
-
printKeyValue("Account",
|
|
9235
|
+
printKeyValue("Account", pc6.bold(session.account.name));
|
|
8365
9236
|
printKeyValue("Email", session.account.email);
|
|
8366
9237
|
printKeyValue("API", link(session.apiOrigin));
|
|
8367
9238
|
printKeyValue("Expires", formatDate(session.expiresAt));
|
|
@@ -8390,11 +9261,11 @@ function printDeployedMaps(maps) {
|
|
|
8390
9261
|
const entries = Object.entries(maps);
|
|
8391
9262
|
if (entries.length === 0) return;
|
|
8392
9263
|
console.log(`
|
|
8393
|
-
${
|
|
9264
|
+
${pc6.bold("Maps")}`);
|
|
8394
9265
|
for (const [name, map] of entries) {
|
|
8395
9266
|
for (const [themeName, theme] of Object.entries(map.themes)) {
|
|
8396
9267
|
const label = `${name}/${themeName}`;
|
|
8397
|
-
console.log(` ${
|
|
9268
|
+
console.log(` ${pc6.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
|
|
8398
9269
|
}
|
|
8399
9270
|
}
|
|
8400
9271
|
}
|
|
@@ -8412,7 +9283,7 @@ function plural(count, singular, pluralForm = `${singular}s`) {
|
|
|
8412
9283
|
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
8413
9284
|
}
|
|
8414
9285
|
function command(value) {
|
|
8415
|
-
return
|
|
9286
|
+
return pc6.cyan(value);
|
|
8416
9287
|
}
|
|
8417
9288
|
function cliInvocation(parts) {
|
|
8418
9289
|
return parts.map(quoteCliArgument).join(" ");
|
|
@@ -8421,10 +9292,10 @@ function quoteCliArgument(value) {
|
|
|
8421
9292
|
return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
8422
9293
|
}
|
|
8423
9294
|
function link(value) {
|
|
8424
|
-
return
|
|
9295
|
+
return pc6.cyan(value);
|
|
8425
9296
|
}
|
|
8426
9297
|
function pathLabel(value) {
|
|
8427
|
-
return
|
|
9298
|
+
return pc6.cyan(value);
|
|
8428
9299
|
}
|
|
8429
9300
|
function starterConfig() {
|
|
8430
9301
|
return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
|
|
@@ -8470,15 +9341,15 @@ function printHostedCompatibilityIssues(issues) {
|
|
|
8470
9341
|
}
|
|
8471
9342
|
function printMapStatus(status, apiUrl) {
|
|
8472
9343
|
printTitle("Tileflow status");
|
|
8473
|
-
printKeyValue("Map",
|
|
9344
|
+
printKeyValue("Map", pc6.bold(status.mapId));
|
|
8474
9345
|
console.log(`
|
|
8475
|
-
${
|
|
9346
|
+
${pc6.bold("Styles")}`);
|
|
8476
9347
|
if (status.styles.length === 0) {
|
|
8477
9348
|
logMuted(" No styles deployed.");
|
|
8478
9349
|
}
|
|
8479
9350
|
for (const style of status.styles) {
|
|
8480
9351
|
console.log(
|
|
8481
|
-
` ${
|
|
9352
|
+
` ${pc6.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes3(style.size).padStart(10)} ${pc6.gray(formatDate(style.uploaded))}`
|
|
8482
9353
|
);
|
|
8483
9354
|
}
|
|
8484
9355
|
}
|