@tileflow/dev 0.1.0-alpha.19 → 0.1.0-alpha.20

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.
@@ -1,12 +1,16 @@
1
+ import {
2
+ inspectTileflowPmtilesForLocalUse
3
+ } from "./chunk-ONYQNLNA.js";
1
4
  import {
2
5
  getTileflowFontWatchPaths,
6
+ joinTileflowPublicUrl,
3
7
  prepareTileflowStyleFonts,
4
8
  replaceTileflowStyleFontSources
5
- } from "./chunk-VEB43KDS.js";
9
+ } from "./chunk-RSBV4L2Y.js";
6
10
  import {
7
11
  getTileflowIconWatchPaths,
8
12
  prepareTileflowCatalogIcons
9
- } from "./chunk-BALG4DSG.js";
13
+ } from "./chunk-CENQR5Q2.js";
10
14
  import {
11
15
  TileflowStyleValidationError,
12
16
  normalizeTileflowStyleValidationIssues,
@@ -17,7 +21,7 @@ import {
17
21
  defaultTileflowConfigPath,
18
22
  getTileflowMapNames,
19
23
  loadValidTileflowConfigWithInputs
20
- } from "./chunk-BKHIZ7H2.js";
24
+ } from "./chunk-6MGFMKG3.js";
21
25
  import {
22
26
  isPathWithin
23
27
  } from "./chunk-D4OE4ITL.js";
@@ -26,28 +30,27 @@ import {
26
30
  import { createHash } from "crypto";
27
31
  import { realpathSync as realpathSync2 } from "fs";
28
32
  import {
29
- copyFile,
30
- lstat,
31
- mkdir,
33
+ copyFile as copyFile2,
34
+ lstat as lstat2,
35
+ mkdir as mkdir2,
32
36
  mkdtemp,
33
- open,
37
+ open as open2,
34
38
  readFile,
35
- realpath,
39
+ realpath as realpath2,
36
40
  rename,
37
- rm,
41
+ rm as rm2,
38
42
  unlink
39
43
  } from "fs/promises";
40
44
  import { createRequire } from "module";
41
- import { dirname as dirname2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
45
+ import { dirname as dirname2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
42
46
  import {
43
- compareCodeUnits,
47
+ compareCodeUnits as compareCodeUnits2,
44
48
  getTileflowStyleFontFaces,
45
49
  serializeCanonicalJson,
46
50
  tileflowMapIdSchema,
47
51
  tileflowThemeNameSchema
48
52
  } from "@tileflow/core";
49
53
  import {
50
- collectTileflowMapBuildLineage,
51
54
  createManifest,
52
55
  createStyleFromCatalog,
53
56
  createStylesFromCatalog,
@@ -57,10 +60,325 @@ import {
57
60
  } from "@tileflow/core/build";
58
61
  import { parseTileflowRuntimeManifest } from "@tileflow/core/manifest";
59
62
 
63
+ // src/local-tilesets.ts
64
+ import { randomUUID } from "crypto";
65
+ import { constants } from "fs";
66
+ import { chmod, copyFile, lstat, mkdir, open, readdir, realpath, rm, stat } from "fs/promises";
67
+ import { basename, resolve } from "path";
68
+ import { compareCodeUnits } from "@tileflow/core";
69
+ var minimumLocalPmtilesBytes = 127n;
70
+ var maximumLocalPmtilesBytes = 8000000000n;
71
+ async function prepareTileflowLocalTilesets(project, styles, options) {
72
+ const hasSources = Object.values(project.maps).some(
73
+ (map) => Object.keys(map.sources ?? {}).length > 0
74
+ );
75
+ if (!hasSources) {
76
+ return { dispose: async () => void 0, files: [], styles, watchPaths: [] };
77
+ }
78
+ const logicalProjectRoot = resolve(options.cwd);
79
+ const projectRoot = await realpath(logicalProjectRoot).catch(() => logicalProjectRoot);
80
+ const snapshotStore = resolve(logicalProjectRoot, ".tileflow/cache/pmtiles-snapshots/v1");
81
+ await assertSafeSnapshotStore(logicalProjectRoot, snapshotStore);
82
+ await collectAbandonedSnapshotGenerations(snapshotStore);
83
+ const generationId = `snapshot-${process.pid}-${randomUUID().replaceAll("-", "")}`;
84
+ const generationDirectory = resolve(snapshotStore, generationId);
85
+ await mkdir(generationDirectory);
86
+ const bindings = /* @__PURE__ */ new Map();
87
+ const files = /* @__PURE__ */ new Map();
88
+ const snapshots = /* @__PURE__ */ new Map();
89
+ const output = Object.fromEntries(
90
+ Object.entries(styles).map(([mapName, family]) => [
91
+ mapName,
92
+ Object.fromEntries(
93
+ Object.entries(family).map(([themeName, style]) => [themeName, cloneStyle(style)])
94
+ )
95
+ ])
96
+ );
97
+ let disposed = false;
98
+ const dispose = async () => {
99
+ if (disposed) return;
100
+ disposed = true;
101
+ if (!isPathWithin(snapshotStore, generationDirectory)) {
102
+ throw new Error("Local PMTiles snapshot generation escaped its store.");
103
+ }
104
+ await rm(generationDirectory, { force: true, recursive: true });
105
+ };
106
+ try {
107
+ for (const mapName of Object.keys(project.maps).sort(compareCodeUnits)) {
108
+ const map = project.maps[mapName];
109
+ for (const [sourceId, source] of Object.entries(map.sources ?? {}).sort(
110
+ ([left], [right]) => compareCodeUnits(left, right)
111
+ )) {
112
+ const requestedPath = resolve(options.baseDirectory, source.local);
113
+ const sourcePath = await realpath(requestedPath).catch(() => requestedPath);
114
+ if (!isPathWithin(projectRoot, sourcePath)) {
115
+ throw localTilesetError(
116
+ "TF_LOCAL_TILESET_OUTSIDE_ROOT",
117
+ `maps.${mapName}.sources.${sourceId}.local`,
118
+ "Local hosted tileset must stay inside the project root."
119
+ );
120
+ }
121
+ const sourceStat = await lstat(sourcePath).catch(() => null);
122
+ if (!sourceStat?.isFile() || sourceStat.isSymbolicLink()) {
123
+ throw localTilesetError(
124
+ "TF_LOCAL_TILESET_NOT_FOUND",
125
+ `maps.${mapName}.sources.${sourceId}.local`,
126
+ `Local hosted tileset "${sourceId}" was not found.`
127
+ );
128
+ }
129
+ let snapshot = snapshots.get(sourcePath);
130
+ if (!snapshot) {
131
+ snapshot = await createLocalTilesetSnapshot(
132
+ sourcePath,
133
+ generationDirectory,
134
+ snapshots.size
135
+ );
136
+ snapshots.set(sourcePath, snapshot);
137
+ }
138
+ const file = {
139
+ byteLength: snapshot.byteLength,
140
+ etag: snapshot.etag,
141
+ fileName: `tilesets/${source.tileset}.pmtiles`,
142
+ logicalId: source.tileset,
143
+ sourcePath: snapshot.sourcePath
144
+ };
145
+ const existing = bindings.get(source.tileset);
146
+ if (existing && existing.originalSourcePath !== sourcePath) {
147
+ throw localTilesetError(
148
+ "TF_LOCAL_TILESET_BINDING_CONFLICT",
149
+ `maps.${mapName}.sources.${sourceId}.local`,
150
+ `Logical tileset "${source.tileset}" resolves to different local archives in one generation.`
151
+ );
152
+ }
153
+ bindings.set(source.tileset, { file, originalSourcePath: sourcePath });
154
+ files.set(file.fileName, files.get(file.fileName) ?? file);
155
+ validateSourceContract(
156
+ mapName,
157
+ sourceId,
158
+ source.type,
159
+ output[mapName] ?? {},
160
+ snapshot.inspection
161
+ );
162
+ const publicUrl = joinTileflowPublicUrl(options.assetBaseUrl, file.fileName);
163
+ for (const style of Object.values(output[mapName] ?? {})) {
164
+ const definition = style.sources[sourceId];
165
+ if (!definition) {
166
+ throw localTilesetError(
167
+ "TF_LOCAL_TILESET_SOURCE_MISSING",
168
+ `maps.${mapName}.sources.${sourceId}`,
169
+ `Compiled style is missing hosted source "${sourceId}".`
170
+ );
171
+ }
172
+ definition.url = `tileflow-pmtiles://${publicUrl}`;
173
+ delete definition.tiles;
174
+ }
175
+ }
176
+ }
177
+ return {
178
+ dispose,
179
+ files: [...files.values()].sort(
180
+ (left, right) => compareCodeUnits(left.fileName, right.fileName)
181
+ ),
182
+ styles: output,
183
+ watchPaths: [...snapshots.keys()].sort(compareCodeUnits)
184
+ };
185
+ } catch (error) {
186
+ await dispose().catch(() => void 0);
187
+ throw error;
188
+ }
189
+ }
190
+ async function createLocalTilesetSnapshot(sourcePath, generationDirectory, index) {
191
+ const sourceHandle = await open(sourcePath, "r");
192
+ const snapshotPath = resolve(generationDirectory, `${index}.pmtiles`);
193
+ try {
194
+ const before = sourceIdentity(await sourceHandle.stat({ bigint: true }));
195
+ if (before.size < minimumLocalPmtilesBytes || before.size > maximumLocalPmtilesBytes) {
196
+ throw localTilesetError(
197
+ "TF_TILESET_ARCHIVE_INVALID",
198
+ "archive",
199
+ "Expected one bounded PMTiles file."
200
+ );
201
+ }
202
+ await copyFile(sourcePath, snapshotPath, constants.COPYFILE_FICLONE);
203
+ await syncFile(snapshotPath);
204
+ const [afterHandle, afterPath, snapshotStat] = await Promise.all([
205
+ sourceHandle.stat({ bigint: true }).then(sourceIdentity),
206
+ stat(sourcePath, { bigint: true }).then(sourceIdentity),
207
+ stat(snapshotPath, { bigint: true })
208
+ ]);
209
+ assertSourceIdentity(before, afterHandle);
210
+ assertSourceIdentity(before, afterPath);
211
+ if (!snapshotStat.isFile() || snapshotStat.size !== before.size) {
212
+ throw localTilesetError(
213
+ "TF_LOCAL_TILESET_CHANGED",
214
+ "local",
215
+ "Local hosted tileset changed while its immutable snapshot was prepared."
216
+ );
217
+ }
218
+ const inspection = await inspectTileflowPmtilesForLocalUse(snapshotPath);
219
+ await chmod(snapshotPath, 292);
220
+ return Object.freeze({
221
+ byteLength: Number(before.size),
222
+ etag: `"${generationIdFromDirectory(generationDirectory)}-${index}"`,
223
+ inspection,
224
+ sourcePath: snapshotPath
225
+ });
226
+ } finally {
227
+ await sourceHandle.close();
228
+ }
229
+ }
230
+ function generationIdFromDirectory(path) {
231
+ const generationId = basename(path);
232
+ if (!/^snapshot-\d+-[a-f0-9]{32}$/u.test(generationId)) {
233
+ throw new Error("Local PMTiles snapshot generation is invalid.");
234
+ }
235
+ return generationId;
236
+ }
237
+ function sourceIdentity(value) {
238
+ if (!value.isFile()) throw new Error("Local PMTiles source is not a file.");
239
+ return Object.freeze({
240
+ device: BigInt(value.dev),
241
+ inode: BigInt(value.ino),
242
+ mtimeNanoseconds: "mtimeNs" in value ? BigInt(value.mtimeNs) : BigInt(Math.round(value.mtimeMs * 1e6)),
243
+ size: BigInt(value.size)
244
+ });
245
+ }
246
+ function assertSourceIdentity(before, after) {
247
+ if (before.device !== after.device || before.inode !== after.inode || before.mtimeNanoseconds !== after.mtimeNanoseconds || before.size !== after.size) {
248
+ throw localTilesetError(
249
+ "TF_LOCAL_TILESET_CHANGED",
250
+ "local",
251
+ "Local hosted tileset changed while its immutable snapshot was prepared."
252
+ );
253
+ }
254
+ }
255
+ function validateSourceContract(mapName, sourceId, sourceType, styles, inspection) {
256
+ const isVectorArchive = inspection.contract.tileType === "mlt" || inspection.contract.tileType === "mvt";
257
+ if (sourceType === "vector" !== isVectorArchive) {
258
+ throw localTilesetError(
259
+ "TF_SOURCE_TILE_TYPE_MISMATCH",
260
+ `maps.${mapName}.sources.${sourceId}.type`,
261
+ `Source "${sourceId}" declares ${sourceType} but the PMTiles archive contains ${inspection.contract.tileType}.`
262
+ );
263
+ }
264
+ if (!isVectorArchive || !inspection.contract.sourceLayersDeclared) return;
265
+ const availableLayers = new Map(
266
+ inspection.contract.sourceLayers.map((layer) => [layer.id, layer])
267
+ );
268
+ for (const style of Object.values(styles)) {
269
+ const requirements = asRecord(style.metadata?.["tileflow:sourceRequirements"]);
270
+ const sources = asRecord(requirements?.sources);
271
+ const source = asRecord(sources?.[sourceId]);
272
+ const sourceLayers = Array.isArray(source?.sourceLayers) ? source.sourceLayers : [];
273
+ for (const candidate of sourceLayers) {
274
+ const required = asRecord(candidate);
275
+ const layerId = required?.id;
276
+ if (typeof layerId !== "string") continue;
277
+ const available = availableLayers.get(layerId);
278
+ if (!available) {
279
+ const candidates = [...availableLayers.keys()].sort(compareCodeUnits);
280
+ throw localTilesetError(
281
+ "TF_SOURCE_LAYER_NOT_FOUND",
282
+ `maps.${mapName}.sources.${sourceId}.sourceLayers.${layerId}`,
283
+ `Source "${sourceId}" has no source-layer "${layerId}". Available: ${candidates.join(", ") || "(none)"}.`
284
+ );
285
+ }
286
+ if (!available.fieldsDeclared) continue;
287
+ const availableFields = new Map(available.fields.map((field) => [field.name, field.type]));
288
+ const requiredFields = Array.isArray(required?.fields) ? required.fields : [];
289
+ for (const fieldCandidate of requiredFields) {
290
+ const field = asRecord(fieldCandidate);
291
+ const fieldName = field?.name;
292
+ if (typeof fieldName !== "string") continue;
293
+ if (!availableFields.has(fieldName)) {
294
+ const candidates = [...availableFields.keys()].sort(compareCodeUnits);
295
+ throw localTilesetError(
296
+ "TF_SOURCE_FIELD_NOT_FOUND",
297
+ `maps.${mapName}.sources.${sourceId}.sourceLayers.${layerId}.fields.${fieldName}`,
298
+ `Source-layer "${layerId}" has no field "${fieldName}". Available: ${candidates.join(", ") || "(none)"}.`
299
+ );
300
+ }
301
+ const requiredType = field?.type;
302
+ const actualType = availableFields.get(fieldName);
303
+ if (typeof requiredType === "string" && actualType !== void 0 && requiredType !== actualType) {
304
+ throw localTilesetError(
305
+ "TF_SOURCE_FIELD_TYPE_MISMATCH",
306
+ `maps.${mapName}.sources.${sourceId}.sourceLayers.${layerId}.fields.${fieldName}`,
307
+ `Field "${layerId}.${fieldName}" is ${actualType}; ${requiredType} is required.`
308
+ );
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ async function collectAbandonedSnapshotGenerations(store) {
315
+ const entries = await readdir(store, { withFileTypes: true }).catch(() => []);
316
+ for (const entry of entries) {
317
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
318
+ const match = /^snapshot-(\d+)-[a-f0-9]{32}$/u.exec(entry.name);
319
+ if (!match?.[1] || processIsAlive(Number(match[1]))) continue;
320
+ const path = resolve(store, entry.name);
321
+ if (isPathWithin(store, path)) await rm(path, { force: true, recursive: true });
322
+ }
323
+ }
324
+ async function assertSafeSnapshotStore(projectRoot, store) {
325
+ const segments = [".tileflow", "cache", "pmtiles-snapshots", "v1"];
326
+ let current = projectRoot;
327
+ for (const segment of segments) {
328
+ current = resolve(current, segment);
329
+ try {
330
+ const value = await lstat(current);
331
+ if (!value.isDirectory() || value.isSymbolicLink()) {
332
+ throw new Error(`Local PMTiles snapshot path is unsafe: ${current}`);
333
+ }
334
+ } catch (error) {
335
+ if (!hasErrorCode(error, "ENOENT")) throw error;
336
+ await mkdir(current).catch((mkdirError) => {
337
+ if (!hasErrorCode(mkdirError, "EEXIST")) throw mkdirError;
338
+ });
339
+ const created = await lstat(current);
340
+ if (!created.isDirectory() || created.isSymbolicLink()) {
341
+ throw new Error(`Local PMTiles snapshot path is unsafe: ${current}`);
342
+ }
343
+ }
344
+ }
345
+ if (resolve(current) !== resolve(store)) {
346
+ throw new Error("Local PMTiles snapshot path escaped its project root.");
347
+ }
348
+ }
349
+ function processIsAlive(pid) {
350
+ try {
351
+ process.kill(pid, 0);
352
+ return true;
353
+ } catch (error) {
354
+ return !hasErrorCode(error, "ESRCH");
355
+ }
356
+ }
357
+ async function syncFile(path) {
358
+ const handle = await open(path, "r");
359
+ try {
360
+ await handle.sync();
361
+ } finally {
362
+ await handle.close();
363
+ }
364
+ }
365
+ function cloneStyle(style) {
366
+ return JSON.parse(JSON.stringify(style));
367
+ }
368
+ function localTilesetError(code, path, message) {
369
+ return Object.assign(new Error(message), { code, path, phase: "local-tileset-resolution" });
370
+ }
371
+ function asRecord(value) {
372
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
373
+ }
374
+ function hasErrorCode(error, code) {
375
+ return Boolean(error && typeof error === "object" && error.code === code);
376
+ }
377
+
60
378
  // src/session.ts
61
379
  import { watch } from "chokidar";
62
380
  import { realpathSync } from "fs";
63
- import { dirname, extname, isAbsolute, relative, resolve, sep, win32 } from "path";
381
+ import { dirname, extname, isAbsolute, relative, resolve as resolve2, sep, win32 } from "path";
64
382
  var tileflowArtifactSessionSchemaVersion = 1;
65
383
  var configExtensions = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".json", ".mjs", ".mts", ".ts", ".tsx"]);
66
384
  var iconExtensions = /* @__PURE__ */ new Set([".jpeg", ".jpg", ".png", ".svg", ".webp"]);
@@ -96,20 +414,23 @@ var TileflowArtifactSessionImpl = class {
96
414
  #ignoredPaths;
97
415
  #watchEnabled;
98
416
  #closed = false;
417
+ #closing;
99
418
  #debounceTimer;
100
419
  #generation = 0;
101
- #lastGoodArtifacts;
420
+ #current;
421
+ #artifactGenerations = /* @__PURE__ */ new Set();
102
422
  #lastGoodGeneration;
423
+ #refreshes = /* @__PURE__ */ new Set();
103
424
  #refreshToken = 0;
104
425
  #state = { generation: 0, status: "building" };
105
426
  #watcher;
106
427
  #watchedArtifactPaths = /* @__PURE__ */ new Set();
107
428
  constructor(options, buildArtifacts, discoverWatchPaths) {
108
- this.#cwd = resolve(options.cwd ?? process.cwd());
109
- this.#configPath = resolve(this.#cwd, options.config ?? "tileflow.config.ts");
429
+ this.#cwd = resolve2(options.cwd ?? process.cwd());
430
+ this.#configPath = resolve2(this.#cwd, options.config ?? "tileflow.config.ts");
110
431
  this.#debounceMs = Math.max(0, Math.min(options.debounceMs ?? 75, 1e3));
111
432
  this.#watchEnabled = options.watch ?? false;
112
- this.#ignoredPaths = (options.ignoredPaths ?? []).map((path) => resolve(this.#cwd, path));
433
+ this.#ignoredPaths = (options.ignoredPaths ?? []).map((path) => resolve2(this.#cwd, path));
113
434
  this.#buildArtifacts = buildArtifacts;
114
435
  this.#discoverWatchPaths = discoverWatchPaths;
115
436
  this.#buildOptions = {
@@ -118,7 +439,8 @@ var TileflowArtifactSessionImpl = class {
118
439
  config: options.config,
119
440
  cwd: this.#cwd,
120
441
  inspection: options.inspection,
121
- styleBaseUrl: options.styleBaseUrl
442
+ styleBaseUrl: options.styleBaseUrl,
443
+ target: options.target
122
444
  };
123
445
  }
124
446
  async start() {
@@ -165,7 +487,27 @@ var TileflowArtifactSessionImpl = class {
165
487
  return this.#state;
166
488
  }
167
489
  getLastGoodArtifacts() {
168
- return this.#lastGoodArtifacts;
490
+ return this.#current?.artifacts;
491
+ }
492
+ acquireArtifacts(generation) {
493
+ const current = this.#current;
494
+ if (this.#closed || !current?.accepting || generation !== void 0 && generation !== current.generation) {
495
+ return void 0;
496
+ }
497
+ current.references += 1;
498
+ let released = false;
499
+ return Object.freeze({
500
+ artifacts: current.artifacts,
501
+ generation: current.generation,
502
+ release: async () => {
503
+ if (released) return;
504
+ released = true;
505
+ current.references -= 1;
506
+ if (!current.accepting && current.references === 0) {
507
+ await this.#disposeGeneration(current);
508
+ }
509
+ }
510
+ });
169
511
  }
170
512
  subscribe(listener) {
171
513
  if (this.#closed) return () => void 0;
@@ -173,6 +515,15 @@ var TileflowArtifactSessionImpl = class {
173
515
  return () => this.#listeners.delete(listener);
174
516
  }
175
517
  async refresh(_reason = "manual") {
518
+ const refresh = this.#refresh();
519
+ this.#refreshes.add(refresh);
520
+ try {
521
+ await refresh;
522
+ } finally {
523
+ this.#refreshes.delete(refresh);
524
+ }
525
+ }
526
+ async #refresh() {
176
527
  if (this.#closed) return;
177
528
  const generation = ++this.#generation;
178
529
  const token = ++this.#refreshToken;
@@ -183,10 +534,20 @@ var TileflowArtifactSessionImpl = class {
183
534
  });
184
535
  try {
185
536
  const artifacts = await this.#buildArtifacts(this.#buildOptions);
186
- if (this.#closed || token !== this.#refreshToken) return;
187
- this.#lastGoodArtifacts = artifacts;
537
+ if (this.#closed || token !== this.#refreshToken) {
538
+ await disposeArtifacts(artifacts);
539
+ return;
540
+ }
541
+ const previous = this.#current;
542
+ const current = createArtifactGeneration(generation, artifacts);
543
+ this.#artifactGenerations.add(current);
544
+ this.#current = current;
188
545
  this.#lastGoodGeneration = generation;
189
546
  this.#updateArtifactWatchPaths(artifacts.watchPaths);
547
+ if (previous) {
548
+ previous.accepting = false;
549
+ if (previous.references === 0) await this.#disposeGeneration(previous);
550
+ }
190
551
  this.#publish({
191
552
  artifacts,
192
553
  generation,
@@ -206,16 +567,27 @@ var TileflowArtifactSessionImpl = class {
206
567
  this.#publishInvalid(generation, error);
207
568
  }
208
569
  }
209
- async close() {
210
- if (this.#closed) return;
570
+ close() {
571
+ this.#closing ??= this.#close();
572
+ return this.#closing;
573
+ }
574
+ async #close() {
211
575
  this.#closed = true;
212
576
  this.#refreshToken += 1;
213
577
  if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
214
578
  this.#listeners.clear();
215
579
  await this.#watcher?.close();
580
+ const retained = [...this.#artifactGenerations];
581
+ this.#current = void 0;
582
+ for (const generation of retained) {
583
+ generation.accepting = false;
584
+ if (generation.references === 0) void this.#disposeGeneration(generation);
585
+ }
586
+ await Promise.allSettled([...this.#refreshes]);
587
+ await Promise.all(retained.map((generation) => generation.completion));
216
588
  }
217
589
  #ignoreWatchPath(path, isDirectory) {
218
- const resolvedPath = resolve(path);
590
+ const resolvedPath = resolve2(path);
219
591
  const relativePath = relative(dirname(this.#configPath), resolvedPath);
220
592
  const segments = relativePath.split(sep).filter(Boolean);
221
593
  if (this.#ignoredPaths.some((path2) => isSameOrInside(path2, resolvedPath))) return true;
@@ -229,7 +601,7 @@ var TileflowArtifactSessionImpl = class {
229
601
  return true;
230
602
  }
231
603
  #shouldRefreshForWatchEvent(path) {
232
- const resolvedPath = resolve(path);
604
+ const resolvedPath = resolve2(path);
233
605
  const extension = extname(resolvedPath).toLowerCase();
234
606
  if (!iconExtensions.has(extension) && !fontExtensions.has(extension)) return true;
235
607
  return [...this.#watchedArtifactPaths].some((path2) => isSameOrInside(path2, resolvedPath));
@@ -257,7 +629,7 @@ var TileflowArtifactSessionImpl = class {
257
629
  }
258
630
  #updateArtifactWatchPaths(paths) {
259
631
  if (!this.#watcher) return;
260
- const next = new Set(paths.map((path) => resolve(this.#cwd, path)));
632
+ const next = new Set(paths.map((path) => resolve2(this.#cwd, path)));
261
633
  const previous = this.#watchedArtifactPaths;
262
634
  this.#watchedArtifactPaths = next;
263
635
  for (const path of previous) {
@@ -271,14 +643,54 @@ var TileflowArtifactSessionImpl = class {
271
643
  }
272
644
  }
273
645
  }
646
+ #disposeGeneration(generation) {
647
+ if (generation.disposeStarted) return generation.completion;
648
+ generation.disposeStarted = true;
649
+ void disposeArtifacts(generation.artifacts).then(
650
+ () => {
651
+ this.#artifactGenerations.delete(generation);
652
+ generation.resolveCompletion();
653
+ },
654
+ (error) => {
655
+ this.#artifactGenerations.delete(generation);
656
+ generation.rejectCompletion(error);
657
+ }
658
+ );
659
+ return generation.completion;
660
+ }
274
661
  };
662
+ function createArtifactGeneration(generation, artifacts) {
663
+ let rejectCompletion;
664
+ let resolveCompletion;
665
+ const completion = new Promise((resolvePromise, rejectPromise) => {
666
+ rejectCompletion = rejectPromise;
667
+ resolveCompletion = resolvePromise;
668
+ });
669
+ return {
670
+ accepting: true,
671
+ artifacts,
672
+ completion,
673
+ disposeStarted: false,
674
+ generation,
675
+ references: 0,
676
+ rejectCompletion,
677
+ resolveCompletion
678
+ };
679
+ }
680
+ async function disposeArtifacts(artifacts) {
681
+ const dispose = artifacts.dispose;
682
+ artifacts.dispose = void 0;
683
+ await dispose?.();
684
+ }
275
685
  function isSameOrInside(root, candidate) {
276
- const path = relative(resolve(root), resolve(candidate));
686
+ const path = relative(resolve2(root), resolve2(candidate));
277
687
  return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
278
688
  }
279
689
  function createTileflowArtifactDiagnostics(error, cwd) {
280
690
  const inheritedCode = optionalDiagnosticField(error, "code");
691
+ const inheritedPath = optionalDiagnosticPath(error);
281
692
  const inheritedPhase = optionalDiagnosticField(error, "phase") ?? optionalNestedDiagnosticField(error, "phase");
693
+ const semanticResolutionPath = isTileflowMapResolutionDiagnostic(error, inheritedPath) ? sanitizeDiagnosticSecrets(inheritedPath) : void 0;
282
694
  const issueList = getIssueList(error);
283
695
  if (issueList.length > 0) {
284
696
  return normalizeDiagnostics(
@@ -293,8 +705,11 @@ function createTileflowArtifactDiagnostics(error, cwd) {
293
705
  return normalizeDiagnostics([
294
706
  {
295
707
  ...inheritedCode ? { code: inheritedCode } : {},
296
- message: sanitizeMessage(error instanceof Error ? error.message : "Unknown build error", cwd),
297
- path: "",
708
+ message: sanitizeMessage(
709
+ error instanceof Error ? removeSemanticResolutionPath(error.message, semanticResolutionPath) : "Unknown build error",
710
+ cwd
711
+ ),
712
+ path: semanticResolutionPath ?? sanitizePath(inheritedPath ?? "", cwd),
298
713
  ...inheritedPhase ? { phase: inheritedPhase } : {}
299
714
  }
300
715
  ]);
@@ -348,20 +763,34 @@ function sanitizeMessage(value, cwd) {
348
763
  function sanitizePath(value, cwd) {
349
764
  if (!value) return "";
350
765
  if (win32.isAbsolute(value) && !isAbsolute(value)) return "(external)";
351
- if (!isAbsolute(value)) return value.replaceAll("\\", "/");
766
+ if (!isAbsolute(value)) return sanitizeDiagnosticSecrets(value.replaceAll("\\", "/"));
352
767
  const local = relative(canonicalPath(cwd), canonicalPath(value)).replaceAll(sep, "/");
353
- return local.startsWith("../") || local === ".." ? "(external)" : local || ".";
768
+ return local.startsWith("../") || local === ".." ? "(external)" : sanitizeDiagnosticSecrets(local || ".");
769
+ }
770
+ function optionalDiagnosticPath(value) {
771
+ if (!value || typeof value !== "object") return void 0;
772
+ const path = value.path;
773
+ return typeof path === "string" && path ? path : void 0;
774
+ }
775
+ function isTileflowMapResolutionDiagnostic(value, path) {
776
+ if (!value || typeof value !== "object" || !path) return false;
777
+ const record = value;
778
+ return record.name === "TileflowMapResolutionError" && typeof record.code === "string" && typeof record.mapId === "string" && path.startsWith("/modules/") && !/[\p{Cc}]/u.test(path) && !/~(?:[^01]|$)/u.test(path);
779
+ }
780
+ function removeSemanticResolutionPath(message, path) {
781
+ if (!path) return message;
782
+ return message.replace(` at ${path}:`, ":");
354
783
  }
355
784
  function localPathAliases(path) {
356
- return [.../* @__PURE__ */ new Set([resolve(path), canonicalPath(path)])].sort(
785
+ return [.../* @__PURE__ */ new Set([resolve2(path), canonicalPath(path)])].sort(
357
786
  (left, right) => right.length - left.length
358
787
  );
359
788
  }
360
789
  function canonicalPath(path) {
361
790
  try {
362
- return realpathSync.native(resolve(path));
791
+ return realpathSync.native(resolve2(path));
363
792
  } catch {
364
- return resolve(path);
793
+ return resolve2(path);
365
794
  }
366
795
  }
367
796
  function normalizeDiagnostics(diagnostics) {
@@ -408,6 +837,15 @@ var TileflowHostedManifestOverwriteError = class extends Error {
408
837
  this.name = "TileflowHostedManifestOverwriteError";
409
838
  }
410
839
  };
840
+ var TileflowLocalTilesetProductionError = class extends Error {
841
+ code = "TF_LOCAL_TILESET_PRODUCTION_UNRESOLVED";
842
+ constructor() {
843
+ super(
844
+ "Production builds cannot publish local PMTiles sources. Publish the tileset explicitly or provide an application-owned production source."
845
+ );
846
+ this.name = "TileflowLocalTilesetProductionError";
847
+ }
848
+ };
411
849
  function createTileflowManifest(project, options = {}) {
412
850
  return createManifest(project, options);
413
851
  }
@@ -452,10 +890,10 @@ function createTileflowStylesWithInspection(project, options = {}) {
452
890
  }
453
891
  function assertValidTileflowStyles(project, styles) {
454
892
  const issues = [];
455
- for (const mapName of getTileflowMapNames(project).sort(compareCodeUnits)) {
893
+ for (const mapName of getTileflowMapNames(project).sort(compareCodeUnits2)) {
456
894
  const family = styles[mapName];
457
895
  if (!family) continue;
458
- for (const themeName of Object.keys(family).sort(compareCodeUnits)) {
896
+ for (const themeName of Object.keys(family).sort(compareCodeUnits2)) {
459
897
  const style = family[themeName];
460
898
  issues.push(...validateTileflowStyle(style, `${mapName}/${themeName}`));
461
899
  }
@@ -465,6 +903,9 @@ function assertValidTileflowStyles(project, styles) {
465
903
  }
466
904
  }
467
905
  async function createTileflowArtifactPlan(prepared, options = {}) {
906
+ if (options.target === "production" && hasLocalTilesetSources(prepared.project)) {
907
+ throw new TileflowLocalTilesetProductionError();
908
+ }
468
909
  const inspected = options.inspection ? createTileflowStylesWithInspection(prepared.project, {
469
910
  apiBaseUrl: options.apiBaseUrl,
470
911
  mapAssets: prepared.mapAssets
@@ -473,71 +914,87 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
473
914
  apiBaseUrl: options.apiBaseUrl,
474
915
  mapAssets: prepared.mapAssets
475
916
  });
476
- const preparedFonts = await prepareTileflowStyleFonts(prepared.project, compiledStyles, {
917
+ const localTilesets = await prepareTileflowLocalTilesets(prepared.project, compiledStyles, {
477
918
  assetBaseUrl: resolveAssetBaseUrl(options),
478
919
  baseDirectory: prepared.baseDirectory,
479
- cwd: prepared.cwd,
480
- target: "local"
920
+ cwd: prepared.cwd
481
921
  });
482
- const styles = preparedFonts.styles;
483
- const assets = [...prepared.assets, ...preparedFonts.assets];
484
- const provenance = await createTileflowBuildProvenance(prepared.cwd);
485
- const buildManifest = await createTileflowMapBuildManifest(
486
- Object.fromEntries(
487
- Object.keys(prepared.project.maps).sort(compareCodeUnits).map((mapName) => {
488
- const map = prepared.project.maps[mapName];
489
- const style = styles[mapName];
490
- return [
491
- mapName,
492
- {
493
- assets: getMapBuildAssets(
494
- mapName,
495
- assets,
496
- preparedFonts.bundles[mapName]?.files ?? []
497
- ),
498
- lineage: prepared.project.mapMetadata?.[mapName]?.lineage ?? collectTileflowMapBuildLineage(map),
499
- map,
500
- sourceAssets: {
501
- fonts: preparedFonts.sourceIdentities[mapName] ?? [],
502
- icons: prepared.mapIconSources[mapName] ?? []
503
- },
504
- styles: style
505
- }
506
- ];
507
- })
508
- ),
509
- { provenance }
510
- );
511
- const stableManifest = createFontAwareManifest(
512
- createManifest(prepared.project, { styleBaseUrl: options.styleBaseUrl }),
513
- styles
514
- );
515
- const inputs = {
516
- directories: uniqueStrings(
517
- [...prepared.watchPaths, ...preparedFonts.watchPaths].map(canonicalInputPath)
518
- ),
519
- files: uniqueStrings((options.inputFiles ?? []).map(canonicalInputPath))
520
- };
521
- const partial = {
522
- assets,
523
- buildManifest,
524
- manifest: stableManifest,
525
- project: prepared.project,
526
- ...inspected ? { styleInspections: inspected.inspections } : {},
527
- styles,
528
- watchPaths: uniqueStrings([...inputs.files, ...inputs.directories])
529
- };
530
- const generation = hashArtifactGeneration(getStableTileflowArtifactFiles(partial));
531
- const manifest = createGenerationManifest(stableManifest, generation, assets);
532
- const generationArtifacts = { ...partial, manifest };
533
- return {
534
- ...generationArtifacts,
535
- files: createGenerationArtifactFiles(generationArtifacts, generation),
536
- inputs,
537
- project: prepared.project,
538
- schemaVersion: tileflowArtifactPlanSchemaVersion,
539
- sourceProject: prepared.sourceProject
540
- };
922
+ try {
923
+ const preparedFonts = await prepareTileflowStyleFonts(prepared.project, localTilesets.styles, {
924
+ assetBaseUrl: resolveAssetBaseUrl(options),
925
+ baseDirectory: prepared.baseDirectory,
926
+ cwd: prepared.cwd,
927
+ target: "local"
928
+ });
929
+ const styles = preparedFonts.styles;
930
+ const assets = [...prepared.assets, ...preparedFonts.assets];
931
+ const provenance = await createTileflowBuildProvenance(prepared.cwd);
932
+ const buildManifest = await createTileflowMapBuildManifest(
933
+ Object.fromEntries(
934
+ Object.keys(prepared.project.maps).sort(compareCodeUnits2).map((mapName) => {
935
+ const map = prepared.project.maps[mapName];
936
+ const style = styles[mapName];
937
+ return [
938
+ mapName,
939
+ {
940
+ assets: getMapBuildAssets(
941
+ mapName,
942
+ assets,
943
+ preparedFonts.bundles[mapName]?.files ?? []
944
+ ),
945
+ lineage: prepared.project.mapMetadata?.[mapName]?.lineage ?? [
946
+ { id: map.id, mapVersion: map.version }
947
+ ],
948
+ map,
949
+ sourceAssets: {
950
+ fonts: preparedFonts.sourceIdentities[mapName] ?? [],
951
+ icons: prepared.mapIconSources[mapName] ?? []
952
+ },
953
+ styles: style
954
+ }
955
+ ];
956
+ })
957
+ ),
958
+ { provenance }
959
+ );
960
+ const stableManifest = createFontAwareManifest(
961
+ createManifest(prepared.project, { styleBaseUrl: options.styleBaseUrl }),
962
+ styles
963
+ );
964
+ const inputs = {
965
+ directories: uniqueStrings(
966
+ [...prepared.watchPaths, ...preparedFonts.watchPaths].map(canonicalInputPath)
967
+ ),
968
+ files: uniqueStrings(
969
+ [...options.inputFiles ?? [], ...localTilesets.watchPaths].map(canonicalInputPath)
970
+ )
971
+ };
972
+ const partial = {
973
+ assets,
974
+ buildManifest,
975
+ dispose: localTilesets.dispose,
976
+ manifest: stableManifest,
977
+ project: prepared.project,
978
+ ...localTilesets.files.length > 0 ? { localTilesets: localTilesets.files } : {},
979
+ ...inspected ? { styleInspections: inspected.inspections } : {},
980
+ styles,
981
+ watchPaths: uniqueStrings([...inputs.files, ...inputs.directories])
982
+ };
983
+ const generation = hashArtifactGeneration(getStableTileflowArtifactFiles(partial));
984
+ const manifest = createGenerationManifest(stableManifest, generation, assets);
985
+ const generationArtifacts = { ...partial, manifest };
986
+ return {
987
+ ...generationArtifacts,
988
+ files: createGenerationArtifactFiles(generationArtifacts, generation),
989
+ inputs,
990
+ project: prepared.project,
991
+ schemaVersion: tileflowArtifactPlanSchemaVersion,
992
+ sourceProject: prepared.sourceProject
993
+ };
994
+ } catch (error) {
995
+ await localTilesets.dispose().catch(() => void 0);
996
+ throw error;
997
+ }
541
998
  }
542
999
  async function createTileflowBuildArtifacts(options = {}) {
543
1000
  const loaded = await loadValidTileflowConfigWithInputs(
@@ -598,7 +1055,7 @@ function validateArtifactFiles(files) {
598
1055
  }
599
1056
  seen.add(file.fileName);
600
1057
  }
601
- return [...files].sort((left, right) => compareCodeUnits(left.fileName, right.fileName));
1058
+ return [...files].sort((left, right) => compareCodeUnits2(left.fileName, right.fileName));
602
1059
  }
603
1060
  function createGenerationManifest(manifest, generation, assets) {
604
1061
  return {
@@ -701,16 +1158,16 @@ function getMapBuildAssets(mapName, assets, fontAssets) {
701
1158
  selected.set(asset.fileName, asset);
702
1159
  }
703
1160
  return [...selected.values()].sort(
704
- (left, right) => compareCodeUnits(left.fileName, right.fileName)
1161
+ (left, right) => compareCodeUnits2(left.fileName, right.fileName)
705
1162
  );
706
1163
  }
707
1164
  function hashBytes(source) {
708
1165
  return createHash("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
709
1166
  }
710
1167
  async function createTileflowBuildProvenance(cwd) {
711
- const projectDirectory = resolve2(cwd);
1168
+ const projectDirectory = resolve3(cwd);
712
1169
  const projectRequire = createRequire(
713
- resolve2(projectDirectory, "__tileflow_build_provenance__.cjs")
1170
+ resolve3(projectDirectory, "__tileflow_build_provenance__.cjs")
714
1171
  );
715
1172
  const toolRequire = createRequire(import.meta.url);
716
1173
  const packages = {};
@@ -767,10 +1224,10 @@ async function findNearestLockfile(cwd) {
767
1224
  try {
768
1225
  return {
769
1226
  format: candidate.format,
770
- source: await readFile(resolve2(directory, candidate.fileName))
1227
+ source: await readFile(resolve3(directory, candidate.fileName))
771
1228
  };
772
1229
  } catch (error) {
773
- if (!hasErrorCode(error, "ENOENT")) throw error;
1230
+ if (!hasErrorCode2(error, "ENOENT")) throw error;
774
1231
  }
775
1232
  }
776
1233
  const parent = dirname2(directory);
@@ -824,30 +1281,46 @@ function getArtifactInputGraph(artifacts) {
824
1281
  return inputs && Array.isArray(inputs.files) && Array.isArray(inputs.directories) ? inputs : void 0;
825
1282
  }
826
1283
  async function writeTileflowBuildArtifacts(options) {
827
- const artifacts = await createTileflowBuildArtifacts(options);
828
- await writeTileflowArtifactPlan(artifacts, options);
829
- return artifacts;
1284
+ const artifacts = await createTileflowBuildArtifacts({ ...options, target: "production" });
1285
+ try {
1286
+ await writeTileflowArtifactPlan(artifacts, options);
1287
+ return { ...artifacts, dispose: void 0, localTilesets: void 0 };
1288
+ } finally {
1289
+ await disposeTileflowBuildArtifacts(artifacts);
1290
+ }
1291
+ }
1292
+ async function disposeTileflowBuildArtifacts(artifacts) {
1293
+ const dispose = artifacts.dispose;
1294
+ artifacts.dispose = void 0;
1295
+ await dispose?.();
830
1296
  }
831
1297
  async function writeTileflowArtifactPlan(artifacts, options) {
1298
+ assertNoLocalTilesetsForProduction(artifacts);
832
1299
  const cwd = options.cwd ?? process.cwd();
833
1300
  const output = await prepareTileflowOutputDirectory(cwd, options.outDir);
834
1301
  const files = getTileflowArtifactFiles(artifacts);
835
- await assertTileflowSelfHostedManifestTarget(resolve2(output.outDir, "manifest.json"), options);
1302
+ await assertTileflowSelfHostedManifestTarget(resolve3(output.outDir, "manifest.json"), options);
836
1303
  await commitTileflowArtifactFiles(output, files);
837
1304
  }
1305
+ function assertNoLocalTilesetsForProduction(artifacts) {
1306
+ if (artifacts.localTilesets?.length) throw new TileflowLocalTilesetProductionError();
1307
+ }
1308
+ function hasLocalTilesetSources(project) {
1309
+ return Object.values(project.maps).some((map) => Object.keys(map.sources ?? {}).length > 0);
1310
+ }
838
1311
  async function assertTileflowSelfHostedManifestTarget(manifestPath, options = {}) {
839
1312
  if (options.overwriteHostedManifest) return;
840
1313
  let source;
841
1314
  try {
842
- source = await readFile(resolve2(manifestPath), "utf8");
1315
+ source = await readFile(resolve3(manifestPath), "utf8");
843
1316
  } catch (error) {
844
- if (hasErrorCode(error, "ENOENT")) return;
1317
+ if (hasErrorCode2(error, "ENOENT")) return;
845
1318
  throw error;
846
1319
  }
847
1320
  try {
848
1321
  const manifest = parseTileflowRuntimeManifest(JSON.parse(source));
849
1322
  if (isHostedRuntimeManifest(manifest)) {
850
- throw new TileflowHostedManifestOverwriteError(resolve2(manifestPath));
1323
+ throw new TileflowHostedManifestOverwriteError(resolve3(manifestPath));
851
1324
  }
852
1325
  } catch (error) {
853
1326
  if (error instanceof TileflowHostedManifestOverwriteError) throw error;
@@ -874,7 +1347,8 @@ async function commitTileflowArtifactFiles(output, files) {
874
1347
  const generation = getArtifactGeneration(files);
875
1348
  const retainedGenerations = previous ? generation === previous.generation ? getManagedGenerations(previous.files) : [previous.generation] : [];
876
1349
  const retainedPrefixes = retainedGenerations.map((item) => `generations/${item}/`);
877
- const retainedNames = previous ? previous.files.filter((name) => retainedPrefixes.some((prefix) => name.startsWith(prefix))) : [];
1350
+ const retainedGenerationNames = previous ? previous.files.filter((name) => retainedPrefixes.some((prefix) => name.startsWith(prefix))) : [];
1351
+ const retainedNames = uniqueStrings(retainedGenerationNames);
878
1352
  const inventoryNames = uniqueStrings([...desiredNames, ...retainedNames]);
879
1353
  const inventory = {
880
1354
  files: inventoryNames,
@@ -894,15 +1368,15 @@ async function commitTileflowArtifactFiles(output, files) {
894
1368
  ...staleNames,
895
1369
  tileflowArtifactInventoryFileName
896
1370
  ]);
897
- const stageDir = await mkdtemp(resolve2(outDir, ".tileflow-stage-"));
898
- const newDir = resolve2(stageDir, "new");
899
- const backupDir = resolve2(stageDir, "backup");
1371
+ const stageDir = await mkdtemp(resolve3(outDir, ".tileflow-stage-"));
1372
+ const newDir = resolve3(stageDir, "new");
1373
+ const backupDir = resolve3(stageDir, "backup");
900
1374
  const backedUp = /* @__PURE__ */ new Set();
901
1375
  const installed = /* @__PURE__ */ new Set();
902
1376
  try {
903
1377
  for (const file of stagedFiles) {
904
1378
  const stagePath = managedPath(newDir, file.fileName);
905
- await mkdir(dirname2(stagePath), { recursive: true });
1379
+ await mkdir2(dirname2(stagePath), { recursive: true });
906
1380
  await writeFileDurably(stagePath, file.source);
907
1381
  }
908
1382
  for (const name of affectedNames) {
@@ -910,21 +1384,21 @@ async function commitTileflowArtifactFiles(output, files) {
910
1384
  const targetPath = managedPath(outDir, name);
911
1385
  if (!await isFile(targetPath)) continue;
912
1386
  const backupPath = managedPath(backupDir, name);
913
- await mkdir(dirname2(backupPath), { recursive: true });
914
- await copyFile(targetPath, backupPath);
1387
+ await mkdir2(dirname2(backupPath), { recursive: true });
1388
+ await copyFile2(targetPath, backupPath);
915
1389
  backedUp.add(name);
916
1390
  }
917
1391
  const commitOrder = stagedFiles.map((file) => file.fileName).sort((left, right) => {
918
1392
  const leftRank = commitRank(left);
919
1393
  const rightRank = commitRank(right);
920
- return leftRank - rightRank || compareCodeUnits(left, right);
1394
+ return leftRank - rightRank || compareCodeUnits2(left, right);
921
1395
  });
922
1396
  for (const name of commitOrder.filter(
923
1397
  (candidate) => candidate !== tileflowArtifactInventoryFileName
924
1398
  )) {
925
1399
  const sourcePath = managedPath(newDir, name);
926
1400
  const targetPath = managedPath(outDir, name);
927
- await mkdir(dirname2(targetPath), { recursive: true });
1401
+ await mkdir2(dirname2(targetPath), { recursive: true });
928
1402
  await rename(sourcePath, targetPath);
929
1403
  installed.add(name);
930
1404
  }
@@ -939,7 +1413,7 @@ async function commitTileflowArtifactFiles(output, files) {
939
1413
  await rollbackManagedFiles(outDir, backupDir, affectedNames, backedUp, installed);
940
1414
  throw error;
941
1415
  } finally {
942
- await rm(stageDir, { force: true, recursive: true });
1416
+ await rm2(stageDir, { force: true, recursive: true });
943
1417
  }
944
1418
  }
945
1419
  function getArtifactGeneration(files) {
@@ -971,8 +1445,8 @@ async function rollbackManagedFiles(outDir, backupDir, affectedNames, backedUp,
971
1445
  await removeManagedFile(targetPath);
972
1446
  }
973
1447
  if (backedUp.has(name)) {
974
- await mkdir(dirname2(targetPath), { recursive: true });
975
- await copyFile(managedPath(backupDir, name), targetPath);
1448
+ await mkdir2(dirname2(targetPath), { recursive: true });
1449
+ await copyFile2(managedPath(backupDir, name), targetPath);
976
1450
  }
977
1451
  } catch (error) {
978
1452
  rollbackErrors.push(error);
@@ -988,7 +1462,7 @@ async function readTileflowArtifactInventory(outDir) {
988
1462
  try {
989
1463
  source = await readFile(inventoryPath, "utf8");
990
1464
  } catch (error) {
991
- if (hasErrorCode(error, "ENOENT")) return void 0;
1465
+ if (hasErrorCode2(error, "ENOENT")) return void 0;
992
1466
  throw error;
993
1467
  }
994
1468
  let candidate;
@@ -1020,7 +1494,7 @@ async function readTileflowArtifactInventory(outDir) {
1020
1494
  }
1021
1495
  async function assertManagedTargetIsSafe(outDir, fileName) {
1022
1496
  assertSafeManagedFileName(fileName);
1023
- const rootStats = await lstat(outDir);
1497
+ const rootStats = await lstat2(outDir);
1024
1498
  if (rootStats.isSymbolicLink()) {
1025
1499
  throw new Error(`Tileflow refuses to write through a symbolic link: ${outDir}`);
1026
1500
  }
@@ -1030,9 +1504,9 @@ async function assertManagedTargetIsSafe(outDir, fileName) {
1030
1504
  const segments = fileName.split("/");
1031
1505
  let current = outDir;
1032
1506
  for (let index = 0; index < segments.length; index += 1) {
1033
- current = resolve2(current, segments[index]);
1507
+ current = resolve3(current, segments[index]);
1034
1508
  try {
1035
- const stats = await lstat(current);
1509
+ const stats = await lstat2(current);
1036
1510
  if (stats.isSymbolicLink()) {
1037
1511
  throw new Error(`Tileflow refuses to write through a symbolic link: ${fileName}`);
1038
1512
  }
@@ -1043,34 +1517,34 @@ async function assertManagedTargetIsSafe(outDir, fileName) {
1043
1517
  throw new Error(`Tileflow artifact target is not a file: ${fileName}`);
1044
1518
  }
1045
1519
  } catch (error) {
1046
- if (hasErrorCode(error, "ENOENT")) return;
1520
+ if (hasErrorCode2(error, "ENOENT")) return;
1047
1521
  throw error;
1048
1522
  }
1049
1523
  }
1050
1524
  }
1051
1525
  async function prepareTileflowOutputDirectory(cwd, requestedOutDir) {
1052
- const logicalRoot = resolve2(cwd);
1053
- const logicalOutDir = resolve2(logicalRoot, requestedOutDir);
1526
+ const logicalRoot = resolve3(cwd);
1527
+ const logicalOutDir = resolve3(logicalRoot, requestedOutDir);
1054
1528
  if (!isPathWithin(logicalRoot, logicalOutDir)) {
1055
1529
  throw new Error(`Tileflow artifact output escapes its working directory: ${logicalOutDir}`);
1056
1530
  }
1057
- const rootStats = await lstat(logicalRoot);
1531
+ const rootStats = await lstat2(logicalRoot);
1058
1532
  if (!rootStats.isDirectory()) {
1059
1533
  throw new Error(`Tileflow artifact working directory is not a directory: ${logicalRoot}`);
1060
1534
  }
1061
- const rootDir = await realpath(logicalRoot);
1535
+ const rootDir = await realpath2(logicalRoot);
1062
1536
  const relativeOutDir = relative2(logicalRoot, logicalOutDir);
1063
1537
  const segments = relativeOutDir ? relativeOutDir.split(sep2) : [];
1064
1538
  let current = rootDir;
1065
1539
  for (const segment of segments) {
1066
- current = resolve2(current, segment);
1540
+ current = resolve3(current, segment);
1067
1541
  let stats;
1068
1542
  try {
1069
- stats = await lstat(current);
1543
+ stats = await lstat2(current);
1070
1544
  } catch (error) {
1071
- if (!hasErrorCode(error, "ENOENT")) throw error;
1072
- await mkdir(current);
1073
- stats = await lstat(current);
1545
+ if (!hasErrorCode2(error, "ENOENT")) throw error;
1546
+ await mkdir2(current);
1547
+ stats = await lstat2(current);
1074
1548
  }
1075
1549
  if (stats.isSymbolicLink()) {
1076
1550
  throw new Error(`Tileflow refuses to write through a symbolic link: ${current}`);
@@ -1079,7 +1553,7 @@ async function prepareTileflowOutputDirectory(cwd, requestedOutDir) {
1079
1553
  throw new Error(`Tileflow artifact output ancestor is not a directory: ${current}`);
1080
1554
  }
1081
1555
  }
1082
- const outDir = await realpath(current);
1556
+ const outDir = await realpath2(current);
1083
1557
  const output = { outDir, rootDir };
1084
1558
  await assertTileflowOutputDirectoryIsSafe(output);
1085
1559
  return output;
@@ -1088,14 +1562,14 @@ async function assertTileflowOutputDirectoryIsSafe(output) {
1088
1562
  if (!isPathWithin(output.rootDir, output.outDir)) {
1089
1563
  throw new Error(`Tileflow artifact output escapes its working directory: ${output.outDir}`);
1090
1564
  }
1091
- const stats = await lstat(output.outDir);
1565
+ const stats = await lstat2(output.outDir);
1092
1566
  if (stats.isSymbolicLink()) {
1093
1567
  throw new Error(`Tileflow refuses to write through a symbolic link: ${output.outDir}`);
1094
1568
  }
1095
1569
  if (!stats.isDirectory()) {
1096
1570
  throw new Error(`Tileflow artifact output is not a directory: ${output.outDir}`);
1097
1571
  }
1098
- const canonicalOutDir = await realpath(output.outDir);
1572
+ const canonicalOutDir = await realpath2(output.outDir);
1099
1573
  if (canonicalOutDir !== output.outDir || !isPathWithin(output.rootDir, canonicalOutDir)) {
1100
1574
  throw new Error(`Tileflow artifact output escapes its working directory: ${output.outDir}`);
1101
1575
  }
@@ -1110,21 +1584,21 @@ function assertTileflowArtifactFileName(fileName) {
1110
1584
  const logicalName = fileName.replace(/^generations\/[a-f0-9]{64}\//, "");
1111
1585
  const styleMatch = /^styles\/([^/]+)\/([^/]+)\.json$/u.exec(logicalName);
1112
1586
  const iconMatch = /^icons\/([^/]+)\/sprite(?:@2x)?\.(?:json|png)$/u.exec(logicalName);
1113
- if (fileName === "manifest.json" || fileName === tileflowMapBuildManifestFileName || styleMatch && tileflowMapIdSchema.safeParse(styleMatch[1]).success && tileflowThemeNameSchema.safeParse(styleMatch[2]).success || iconMatch && tileflowMapIdSchema.safeParse(iconMatch[1]).success || /^fonts\/[a-z0-9]+(?:-[a-z0-9]+)*-[a-f0-9]{64}\.(?:otf|ttf|woff2)$/.test(logicalName) || /^fonts\/licenses\/license-[a-f0-9]{64}\.txt$/.test(logicalName)) {
1587
+ if (fileName === "manifest.json" || fileName === tileflowMapBuildManifestFileName || styleMatch && tileflowMapIdSchema.safeParse(styleMatch[1]).success && tileflowThemeNameSchema.safeParse(styleMatch[2]).success || iconMatch && tileflowMapIdSchema.safeParse(iconMatch[1]).success || /^tilesets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.pmtiles$/u.test(logicalName) || /^fonts\/[a-z0-9]+(?:-[a-z0-9]+)*-[a-f0-9]{64}\.(?:otf|ttf|woff2)$/.test(logicalName) || /^fonts\/licenses\/license-[a-f0-9]{64}\.txt$/.test(logicalName)) {
1114
1588
  return;
1115
1589
  }
1116
1590
  throw new Error(`Unexpected Tileflow managed artifact file name: ${fileName}`);
1117
1591
  }
1118
1592
  function managedPath(root, fileName) {
1119
1593
  assertSafeManagedFileName(fileName);
1120
- const path = resolve2(root, ...fileName.split("/"));
1594
+ const path = resolve3(root, ...fileName.split("/"));
1121
1595
  if (!isPathWithin(root, path)) {
1122
1596
  throw new Error(`Unsafe Tileflow artifact file name: ${fileName}`);
1123
1597
  }
1124
1598
  return path;
1125
1599
  }
1126
1600
  async function writeFileDurably(path, source) {
1127
- const handle = await open(path, "wx");
1601
+ const handle = await open2(path, "wx");
1128
1602
  try {
1129
1603
  await handle.writeFile(source);
1130
1604
  await handle.sync();
@@ -1134,19 +1608,19 @@ async function writeFileDurably(path, source) {
1134
1608
  }
1135
1609
  async function isFile(path) {
1136
1610
  try {
1137
- return (await lstat(path)).isFile();
1611
+ return (await lstat2(path)).isFile();
1138
1612
  } catch (error) {
1139
- if (hasErrorCode(error, "ENOENT")) return false;
1613
+ if (hasErrorCode2(error, "ENOENT")) return false;
1140
1614
  throw error;
1141
1615
  }
1142
1616
  }
1143
1617
  async function removeManagedFile(path) {
1144
1618
  try {
1145
- const stats = await lstat(path);
1619
+ const stats = await lstat2(path);
1146
1620
  if (!stats.isFile()) throw new Error(`Refusing to remove non-file artifact target: ${path}`);
1147
1621
  await unlink(path);
1148
1622
  } catch (error) {
1149
- if (!hasErrorCode(error, "ENOENT")) throw error;
1623
+ if (!hasErrorCode2(error, "ENOENT")) throw error;
1150
1624
  }
1151
1625
  }
1152
1626
  function hashArtifactGeneration(files) {
@@ -1169,18 +1643,18 @@ function isRelativePublicUrl(value) {
1169
1643
  return !(value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:"));
1170
1644
  }
1171
1645
  function uniqueStrings(values) {
1172
- return [...new Set(values)].sort(compareCodeUnits);
1646
+ return [...new Set(values)].sort(compareCodeUnits2);
1173
1647
  }
1174
- function hasErrorCode(error, code) {
1648
+ function hasErrorCode2(error, code) {
1175
1649
  return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
1176
1650
  }
1177
1651
  function canonicalInputPath(path) {
1178
- const resolved = resolve2(path);
1652
+ const resolved = resolve3(path);
1179
1653
  try {
1180
1654
  return realpathSync2.native(resolved);
1181
1655
  } catch {
1182
1656
  try {
1183
- return resolve2(realpathSync2.native(dirname2(resolved)), resolved.split(/[\\/]/).at(-1));
1657
+ return resolve3(realpathSync2.native(dirname2(resolved)), resolved.split(/[\\/]/).at(-1));
1184
1658
  } catch {
1185
1659
  return resolved;
1186
1660
  }
@@ -1188,11 +1662,13 @@ function canonicalInputPath(path) {
1188
1662
  }
1189
1663
 
1190
1664
  export {
1665
+ prepareTileflowLocalTilesets,
1191
1666
  tileflowArtifactSessionSchemaVersion,
1192
1667
  createTileflowArtifactDiagnostics,
1193
1668
  tileflowArtifactPlanSchemaVersion,
1194
1669
  tileflowArtifactInventoryFileName,
1195
1670
  TileflowHostedManifestOverwriteError,
1671
+ TileflowLocalTilesetProductionError,
1196
1672
  createTileflowManifest,
1197
1673
  createTileflowStyle,
1198
1674
  createTileflowStyles,
@@ -1205,6 +1681,8 @@ export {
1205
1681
  isTileflowArtifactInputPath,
1206
1682
  refreshTileflowArtifactSession,
1207
1683
  writeTileflowBuildArtifacts,
1684
+ disposeTileflowBuildArtifacts,
1208
1685
  writeTileflowArtifactPlan,
1686
+ assertNoLocalTilesetsForProduction,
1209
1687
  assertTileflowSelfHostedManifestTarget
1210
1688
  };