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/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 dirname10, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
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 pc3 from "picocolors";
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 parseResolvedTileflowMap3,
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 getTileflowMapNames3,
39
+ getTileflowMapNames as getTileflowMapNames4,
40
40
  loadTileflowConfigWithInputs as loadTileflowConfigWithInputs3,
41
- TileflowValidationError as TileflowValidationError4
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 { compileTileflowIconPackages as compileTileflowIconPackages2, TileflowIconCompilationError as TileflowIconCompilationError3 } from "@tileflow/dev/icons";
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 { compileTileflowIconPackages } from "@tileflow/dev/icons";
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-2 JSON").action(async (options) => {
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: 2,
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 && sameStrings(candidate.directories, mapIcons.directories)
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: "directories",
3696
- directories: [...mapIcons.directories],
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
- sources: [...catalog.icons].sort((left, right) => compareCodeUnits6(left.id, right.id)).map((icon) => ({
3706
- id: icon.id,
3707
- path: icon.source.path,
3708
- format: icon.source.format,
3709
- byteLength: icon.source.byteLength,
3710
- dimensions: icon.source.dimensions ? { width: icon.source.dimensions.width, height: icon.source.dimensions.height } : null
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
- console.error(error instanceof Error ? error.message : "Icon catalog listing failed.");
4662
+ return { selected: selected.sort(compareCodeUnits7) };
3728
4663
  }
3729
- function printIssues(heading, issues) {
3730
- console.error(
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(`${serializeCanonicalJson(manifest)}
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(`${serializeCanonicalJson(schema)}
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 pc from "picocolors";
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 emitJson(document);
4791
+ if (options.json) return emitJson2(document);
3859
4792
  if (!result.items.length) {
3860
- console.log(pc.gray("No managed destinations found."));
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 ? pc.yellow("\u25CB") : pc.green("\u25CF")} ${pc.bold(target.project.name)} ${pc.gray(target.reference)}${target.project.archivedAt ? pc.gray(" (archived)") : ""}`
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 emitJson(document);
3918
- console.log(`${pc.green("\u2713")} Application ${pc.bold(options.name.trim())} is ready.`);
3919
- console.log(pc.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
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 emitJson(document);
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(`${pc.bold(project.name)} ${pc.gray(target)}`);
3942
- console.log(project.archivedAt ? pc.yellow("Archived") : pc.green("Active"));
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 emitJson(document);
3969
- console.log(`${pc.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
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", safeMessage(error));
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
- emitJson({ schemaVersion: 1, ok: false, error: { code, ...details } });
5023
+ emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
4091
5024
  } else {
4092
- console.error(`${pc.red("\u2715")} ${message}`);
5025
+ console.error(`${pc4.red("\u2715")} ${message}`);
4093
5026
  }
4094
5027
  process.exitCode = 1;
4095
5028
  }
4096
- function emitJson(value) {
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 safeMessage(error) {
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 pc2 from "picocolors";
4128
- import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
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 z6 } from "zod";
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 z5 } from "zod";
4136
- import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
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(serializeCanonicalJson2(contentIdentity)).digest("hex"),
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
- serializeCanonicalJson2({
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 = z5.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
4699
- var uploadPartStatusSchema = z5.object({
4700
- byteCount: z5.number().int().min(1).max(teamTilesetPartSize),
4701
- partNumber: z5.number().int().min(1).max(239),
4702
- uploaded: z5.boolean(),
4703
- validationState: z5.enum(["mismatch", "pending", "processing", "valid"])
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 = z5.object({
4706
- byteCount: z5.number().int().min(1).max(maximumTeamTilesetBytes),
4707
- contentHash: z5.string().regex(/^[0-9a-f]{64}$/u),
4708
- contentHashAlgorithm: z5.literal("sha256-fixed-parts-v1"),
4709
- publishedVersion: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4710
- schemaVersion: z5.literal(1),
4711
- state: z5.literal("published"),
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 = z5.object({
4715
- byteCount: z5.number().int().min(1).max(maximumTeamTilesetBytes),
4716
- contentHash: z5.string().regex(/^[0-9a-f]{64}$/u),
4717
- contentHashAlgorithm: z5.literal("sha256-fixed-parts-v1"),
4718
- expiresAt: z5.iso.datetime({ offset: true }),
4719
- failure: z5.object({ code: z5.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
4720
- partCount: z5.number().int().min(1).max(239),
4721
- parts: z5.array(uploadPartStatusSchema).max(239),
4722
- partSize: z5.literal(teamTilesetPartSize),
4723
- publishedVersion: z5.null(),
4724
- schemaVersion: z5.literal(1),
4725
- state: z5.enum([
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: z5.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
4739
- versionId: z5.null()
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 = z5.union([publishedVersionSchema, uploadControlSchema]);
4742
- var authorizationSchema = z5.object({
4743
- expiresAt: z5.iso.datetime({ offset: true }),
4744
- headers: z5.object({
4745
- "Content-Length": z5.string().regex(/^[1-9][0-9]{0,9}$/u),
4746
- "Content-MD5": z5.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
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: z5.literal("PUT"),
4749
- partNumber: z5.number().int().min(1).max(239),
4750
- url: z5.url().max(8192).refine((value) => {
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 = z5.object({
4756
- authorizations: z5.array(authorizationSchema).min(1).max(16),
4757
- schemaVersion: z5.literal(1)
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 = z5.object({
4760
- changed: z5.boolean(),
4761
- etag: z5.string().regex(/^[0-9a-f]{32}$/u),
4762
- partNumber: z5.number().int().min(1).max(239),
4763
- schemaVersion: z5.literal(1)
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(`${serializeCanonicalJson3(inspection)}
5716
+ process.stdout.write(`${serializeCanonicalJson4(inspection)}
4784
5717
  `);
4785
5718
  return;
4786
5719
  }
4787
- console.log(`${pc2.green("\u2713")} PMTiles archive is valid.`);
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(`${pc2.yellow("!")} ${warning.message}`);
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
- `${serializeCanonicalJson3({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
5739
+ `${serializeCanonicalJson4({ error: { code, message, path }, ok: false, schemaVersion: 1 })}
4807
5740
  `
4808
5741
  );
4809
5742
  } else {
4810
- console.error(`${pc2.red("Error:")} ${message}`);
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 emitJson2(document);
5825
+ if (options.json) return emitJson(document);
4893
5826
  console.log(
4894
- `${pc2.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc2.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
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 emitJson2(document);
4914
- if (items.length === 0) return console.log(pc2.gray("No Team tilesets."));
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(` ${pc2.bold(item.logicalName)} ${pc2.gray(item.status)}`);
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 emitJson2(document);
5875
+ if (options.json) return emitJson(document);
4943
5876
  const item = parsed.tileset;
4944
- console.log(`${pc2.bold(item.name)} ${pc2.gray(item.state)}`);
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 emitJson2(document);
4995
- console.log(`${pc2.green("\u2713")} Purge ${parsed.reclamation}.`);
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 = z6.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
5153
- var physicalTilesetIdSchema = z6.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
5154
- var cursorSchema = z6.string().min(1).max(2048).nullable();
5155
- var tilesetInventoryItemSchema = z6.object({
5156
- currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
5157
- hasCurrent: z6.boolean(),
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: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
5160
- name: z6.string().min(1).max(200),
5161
- retainedBytes: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
5162
- retainedDeploymentCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
5163
- status: z6.enum(["active", "purging", "retired"]),
5164
- updatedAt: z6.iso.datetime({ offset: true }),
5165
- usedByMapCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
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 = z6.object({
5168
- nextCursor: cursorSchema.optional(),
5169
- schemaVersion: z6.literal(1),
5170
- tilesets: z6.array(tilesetInventoryItemSchema).max(100)
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 = z6.object({
5173
- active: z6.boolean(),
6010
+ var tilesetDependencySchema = z7.object({
6011
+ active: z7.boolean(),
5174
6012
  deploymentId: safeIdentifierSchema2,
5175
- deploymentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
5176
- environment: z6.string().min(1).max(64),
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 = z6.object({
5181
- schemaVersion: z6.literal(1),
5182
- tileset: z6.object({
5183
- attribution: z6.string().max(4096).nullable(),
5184
- currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
5185
- dependencies: z6.array(tilesetDependencySchema).max(25),
5186
- dependencyNextCursor: cursorSchema,
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: z6.string().min(1).max(200),
5189
- slug: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
5190
- state: z6.enum(["active", "purging", "purged"])
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 = z6.object({
6031
+ var purgeDependencySchema = z7.object({
5194
6032
  deploymentId: safeIdentifierSchema2,
5195
6033
  mapId: safeIdentifierSchema2,
5196
6034
  sourceId: safeIdentifierSchema2
5197
6035
  }).strict();
5198
- var tilesetInUseResponseSchema = z6.object({
5199
- code: z6.literal("TF_TILESET_IN_USE"),
5200
- dependencies: z6.array(purgeDependencySchema).max(32),
5201
- error: z6.literal("Tileset is used by retained Map deployments"),
5202
- nextCursor: cursorSchema
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 = z6.object({
5205
- deliveryFence: z6.enum(["complete", "pending"]),
5206
- pendingVersions: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
5207
- reclamation: z6.enum(["complete", "pending"]),
5208
- state: z6.enum(["purged", "purging"]),
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 dirname9, isAbsolute as isAbsolute3, parse as parse2, relative as relative4, resolve as resolve6, sep as sep4 } from "path";
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 compareCodeUnits8 } from "@tileflow/core";
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 dirname8, extname, isAbsolute as isAbsolute2, parse, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
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 compareCodeUnits7,
6080
+ compareCodeUnits as compareCodeUnits8,
5243
6081
  normalizeTileflowCaptureScene as normalizeTileflowCaptureScene2,
5244
- parseResolvedTileflowMap as parseResolvedTileflowMap2,
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(compareCodeUnits7)
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(compareCodeUnits7);
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 = parseResolvedTileflowMap2(candidate);
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 = dirname8(reportPath);
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) => compareCodeUnits7(left.path, right.path))
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(dirname8(inventoryPath), boundary);
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(dirname8(path), boundary);
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(compareCodeUnits7);
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(compareCodeUnits7);
6267
- const expected = [...keys].sort(compareCodeUnits7);
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 = dirname8(current);
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}`) && !isAbsolute2(path);
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) => compareCodeUnits8(left.comparison.scene, right.comparison.scene)).map(({ comparison, baselinePath, actualPath, diffPath, reportPath }) => ({
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(compareCodeUnits8)
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) => compareCodeUnits8(left.scene, right.scene))
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(dirname9(path), boundary);
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}`) && !isAbsolute3(path);
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(pc3.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
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", pc3.bold("madrid"));
7694
+ printKeyValue("Map", pc6.bold("madrid"));
6856
7695
  printNextSteps([
6857
- `Install ${pc3.cyan("@tileflow/core")} and ${pc3.cyan("@tileflow/maps")} in this project if needed.`,
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(async (options) => {
6951
- if (options.target !== "local" && options.target !== "hosted") {
6952
- if (options.json) {
6953
- const failure = createTileflowCommandFailureDocument4(
6954
- "validate",
6955
- {
6956
- code: "INVALID_TARGET",
6957
- issues: [
6958
- {
6959
- code: "INVALID_TARGET",
6960
- message: `Invalid validation target: ${options.target}`,
6961
- path: "target",
6962
- phase: "command-validation"
6963
- }
6964
- ],
6965
- phase: "command-validation"
6966
- },
6967
- process.cwd(),
6968
- { code: "INVALID_TARGET", phase: "command-validation" }
6969
- );
6970
- process.stderr.write(serializeTileflowCommandDocument4(failure));
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
- logError(`Invalid validation target: ${options.target}`);
6975
- printNextSteps([
6976
- `Use ${command("tileflow validate --target local")} or ${command("tileflow validate --target hosted")}.`
6977
- ]);
6978
- process.exitCode = 1;
6979
- return;
6980
- }
6981
- let failureDefaults = { code: "CONFIG_LOAD_FAILED", phase: "config-load" };
6982
- try {
6983
- if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
6984
- const loaded = await withTileflowConfigSecretsHidden(
6985
- () => loadTileflowConfigWithInputs3(options.config)
6986
- );
6987
- const project = loaded.project;
6988
- const baseDirectory = dirname10(loaded.configFile);
6989
- failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
6990
- assertValidTileflowConfig2(project);
6991
- const mapNames = getTileflowMapNames3(project).sort();
6992
- failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
6993
- const compiledIcons = await compileTileflowIconPackages2(project, {
6994
- baseDirectory,
6995
- cwd: process.cwd(),
6996
- target: options.target
6997
- });
6998
- const mapAssets = createCompiledMapAssets(
6999
- compiledIcons,
7000
- (binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`
7001
- );
7002
- failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
7003
- const compiledStyles = createTileflowStyles(project, {
7004
- apiBaseUrl: options.apiBaseUrl,
7005
- mapAssets
7006
- });
7007
- failureDefaults = { code: "TF_LOCAL_TILESET_INVALID", phase: "local-tileset-resolution" };
7008
- const localTilesets = await prepareTileflowLocalTilesets(project, compiledStyles, {
7009
- assetBaseUrl: "/tileflow",
7010
- baseDirectory,
7011
- cwd: process.cwd()
7012
- });
7013
- failureDefaults = { code: "FONT_COMPILATION_FAILED", phase: "font-compilation" };
7014
- const { styles } = await prepareTileflowStyleFonts(project, localTilesets.styles, {
7015
- assetBaseUrl: "/tileflow",
7016
- baseDirectory,
7017
- cwd: process.cwd(),
7018
- target: options.target
7019
- });
7020
- failureDefaults = { code: "HOSTED_INCOMPATIBLE", phase: "hosted-validation" };
7021
- const hostedIssues = options.target === "hosted" ? inspectTileflowHostedCompatibility(project, styles) : [];
7022
- if (hostedIssues.length > 0) {
7023
- if (!options.json) {
7024
- printHostedCompatibilityIssues(hostedIssues);
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
- message: issue.message,
7032
- path: issue.path,
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
- phase: "hosted-validation"
7036
- });
7037
- }
7038
- const themeWarnings = mapNames.flatMap(
7039
- (mapName) => auditTileflowMapThemeValues(project.maps[mapName]).filter(
7040
- ({ severity }) => severity === "warning"
7041
- )
7042
- );
7043
- const diagnostics = themeWarnings.length === 0 ? [] : createTileflowStructuredDiagnostics2({ diagnostics: themeWarnings }, process.cwd(), {
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
- process.stdout.write(
7067
- serializeTileflowCommandDocument4({
7068
- ...summary,
7069
- target: options.target,
7070
- maps: mapNames,
7071
- checks,
7072
- diagnostics
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
- return;
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
- ).action(async (options) => {
7099
- logInfo(`Building ${pathLabel(options.config)}.`);
7100
- await withTileflowConfigSecretsHidden(
7101
- () => writeTileflowBuildArtifacts({
7102
- config: options.config,
7103
- outDir: options.out,
7104
- styleBaseUrl: ".",
7105
- apiBaseUrl: options.apiBaseUrl
7106
- })
7107
- );
7108
- logSuccess("Built Tileflow artifacts.");
7109
- printKeyValue("Output", pathLabel(resolve7(process.cwd(), options.out)));
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 = dirname10(loaded.configFile);
8149
+ const baseDirectory = dirname11(loaded.configFile);
7300
8150
  assertValidTileflowConfig2(project);
7301
- const configuredMapNames = getTileflowMapNames3(project);
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 ${pc3.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
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 ${pc3.bold(mapName)} (${plural(themeNames.length, "theme")}).`
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 = parseResolvedTileflowMap3(deploymentProject.maps[mapName]);
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 ${pc3.bold(mapName)}${versionLabel}.`);
8480
+ logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
7621
8481
  } else {
7622
- logSuccess(`Published ${pc3.bold(mapName)}${versionLabel}.`);
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}`) || isAbsolute4(relativeConfigPath)) {
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", pc3.bold(authorization.userCode));
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(`${pc3.cyan("?")} Press Enter to open Tileflow Dashboard...`);
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: dirname10(outputPath),
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 ${pc3.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
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 TileflowValidationError4) {
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 TileflowValidationError4) {
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 ${pc3.cyan("@tileflow/core")} from this project.`);
9170
+ logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
8300
9171
  printNextSteps([
8301
- `Install ${pc3.cyan("@tileflow/core")} in this project.`,
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 ${pc3.cyan("@tileflow/maps")} from this project.`);
9178
+ logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
8308
9179
  printNextSteps([
8309
- `Install ${pc3.cyan("@tileflow/maps")} in this project.`,
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(`${pc3.green("\u2713")} ${message}`);
9191
+ console.log(`${pc6.green("\u2713")} ${message}`);
8321
9192
  }
8322
9193
  function logError(message) {
8323
- console.log(`${pc3.red("\u2715")} ${message}`);
9194
+ console.log(`${pc6.red("\u2715")} ${message}`);
8324
9195
  }
8325
9196
  function logWarning(message) {
8326
- console.log(`${pc3.yellow("!")} ${message}`);
9197
+ console.log(`${pc6.yellow("!")} ${message}`);
8327
9198
  }
8328
9199
  function logInfo(message) {
8329
- console.log(`${pc3.cyan("i")} ${message}`);
9200
+ console.log(`${pc6.cyan("i")} ${message}`);
8330
9201
  }
8331
9202
  function logMuted(message) {
8332
- console.log(pc3.gray(message));
9203
+ console.log(pc6.gray(message));
8333
9204
  }
8334
9205
  function printTitle(title, subtitle) {
8335
- console.log(pc3.bold(title));
9206
+ console.log(pc6.bold(title));
8336
9207
  if (subtitle) {
8337
- console.log(pc3.gray(subtitle));
9208
+ console.log(pc6.gray(subtitle));
8338
9209
  }
8339
9210
  }
8340
9211
  function printKeyValue(label, value) {
8341
- console.log(`${pc3.gray(`${label}:`.padEnd(13))} ${value}`);
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(` ${pc3.green("\u2713")} ${item}`);
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
- ${pc3.bold("Next steps")}`);
9222
+ ${pc6.bold("Next steps")}`);
8352
9223
  for (const step of steps) {
8353
- console.log(` ${pc3.cyan("\u2022")} ${step}`);
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 ? pc3.cyan(message.path) : pc3.cyan("(root)");
8360
- console.log(` ${pc3.red("\u2715")} ${path} ${pc3.gray("-")} ${message.message}`);
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", pc3.bold(session.account.name));
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
- ${pc3.bold("Maps")}`);
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(` ${pc3.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
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 pc3.cyan(value);
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 pc3.cyan(value);
9295
+ return pc6.cyan(value);
8425
9296
  }
8426
9297
  function pathLabel(value) {
8427
- return pc3.cyan(value);
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", pc3.bold(status.mapId));
9344
+ printKeyValue("Map", pc6.bold(status.mapId));
8474
9345
  console.log(`
8475
- ${pc3.bold("Styles")}`);
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
- ` ${pc3.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes3(style.size).padStart(10)} ${pc3.gray(formatDate(style.uploaded))}`
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
  }