@tileflow/dev 0.1.0-alpha.26 → 0.1.0-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,12 @@ manifest pointer, tracks its own files, and refuses to replace a hosted manifest
73
73
  Production rejects unresolved local PMTiles; publish data explicitly or provide an application-owned
74
74
  production source. Local development snapshots do not publish or redistribute the original dataset.
75
75
 
76
+ Maps whose labels use a line-fitted `text.background` draw text with derived glyph stacks. Local
77
+ previews and captures route such a map's glyph URL through `__glyphs/<map>/…`. Ordinary stacks pass
78
+ through from the declared provider; derived ranges are computed from their source ranges, which are
79
+ cached under `.tileflow/cache/glyphs/v1`. Production output keeps the declared provider, which must
80
+ serve derived stacks.
81
+
76
82
  ## Keep a watched request handler
77
83
 
78
84
  Use one session for repeated work and close it when the owning server stops:
@@ -137,8 +143,9 @@ This is the one normal icon-preparation path. `compileTileflowIconPackages`,
137
143
  `prepareTileflowCatalogIcons`, `inspectTileflowIconCatalogs` and `getTileflowIconWatchPaths` all use
138
144
  it, so `createTileflowBuildArtifacts`, `createTileflowArtifactSession`, and every adapter that
139
145
  enters them accept the same explicit `icons` resolution options: `cacheRoot`, `offline`,
140
- `deliveryOrigins`, `fetch` and `signal`. Nothing selects a cache, an origin, or a transport from
141
- implicit process state, and no build or watch polls a catalog head. `prepareTileflowCatalogIcons`
146
+ `deliveryOrigins`, `fetch` and `signal`. An explicit `cacheRoot` takes precedence over
147
+ `TILEFLOW_ICON_CACHE_DIR`, then the OS cache default. Origins and transport use their documented
148
+ defaults unless supplied explicitly; no build or watch polls a catalog head. `prepareTileflowCatalogIcons`
142
149
  returns `mapIconCompositions` alongside `mapIconSources`, and build-manifest entries carry the
143
150
  receipt as `sourceAssets.iconComposition` with `mapRevisionSchemaVersion: 2`.
144
151
 
@@ -159,8 +166,8 @@ Applications may explicitly configure trusted `deliveryOrigins`; locks cannot ad
159
166
  forbids all fetches and fails on missing or corrupt entries. Complete-map offline rendering still
160
167
  depends on unrelated tiles, glyphs, and other external resources.
161
168
 
162
- The cache lives under the OS user cache with `tileflow/icons/v1/<contentHash>`. `cacheRoot` or
163
- `TILEFLOW_ICON_CACHE_DIR` overrides its parent. Atomic directory installation and exact-entry
169
+ The cache lives under the OS user cache with `tileflow/icons/v1/<contentHash>`. `cacheRoot`, then
170
+ `TILEFLOW_ICON_CACHE_DIR`, overrides its parent. Atomic directory installation and exact-entry
164
171
  retirement support concurrent writers and corruption repair. Symlink entries are rejected. This is
165
172
  a disposable cache, not a security sandbox against another process controlling the same user
166
173
  account. Incomplete temporary entries are never accepted as published cache entries.
@@ -34,15 +34,10 @@ The generic font preparation pipeline reads map-owned TTF, OTF, and WOFF2 direct
34
34
  embed fonts in `@tileflow/dev`. Each source directory must carry its own `LICENSE.txt`, which is
35
35
  copied into the deterministic build artifacts whenever that directory contributes a selected face.
36
36
 
37
- The Cyberpunk map in `@tileflow/maps` currently owns unmodified Oxanium Medium and SemiBold TTFs.
38
- Oxanium is copyright 2019 The Oxanium Project Authors and is distributed under the SIL Open Font
39
- License 1.1. The source and binaries are pinned to upstream commit
40
- `a8f39e0c71186190027a093e9001459410192d1e`; the complete license accompanies the files at
41
- `@tileflow/maps/assets/cyberpunk/fonts/LICENSE.txt`.
42
-
43
- - Source: <https://github.com/sevmeyer/oxanium/tree/a8f39e0c71186190027a093e9001459410192d1e>
44
- - Google Fonts specimen: <https://fonts.google.com/specimen/Oxanium>
45
- - License: <https://github.com/sevmeyer/oxanium/blob/a8f39e0c71186190027a093e9001459410192d1e/OFL.txt>
37
+ The source repository's generic font tests retain pinned Oxanium fixtures and their complete
38
+ SIL Open Font License 1.1 notice under `test/fixtures/fonts/oxanium/`. These fixtures are not
39
+ included in the published package. Community map fonts are owned by
40
+ [Community Maps](https://github.com/tileflow/community-maps).
46
41
 
47
42
  ## fontkit 2.0.4
48
43
 
@@ -5,6 +5,38 @@ import { TileflowRenderer, TileflowNativeBuildRecord } from '@tileflow/core/nati
5
5
  import { TileflowBuildAsset, TileflowIconResolutionOptions, PreparedTileflowBuildCatalog, TileflowSourceCatalog, PreparedTileflowCatalog } from './icons.js';
6
6
  export { defaultTileflowConfigPath } from './config.js';
7
7
 
8
+ /**
9
+ * Local glyph serving for line-fitted text backgrounds.
10
+ *
11
+ * A style whose text uses a derived `<stack> lines-v1-…` font stack needs a glyph provider that
12
+ * derives those ranges. Local previews and captures route such a map's glyph URL through
13
+ * `__glyphs/<map>/…`: ordinary stacks pass through from the map's declared provider unchanged, and
14
+ * derived stacks are computed from their source stack's range. Production output keeps the
15
+ * declared provider, which must serve derived stacks itself.
16
+ */
17
+ declare const tileflowLineBackgroundGlyphRoute = "__glyphs";
18
+ /** Map name to the upstream glyph URL template its styles declared. Local only; never serialized. */
19
+ type TileflowLineBackgroundGlyphProviders = Readonly<Record<string, string>>;
20
+ type TileflowLineBackgroundGlyphResponderOptions = {
21
+ /** Project root; ranges are cached under `.tileflow/cache/glyphs/v1`. */
22
+ cwd: string;
23
+ fetch?: typeof globalThis.fetch;
24
+ };
25
+ type TileflowLineBackgroundGlyphResponder = (upstream: string, fontStack: string, range: string) => Promise<Response>;
26
+ /** Whether a compiled style draws text with a derived line-background stack. */
27
+ declare function usesTileflowLineBackgroundGlyphs(style: MapLibreStyle): boolean;
28
+ /** Parses `/__glyphs/<map>/<fontstack>/<range>.pbf` from a decoded route path. */
29
+ declare function parseTileflowLineBackgroundGlyphPath(path: string): {
30
+ fontStack: string;
31
+ mapName: string;
32
+ range: string;
33
+ } | undefined;
34
+ /**
35
+ * Serves one glyph range for a declared upstream template. Upstream ranges are cached on disk;
36
+ * derived ranges are recomputed from them and kept in a bounded in-memory cache.
37
+ */
38
+ declare function createTileflowLineBackgroundGlyphResponder(options: TileflowLineBackgroundGlyphResponderOptions): TileflowLineBackgroundGlyphResponder;
39
+
8
40
  type TileflowLocalTilesetFile = Readonly<{
9
41
  byteLength: number;
10
42
  etag: string;
@@ -149,6 +181,8 @@ type TileflowBuildArtifacts = {
149
181
  project: TileflowBuildCatalog;
150
182
  /** Local-only archive ports. Paths never enter serialized build artifacts. */
151
183
  localTilesets?: TileflowLocalTilesetFile[];
184
+ /** Local-only upstream glyph templates for maps whose text uses line-fitted backgrounds. */
185
+ lineBackgroundGlyphs?: TileflowLineBackgroundGlyphProviders;
152
186
  /** Optional local-only provenance. It is never serialized as a build artifact. */
153
187
  styleInspections?: TileflowBuildStyleInspections;
154
188
  styles: TileflowBuildStyles;
@@ -222,4 +256,4 @@ declare function assertTileflowSelfHostedManifestTarget(manifestPath: string, op
222
256
  overwriteHostedManifest?: boolean;
223
257
  }): Promise<void>;
224
258
 
225
- export { type CreateTileflowArtifactPlanOptions, PreparedTileflowBuildCatalog, PreparedTileflowCatalog, type TileflowArtifactAcquisition, type TileflowArtifactDiagnostic, type TileflowArtifactFile, type TileflowArtifactInputGraph, type TileflowArtifactPlan, type TileflowArtifactSession, type TileflowArtifactSessionOptions, type TileflowArtifactSessionState, type TileflowBuildArtifacts, type TileflowBuildArtifactsOptions, TileflowBuildAsset, type TileflowBuildStyleInspections, TileflowHostedManifestOverwriteError, TileflowIconResolutionOptions, type TileflowLocalTilesetFile, TileflowLocalTilesetProductionError, type TileflowManifestOptions, TileflowSourceCatalog, type WriteTileflowArtifactPlanOptions, type WriteTileflowBuildArtifactsOptions, assertNoLocalTilesetsForProduction, assertTileflowSelfHostedManifestTarget, createTileflowArtifactDiagnostics, createTileflowArtifactPlan, createTileflowArtifactSession, createTileflowBuildArtifacts, createTileflowBuildProvenance, createTileflowManifest, createTileflowStyle, createTileflowStyles, disposeTileflowBuildArtifacts, getTileflowArtifactFiles, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowWatchPaths, isTileflowArtifactInputPath, joinTileflowPublicUrl, normalizeTileflowBasePath, prepareTileflowHostedNativeDeployment, prepareTileflowLocalTilesets, refreshTileflowArtifactSession, resolveTileflowArtifactPublicUrls, tileflowArtifactInventoryFileName, tileflowArtifactPlanSchemaVersion, tileflowArtifactSessionSchemaVersion, writeTileflowArtifactPlan, writeTileflowBuildArtifacts };
259
+ export { type CreateTileflowArtifactPlanOptions, PreparedTileflowBuildCatalog, PreparedTileflowCatalog, type TileflowArtifactAcquisition, type TileflowArtifactDiagnostic, type TileflowArtifactFile, type TileflowArtifactInputGraph, type TileflowArtifactPlan, type TileflowArtifactSession, type TileflowArtifactSessionOptions, type TileflowArtifactSessionState, type TileflowBuildArtifacts, type TileflowBuildArtifactsOptions, TileflowBuildAsset, type TileflowBuildStyleInspections, TileflowHostedManifestOverwriteError, TileflowIconResolutionOptions, type TileflowLineBackgroundGlyphProviders, type TileflowLineBackgroundGlyphResponder, type TileflowLocalTilesetFile, TileflowLocalTilesetProductionError, type TileflowManifestOptions, TileflowSourceCatalog, type WriteTileflowArtifactPlanOptions, type WriteTileflowBuildArtifactsOptions, assertNoLocalTilesetsForProduction, assertTileflowSelfHostedManifestTarget, createTileflowArtifactDiagnostics, createTileflowArtifactPlan, createTileflowArtifactSession, createTileflowBuildArtifacts, createTileflowBuildProvenance, createTileflowLineBackgroundGlyphResponder, createTileflowManifest, createTileflowStyle, createTileflowStyles, disposeTileflowBuildArtifacts, getTileflowArtifactFiles, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowWatchPaths, isTileflowArtifactInputPath, joinTileflowPublicUrl, normalizeTileflowBasePath, parseTileflowLineBackgroundGlyphPath, prepareTileflowHostedNativeDeployment, prepareTileflowLocalTilesets, refreshTileflowArtifactSession, resolveTileflowArtifactPublicUrls, tileflowArtifactInventoryFileName, tileflowArtifactPlanSchemaVersion, tileflowArtifactSessionSchemaVersion, tileflowLineBackgroundGlyphRoute, usesTileflowLineBackgroundGlyphs, writeTileflowArtifactPlan, writeTileflowBuildArtifacts };
package/dist/artifacts.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  createTileflowArtifactSession,
9
9
  createTileflowBuildArtifacts,
10
10
  createTileflowBuildProvenance,
11
+ createTileflowLineBackgroundGlyphResponder,
11
12
  createTileflowManifest,
12
13
  createTileflowStyle,
13
14
  createTileflowStyles,
@@ -15,15 +16,18 @@ import {
15
16
  getTileflowArtifactFiles,
16
17
  getTileflowWatchPaths,
17
18
  isTileflowArtifactInputPath,
19
+ parseTileflowLineBackgroundGlyphPath,
18
20
  prepareTileflowHostedNativeDeployment,
19
21
  prepareTileflowLocalTilesets,
20
22
  refreshTileflowArtifactSession,
21
23
  tileflowArtifactInventoryFileName,
22
24
  tileflowArtifactPlanSchemaVersion,
23
25
  tileflowArtifactSessionSchemaVersion,
26
+ tileflowLineBackgroundGlyphRoute,
27
+ usesTileflowLineBackgroundGlyphs,
24
28
  writeTileflowArtifactPlan,
25
29
  writeTileflowBuildArtifacts
26
- } from "./chunk-QAMFAM3S.js";
30
+ } from "./chunk-4HVIEMKM.js";
27
31
  import "./chunk-ONYQNLNA.js";
28
32
  import {
29
33
  getTileflowAssetBasePath,
@@ -49,6 +53,7 @@ export {
49
53
  createTileflowArtifactSession,
50
54
  createTileflowBuildArtifacts,
51
55
  createTileflowBuildProvenance,
56
+ createTileflowLineBackgroundGlyphResponder,
52
57
  createTileflowManifest,
53
58
  createTileflowStyle,
54
59
  createTileflowStyles,
@@ -61,6 +66,7 @@ export {
61
66
  isTileflowArtifactInputPath,
62
67
  joinTileflowPublicUrl,
63
68
  normalizeTileflowBasePath,
69
+ parseTileflowLineBackgroundGlyphPath,
64
70
  prepareTileflowHostedNativeDeployment,
65
71
  prepareTileflowLocalTilesets,
66
72
  refreshTileflowArtifactSession,
@@ -68,6 +74,8 @@ export {
68
74
  tileflowArtifactInventoryFileName,
69
75
  tileflowArtifactPlanSchemaVersion,
70
76
  tileflowArtifactSessionSchemaVersion,
77
+ tileflowLineBackgroundGlyphRoute,
78
+ usesTileflowLineBackgroundGlyphs,
71
79
  writeTileflowArtifactPlan,
72
80
  writeTileflowBuildArtifacts
73
81
  };
@@ -28,22 +28,22 @@ import {
28
28
  } from "./chunk-D4OE4ITL.js";
29
29
 
30
30
  // src/artifacts.ts
31
- import { createHash as createHash3 } from "crypto";
31
+ import { createHash as createHash4 } from "crypto";
32
32
  import { realpathSync as realpathSync2 } from "fs";
33
33
  import {
34
34
  copyFile as copyFile2,
35
- lstat as lstat2,
36
- mkdir as mkdir2,
35
+ lstat as lstat3,
36
+ mkdir as mkdir3,
37
37
  mkdtemp,
38
38
  open as open2,
39
- readFile,
39
+ readFile as readFile2,
40
40
  realpath as realpath2,
41
- rename,
42
- rm as rm2,
41
+ rename as rename2,
42
+ rm as rm3,
43
43
  unlink
44
44
  } from "fs/promises";
45
45
  import { createRequire } from "module";
46
- import { dirname as dirname2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
46
+ import { dirname as dirname2, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
47
47
  import {
48
48
  compareCodeUnits as compareCodeUnits2,
49
49
  getTileflowStyleFontFaces as getTileflowStyleFontFaces2,
@@ -68,11 +68,194 @@ import {
68
68
  TileflowNativeCompatibilityError as TileflowNativeCompatibilityError5
69
69
  } from "@tileflow/core/native-profile";
70
70
 
71
+ // src/line-background-glyphs.ts
72
+ import { createHash, randomUUID } from "crypto";
73
+ import { lstat, mkdir, readFile, rename, rm, writeFile } from "fs/promises";
74
+ import { resolve } from "path";
75
+ import {
76
+ deriveTileflowLineBackgroundGlyphs,
77
+ parseTileflowLineBackgroundFontStack
78
+ } from "@tileflow/core";
79
+ var tileflowLineBackgroundGlyphRoute = "__glyphs";
80
+ var RANGE = /^\d{1,6}-\d{1,6}$/u;
81
+ var FONT_STACK = /^[\p{L}\p{N} _.,-]{1,256}$/u;
82
+ var MAP_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
83
+ var MAX_RANGE_BYTES = 8 * 1024 * 1024;
84
+ var MAX_DERIVED_ENTRIES = 512;
85
+ var CACHE_SEGMENTS = [".tileflow", "cache", "glyphs", "v1"];
86
+ function usesTileflowLineBackgroundGlyphs(style) {
87
+ return style.layers.some((layer) => {
88
+ const layout = layer.layout;
89
+ const font = layout?.["text-font"];
90
+ return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
91
+ });
92
+ }
93
+ function prepareTileflowLineBackgroundGlyphs(styles, options) {
94
+ const base = absoluteHttpUrl(options.assetBaseUrl);
95
+ if (!base) return { providers: {}, styles };
96
+ const providers = {};
97
+ const output = {};
98
+ for (const [mapName, themes] of Object.entries(styles)) {
99
+ const family = Object.values(themes);
100
+ const upstream = family.find((style) => typeof style.glyphs === "string")?.glyphs;
101
+ if (!MAP_NAME.test(mapName) || !upstream || !absoluteHttpUrl(upstream) || !family.some(usesTileflowLineBackgroundGlyphs) || family.some((style) => style.glyphs !== upstream)) {
102
+ output[mapName] = themes;
103
+ continue;
104
+ }
105
+ providers[mapName] = upstream;
106
+ const glyphs = `${base.replace(/\/+$/u, "")}/${tileflowLineBackgroundGlyphRoute}/${mapName}/{fontstack}/{range}.pbf`;
107
+ output[mapName] = Object.fromEntries(
108
+ Object.entries(themes).map(([themeName, style]) => [themeName, { ...style, glyphs }])
109
+ );
110
+ }
111
+ return { providers, styles: output };
112
+ }
113
+ function parseTileflowLineBackgroundGlyphPath(path) {
114
+ const match = /^\/__glyphs\/([^/]+)\/([^/]+)\/([^/]+)\.pbf$/u.exec(path);
115
+ if (!match) return void 0;
116
+ let fontStack;
117
+ try {
118
+ fontStack = decodeURIComponent(match[2]);
119
+ } catch {
120
+ return void 0;
121
+ }
122
+ if (!MAP_NAME.test(match[1]) || !FONT_STACK.test(fontStack) || !RANGE.test(match[3])) {
123
+ return void 0;
124
+ }
125
+ return { fontStack, mapName: match[1], range: match[3] };
126
+ }
127
+ function createTileflowLineBackgroundGlyphResponder(options) {
128
+ const fetchRange = options.fetch ?? globalThis.fetch;
129
+ const derived = /* @__PURE__ */ new Map();
130
+ const upstreamRanges = /* @__PURE__ */ new Map();
131
+ const readUpstream = (upstream, fontStack, range) => {
132
+ const key2 = `${upstream}
133
+ ${fontStack}
134
+ ${range}`;
135
+ let pending = upstreamRanges.get(key2);
136
+ if (!pending) {
137
+ pending = loadUpstreamRange(options.cwd, fetchRange, upstream, fontStack, range).finally(
138
+ () => upstreamRanges.delete(key2)
139
+ );
140
+ upstreamRanges.set(key2, pending);
141
+ }
142
+ return pending;
143
+ };
144
+ return async (upstream, fontStack, range) => {
145
+ if (!absoluteHttpUrl(upstream) || !FONT_STACK.test(fontStack) || !RANGE.test(range)) {
146
+ return notFound();
147
+ }
148
+ const backed = parseTileflowLineBackgroundFontStack(fontStack);
149
+ if (!backed) {
150
+ if (fontStack.split(",").some((stack) => parseTileflowLineBackgroundFontStack(stack))) {
151
+ return notFound();
152
+ }
153
+ return glyphResponse(await readUpstream(upstream, fontStack, range));
154
+ }
155
+ const key2 = `${upstream}
156
+ ${fontStack}
157
+ ${range}`;
158
+ let pending = derived.get(key2);
159
+ if (!pending) {
160
+ pending = readUpstream(upstream, backed.source, range).then(
161
+ (source) => source ? deriveTileflowLineBackgroundGlyphs(source, backed.metrics) : void 0
162
+ );
163
+ pending.catch(() => derived.delete(key2));
164
+ derived.set(key2, pending);
165
+ if (derived.size > MAX_DERIVED_ENTRIES) derived.delete(derived.keys().next().value);
166
+ }
167
+ return glyphResponse(await pending);
168
+ };
169
+ }
170
+ async function loadUpstreamRange(cwd, fetchRange, upstream, fontStack, range) {
171
+ const directory = await cacheDirectory(cwd, upstream);
172
+ const file = resolve(directory, `${digest(fontStack)}-${range}.pbf`);
173
+ const cached = await readFile(file).catch((error) => {
174
+ if (hasCode(error, "ENOENT")) return void 0;
175
+ throw error;
176
+ });
177
+ if (cached) return new Uint8Array(cached);
178
+ const url = upstream.replace("{fontstack}", encodeURIComponent(fontStack)).replace("{range}", range);
179
+ const response = await fetchRange(url, {
180
+ credentials: "omit",
181
+ headers: { Accept: "application/x-protobuf" },
182
+ redirect: "error",
183
+ signal: AbortSignal.timeout(3e4)
184
+ });
185
+ if (response.status === 404) return void 0;
186
+ if (response.status !== 200) {
187
+ throw new Error(`Glyph provider responded ${response.status} for ${fontStack} ${range}.`);
188
+ }
189
+ const length = Number(response.headers.get("content-length") ?? 0);
190
+ if (length > MAX_RANGE_BYTES) throw new Error(`Glyph range ${range} exceeds the size limit.`);
191
+ const bytes = new Uint8Array(await response.arrayBuffer());
192
+ if (bytes.byteLength > MAX_RANGE_BYTES)
193
+ throw new Error(`Glyph range ${range} exceeds the size limit.`);
194
+ const temporary = `${file}.${randomUUID()}.tmp`;
195
+ try {
196
+ await writeFile(temporary, bytes, { flag: "wx" });
197
+ await rename(temporary, file);
198
+ } catch {
199
+ await rm(temporary, { force: true }).catch(() => void 0);
200
+ }
201
+ return bytes;
202
+ }
203
+ async function cacheDirectory(cwd, upstream) {
204
+ let current = resolve(cwd);
205
+ for (const segment of [...CACHE_SEGMENTS, digest(upstream)]) {
206
+ current = resolve(current, segment);
207
+ try {
208
+ const stats = await lstat(current);
209
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
210
+ throw new Error(`Glyph cache path is unsafe: ${current}`);
211
+ }
212
+ } catch (error) {
213
+ if (!hasCode(error, "ENOENT")) throw error;
214
+ await mkdir(current).catch((mkdirError) => {
215
+ if (!hasCode(mkdirError, "EEXIST")) throw mkdirError;
216
+ });
217
+ }
218
+ }
219
+ return current;
220
+ }
221
+ function glyphResponse(bytes) {
222
+ if (!bytes) return notFound();
223
+ return new Response(new Uint8Array(bytes), {
224
+ headers: {
225
+ "Access-Control-Allow-Origin": "*",
226
+ "Cache-Control": "no-cache",
227
+ "Content-Type": "application/x-protobuf"
228
+ },
229
+ status: 200
230
+ });
231
+ }
232
+ function notFound() {
233
+ return new Response(JSON.stringify({ error: "Unknown glyph range." }), {
234
+ headers: { "Content-Type": "application/json; charset=utf-8" },
235
+ status: 404
236
+ });
237
+ }
238
+ function absoluteHttpUrl(value) {
239
+ if (!value) return void 0;
240
+ try {
241
+ const url = new URL(value.replace("{fontstack}", "stack").replace("{range}", "0-255"));
242
+ return url.protocol === "http:" || url.protocol === "https:" ? value : void 0;
243
+ } catch {
244
+ return void 0;
245
+ }
246
+ }
247
+ function digest(value) {
248
+ return createHash("sha256").update(value).digest("hex").slice(0, 32);
249
+ }
250
+ function hasCode(error, code) {
251
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
252
+ }
253
+
71
254
  // src/local-tilesets.ts
72
- import { randomUUID } from "crypto";
255
+ import { randomUUID as randomUUID2 } from "crypto";
73
256
  import { constants } from "fs";
74
- import { chmod, copyFile, lstat, mkdir, open, readdir, realpath, rm, stat } from "fs/promises";
75
- import { basename, resolve } from "path";
257
+ import { chmod, copyFile, lstat as lstat2, mkdir as mkdir2, open, readdir, realpath, rm as rm2, stat } from "fs/promises";
258
+ import { basename, resolve as resolve2 } from "path";
76
259
  import { compareCodeUnits } from "@tileflow/core";
77
260
  var minimumLocalPmtilesBytes = 127n;
78
261
  var maximumLocalPmtilesBytes = 8000000000n;
@@ -83,14 +266,14 @@ async function prepareTileflowLocalTilesets(project, styles, options) {
83
266
  if (!hasSources) {
84
267
  return { dispose: async () => void 0, files: [], styles, watchPaths: [] };
85
268
  }
86
- const logicalProjectRoot = resolve(options.cwd);
269
+ const logicalProjectRoot = resolve2(options.cwd);
87
270
  const projectRoot = await realpath(logicalProjectRoot).catch(() => logicalProjectRoot);
88
- const snapshotStore = resolve(logicalProjectRoot, ".tileflow/cache/pmtiles-snapshots/v1");
271
+ const snapshotStore = resolve2(logicalProjectRoot, ".tileflow/cache/pmtiles-snapshots/v1");
89
272
  await assertSafeSnapshotStore(logicalProjectRoot, snapshotStore);
90
273
  await collectAbandonedSnapshotGenerations(snapshotStore);
91
- const generationId = `snapshot-${process.pid}-${randomUUID().replaceAll("-", "")}`;
92
- const generationDirectory = resolve(snapshotStore, generationId);
93
- await mkdir(generationDirectory);
274
+ const generationId = `snapshot-${process.pid}-${randomUUID2().replaceAll("-", "")}`;
275
+ const generationDirectory = resolve2(snapshotStore, generationId);
276
+ await mkdir2(generationDirectory);
94
277
  const bindings = /* @__PURE__ */ new Map();
95
278
  const files = /* @__PURE__ */ new Map();
96
279
  const snapshots = /* @__PURE__ */ new Map();
@@ -109,7 +292,7 @@ async function prepareTileflowLocalTilesets(project, styles, options) {
109
292
  if (!isPathWithin(snapshotStore, generationDirectory)) {
110
293
  throw new Error("Local PMTiles snapshot generation escaped its store.");
111
294
  }
112
- await rm(generationDirectory, { force: true, recursive: true });
295
+ await rm2(generationDirectory, { force: true, recursive: true });
113
296
  };
114
297
  try {
115
298
  for (const mapName of Object.keys(project.maps).sort(compareCodeUnits)) {
@@ -117,7 +300,7 @@ async function prepareTileflowLocalTilesets(project, styles, options) {
117
300
  for (const [sourceId, source] of Object.entries(map.sources ?? {}).sort(
118
301
  ([left], [right]) => compareCodeUnits(left, right)
119
302
  )) {
120
- const requestedPath = resolve(options.baseDirectory, source.local);
303
+ const requestedPath = resolve2(options.baseDirectory, source.local);
121
304
  const sourcePath = await realpath(requestedPath).catch(() => requestedPath);
122
305
  if (!isPathWithin(projectRoot, sourcePath)) {
123
306
  throw localTilesetError(
@@ -126,7 +309,7 @@ async function prepareTileflowLocalTilesets(project, styles, options) {
126
309
  "Local hosted tileset must stay inside the project root."
127
310
  );
128
311
  }
129
- const sourceStat = await lstat(sourcePath).catch(() => null);
312
+ const sourceStat = await lstat2(sourcePath).catch(() => null);
130
313
  if (!sourceStat?.isFile() || sourceStat.isSymbolicLink()) {
131
314
  throw localTilesetError(
132
315
  "TF_LOCAL_TILESET_NOT_FOUND",
@@ -197,7 +380,7 @@ async function prepareTileflowLocalTilesets(project, styles, options) {
197
380
  }
198
381
  async function createLocalTilesetSnapshot(sourcePath, generationDirectory, index) {
199
382
  const sourceHandle = await open(sourcePath, "r");
200
- const snapshotPath = resolve(generationDirectory, `${index}.pmtiles`);
383
+ const snapshotPath = resolve2(generationDirectory, `${index}.pmtiles`);
201
384
  try {
202
385
  const before = sourceIdentity(await sourceHandle.stat({ bigint: true }));
203
386
  if (before.size < minimumLocalPmtilesBytes || before.size > maximumLocalPmtilesBytes) {
@@ -325,32 +508,32 @@ async function collectAbandonedSnapshotGenerations(store) {
325
508
  if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
326
509
  const match = /^snapshot-(\d+)-[a-f0-9]{32}$/u.exec(entry.name);
327
510
  if (!match?.[1] || processIsAlive(Number(match[1]))) continue;
328
- const path = resolve(store, entry.name);
329
- if (isPathWithin(store, path)) await rm(path, { force: true, recursive: true });
511
+ const path = resolve2(store, entry.name);
512
+ if (isPathWithin(store, path)) await rm2(path, { force: true, recursive: true });
330
513
  }
331
514
  }
332
515
  async function assertSafeSnapshotStore(projectRoot, store) {
333
516
  const segments = [".tileflow", "cache", "pmtiles-snapshots", "v1"];
334
517
  let current = projectRoot;
335
518
  for (const segment of segments) {
336
- current = resolve(current, segment);
519
+ current = resolve2(current, segment);
337
520
  try {
338
- const value = await lstat(current);
521
+ const value = await lstat2(current);
339
522
  if (!value.isDirectory() || value.isSymbolicLink()) {
340
523
  throw new Error(`Local PMTiles snapshot path is unsafe: ${current}`);
341
524
  }
342
525
  } catch (error) {
343
526
  if (!hasErrorCode(error, "ENOENT")) throw error;
344
- await mkdir(current).catch((mkdirError) => {
527
+ await mkdir2(current).catch((mkdirError) => {
345
528
  if (!hasErrorCode(mkdirError, "EEXIST")) throw mkdirError;
346
529
  });
347
- const created = await lstat(current);
530
+ const created = await lstat2(current);
348
531
  if (!created.isDirectory() || created.isSymbolicLink()) {
349
532
  throw new Error(`Local PMTiles snapshot path is unsafe: ${current}`);
350
533
  }
351
534
  }
352
535
  }
353
- if (resolve(current) !== resolve(store)) {
536
+ if (resolve2(current) !== resolve2(store)) {
354
537
  throw new Error("Local PMTiles snapshot path escaped its project root.");
355
538
  }
356
539
  }
@@ -384,7 +567,7 @@ function hasErrorCode(error, code) {
384
567
  }
385
568
 
386
569
  // src/native-artifacts.ts
387
- import { createHash } from "crypto";
570
+ import { createHash as createHash2 } from "crypto";
388
571
  import {
389
572
  convertFilter,
390
573
  validateStyleMin
@@ -982,8 +1165,8 @@ function lowerTileflowNativeCompiledStyles(input) {
982
1165
  transformations.push({
983
1166
  map,
984
1167
  theme,
985
- inputStyleSha256: createHash("sha256").update(serializeCanonicalJson(original)).digest("hex"),
986
- loweredStyleSha256: createHash("sha256").update(serializeCanonicalJson(lowered.style)).digest("hex"),
1168
+ inputStyleSha256: createHash2("sha256").update(serializeCanonicalJson(original)).digest("hex"),
1169
+ loweredStyleSha256: createHash2("sha256").update(serializeCanonicalJson(lowered.style)).digest("hex"),
987
1170
  inputLayers: lowered.inputLayers,
988
1171
  outputLayers: lowered.outputLayers,
989
1172
  projection: lowered.projection,
@@ -1097,7 +1280,7 @@ function isNativeFontAsset(asset) {
1097
1280
  if (!/\.(?:ttf|otf)$/u.test(asset.fileName) || !["00010000", "4f54544f"].includes(signature))
1098
1281
  return false;
1099
1282
  const expectedHash = /-([a-f0-9]{64})\.(?:ttf|otf)$/u.exec(asset.fileName)?.[1];
1100
- return expectedHash === createHash("sha256").update(bytes).digest("hex");
1283
+ return expectedHash === createHash2("sha256").update(bytes).digest("hex");
1101
1284
  }
1102
1285
  function assertTileflowNativeGeneratedStyle(style, map, theme) {
1103
1286
  const issues = validateTileflowNativePreparedStyle(style, { documentUrl: documentUrl(map, theme) });
@@ -1171,7 +1354,7 @@ function validNativeSprites(map, assets) {
1171
1354
  // src/session.ts
1172
1355
  import { watch } from "chokidar";
1173
1356
  import { realpathSync, statSync } from "fs";
1174
- import { basename as basename2, dirname, extname, isAbsolute, relative, resolve as resolve2, sep, win32 } from "path";
1357
+ import { basename as basename2, dirname, extname, isAbsolute, relative, resolve as resolve3, sep, win32 } from "path";
1175
1358
  import { tileflowIconMetadataFileName } from "@tileflow/core";
1176
1359
  import { TileflowNativeCompatibilityError as TileflowNativeCompatibilityError2 } from "@tileflow/core/native-profile";
1177
1360
  var tileflowArtifactSessionSchemaVersion = 1;
@@ -1224,13 +1407,13 @@ var TileflowArtifactSessionImpl = class {
1224
1407
  #watchedArtifactDirectories = /* @__PURE__ */ new Map();
1225
1408
  #watchedArtifactPaths = /* @__PURE__ */ new Set();
1226
1409
  constructor(options, buildArtifacts, discoverWatchPaths) {
1227
- this.#cwd = resolve2(options.cwd ?? process.cwd());
1228
- const configPath = resolve2(this.#cwd, options.config ?? "tileflow.config.ts");
1229
- this.#configPath = resolve2(canonicalWatchPath(dirname(configPath)), basename2(configPath));
1410
+ this.#cwd = resolve3(options.cwd ?? process.cwd());
1411
+ const configPath = resolve3(this.#cwd, options.config ?? "tileflow.config.ts");
1412
+ this.#configPath = resolve3(canonicalWatchPath(dirname(configPath)), basename2(configPath));
1230
1413
  this.#debounceMs = Math.max(0, Math.min(options.debounceMs ?? 75, 1e3));
1231
1414
  this.#watchEnabled = options.watch ?? false;
1232
1415
  this.#ignoredPaths = (options.ignoredPaths ?? []).map(
1233
- (path) => canonicalWatchPath(resolve2(this.#cwd, path))
1416
+ (path) => canonicalWatchPath(resolve3(this.#cwd, path))
1234
1417
  );
1235
1418
  this.#buildArtifacts = buildArtifacts;
1236
1419
  this.#discoverWatchPaths = discoverWatchPaths;
@@ -1463,7 +1646,7 @@ var TileflowArtifactSessionImpl = class {
1463
1646
  }
1464
1647
  #updateArtifactWatchPaths(paths) {
1465
1648
  if (!this.#watcher) return;
1466
- const next = new Set(paths.map((path) => canonicalWatchPath(resolve2(this.#cwd, path))));
1649
+ const next = new Set(paths.map((path) => canonicalWatchPath(resolve3(this.#cwd, path))));
1467
1650
  const previous = this.#watchedArtifactPaths;
1468
1651
  this.#watchedArtifactPaths = next;
1469
1652
  const previousDirectories = this.#watchedArtifactDirectories;
@@ -1528,7 +1711,7 @@ async function disposeArtifacts(artifacts) {
1528
1711
  await dispose?.();
1529
1712
  }
1530
1713
  function isSameOrInside(root, candidate) {
1531
- const path = relative(resolve2(root), resolve2(candidate));
1714
+ const path = relative(resolve3(root), resolve3(candidate));
1532
1715
  return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
1533
1716
  }
1534
1717
  function directoryModificationTime(path) {
@@ -1546,15 +1729,15 @@ function resolveRawWatchPath(path, details) {
1546
1729
  const watchedPath = details.watchedPath;
1547
1730
  if (typeof watchedPath !== "string" || !isAbsolute(watchedPath)) return void 0;
1548
1731
  return canonicalWatchPath(
1549
- basename2(watchedPath) === path ? watchedPath : resolve2(watchedPath, path)
1732
+ basename2(watchedPath) === path ? watchedPath : resolve3(watchedPath, path)
1550
1733
  );
1551
1734
  }
1552
1735
  function canonicalWatchPath(path) {
1553
- const resolved = resolve2(path);
1736
+ const resolved = resolve3(path);
1554
1737
  let ancestor = resolved;
1555
1738
  while (true) {
1556
1739
  try {
1557
- return resolve2(realpathSync.native(ancestor), relative(ancestor, resolved));
1740
+ return resolve3(realpathSync.native(ancestor), relative(ancestor, resolved));
1558
1741
  } catch {
1559
1742
  const parent = dirname(ancestor);
1560
1743
  if (parent === ancestor) return resolved;
@@ -1661,15 +1844,15 @@ function removeSemanticResolutionPath(message, path) {
1661
1844
  return message.replace(` at ${path}:`, ":");
1662
1845
  }
1663
1846
  function localPathAliases(path) {
1664
- return [.../* @__PURE__ */ new Set([resolve2(path), canonicalPath(path)])].sort(
1847
+ return [.../* @__PURE__ */ new Set([resolve3(path), canonicalPath(path)])].sort(
1665
1848
  (left, right) => right.length - left.length
1666
1849
  );
1667
1850
  }
1668
1851
  function canonicalPath(path) {
1669
1852
  try {
1670
- return realpathSync.native(resolve2(path));
1853
+ return realpathSync.native(resolve3(path));
1671
1854
  } catch {
1672
- return resolve2(path);
1855
+ return resolve3(path);
1673
1856
  }
1674
1857
  }
1675
1858
  function normalizeDiagnostics(diagnostics) {
@@ -1705,7 +1888,7 @@ function boundDiagnosticText(value, limit) {
1705
1888
  }
1706
1889
 
1707
1890
  // src/hosted-native-artifacts.ts
1708
- import { createHash as createHash2 } from "crypto";
1891
+ import { createHash as createHash3 } from "crypto";
1709
1892
  import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
1710
1893
  import {
1711
1894
  inferTileflowDataRequirements,
@@ -1835,7 +2018,7 @@ function finalizeNativeHostedProvenance(input, lowered, changes) {
1835
2018
  }
1836
2019
 
1837
2020
  // src/hosted-native-artifacts.ts
1838
- var digest = (value) => createHash2("sha256").update(serializeCanonicalJson3(value)).digest("hex");
2021
+ var digest2 = (value) => createHash3("sha256").update(serializeCanonicalJson3(value)).digest("hex");
1839
2022
  var invalidSource = () => new TileflowNativeCompatibilityError4([
1840
2023
  createTileflowNativeDiagnostic3("NATIVE_UNSUPPORTED_SOURCE", "/sources")
1841
2024
  ]);
@@ -1881,15 +2064,15 @@ async function prepareTileflowHostedNativeDeployment(input) {
1881
2064
  if (!theme) throw invalidSource();
1882
2065
  mapBuild.themes[name] = {
1883
2066
  ...theme,
1884
- styleSha256: digest(style),
2067
+ styleSha256: digest2(style),
1885
2068
  dataRequirements: inferTileflowDataRequirements(style),
1886
2069
  sourceRequirements: inferTileflowSourceRequirements(style)
1887
2070
  };
1888
2071
  }
1889
2072
  const transformations = lowered.transformations.map((item) => ({
1890
2073
  ...item,
1891
- inputStyleSha256: digest(webStyles[item.theme]),
1892
- loweredStyleSha256: digest({
2074
+ inputStyleSha256: digest2(webStyles[item.theme]),
2075
+ loweredStyleSha256: digest2({
1893
2076
  ...lowered.styles[input.mapId][item.theme],
1894
2077
  sources: webStyles[item.theme].sources
1895
2078
  })
@@ -1904,7 +2087,7 @@ async function prepareTileflowHostedNativeDeployment(input) {
1904
2087
  web: { buildManifest: input.buildManifest, styles: webStyles },
1905
2088
  native: {
1906
2089
  buildManifest: nativeBuild,
1907
- buildRecord: createTileflowNativeBuildRecord(digest(nativeBuild), transformations),
2090
+ buildRecord: createTileflowNativeBuildRecord(digest2(nativeBuild), transformations),
1908
2091
  styles: projected
1909
2092
  }
1910
2093
  }
@@ -2032,7 +2215,10 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
2032
2215
  throw error;
2033
2216
  });
2034
2217
  const assets = [...prepared.assets, ...preparedFonts.assets];
2035
- const styles = renderer === "native" ? prepareTileflowNativeStyles(preparedFonts.styles, assets) : preparedFonts.styles;
2218
+ const lineBackgroundGlyphs = renderer === "native" || options.target === "production" ? { providers: {}, styles: preparedFonts.styles } : prepareTileflowLineBackgroundGlyphs(preparedFonts.styles, {
2219
+ assetBaseUrl: resolveRendererAssetBaseUrl(options)
2220
+ });
2221
+ const styles = renderer === "native" ? prepareTileflowNativeStyles(preparedFonts.styles, assets) : lineBackgroundGlyphs.styles;
2036
2222
  const provenance = await createTileflowBuildProvenance(prepared.cwd);
2037
2223
  const buildManifest = await createTileflowMapBuildManifest(
2038
2224
  Object.fromEntries(
@@ -2092,6 +2278,7 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
2092
2278
  manifest: stableManifest,
2093
2279
  project: prepared.project,
2094
2280
  ...localTilesets.files.length > 0 ? { localTilesets: localTilesets.files } : {},
2281
+ ...Object.keys(lineBackgroundGlyphs.providers).length > 0 ? { lineBackgroundGlyphs: lineBackgroundGlyphs.providers } : {},
2095
2282
  ...inspected ? { styleInspections: inspected.inspections } : {},
2096
2283
  styles,
2097
2284
  watchPaths: uniqueStrings([...inputs.files, ...inputs.directories])
@@ -2292,12 +2479,12 @@ function getMapBuildAssets(mapName, assets, fontAssets) {
2292
2479
  );
2293
2480
  }
2294
2481
  function hashBytes(source) {
2295
- return createHash3("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
2482
+ return createHash4("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
2296
2483
  }
2297
2484
  async function createTileflowBuildProvenance(cwd) {
2298
- const projectDirectory = resolve3(cwd);
2485
+ const projectDirectory = resolve4(cwd);
2299
2486
  const projectRequire = createRequire(
2300
- resolve3(projectDirectory, "__tileflow_build_provenance__.cjs")
2487
+ resolve4(projectDirectory, "__tileflow_build_provenance__.cjs")
2301
2488
  );
2302
2489
  const toolRequire = createRequire(import.meta.url);
2303
2490
  const packages = {};
@@ -2331,7 +2518,7 @@ async function readPackageVersion(expectedName, path) {
2331
2518
  if (!path) throw new Error(`Unable to resolve build package provenance for ${expectedName}.`);
2332
2519
  let parsed;
2333
2520
  try {
2334
- parsed = JSON.parse(await readFile(path, "utf8"));
2521
+ parsed = JSON.parse(await readFile2(path, "utf8"));
2335
2522
  } catch {
2336
2523
  throw new Error(`Unable to read build package provenance for ${expectedName}.`);
2337
2524
  }
@@ -2354,7 +2541,7 @@ async function findNearestLockfile(cwd) {
2354
2541
  try {
2355
2542
  return {
2356
2543
  format: candidate.format,
2357
- source: await readFile(resolve3(directory, candidate.fileName))
2544
+ source: await readFile2(resolve4(directory, candidate.fileName))
2358
2545
  };
2359
2546
  } catch (error) {
2360
2547
  if (!hasErrorCode2(error, "ENOENT")) throw error;
@@ -2430,7 +2617,7 @@ async function writeTileflowArtifactPlan(artifacts, options) {
2430
2617
  const outDir = artifacts.nativeBuild ? `${options.outDir.replace(/[\\/]$/u, "")}/native` : options.outDir;
2431
2618
  const output = await prepareTileflowOutputDirectory(cwd, outDir);
2432
2619
  const files = getTileflowArtifactFiles(artifacts);
2433
- await assertTileflowSelfHostedManifestTarget(resolve3(output.outDir, "manifest.json"), options);
2620
+ await assertTileflowSelfHostedManifestTarget(resolve4(output.outDir, "manifest.json"), options);
2434
2621
  await commitTileflowArtifactFiles(output, files);
2435
2622
  }
2436
2623
  function assertNoLocalTilesetsForProduction(artifacts) {
@@ -2443,7 +2630,7 @@ async function assertTileflowSelfHostedManifestTarget(manifestPath, options = {}
2443
2630
  if (options.overwriteHostedManifest) return;
2444
2631
  let source;
2445
2632
  try {
2446
- source = await readFile(resolve3(manifestPath), "utf8");
2633
+ source = await readFile2(resolve4(manifestPath), "utf8");
2447
2634
  } catch (error) {
2448
2635
  if (hasErrorCode2(error, "ENOENT")) return;
2449
2636
  throw error;
@@ -2451,7 +2638,7 @@ async function assertTileflowSelfHostedManifestTarget(manifestPath, options = {}
2451
2638
  try {
2452
2639
  const manifest = parseTileflowRuntimeManifest(JSON.parse(source));
2453
2640
  if (isHostedRuntimeManifest(manifest)) {
2454
- throw new TileflowHostedManifestOverwriteError(resolve3(manifestPath));
2641
+ throw new TileflowHostedManifestOverwriteError(resolve4(manifestPath));
2455
2642
  }
2456
2643
  } catch (error) {
2457
2644
  if (error instanceof TileflowHostedManifestOverwriteError) throw error;
@@ -2499,15 +2686,15 @@ async function commitTileflowArtifactFiles(output, files) {
2499
2686
  ...staleNames,
2500
2687
  tileflowArtifactInventoryFileName
2501
2688
  ]);
2502
- const stageDir = await mkdtemp(resolve3(outDir, ".tileflow-stage-"));
2503
- const newDir = resolve3(stageDir, "new");
2504
- const backupDir = resolve3(stageDir, "backup");
2689
+ const stageDir = await mkdtemp(resolve4(outDir, ".tileflow-stage-"));
2690
+ const newDir = resolve4(stageDir, "new");
2691
+ const backupDir = resolve4(stageDir, "backup");
2505
2692
  const backedUp = /* @__PURE__ */ new Set();
2506
2693
  const installed = /* @__PURE__ */ new Set();
2507
2694
  try {
2508
2695
  for (const file of stagedFiles) {
2509
2696
  const stagePath = managedPath(newDir, file.fileName);
2510
- await mkdir2(dirname2(stagePath), { recursive: true });
2697
+ await mkdir3(dirname2(stagePath), { recursive: true });
2511
2698
  await writeFileDurably(stagePath, file.source);
2512
2699
  }
2513
2700
  for (const name of affectedNames) {
@@ -2515,7 +2702,7 @@ async function commitTileflowArtifactFiles(output, files) {
2515
2702
  const targetPath = managedPath(outDir, name);
2516
2703
  if (!await isFile(targetPath)) continue;
2517
2704
  const backupPath = managedPath(backupDir, name);
2518
- await mkdir2(dirname2(backupPath), { recursive: true });
2705
+ await mkdir3(dirname2(backupPath), { recursive: true });
2519
2706
  await copyFile2(targetPath, backupPath);
2520
2707
  backedUp.add(name);
2521
2708
  }
@@ -2529,8 +2716,8 @@ async function commitTileflowArtifactFiles(output, files) {
2529
2716
  )) {
2530
2717
  const sourcePath = managedPath(newDir, name);
2531
2718
  const targetPath = managedPath(outDir, name);
2532
- await mkdir2(dirname2(targetPath), { recursive: true });
2533
- await rename(sourcePath, targetPath);
2719
+ await mkdir3(dirname2(targetPath), { recursive: true });
2720
+ await rename2(sourcePath, targetPath);
2534
2721
  installed.add(name);
2535
2722
  }
2536
2723
  for (const name of staleNames) {
@@ -2538,13 +2725,13 @@ async function commitTileflowArtifactFiles(output, files) {
2538
2725
  }
2539
2726
  const stagedInventoryPath = managedPath(newDir, tileflowArtifactInventoryFileName);
2540
2727
  const inventoryPath = managedPath(outDir, tileflowArtifactInventoryFileName);
2541
- await rename(stagedInventoryPath, inventoryPath);
2728
+ await rename2(stagedInventoryPath, inventoryPath);
2542
2729
  installed.add(tileflowArtifactInventoryFileName);
2543
2730
  } catch (error) {
2544
2731
  await rollbackManagedFiles(outDir, backupDir, affectedNames, backedUp, installed);
2545
2732
  throw error;
2546
2733
  } finally {
2547
- await rm2(stageDir, { force: true, recursive: true });
2734
+ await rm3(stageDir, { force: true, recursive: true });
2548
2735
  }
2549
2736
  }
2550
2737
  function getArtifactGeneration(files) {
@@ -2576,7 +2763,7 @@ async function rollbackManagedFiles(outDir, backupDir, affectedNames, backedUp,
2576
2763
  await removeManagedFile(targetPath);
2577
2764
  }
2578
2765
  if (backedUp.has(name)) {
2579
- await mkdir2(dirname2(targetPath), { recursive: true });
2766
+ await mkdir3(dirname2(targetPath), { recursive: true });
2580
2767
  await copyFile2(managedPath(backupDir, name), targetPath);
2581
2768
  }
2582
2769
  } catch (error) {
@@ -2591,7 +2778,7 @@ async function readTileflowArtifactInventory(outDir) {
2591
2778
  const inventoryPath = managedPath(outDir, tileflowArtifactInventoryFileName);
2592
2779
  let source;
2593
2780
  try {
2594
- source = await readFile(inventoryPath, "utf8");
2781
+ source = await readFile2(inventoryPath, "utf8");
2595
2782
  } catch (error) {
2596
2783
  if (hasErrorCode2(error, "ENOENT")) return void 0;
2597
2784
  throw error;
@@ -2625,7 +2812,7 @@ async function readTileflowArtifactInventory(outDir) {
2625
2812
  }
2626
2813
  async function assertManagedTargetIsSafe(outDir, fileName) {
2627
2814
  assertSafeManagedFileName(fileName);
2628
- const rootStats = await lstat2(outDir);
2815
+ const rootStats = await lstat3(outDir);
2629
2816
  if (rootStats.isSymbolicLink()) {
2630
2817
  throw new Error(`Tileflow refuses to write through a symbolic link: ${outDir}`);
2631
2818
  }
@@ -2635,9 +2822,9 @@ async function assertManagedTargetIsSafe(outDir, fileName) {
2635
2822
  const segments = fileName.split("/");
2636
2823
  let current = outDir;
2637
2824
  for (let index = 0; index < segments.length; index += 1) {
2638
- current = resolve3(current, segments[index]);
2825
+ current = resolve4(current, segments[index]);
2639
2826
  try {
2640
- const stats = await lstat2(current);
2827
+ const stats = await lstat3(current);
2641
2828
  if (stats.isSymbolicLink()) {
2642
2829
  throw new Error(`Tileflow refuses to write through a symbolic link: ${fileName}`);
2643
2830
  }
@@ -2654,12 +2841,12 @@ async function assertManagedTargetIsSafe(outDir, fileName) {
2654
2841
  }
2655
2842
  }
2656
2843
  async function prepareTileflowOutputDirectory(cwd, requestedOutDir) {
2657
- const logicalRoot = resolve3(cwd);
2658
- const logicalOutDir = resolve3(logicalRoot, requestedOutDir);
2844
+ const logicalRoot = resolve4(cwd);
2845
+ const logicalOutDir = resolve4(logicalRoot, requestedOutDir);
2659
2846
  if (!isPathWithin(logicalRoot, logicalOutDir)) {
2660
2847
  throw new Error(`Tileflow artifact output escapes its working directory: ${logicalOutDir}`);
2661
2848
  }
2662
- const rootStats = await lstat2(logicalRoot);
2849
+ const rootStats = await lstat3(logicalRoot);
2663
2850
  if (!rootStats.isDirectory()) {
2664
2851
  throw new Error(`Tileflow artifact working directory is not a directory: ${logicalRoot}`);
2665
2852
  }
@@ -2668,14 +2855,14 @@ async function prepareTileflowOutputDirectory(cwd, requestedOutDir) {
2668
2855
  const segments = relativeOutDir ? relativeOutDir.split(sep2) : [];
2669
2856
  let current = rootDir;
2670
2857
  for (const segment of segments) {
2671
- current = resolve3(current, segment);
2858
+ current = resolve4(current, segment);
2672
2859
  let stats;
2673
2860
  try {
2674
- stats = await lstat2(current);
2861
+ stats = await lstat3(current);
2675
2862
  } catch (error) {
2676
2863
  if (!hasErrorCode2(error, "ENOENT")) throw error;
2677
- await mkdir2(current);
2678
- stats = await lstat2(current);
2864
+ await mkdir3(current);
2865
+ stats = await lstat3(current);
2679
2866
  }
2680
2867
  if (stats.isSymbolicLink()) {
2681
2868
  throw new Error(`Tileflow refuses to write through a symbolic link: ${current}`);
@@ -2693,7 +2880,7 @@ async function assertTileflowOutputDirectoryIsSafe(output) {
2693
2880
  if (!isPathWithin(output.rootDir, output.outDir)) {
2694
2881
  throw new Error(`Tileflow artifact output escapes its working directory: ${output.outDir}`);
2695
2882
  }
2696
- const stats = await lstat2(output.outDir);
2883
+ const stats = await lstat3(output.outDir);
2697
2884
  if (stats.isSymbolicLink()) {
2698
2885
  throw new Error(`Tileflow refuses to write through a symbolic link: ${output.outDir}`);
2699
2886
  }
@@ -2722,7 +2909,7 @@ function assertTileflowArtifactFileName(fileName) {
2722
2909
  }
2723
2910
  function managedPath(root, fileName) {
2724
2911
  assertSafeManagedFileName(fileName);
2725
- const path = resolve3(root, ...fileName.split("/"));
2912
+ const path = resolve4(root, ...fileName.split("/"));
2726
2913
  if (!isPathWithin(root, path)) {
2727
2914
  throw new Error(`Unsafe Tileflow artifact file name: ${fileName}`);
2728
2915
  }
@@ -2739,7 +2926,7 @@ async function writeFileDurably(path, source) {
2739
2926
  }
2740
2927
  async function isFile(path) {
2741
2928
  try {
2742
- return (await lstat2(path)).isFile();
2929
+ return (await lstat3(path)).isFile();
2743
2930
  } catch (error) {
2744
2931
  if (hasErrorCode2(error, "ENOENT")) return false;
2745
2932
  throw error;
@@ -2747,7 +2934,7 @@ async function isFile(path) {
2747
2934
  }
2748
2935
  async function removeManagedFile(path) {
2749
2936
  try {
2750
- const stats = await lstat2(path);
2937
+ const stats = await lstat3(path);
2751
2938
  if (!stats.isFile()) throw new Error(`Refusing to remove non-file artifact target: ${path}`);
2752
2939
  await unlink(path);
2753
2940
  } catch (error) {
@@ -2755,7 +2942,7 @@ async function removeManagedFile(path) {
2755
2942
  }
2756
2943
  }
2757
2944
  function hashArtifactGeneration(files) {
2758
- const hash = createHash3("sha256");
2945
+ const hash = createHash4("sha256");
2759
2946
  for (const file of files) {
2760
2947
  const source = typeof file.source === "string" ? Buffer.from(file.source, "utf8") : Buffer.from(file.source);
2761
2948
  hash.update(`${file.fileName}\0${file.contentType}\0${source.byteLength}\0`, "utf8");
@@ -2787,12 +2974,12 @@ function hasErrorCode2(error, code) {
2787
2974
  return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
2788
2975
  }
2789
2976
  function canonicalInputPath(path) {
2790
- const resolved = resolve3(path);
2977
+ const resolved = resolve4(path);
2791
2978
  try {
2792
2979
  return realpathSync2.native(resolved);
2793
2980
  } catch {
2794
2981
  try {
2795
- return resolve3(realpathSync2.native(dirname2(resolved)), resolved.split(/[\\/]/).at(-1));
2982
+ return resolve4(realpathSync2.native(dirname2(resolved)), resolved.split(/[\\/]/).at(-1));
2796
2983
  } catch {
2797
2984
  return resolved;
2798
2985
  }
@@ -2800,6 +2987,10 @@ function canonicalInputPath(path) {
2800
2987
  }
2801
2988
 
2802
2989
  export {
2990
+ tileflowLineBackgroundGlyphRoute,
2991
+ usesTileflowLineBackgroundGlyphs,
2992
+ parseTileflowLineBackgroundGlyphPath,
2993
+ createTileflowLineBackgroundGlyphResponder,
2803
2994
  prepareTileflowLocalTilesets,
2804
2995
  tileflowArtifactSessionSchemaVersion,
2805
2996
  createTileflowArtifactDiagnostics,
@@ -168,7 +168,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
168
168
  body { font-family: ui-sans-serif, system-ui, sans-serif; }
169
169
  #map {
170
170
  --tileflow-globe-radius: 220px;
171
- background-color: #2F5070;
171
+ background-color: var(--tileflow-space, #2F5070);
172
172
  background-image:
173
173
  radial-gradient(circle at 18% 24%, rgba(225, 240, 250, 0.34) 0 1px, transparent 1.4px),
174
174
  radial-gradient(circle at 72% 62%, rgba(225, 240, 250, 0.22) 0 1px, transparent 1.3px),
@@ -191,12 +191,12 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
191
191
  top: 50%;
192
192
  width: calc(var(--tileflow-globe-radius) * 2);
193
193
  height: calc(var(--tileflow-globe-radius) * 2);
194
- border: 1px solid rgba(248, 252, 255, 0.88);
194
+ border: 1px solid var(--tileflow-rim-line, rgba(248, 252, 255, 0.88));
195
195
  border-radius: 50%;
196
196
  box-shadow:
197
- 0 0 9px 3px rgba(248, 252, 255, 0.9),
198
- 0 0 28px 10px rgba(176, 220, 246, 0.52),
199
- 0 0 58px 20px rgba(132, 195, 232, 0.2);
197
+ 0 0 9px 3px var(--tileflow-rim-core, rgba(248, 252, 255, 0.9)),
198
+ 0 0 28px 10px var(--tileflow-rim-glow, rgba(176, 220, 246, 0.52)),
199
+ 0 0 58px 20px var(--tileflow-rim-haze, rgba(132, 195, 232, 0.2));
200
200
  pointer-events: none;
201
201
  transform: translate(-50%, -50%);
202
202
  }
@@ -233,7 +233,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
233
233
  white-space: pre-wrap;
234
234
  }
235
235
  .maplibregl-ctrl-group .tileflow-3d-toggle,
236
- .maplibregl-ctrl-group .tileflow-tree-toggle {
236
+ .maplibregl-ctrl-group .tileflow-tree-toggle,
237
+ .maplibregl-ctrl-group .tileflow-theme-toggle {
237
238
  width: auto;
238
239
  min-width: 58px;
239
240
  padding: 0 9px;
@@ -247,7 +248,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
247
248
  color: #174EA6;
248
249
  }
249
250
  .maplibregl-ctrl-group .tileflow-3d-toggle:hover,
250
- .maplibregl-ctrl-group .tileflow-tree-toggle:hover {
251
+ .maplibregl-ctrl-group .tileflow-tree-toggle:hover,
252
+ .maplibregl-ctrl-group .tileflow-theme-toggle:hover {
251
253
  background-color: #F1F3F4;
252
254
  }
253
255
  </style>
@@ -275,6 +277,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
275
277
  const previewLabel = ${JSON.stringify(preview?.label)};
276
278
  const styleUrl = ${JSON.stringify(styleUrl)};
277
279
  const previewMapOptions = ${JSON.stringify(mapOptions)};
280
+ const previewTheme = ${serializeInlineJson(preview?.themeName ?? null)};
281
+ const previewThemes = ${serializeInlineJson(preview?.themeNames ?? [])};
278
282
  const isSemanticPreview = ${JSON.stringify(isSemanticPreview)};
279
283
  const previewFontFaces = ${serializeInlineJson(fontFaces)};
280
284
  const treeSearchParameters = new URL(location.href).searchParams;
@@ -985,6 +989,30 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
985
989
  );
986
990
  container.classList.add("tileflow-globe");
987
991
  container.style.setProperty("--tileflow-globe-radius", radius.toFixed(2) + "px");
992
+ applyAtmosphereBackdrop(map, container);
993
+ }
994
+
995
+ // Uses the map's declared atmosphere for the preview's space and rim, so a themed globe shows
996
+ // its own colours; maps without one keep the default backdrop.
997
+ function applyAtmosphereBackdrop(map, container) {
998
+ const atmosphere = map.getStyle?.()?.metadata?.["tileflow:atmosphere"];
999
+ const rgba = (hex, alpha) => {
1000
+ const match = /^#([0-9a-f]{6})$/i.exec(String(hex ?? ""));
1001
+ if (!match) return undefined;
1002
+ const value = Number.parseInt(match[1], 16);
1003
+ return "rgba(" + (value >> 16) + ", " + ((value >> 8) & 255) + ", " + (value & 255) + ", " + alpha + ")";
1004
+ };
1005
+ const properties = {
1006
+ "--tileflow-space": rgba(atmosphere?.spaceColor, 1),
1007
+ "--tileflow-rim-line": rgba(atmosphere?.horizonColor, 0.88),
1008
+ "--tileflow-rim-core": rgba(atmosphere?.horizonColor, 0.9),
1009
+ "--tileflow-rim-glow": rgba(atmosphere?.horizonColor, 0.52),
1010
+ "--tileflow-rim-haze": rgba(atmosphere?.skyColor, 0.35),
1011
+ };
1012
+ for (const [name, value] of Object.entries(properties)) {
1013
+ if (value) container.style.setProperty(name, value);
1014
+ else container.style.removeProperty(name);
1015
+ }
988
1016
  }
989
1017
 
990
1018
  function createBuildingWireframeLayer(map, styleLayer) {
@@ -3777,6 +3805,38 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
3777
3805
  }
3778
3806
  }
3779
3807
 
3808
+ // Switches a map preview between its themes. The preview reloads with the next theme and keeps
3809
+ // the camera and toggles already written to the URL; scene previews own their theme.
3810
+ class ThemeControl {
3811
+ onAdd(map) {
3812
+ this.map = map;
3813
+ this.container = document.createElement("div");
3814
+ this.container.className = "maplibregl-ctrl maplibregl-ctrl-group";
3815
+ this.button = document.createElement("button");
3816
+ this.button.type = "button";
3817
+ this.button.className = "tileflow-theme-toggle";
3818
+ const next = previewThemes[(previewThemes.indexOf(previewTheme) + 1) % previewThemes.length];
3819
+ this.button.textContent = String(previewTheme).toUpperCase();
3820
+ this.button.title = "Switch to the " + next + " theme";
3821
+ this.button.setAttribute("aria-label", "Switch to the " + next + " theme");
3822
+ this.handleClick = () => {
3823
+ writeCameraToUrl(map);
3824
+ const url = new URL(location.href);
3825
+ url.searchParams.set("theme", next);
3826
+ location.assign(url.href);
3827
+ };
3828
+ this.button.addEventListener("click", this.handleClick);
3829
+ this.container.appendChild(this.button);
3830
+ return this.container;
3831
+ }
3832
+
3833
+ onRemove() {
3834
+ this.button?.removeEventListener("click", this.handleClick);
3835
+ this.container?.remove();
3836
+ this.map = undefined;
3837
+ }
3838
+ }
3839
+
3780
3840
  const previewLayerGroupIds = [
3781
3841
  "labels", "pois", "roads", "transit", "buildings", "landuse", "water"
3782
3842
  ];
@@ -3913,6 +3973,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
3913
3973
  map.addControl(threeDimensionalControl, "top-right");
3914
3974
  map.addControl(treeControl, "top-right");
3915
3975
  }
3976
+ if (previewThemes.length > 1) map.addControl(new ThemeControl(), "top-right");
3916
3977
  let ensuringThreeDimensionalLayers;
3917
3978
  const treeRuntimeMinimumZoom = 16;
3918
3979
  const ensureThreeDimensionalLayers = () => {
@@ -4200,7 +4261,8 @@ function resolveTileflowPreview(project, selection = {}) {
4200
4261
  },
4201
4262
  label: `${mapName} / ${selectedTheme.name}`,
4202
4263
  mapName,
4203
- themeName: selectedTheme.name
4264
+ themeName: selectedTheme.name,
4265
+ themeNames: Object.keys(resolvedMap.themes)
4204
4266
  };
4205
4267
  }
4206
4268
  function resolvePreviewTheme(map, requested) {
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  createTileflowArtifactSession,
3
- tileflowArtifactSessionSchemaVersion
4
- } from "./chunk-QAMFAM3S.js";
3
+ createTileflowLineBackgroundGlyphResponder,
4
+ parseTileflowLineBackgroundGlyphPath,
5
+ tileflowArtifactSessionSchemaVersion,
6
+ tileflowLineBackgroundGlyphRoute
7
+ } from "./chunk-4HVIEMKM.js";
5
8
  import {
6
9
  normalizeTileflowBasePath
7
10
  } from "./chunk-UTRO4IBQ.js";
@@ -1709,6 +1712,7 @@ function createTileflowDevRequestHandler(options = {}) {
1709
1712
  const cwd = options.cwd ?? process.cwd();
1710
1713
  const apiBaseUrl = options.apiBaseUrl ?? defaultTileflowApiUrl;
1711
1714
  let ownedSessionPromise;
1715
+ const lineBackgroundGlyphs = createTileflowLineBackgroundGlyphResponder({ cwd });
1712
1716
  let closed = false;
1713
1717
  let lastOrigin;
1714
1718
  const loadOwnedSession = (origin) => {
@@ -1783,6 +1787,12 @@ function createTileflowDevRequestHandler(options = {}) {
1783
1787
  );
1784
1788
  }
1785
1789
  if (!artifacts) return unavailableArtifactsResponse(state);
1790
+ if (path.startsWith(`/${tileflowLineBackgroundGlyphRoute}/`)) {
1791
+ const glyphRequest = parseTileflowLineBackgroundGlyphPath(path);
1792
+ const upstream = glyphRequest ? artifacts.lineBackgroundGlyphs?.[glyphRequest.mapName] : void 0;
1793
+ if (!glyphRequest || !upstream) return jsonResponse2({ error: "Unknown glyph range." }, 404);
1794
+ return await lineBackgroundGlyphs(upstream, glyphRequest.fontStack, glyphRequest.range);
1795
+ }
1786
1796
  if (path.startsWith("/tilesets/")) {
1787
1797
  const fileName = path.replace(/^\/+/, "");
1788
1798
  const localTileset = artifacts.localTilesets?.find(
@@ -1985,7 +1995,7 @@ function getRequestPathname(url) {
1985
1995
  }
1986
1996
  }
1987
1997
  function isOwnedTileflowRequestPath(path) {
1988
- return path === "/" || path === "/build-manifest.json" || path === "/manifest.json" || path === "/__events" || path === "/__status" || getTileflowStyleInspectionSelection(path) !== void 0 || path.startsWith("/generations/") || path.startsWith("/icons/") || path.startsWith("/tilesets/") || getTileflowStyleSelection(path) !== void 0 || path.startsWith("/fonts/") || path.startsWith("/__runtime/");
1998
+ return path === "/" || path === "/build-manifest.json" || path === "/manifest.json" || path === "/__events" || path === "/__status" || getTileflowStyleInspectionSelection(path) !== void 0 || path.startsWith("/generations/") || path.startsWith("/icons/") || path.startsWith("/tilesets/") || getTileflowStyleSelection(path) !== void 0 || path.startsWith("/fonts/") || path.startsWith(`/${tileflowLineBackgroundGlyphRoute}/`) || path.startsWith("/__runtime/");
1989
1999
  }
1990
2000
  function tileflowErrorResponse(error) {
1991
2001
  if (error instanceof Error && "code" in error && error.code === "PREVIEW_SELECTION_INVALID") {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { CreateTileflowArtifactPlanOptions, TileflowArtifactAcquisition, TileflowArtifactDiagnostic, TileflowArtifactFile, TileflowArtifactInputGraph, TileflowArtifactPlan, TileflowArtifactSession, TileflowArtifactSessionOptions, TileflowArtifactSessionState, TileflowBuildArtifacts, TileflowBuildArtifactsOptions, TileflowBuildStyleInspections, TileflowHostedManifestOverwriteError, TileflowLocalTilesetFile, TileflowLocalTilesetProductionError, TileflowManifestOptions, WriteTileflowArtifactPlanOptions, WriteTileflowBuildArtifactsOptions, assertNoLocalTilesetsForProduction, assertTileflowSelfHostedManifestTarget, createTileflowArtifactDiagnostics, createTileflowArtifactPlan, createTileflowArtifactSession, createTileflowBuildArtifacts, createTileflowBuildProvenance, createTileflowManifest, createTileflowStyle, createTileflowStyles, disposeTileflowBuildArtifacts, getTileflowArtifactFiles, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowWatchPaths, isTileflowArtifactInputPath, joinTileflowPublicUrl, normalizeTileflowBasePath, prepareTileflowHostedNativeDeployment, prepareTileflowLocalTilesets, refreshTileflowArtifactSession, resolveTileflowArtifactPublicUrls, tileflowArtifactInventoryFileName, tileflowArtifactPlanSchemaVersion, tileflowArtifactSessionSchemaVersion, writeTileflowArtifactPlan, writeTileflowBuildArtifacts } from './artifacts.js';
1
+ export { CreateTileflowArtifactPlanOptions, TileflowArtifactAcquisition, TileflowArtifactDiagnostic, TileflowArtifactFile, TileflowArtifactInputGraph, TileflowArtifactPlan, TileflowArtifactSession, TileflowArtifactSessionOptions, TileflowArtifactSessionState, TileflowBuildArtifacts, TileflowBuildArtifactsOptions, TileflowBuildStyleInspections, TileflowHostedManifestOverwriteError, TileflowLineBackgroundGlyphProviders, TileflowLineBackgroundGlyphResponder, TileflowLocalTilesetFile, TileflowLocalTilesetProductionError, TileflowManifestOptions, WriteTileflowArtifactPlanOptions, WriteTileflowBuildArtifactsOptions, assertNoLocalTilesetsForProduction, assertTileflowSelfHostedManifestTarget, createTileflowArtifactDiagnostics, createTileflowArtifactPlan, createTileflowArtifactSession, createTileflowBuildArtifacts, createTileflowBuildProvenance, createTileflowLineBackgroundGlyphResponder, createTileflowManifest, createTileflowStyle, createTileflowStyles, disposeTileflowBuildArtifacts, getTileflowArtifactFiles, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowWatchPaths, isTileflowArtifactInputPath, joinTileflowPublicUrl, normalizeTileflowBasePath, parseTileflowLineBackgroundGlyphPath, prepareTileflowHostedNativeDeployment, prepareTileflowLocalTilesets, refreshTileflowArtifactSession, resolveTileflowArtifactPublicUrls, tileflowArtifactInventoryFileName, tileflowArtifactPlanSchemaVersion, tileflowArtifactSessionSchemaVersion, tileflowLineBackgroundGlyphRoute, usesTileflowLineBackgroundGlyphs, writeTileflowArtifactPlan, writeTileflowBuildArtifacts } from './artifacts.js';
2
2
  export { LoadTileflowConfigOptions, LoadedTileflowConfig, TileflowValidationError, assertValidTileflowConfig, defaultTileflowApiUrl, defaultTileflowConfigPath, defaultTileflowManifestPath, getFirstTileflowMapName, getTileflowMapNames, loadTileflowConfig, loadTileflowConfigWithInputs, loadValidTileflowConfig, loadValidTileflowConfigWithInputs } from './config.js';
3
3
  export { CompiledTileflowFontBundle, PrepareTileflowStyleFontsOptions, PreparedTileflowStyleFonts, TileflowFontCompilationError, TileflowFontCompilationIssue, TileflowFontCompilationTarget, bindTileflowStyleFontBundle, getTileflowFontWatchPaths, prepareTileflowStyleFonts, replaceTileflowStyleFontSources } from './fonts.js';
4
4
  export { CompileTileflowIconPackagesOptions, CompileTileflowIconPackagesResult, CompiledTileflowIconPackage, CompiledTileflowIconPackageFile, ComposeTileflowIconSourcesOptions, InspectTileflowIconCatalogsOptions, PreparedTileflowBuildCatalog, PreparedTileflowCatalog, ReadTileflowIconDirectoryResult, TileflowBuildAsset, TileflowComposedIconContributor, TileflowComposedIconSources, TileflowComposedIconWinner, TileflowIconCacheOptions, TileflowIconCatalog, TileflowIconCatalogAtlasRectangle, TileflowIconCatalogContributor, TileflowIconCatalogIcon, TileflowIconCatalogIconSource, TileflowIconCatalogInspection, TileflowIconCatalogMap, TileflowIconCatalogRenderedDensity, TileflowIconCatalogSourceFormat, TileflowIconCompilationError, TileflowIconCompilationIssue, TileflowIconCompilationTarget, TileflowIconDirectoryEntry, TileflowIconDirectorySourceFile, TileflowIconReplacement, TileflowIconResolutionOptions, TileflowMapIconPackageBinding, TileflowRenderedIcon, TileflowRenderedIconCell, TileflowSourceCatalog, VerifiedTileflowIconArtifact, compileTileflowIconPackages, composeTileflowIconSources, getTileflowIconCacheDirectory, getTileflowIconWatchPaths, iconFileExtensions, inspectTileflowIconCatalogs, loadTileflowIconSetArtifact, packTileflowRenderedIcons, prepareTileflowCatalogIcons, readTileflowIconDirectory, readTileflowIconsLockfile, readTileflowIconsLockfileText, renderTileflowIconAtlasPreview, storeTileflowIconSetArtifact, verifyTileflowIconArtifact, writeTileflowIconsLockfile } from './icons.js';
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  readBoundedTileflowResponse,
20
20
  renderTileflowPreviewHtml,
21
21
  resolveTileflowPreview
22
- } from "./chunk-6OJICHB3.js";
22
+ } from "./chunk-CJGM6HTT.js";
23
23
  import {
24
24
  createTileflowArtifactStatus,
25
25
  createTileflowComparisonRequestHandler,
@@ -31,7 +31,7 @@ import {
31
31
  isTileflowRequestUrl,
32
32
  tileflowComparisonSchemaVersion,
33
33
  writeTileflowNodeResponse
34
- } from "./chunk-UCBGTEQV.js";
34
+ } from "./chunk-YOTMY5XT.js";
35
35
  import {
36
36
  TileflowHostedManifestOverwriteError,
37
37
  TileflowLocalTilesetProductionError,
@@ -42,6 +42,7 @@ import {
42
42
  createTileflowArtifactSession,
43
43
  createTileflowBuildArtifacts,
44
44
  createTileflowBuildProvenance,
45
+ createTileflowLineBackgroundGlyphResponder,
45
46
  createTileflowManifest,
46
47
  createTileflowStyle,
47
48
  createTileflowStyles,
@@ -49,15 +50,18 @@ import {
49
50
  getTileflowArtifactFiles,
50
51
  getTileflowWatchPaths,
51
52
  isTileflowArtifactInputPath,
53
+ parseTileflowLineBackgroundGlyphPath,
52
54
  prepareTileflowHostedNativeDeployment,
53
55
  prepareTileflowLocalTilesets,
54
56
  refreshTileflowArtifactSession,
55
57
  tileflowArtifactInventoryFileName,
56
58
  tileflowArtifactPlanSchemaVersion,
57
59
  tileflowArtifactSessionSchemaVersion,
60
+ tileflowLineBackgroundGlyphRoute,
61
+ usesTileflowLineBackgroundGlyphs,
58
62
  writeTileflowArtifactPlan,
59
63
  writeTileflowBuildArtifacts
60
- } from "./chunk-QAMFAM3S.js";
64
+ } from "./chunk-4HVIEMKM.js";
61
65
  import {
62
66
  inspectTileflowPmtiles,
63
67
  inspectTileflowPmtilesForLocalUse
@@ -140,6 +144,7 @@ export {
140
144
  createTileflowCommandSummary,
141
145
  createTileflowComparisonRequestHandler,
142
146
  createTileflowDevRequestHandler,
147
+ createTileflowLineBackgroundGlyphResponder,
143
148
  createTileflowManifest,
144
149
  createTileflowNodeRequest,
145
150
  createTileflowStructuredDiagnostics,
@@ -179,6 +184,7 @@ export {
179
184
  normalizeTileflowBasePath,
180
185
  normalizeTileflowLandmarkManifest,
181
186
  packTileflowRenderedIcons,
187
+ parseTileflowLineBackgroundGlyphPath,
182
188
  prepareTileflowCatalogIcons,
183
189
  prepareTileflowHostedNativeDeployment,
184
190
  prepareTileflowLocalTilesets,
@@ -203,6 +209,8 @@ export {
203
209
  tileflowArtifactSessionSchemaVersion,
204
210
  tileflowCommandDocumentSchemaVersion,
205
211
  tileflowComparisonSchemaVersion,
212
+ tileflowLineBackgroundGlyphRoute,
213
+ usesTileflowLineBackgroundGlyphs,
206
214
  validateTileflowStyle,
207
215
  verifyTileflowIconArtifact,
208
216
  writeTileflowArtifactPlan,
package/dist/preview.d.ts CHANGED
@@ -58,6 +58,8 @@ type ResolvedTileflowPreview = {
58
58
  label: string;
59
59
  mapName: string;
60
60
  themeName: string;
61
+ /** Every theme of a map preview, in declaration order. A scene owns its theme and omits this. */
62
+ themeNames?: readonly string[];
61
63
  viewport?: NormalizedTileflowCaptureScene['viewport'];
62
64
  };
63
65
  declare class TileflowPreviewSelectionError extends Error {
package/dist/preview.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  readBoundedTileflowResponse,
6
6
  renderTileflowPreviewHtml,
7
7
  resolveTileflowPreview
8
- } from "./chunk-6OJICHB3.js";
8
+ } from "./chunk-CJGM6HTT.js";
9
9
  import "./chunk-6MGFMKG3.js";
10
10
  import "./chunk-D4OE4ITL.js";
11
11
  export {
package/dist/server.js CHANGED
@@ -9,11 +9,11 @@ import {
9
9
  isTileflowRequestUrl,
10
10
  tileflowComparisonSchemaVersion,
11
11
  writeTileflowNodeResponse
12
- } from "./chunk-UCBGTEQV.js";
12
+ } from "./chunk-YOTMY5XT.js";
13
13
  import {
14
14
  createTileflowArtifactDiagnostics,
15
15
  tileflowArtifactSessionSchemaVersion
16
- } from "./chunk-QAMFAM3S.js";
16
+ } from "./chunk-4HVIEMKM.js";
17
17
  import "./chunk-ONYQNLNA.js";
18
18
  import {
19
19
  getTileflowAssetBasePath,
@@ -41,14 +41,14 @@ const result = await compileTileflowIconPackages(internalCatalog, {
41
41
 
42
42
  Streets declares `[streetsIcons]` and keeps light/dark image-token targets in that one closure;
43
43
  Cyberpunk and Matrix independently declare `[cyberpunkIcons]` and `[matrixIcons]`. Baedeker,
44
- Ferraris, Härad, Siegfried, Soundings, Verdant, and San Francisto declare only `[baedekerIcons]`,
45
- `[ferrarisIcons]`, `[haradIcons]`, `[siegfriedIcons]`, `[soundingsIcons]`, `[verdantIcons]`, and
46
- `[sanFrancistoIcons]`, respectively. No official root composes another map's assets even though all
44
+ Ferraris, Härad, Siegfried, Soundings, and Blueprint declare only `[baedekerIcons]`,
45
+ `[ferrarisIcons]`, `[haradIcons]`, `[siegfriedIcons]`, `[soundingsIcons]`, and
46
+ `[blueprintIcons]`, respectively. No official root composes another map's assets even though all
47
47
  use the semantic compiler ABI. Baedeker's directory contains eight original travel-atlas patterns;
48
48
  its browser-derived Mapterhorn contours use runtime terrain tiles and are not part of the icon
49
49
  package. Härad's directory contains nine original Tileflow patterns inspired by Lantmäteriet's CC0
50
50
  Häradsekonomiska kartan series from 1859–1934. Soundings owns ten original nautical symbols and
51
- patterns. San Francisto owns four technical blueprint patterns and one schematic POI node; its
51
+ patterns. Blueprint owns four technical blueprint patterns and one schematic POI node; its
52
52
  unbundled Mapterhorn contours remain runtime terrain, and its text uses the canonical Noto Sans
53
53
  glyph provider. An application map may inherit a root's exact array, replace it, clear it with `[]`,
54
54
  or compose it explicitly with a spread.
@@ -236,11 +236,9 @@ Directories apply left to right. `<id>.<ext>` publishes an icon as `<id>`;
236
236
  `<id>.pattern.<ext>` publishes the intrinsic-size pattern as `<id>`. The published ID must already
237
237
  be canonical lower-kebab-case. A later file replaces an earlier file only for the same exact ID;
238
238
  case-only collisions fail. Package maps export `streetsIcons`, `baedekerIcons`, `civicaIcons`,
239
- `ferrarisIcons`, `haradIcons`, `siegfriedIcons`, `soundingsIcons`, `cyberpunkIcons`, `matrixIcons`,
240
- `verdantIcons`, `sanFrancistoIcons`, and `superTileWorldIcons` directory descriptors from
241
- `@tileflow/maps`. Cyberpunk, Matrix, Baedeker, Cívica, Siegfried, and Super Tile World also export
242
- `cyberpunkFonts`, `matrixFonts`, `baedekerFonts`, `civicaFonts`, `siegfriedFonts`, and
243
- `superTileWorldFonts`. Preparation
239
+ `ferrarisIcons`, `haradIcons`, `siegfriedIcons`, `soundingsIcons`, `blueprintIcons` directory descriptors from
240
+ `@tileflow/maps`. Baedeker, Cívica, and Siegfried also export
241
+ `baedekerFonts`, `civicaFonts`, and `siegfriedFonts`. Preparation
244
242
  resolves package descriptors inside their owning package, checks real-path containment, and
245
243
  compiles them through the same pipeline as a config-relative directory. There are no
246
244
  built-in/source/sprite selectors, mappings, icon-specific inheritance, or compatibility aliases.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/dev",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Node development utilities for Tileflow configs and builds.",
6
6
  "type": "module",
@@ -90,7 +90,7 @@
90
90
  "dependencies": {
91
91
  "@mapbox/vector-tile": "3.0.0",
92
92
  "@maplibre/maplibre-gl-style-spec": "24.8.5",
93
- "@tileflow/core": ">=0.1.0-alpha.27 <0.1.0-beta.0",
93
+ "@tileflow/core": ">=0.1.0-alpha.30 <0.1.0-beta.0",
94
94
  "chokidar": "4.0.3",
95
95
  "fflate": "0.8.3",
96
96
  "fontkit": "2.0.4",
@@ -102,7 +102,7 @@
102
102
  "three": "0.185.1"
103
103
  },
104
104
  "devDependencies": {
105
- "@tileflow/maps": "0.1.0-alpha.5"
105
+ "@tileflow/maps": "0.1.0-alpha.7"
106
106
  },
107
107
  "optionalDependencies": {
108
108
  "sharp": "0.35.4"