@tileflow/dev 0.1.0-alpha.17 → 0.1.0-alpha.19
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/GENERATED_OUTPUT_LICENSE.md +21 -0
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +398 -34
- package/THIRD_PARTY_NOTICES.md +69 -0
- package/TRADEMARKS.md +10 -0
- package/dist/artifacts.d.ts +152 -0
- package/dist/artifacts.js +62 -0
- package/dist/chunk-BALG4DSG.js +844 -0
- package/dist/chunk-BKHIZ7H2.js +287 -0
- package/dist/chunk-BL3FK4GO.js +170 -0
- package/dist/chunk-D4OE4ITL.js +13 -0
- package/dist/chunk-GWUMIHL6.js +4208 -0
- package/dist/chunk-KDFTE4Q5.js +156 -0
- package/dist/chunk-O7PE6FB2.js +1210 -0
- package/dist/chunk-OEABXOJH.js +1991 -0
- package/dist/chunk-VEB43KDS.js +670 -0
- package/dist/chunk-ZOYFZ3MT.js +801 -0
- package/dist/chunk-ZRI6PNFL.js +211 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +27 -0
- package/dist/fonts.d.ts +47 -0
- package/dist/fonts.js +16 -0
- package/dist/icons.d.ts +136 -0
- package/dist/icons.js +17 -0
- package/dist/index.d.ts +12 -322
- package/dist/index.js +128 -2491
- package/dist/inspect.d.ts +147 -0
- package/dist/inspect.js +16 -0
- package/dist/preview-assets-6LYL7GP7.js +145 -0
- package/dist/preview.d.ts +69 -0
- package/dist/preview.js +18 -0
- package/dist/server.d.ts +77 -0
- package/dist/server.js +47 -0
- package/dist/validation.d.ts +57 -0
- package/dist/validation.js +22 -0
- package/package.json +62 -6
package/dist/index.js
CHANGED
|
@@ -1,2533 +1,170 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { createJiti } from "jiti";
|
|
3
|
-
import { existsSync, readFileSync, realpathSync as realpathSync2 } from "fs";
|
|
4
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
5
|
-
import { createRequire } from "module";
|
|
6
|
-
import { dirname as dirname2, isAbsolute as isAbsolute3, join, relative as relative3, resolve as resolve3, sep as sep3 } from "path";
|
|
7
1
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "@tileflow/core";
|
|
14
|
-
|
|
15
|
-
// src/icons.ts
|
|
16
|
-
import { readdir, readFile, realpath, stat } from "fs/promises";
|
|
17
|
-
import { basename, extname, isAbsolute, relative, resolve, sep } from "path";
|
|
18
|
-
import { SaxesParser } from "saxes";
|
|
2
|
+
TileflowConfigInspectionError,
|
|
3
|
+
inspectLoadedTileflowConfig,
|
|
4
|
+
inspectTileflowConfig,
|
|
5
|
+
inspectTileflowFeatures
|
|
6
|
+
} from "./chunk-ZOYFZ3MT.js";
|
|
19
7
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
tileflowIconPackageLabelSchema,
|
|
27
|
-
tileflowIconPackageLimits,
|
|
28
|
-
tileflowIconPackageManifestSchema
|
|
29
|
-
} from "@tileflow/core";
|
|
30
|
-
var TileflowIconCompilationError = class extends Error {
|
|
31
|
-
code = "ICON_INVALID";
|
|
32
|
-
issues;
|
|
33
|
-
phase = "icon-compilation";
|
|
34
|
-
constructor(issues) {
|
|
35
|
-
super(
|
|
36
|
-
[
|
|
37
|
-
"Invalid Tileflow icon package",
|
|
38
|
-
...issues.map((issue) => `- ${issue.path}: ${issue.message}`)
|
|
39
|
-
].join("\n")
|
|
40
|
-
);
|
|
41
|
-
this.name = "TileflowIconCompilationError";
|
|
42
|
-
this.issues = issues;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
var iconFileExtensions = /* @__PURE__ */ new Set([".svg", ".png", ".jpg", ".jpeg", ".webp"]);
|
|
46
|
-
var iconSpriteSize = 24;
|
|
47
|
-
async function compileTileflowIconPackages(project, options) {
|
|
48
|
-
const result = await compileIconSources(project, options);
|
|
49
|
-
const packagesByHash = /* @__PURE__ */ new Map();
|
|
50
|
-
for (const compiled of result.compiledBySource.values()) {
|
|
51
|
-
packagesByHash.set(compiled.package.contentHash, compiled.package);
|
|
52
|
-
}
|
|
53
|
-
const bindings = result.resolvedRequests.map((request) => {
|
|
54
|
-
const compiled = result.compiledBySource.get(request.realSourceDir);
|
|
55
|
-
if (!compiled) {
|
|
56
|
-
throw new Error(`Missing compiled icon package for map ${request.mapName}`);
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
label: request.label,
|
|
60
|
-
mapName: request.mapName,
|
|
61
|
-
...request.mapping ? { mapping: request.mapping } : {},
|
|
62
|
-
packageHash: compiled.package.contentHash
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
|
-
return {
|
|
66
|
-
bindings,
|
|
67
|
-
packages: [...packagesByHash.values()].sort(
|
|
68
|
-
(left, right) => compareCodeUnits(left.contentHash, right.contentHash)
|
|
69
|
-
),
|
|
70
|
-
watchPaths: [...result.compiledBySource.keys()].sort(compareCodeUnits)
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
async function inspectTileflowIconCatalogs(project, options) {
|
|
74
|
-
const result = await compileIconSources(project, {
|
|
75
|
-
cwd: options.cwd,
|
|
76
|
-
mapNames: options.mapNames,
|
|
77
|
-
target: "local"
|
|
78
|
-
});
|
|
79
|
-
const resolvedByMap = new Map(
|
|
80
|
-
result.resolvedRequests.map((request) => [request.mapName, request])
|
|
81
|
-
);
|
|
82
|
-
const mappedFromBySource = /* @__PURE__ */ new Map();
|
|
83
|
-
const maps = result.mapRequests.map((request) => {
|
|
84
|
-
if (request.kind !== "local") {
|
|
85
|
-
return {
|
|
86
|
-
name: request.mapName,
|
|
87
|
-
icons: {
|
|
88
|
-
inspectable: false,
|
|
89
|
-
kind: request.kind,
|
|
90
|
-
mappings: createCatalogMappings(request.mapping, "unknown")
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const resolved = resolvedByMap.get(request.mapName);
|
|
95
|
-
if (!resolved || !result.realCwd) {
|
|
96
|
-
throw new Error(`Missing resolved icon source for map ${request.mapName}`);
|
|
97
|
-
}
|
|
98
|
-
const compiled = result.compiledBySource.get(resolved.realSourceDir);
|
|
99
|
-
if (!compiled) {
|
|
100
|
-
throw new Error(`Missing compiled icon catalog for map ${request.mapName}`);
|
|
101
|
-
}
|
|
102
|
-
const iconNames = new Set(compiled.package.manifest.iconNames);
|
|
103
|
-
const mappings = createCatalogMappings(
|
|
104
|
-
request.mapping,
|
|
105
|
-
(iconId) => iconNames.has(iconId) ? "present" : "missing"
|
|
106
|
-
);
|
|
107
|
-
const mappedFromByIcon = mappedFromBySource.get(resolved.realSourceDir) ?? /* @__PURE__ */ new Map();
|
|
108
|
-
for (const mapping of mappings) {
|
|
109
|
-
if (mapping.targetStatus !== "present") {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const mappedFrom = mappedFromByIcon.get(mapping.iconId) ?? [];
|
|
113
|
-
mappedFrom.push({ map: request.mapName, semantic: mapping.semantic });
|
|
114
|
-
mappedFromByIcon.set(mapping.iconId, mappedFrom);
|
|
115
|
-
}
|
|
116
|
-
mappedFromBySource.set(resolved.realSourceDir, mappedFromByIcon);
|
|
117
|
-
return {
|
|
118
|
-
name: request.mapName,
|
|
119
|
-
icons: {
|
|
120
|
-
catalogSourcePath: relativePortablePath(result.realCwd, resolved.realSourceDir),
|
|
121
|
-
kind: "local",
|
|
122
|
-
label: request.label,
|
|
123
|
-
mappings,
|
|
124
|
-
packageHash: compiled.package.contentHash
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
});
|
|
128
|
-
if (!result.realCwd) {
|
|
129
|
-
return { catalogs: [], maps };
|
|
130
|
-
}
|
|
131
|
-
const realCwd = result.realCwd;
|
|
132
|
-
const catalogs = [...result.compiledBySource.entries()].map(([realSourceDir, compiled]) => {
|
|
133
|
-
const sourcePath2 = relativePortablePath(realCwd, realSourceDir);
|
|
134
|
-
const mappedFromByIcon = mappedFromBySource.get(realSourceDir) ?? /* @__PURE__ */ new Map();
|
|
135
|
-
return {
|
|
136
|
-
compiledPackage: compiled.package,
|
|
137
|
-
icons: compiled.icons.map((icon, index) => {
|
|
138
|
-
const manifestIcon = compiled.package.manifest.renderedIcons[index];
|
|
139
|
-
const oneXAtlas = compiled.layoutOneX.index[icon.input.name];
|
|
140
|
-
const twoXAtlas = compiled.layoutTwoX.index[icon.input.name];
|
|
141
|
-
if (!manifestIcon || manifestIcon.name !== icon.input.name || !oneXAtlas || !twoXAtlas) {
|
|
142
|
-
throw new Error(`Missing compiled catalog metadata for icon ${icon.input.name}`);
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
id: icon.input.name,
|
|
146
|
-
mappedFrom: [...mappedFromByIcon.get(icon.input.name) ?? []].sort(compareMappedFrom),
|
|
147
|
-
rendered: {
|
|
148
|
-
oneX: {
|
|
149
|
-
atlas: atlasRectangle(oneXAtlas),
|
|
150
|
-
height: icon.oneX.height,
|
|
151
|
-
pixelRatio: 1,
|
|
152
|
-
pixelSha256: manifestIcon.pixelSha256.oneX,
|
|
153
|
-
width: icon.oneX.width
|
|
154
|
-
},
|
|
155
|
-
twoX: {
|
|
156
|
-
atlas: atlasRectangle(twoXAtlas),
|
|
157
|
-
height: icon.twoX.height,
|
|
158
|
-
pixelRatio: 2,
|
|
159
|
-
pixelSha256: manifestIcon.pixelSha256.twoX,
|
|
160
|
-
width: icon.twoX.width
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
source: {
|
|
164
|
-
byteLength: icon.input.source.byteLength,
|
|
165
|
-
dimensions: { ...icon.dimensions },
|
|
166
|
-
format: icon.input.format,
|
|
167
|
-
path: relativePortablePath(realCwd, icon.input.path)
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
}),
|
|
171
|
-
insideWorkingTree: isPathInside(realCwd, realSourceDir),
|
|
172
|
-
sourcePath: sourcePath2
|
|
173
|
-
};
|
|
174
|
-
}).sort((left, right) => compareCodeUnits(left.sourcePath, right.sourcePath));
|
|
175
|
-
return { catalogs, maps };
|
|
176
|
-
}
|
|
177
|
-
async function compileIconSources(project, options) {
|
|
178
|
-
const mapRequests = getMapIconRequests(project, options.cwd, options.mapNames);
|
|
179
|
-
const requests = mapRequests.filter(
|
|
180
|
-
(request) => request.kind === "local"
|
|
181
|
-
);
|
|
182
|
-
if (requests.length === 0) {
|
|
183
|
-
return {
|
|
184
|
-
compiledBySource: /* @__PURE__ */ new Map(),
|
|
185
|
-
mapRequests,
|
|
186
|
-
realCwd: null,
|
|
187
|
-
resolvedRequests: []
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
const issues = [];
|
|
191
|
-
const realCwd = await resolveWorkingDirectory(options.cwd, issues);
|
|
192
|
-
const resolvedRequests = [];
|
|
193
|
-
for (const request of requests) {
|
|
194
|
-
try {
|
|
195
|
-
const realSourceDir = await realpath(request.sourceDir);
|
|
196
|
-
if (options.target === "hosted" && realCwd && !isPathInside(realCwd, realSourceDir)) {
|
|
197
|
-
issues.push({
|
|
198
|
-
message: "Hosted icon sources must remain inside the selected working tree",
|
|
199
|
-
path: sourcePath(request.mapName)
|
|
200
|
-
});
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
const sourceStat = await stat(realSourceDir);
|
|
204
|
-
if (!sourceStat.isDirectory()) {
|
|
205
|
-
issues.push({
|
|
206
|
-
message: "Icon source must be a directory",
|
|
207
|
-
path: sourcePath(request.mapName)
|
|
208
|
-
});
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
resolvedRequests.push({ ...request, realSourceDir });
|
|
212
|
-
} catch (error) {
|
|
213
|
-
issues.push({
|
|
214
|
-
message: describeFileSystemError(error, "Icon source was not found"),
|
|
215
|
-
path: sourcePath(request.mapName)
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
const inspectedBySource = /* @__PURE__ */ new Map();
|
|
220
|
-
const requestsBySource = groupRequestsBySource(resolvedRequests);
|
|
221
|
-
for (const [realSourceDir, sourceRequests] of requestsBySource) {
|
|
222
|
-
const representative = sourceRequests[0];
|
|
223
|
-
if (!representative) {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
const inspected = await inspectIconSource(
|
|
227
|
-
representative,
|
|
228
|
-
realSourceDir,
|
|
229
|
-
realCwd,
|
|
230
|
-
options.target,
|
|
231
|
-
issues
|
|
232
|
-
);
|
|
233
|
-
if (inspected) {
|
|
234
|
-
inspectedBySource.set(realSourceDir, inspected);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (issues.length > 0) {
|
|
238
|
-
throw new TileflowIconCompilationError(issues);
|
|
239
|
-
}
|
|
240
|
-
const compiledBySource = /* @__PURE__ */ new Map();
|
|
241
|
-
for (const [realSourceDir, inspected] of inspectedBySource) {
|
|
242
|
-
const representative = requestsBySource.get(realSourceDir)?.[0];
|
|
243
|
-
if (!representative) {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
try {
|
|
247
|
-
compiledBySource.set(realSourceDir, await compileInspectedIconSource(inspected));
|
|
248
|
-
} catch (error) {
|
|
249
|
-
issues.push({
|
|
250
|
-
message: error instanceof Error ? error.message : "Icon compilation failed",
|
|
251
|
-
path: sourcePath(representative.mapName)
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (issues.length > 0) {
|
|
256
|
-
throw new TileflowIconCompilationError(issues);
|
|
257
|
-
}
|
|
258
|
-
return { compiledBySource, mapRequests, realCwd, resolvedRequests };
|
|
259
|
-
}
|
|
260
|
-
async function prepareTileflowProjectIcons(project, options) {
|
|
261
|
-
const nextProject = cloneProject(project);
|
|
262
|
-
const compiled = await compileTileflowIconPackages(project, {
|
|
263
|
-
cwd: options.cwd,
|
|
264
|
-
target: "local"
|
|
265
|
-
});
|
|
266
|
-
const packagesByHash = new Map(
|
|
267
|
-
compiled.packages.map((iconPackage) => [iconPackage.contentHash, iconPackage])
|
|
268
|
-
);
|
|
269
|
-
const assets = [];
|
|
270
|
-
for (const binding of compiled.bindings) {
|
|
271
|
-
const mapConfig = nextProject.maps[binding.mapName];
|
|
272
|
-
const iconPackage = packagesByHash.get(binding.packageHash);
|
|
273
|
-
if (!mapConfig || !iconPackage) {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
const spriteUrl = joinUrl(options.assetBaseUrl, `icons/${binding.mapName}/sprite`);
|
|
277
|
-
mapConfig.icons = {
|
|
278
|
-
...binding.mapping ? { mapping: binding.mapping } : {},
|
|
279
|
-
sprite: spriteUrl
|
|
280
|
-
};
|
|
281
|
-
for (const file of iconPackage.files) {
|
|
282
|
-
assets.push({
|
|
283
|
-
contentType: file.contentType === "application/json" ? "application/json; charset=utf-8" : file.contentType,
|
|
284
|
-
fileName: `icons/${binding.mapName}/${file.fileName}`,
|
|
285
|
-
source: file.contentType === "application/json" ? new TextDecoder().decode(file.source) : file.source
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return {
|
|
290
|
-
assets,
|
|
291
|
-
project: nextProject,
|
|
292
|
-
watchPaths: compiled.watchPaths
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
function getTileflowIconWatchPaths(project, cwd) {
|
|
296
|
-
return uniqueStrings(getLocalIconRequests(project, cwd).map((request) => request.sourceDir)).sort(
|
|
297
|
-
compareCodeUnits
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
function getLocalIconRequests(project, cwd) {
|
|
301
|
-
return getMapIconRequests(project, cwd).filter(
|
|
302
|
-
(request) => request.kind === "local"
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
function getMapIconRequests(project, cwd, mapNames) {
|
|
306
|
-
const selectedNames = (mapNames === void 0 ? Object.keys(project.maps) : [...new Set(mapNames)]).sort(compareCodeUnits);
|
|
307
|
-
return selectedNames.map((mapName) => {
|
|
308
|
-
const mapConfig = project.maps[mapName];
|
|
309
|
-
if (!mapConfig) {
|
|
310
|
-
throw new Error(`Unknown Tileflow map: ${mapName}`);
|
|
311
|
-
}
|
|
312
|
-
const iconSet = resolveIconSet(mapConfig.icons, project.icons);
|
|
313
|
-
const mapping = sortMapping(iconSet?.mapping);
|
|
314
|
-
if (iconSet?.source) {
|
|
315
|
-
return {
|
|
316
|
-
kind: "local",
|
|
317
|
-
label: resolvePackageLabel(mapConfig.icons, iconSet.source, project.icons, mapName),
|
|
318
|
-
mapName,
|
|
319
|
-
...mapping ? { mapping } : {},
|
|
320
|
-
source: iconSet.source,
|
|
321
|
-
sourceDir: resolve(cwd, iconSet.source)
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
return {
|
|
325
|
-
kind: iconSet?.sprite ? "external" : "none",
|
|
326
|
-
mapName,
|
|
327
|
-
...mapping ? { mapping } : {}
|
|
328
|
-
};
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function resolveIconSet(icons, iconSets, path = []) {
|
|
332
|
-
if (!icons) {
|
|
333
|
-
return void 0;
|
|
334
|
-
}
|
|
335
|
-
if (typeof icons === "string") {
|
|
336
|
-
const referenced = iconSets?.[icons];
|
|
337
|
-
if (referenced) {
|
|
338
|
-
if (path.includes(icons)) {
|
|
339
|
-
throw new Error(`Circular Tileflow icon set extends: ${[...path, icons].join(" -> ")}`);
|
|
340
|
-
}
|
|
341
|
-
return resolveIconSet(referenced, iconSets, [...path, icons]);
|
|
342
|
-
}
|
|
343
|
-
return isRemoteSpriteReference(icons) ? { sprite: icons } : { source: icons };
|
|
344
|
-
}
|
|
345
|
-
const extended = icons.extends ? resolveIconSet(icons.extends, iconSets, path) : void 0;
|
|
346
|
-
return {
|
|
347
|
-
...extended,
|
|
348
|
-
...icons,
|
|
349
|
-
mapping: {
|
|
350
|
-
...extended?.mapping,
|
|
351
|
-
...icons.mapping
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
async function resolveWorkingDirectory(cwd, issues) {
|
|
356
|
-
try {
|
|
357
|
-
const resolved = await realpath(cwd);
|
|
358
|
-
if (!(await stat(resolved)).isDirectory()) {
|
|
359
|
-
throw new Error("Working tree is not a directory");
|
|
360
|
-
}
|
|
361
|
-
return resolved;
|
|
362
|
-
} catch (error) {
|
|
363
|
-
issues.push({
|
|
364
|
-
message: describeFileSystemError(error, "Selected working tree was not found"),
|
|
365
|
-
path: "cwd"
|
|
366
|
-
});
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
function groupRequestsBySource(requests) {
|
|
371
|
-
const result = /* @__PURE__ */ new Map();
|
|
372
|
-
for (const request of requests) {
|
|
373
|
-
const existing = result.get(request.realSourceDir) ?? [];
|
|
374
|
-
existing.push(request);
|
|
375
|
-
result.set(request.realSourceDir, existing);
|
|
376
|
-
}
|
|
377
|
-
return result;
|
|
378
|
-
}
|
|
379
|
-
async function inspectIconSource(request, realSourceDir, realCwd, target, issues) {
|
|
380
|
-
let entries;
|
|
381
|
-
try {
|
|
382
|
-
entries = await readdir(realSourceDir, { withFileTypes: true });
|
|
383
|
-
} catch (error) {
|
|
384
|
-
issues.push({
|
|
385
|
-
message: describeFileSystemError(error, "Icon source could not be read"),
|
|
386
|
-
path: sourcePath(request.mapName)
|
|
387
|
-
});
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
entries.sort((left, right) => compareCodeUnits(left.name, right.name));
|
|
391
|
-
const candidates = [];
|
|
392
|
-
const names = /* @__PURE__ */ new Set();
|
|
393
|
-
let aggregateBytes = 0;
|
|
394
|
-
for (const entry of entries) {
|
|
395
|
-
const entryPath = resolve(realSourceDir, entry.name);
|
|
396
|
-
const path = iconPath(request.mapName, entry.name);
|
|
397
|
-
if (entry.isDirectory()) {
|
|
398
|
-
if (target === "hosted") {
|
|
399
|
-
issues.push({ message: "Nested directories are not supported", path });
|
|
400
|
-
}
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
let entryStat;
|
|
404
|
-
let realEntryPath = entryPath;
|
|
405
|
-
try {
|
|
406
|
-
if (entry.isSymbolicLink()) {
|
|
407
|
-
if (target === "local") {
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
realEntryPath = await realpath(entryPath);
|
|
411
|
-
if (!realCwd || !isPathInside(realCwd, realEntryPath)) {
|
|
412
|
-
issues.push({ message: "Icon symlink escapes the selected working tree", path });
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
entryStat = await stat(realEntryPath);
|
|
417
|
-
} catch (error) {
|
|
418
|
-
if (target === "hosted") {
|
|
419
|
-
issues.push({
|
|
420
|
-
message: describeFileSystemError(error, "Icon entry could not be read"),
|
|
421
|
-
path
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
if (!entryStat.isFile()) {
|
|
427
|
-
if (target === "hosted") {
|
|
428
|
-
issues.push({ message: "Icon entries must be regular files", path });
|
|
429
|
-
}
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
const extension = extname(entry.name).toLowerCase();
|
|
433
|
-
if (!iconFileExtensions.has(extension)) {
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
const name = entry.name.slice(0, -extname(entry.name).length);
|
|
437
|
-
if (names.has(name)) {
|
|
438
|
-
issues.push({
|
|
439
|
-
message: `Duplicate icon basename "${name}"`,
|
|
440
|
-
path
|
|
441
|
-
});
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
names.add(name);
|
|
445
|
-
if (target === "hosted" && !tileflowHostedIconIdPattern.test(name)) {
|
|
446
|
-
issues.push({
|
|
447
|
-
message: "Hosted icon IDs must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$",
|
|
448
|
-
path
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
if (entryStat.size > tileflowIconPackageLimits.maxSourceFileBytes) {
|
|
452
|
-
issues.push({
|
|
453
|
-
message: `Icon source exceeds ${tileflowIconPackageLimits.maxSourceFileBytes} bytes`,
|
|
454
|
-
path
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
aggregateBytes += entryStat.size;
|
|
458
|
-
candidates.push({
|
|
459
|
-
fileName: entry.name,
|
|
460
|
-
format: iconSourceFormat(extension),
|
|
461
|
-
name,
|
|
462
|
-
path: realEntryPath,
|
|
463
|
-
size: entryStat.size
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
candidates.sort((left, right) => compareCodeUnits(left.name, right.name));
|
|
467
|
-
if (candidates.length === 0) {
|
|
468
|
-
issues.push({
|
|
469
|
-
message: "No supported .svg, .png, .jpg, .jpeg, or .webp icon files were found",
|
|
470
|
-
path: sourcePath(request.mapName)
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
if (candidates.length > tileflowIconPackageLimits.maxIconCount) {
|
|
474
|
-
issues.push({
|
|
475
|
-
message: `Icon source contains more than ${tileflowIconPackageLimits.maxIconCount} icons`,
|
|
476
|
-
path: sourcePath(request.mapName)
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
if (aggregateBytes > tileflowIconPackageLimits.maxSourceBytes) {
|
|
480
|
-
issues.push({
|
|
481
|
-
message: `Icon source exceeds ${tileflowIconPackageLimits.maxSourceBytes} aggregate bytes`,
|
|
482
|
-
path: sourcePath(request.mapName)
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
if (issues.length > 0) {
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
const icons = [];
|
|
489
|
-
for (const candidate of candidates) {
|
|
490
|
-
const displayPath = iconPath(request.mapName, candidate.fileName);
|
|
491
|
-
try {
|
|
492
|
-
const source = await readFile(candidate.path);
|
|
493
|
-
if (source.byteLength !== candidate.size || source.byteLength > tileflowIconPackageLimits.maxSourceFileBytes) {
|
|
494
|
-
throw new Error("Icon changed while it was being validated");
|
|
495
|
-
}
|
|
496
|
-
if (target === "hosted" && extname(candidate.fileName).toLowerCase() === ".svg") {
|
|
497
|
-
validateHostedSvg(source, displayPath);
|
|
498
|
-
}
|
|
499
|
-
icons.push({
|
|
500
|
-
fileName: candidate.fileName,
|
|
501
|
-
format: candidate.format,
|
|
502
|
-
name: candidate.name,
|
|
503
|
-
path: candidate.path,
|
|
504
|
-
source
|
|
505
|
-
});
|
|
506
|
-
} catch (error) {
|
|
507
|
-
issues.push({
|
|
508
|
-
message: error instanceof Error ? error.message : "Icon could not be read",
|
|
509
|
-
path: displayPath
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
if (issues.length > 0) {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
return { icons, realSourceDir };
|
|
517
|
-
}
|
|
518
|
-
async function compileInspectedIconSource(inspected) {
|
|
519
|
-
const layoutOneX = createSpriteLayout(inspected.icons, 1);
|
|
520
|
-
const layoutTwoX = createSpriteLayout(inspected.icons, 2);
|
|
521
|
-
const rendered = await mapWithConcurrency(
|
|
522
|
-
inspected.icons,
|
|
523
|
-
tileflowIconPackageLimits.decodeConcurrency,
|
|
524
|
-
async (icon) => {
|
|
525
|
-
const dimensions = await validateDecodedDimensions(icon);
|
|
526
|
-
const oneX = await renderIcon(icon, 1);
|
|
527
|
-
const twoX = await renderIcon(icon, 2);
|
|
528
|
-
return {
|
|
529
|
-
dimensions,
|
|
530
|
-
input: icon,
|
|
531
|
-
oneX,
|
|
532
|
-
pixelSha256: {
|
|
533
|
-
oneX: await hashTileflowRenderedIconPixels({
|
|
534
|
-
height: oneX.height,
|
|
535
|
-
pixelRatio: 1,
|
|
536
|
-
rgba: oneX.rgba,
|
|
537
|
-
width: oneX.width
|
|
538
|
-
}),
|
|
539
|
-
twoX: await hashTileflowRenderedIconPixels({
|
|
540
|
-
height: twoX.height,
|
|
541
|
-
pixelRatio: 2,
|
|
542
|
-
rgba: twoX.rgba,
|
|
543
|
-
width: twoX.width
|
|
544
|
-
})
|
|
545
|
-
},
|
|
546
|
-
twoX
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
);
|
|
550
|
-
const [oneXImage, twoXImage] = await Promise.all([
|
|
551
|
-
createSpriteImage(
|
|
552
|
-
rendered.map((item) => item.oneX),
|
|
553
|
-
layoutOneX
|
|
554
|
-
),
|
|
555
|
-
createSpriteImage(
|
|
556
|
-
rendered.map((item) => item.twoX),
|
|
557
|
-
layoutTwoX
|
|
558
|
-
)
|
|
559
|
-
]);
|
|
560
|
-
const oneXJson = new TextEncoder().encode(`${serializeCanonicalJson(layoutOneX.index)}
|
|
561
|
-
`);
|
|
562
|
-
const twoXJson = new TextEncoder().encode(`${serializeCanonicalJson(layoutTwoX.index)}
|
|
563
|
-
`);
|
|
564
|
-
const files = [
|
|
565
|
-
{ contentType: "application/json", fileName: "sprite.json", source: oneXJson },
|
|
566
|
-
{ contentType: "image/png", fileName: "sprite.png", source: oneXImage },
|
|
567
|
-
{ contentType: "application/json", fileName: "sprite@2x.json", source: twoXJson },
|
|
568
|
-
{ contentType: "image/png", fileName: "sprite@2x.png", source: twoXImage }
|
|
569
|
-
];
|
|
570
|
-
assertGeneratedFileLimits(files);
|
|
571
|
-
const fileDigests = await Promise.all(files.map((file) => sha256Hex(file.source)));
|
|
572
|
-
const manifest = tileflowIconPackageManifestSchema.parse({
|
|
573
|
-
files: files.map((file, index) => ({
|
|
574
|
-
byteLength: file.source.byteLength,
|
|
575
|
-
contentType: file.contentType,
|
|
576
|
-
name: file.fileName,
|
|
577
|
-
sha256: fileDigests[index]
|
|
578
|
-
})),
|
|
579
|
-
format: "tileflow-icon-package-v1",
|
|
580
|
-
iconNames: inspected.icons.map((icon) => icon.name),
|
|
581
|
-
renderedIcons: inspected.icons.map((icon, index) => {
|
|
582
|
-
const pixels = rendered[index];
|
|
583
|
-
if (!pixels) {
|
|
584
|
-
throw new Error(`Missing rendered pixels for ${icon.name}`);
|
|
585
|
-
}
|
|
586
|
-
return { name: icon.name, pixelSha256: pixels.pixelSha256 };
|
|
587
|
-
}),
|
|
588
|
-
sprites: {
|
|
589
|
-
oneX: { height: layoutOneX.height, pixelRatio: 1, width: layoutOneX.width },
|
|
590
|
-
twoX: { height: layoutTwoX.height, pixelRatio: 2, width: layoutTwoX.width }
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
return {
|
|
594
|
-
icons: rendered,
|
|
595
|
-
layoutOneX,
|
|
596
|
-
layoutTwoX,
|
|
597
|
-
package: {
|
|
598
|
-
contentHash: await hashTileflowIconPackageManifest(manifest),
|
|
599
|
-
files,
|
|
600
|
-
manifest
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
function createSpriteLayout(icons, pixelRatio) {
|
|
605
|
-
const cellSize = iconSpriteSize * pixelRatio;
|
|
606
|
-
const columns = Math.ceil(Math.sqrt(icons.length));
|
|
607
|
-
const rows = Math.ceil(icons.length / columns);
|
|
608
|
-
const width = columns * cellSize;
|
|
609
|
-
const height = rows * cellSize;
|
|
610
|
-
if (width > tileflowIconPackageLimits.maxAtlasDimension || height > tileflowIconPackageLimits.maxAtlasDimension) {
|
|
611
|
-
throw new Error(
|
|
612
|
-
`Generated sprite exceeds ${tileflowIconPackageLimits.maxAtlasDimension} pixels per dimension`
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
const index = Object.fromEntries(
|
|
616
|
-
icons.map((icon, indexNumber) => {
|
|
617
|
-
const column = indexNumber % columns;
|
|
618
|
-
const row = Math.floor(indexNumber / columns);
|
|
619
|
-
return [
|
|
620
|
-
icon.name,
|
|
621
|
-
{
|
|
622
|
-
height: cellSize,
|
|
623
|
-
pixelRatio,
|
|
624
|
-
width: cellSize,
|
|
625
|
-
x: column * cellSize,
|
|
626
|
-
y: row * cellSize
|
|
627
|
-
}
|
|
628
|
-
];
|
|
629
|
-
})
|
|
630
|
-
);
|
|
631
|
-
return { cellSize, columns, height, index, width };
|
|
632
|
-
}
|
|
633
|
-
async function validateDecodedDimensions(icon) {
|
|
634
|
-
const sharp = await loadSharp();
|
|
635
|
-
const metadata = await sharp(icon.source, {
|
|
636
|
-
density: 72,
|
|
637
|
-
failOn: "error",
|
|
638
|
-
limitInputPixels: tileflowIconPackageLimits.maxDecodedPixelsPerIcon
|
|
639
|
-
}).metadata();
|
|
640
|
-
const width = metadata.width ?? 0;
|
|
641
|
-
const height = metadata.height ?? 0;
|
|
642
|
-
if (width <= 0 || height <= 0) {
|
|
643
|
-
throw new Error(`Unable to determine decoded dimensions for ${icon.fileName}`);
|
|
644
|
-
}
|
|
645
|
-
if (width * height > tileflowIconPackageLimits.maxDecodedPixelsPerIcon) {
|
|
646
|
-
throw new Error(
|
|
647
|
-
`${icon.fileName} exceeds ${tileflowIconPackageLimits.maxDecodedPixelsPerIcon} decoded pixels`
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
return { height, width };
|
|
651
|
-
}
|
|
652
|
-
async function renderIcon(icon, pixelRatio) {
|
|
653
|
-
const sharp = await loadSharp();
|
|
654
|
-
const cellSize = iconSpriteSize * pixelRatio;
|
|
655
|
-
const { data, info } = await sharp(icon.source, {
|
|
656
|
-
density: 72 * pixelRatio,
|
|
657
|
-
failOn: "error",
|
|
658
|
-
limitInputPixels: tileflowIconPackageLimits.maxDecodedPixelsPerIcon
|
|
659
|
-
}).resize(cellSize, cellSize, {
|
|
660
|
-
background: { alpha: 0, b: 0, g: 0, r: 0 },
|
|
661
|
-
fit: "contain"
|
|
662
|
-
}).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
663
|
-
if (info.channels !== 4 || info.width !== cellSize || info.height !== cellSize) {
|
|
664
|
-
throw new Error(`Rendered icon ${icon.fileName} did not produce canonical RGBA pixels`);
|
|
665
|
-
}
|
|
666
|
-
const rgba = new Uint8Array(data);
|
|
667
|
-
const png = await sharp(data, {
|
|
668
|
-
raw: { channels: 4, height: info.height, width: info.width }
|
|
669
|
-
}).png({ adaptiveFiltering: false, compressionLevel: 9, palette: false }).toBuffer();
|
|
670
|
-
return { height: info.height, png, rgba, width: info.width };
|
|
671
|
-
}
|
|
672
|
-
async function createSpriteImage(icons, layout) {
|
|
673
|
-
const sharp = await loadSharp();
|
|
674
|
-
const atlas = new Uint8Array(layout.width * layout.height * 4);
|
|
675
|
-
for (const [indexNumber, icon] of icons.entries()) {
|
|
676
|
-
if (icon.width !== layout.cellSize || icon.height !== layout.cellSize) {
|
|
677
|
-
throw new Error("Rendered icon dimensions do not match the sprite layout");
|
|
678
|
-
}
|
|
679
|
-
const left = indexNumber % layout.columns * layout.cellSize;
|
|
680
|
-
const top = Math.floor(indexNumber / layout.columns) * layout.cellSize;
|
|
681
|
-
const rowBytes = icon.width * 4;
|
|
682
|
-
for (let row = 0; row < icon.height; row += 1) {
|
|
683
|
-
const sourceStart = row * rowBytes;
|
|
684
|
-
const targetStart = ((top + row) * layout.width + left) * 4;
|
|
685
|
-
atlas.set(icon.rgba.subarray(sourceStart, sourceStart + rowBytes), targetStart);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return sharp(atlas, {
|
|
689
|
-
raw: {
|
|
690
|
-
channels: 4,
|
|
691
|
-
height: layout.height,
|
|
692
|
-
width: layout.width
|
|
693
|
-
}
|
|
694
|
-
}).png({ adaptiveFiltering: false, compressionLevel: 9, palette: false }).toBuffer();
|
|
695
|
-
}
|
|
696
|
-
function assertGeneratedFileLimits(files) {
|
|
697
|
-
let totalBytes = 0;
|
|
698
|
-
for (const file of files) {
|
|
699
|
-
if (file.source.byteLength > tileflowIconPackageLimits.maxGeneratedFileBytes) {
|
|
700
|
-
throw new Error(
|
|
701
|
-
`${file.fileName} exceeds ${tileflowIconPackageLimits.maxGeneratedFileBytes} generated bytes`
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
totalBytes += file.source.byteLength;
|
|
705
|
-
}
|
|
706
|
-
if (totalBytes > tileflowIconPackageLimits.maxGeneratedPackageBytes) {
|
|
707
|
-
throw new Error(
|
|
708
|
-
`Generated package exceeds ${tileflowIconPackageLimits.maxGeneratedPackageBytes} bytes`
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
function validateHostedSvg(source, displayPath) {
|
|
713
|
-
const parser = new SaxesParser({ fileName: displayPath, xmlns: true });
|
|
714
|
-
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
715
|
-
let styleDepth = 0;
|
|
716
|
-
let styleText = "";
|
|
717
|
-
parser.on("doctype", () => {
|
|
718
|
-
throw new Error("Hosted SVGs cannot contain a document type or entity declarations");
|
|
719
|
-
});
|
|
720
|
-
parser.on("processinginstruction", (instruction) => {
|
|
721
|
-
if (instruction.target.toLowerCase() === "xml-stylesheet") {
|
|
722
|
-
throw new Error("Hosted SVGs cannot load external stylesheets");
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
parser.on("opentag", (tag) => {
|
|
726
|
-
const localName = tag.local.toLowerCase();
|
|
727
|
-
if (localName === "script") {
|
|
728
|
-
throw new Error("Hosted SVGs cannot contain scripts");
|
|
729
|
-
}
|
|
730
|
-
if (localName === "style") {
|
|
731
|
-
styleDepth += 1;
|
|
732
|
-
}
|
|
733
|
-
for (const attribute of Object.values(tag.attributes)) {
|
|
734
|
-
const attributeName = attribute.local.toLowerCase();
|
|
735
|
-
const value = attribute.value.trim();
|
|
736
|
-
if (attributeName.startsWith("on")) {
|
|
737
|
-
throw new Error("Hosted SVGs cannot contain script event handlers");
|
|
738
|
-
}
|
|
739
|
-
if (attributeName === "href" && value.length > 0 && !value.startsWith("#")) {
|
|
740
|
-
throw new Error("Hosted SVG resource references must use local fragments");
|
|
741
|
-
}
|
|
742
|
-
if (containsExternalCssUrl(value)) {
|
|
743
|
-
throw new Error("Hosted SVG resource references must use local fragments");
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
parser.on("text", (text) => {
|
|
748
|
-
if (styleDepth > 0) {
|
|
749
|
-
styleText += text;
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
parser.on("cdata", (text) => {
|
|
753
|
-
if (styleDepth > 0) {
|
|
754
|
-
styleText += text;
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
parser.on("closetag", (tag) => {
|
|
758
|
-
if (tag.local.toLowerCase() !== "style") {
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
styleDepth -= 1;
|
|
762
|
-
if (styleDepth === 0) {
|
|
763
|
-
if (styleText.toLowerCase().includes("@import") || containsExternalCssUrl(styleText)) {
|
|
764
|
-
throw new Error("Hosted SVGs cannot load external stylesheets or resources");
|
|
765
|
-
}
|
|
766
|
-
styleText = "";
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
parser.on("error", (error) => {
|
|
770
|
-
throw error;
|
|
771
|
-
});
|
|
772
|
-
const chunkBytes = 64 * 1024;
|
|
773
|
-
for (let offset = 0; offset < source.byteLength; offset += chunkBytes) {
|
|
774
|
-
parser.write(decoder.decode(source.subarray(offset, offset + chunkBytes), { stream: true }));
|
|
775
|
-
}
|
|
776
|
-
parser.write(decoder.decode());
|
|
777
|
-
parser.close();
|
|
778
|
-
}
|
|
779
|
-
function containsExternalCssUrl(value) {
|
|
780
|
-
const lowerValue = value.toLowerCase();
|
|
781
|
-
let cursor = 0;
|
|
782
|
-
while (cursor < value.length) {
|
|
783
|
-
const start = lowerValue.indexOf("url(", cursor);
|
|
784
|
-
if (start === -1) {
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
const end = value.indexOf(")", start + 4);
|
|
788
|
-
if (end === -1) {
|
|
789
|
-
return true;
|
|
790
|
-
}
|
|
791
|
-
const rawTarget = value.slice(start + 4, end).trim();
|
|
792
|
-
const target = stripMatchingQuotes(rawTarget);
|
|
793
|
-
if (!target.startsWith("#")) {
|
|
794
|
-
return true;
|
|
795
|
-
}
|
|
796
|
-
cursor = end + 1;
|
|
797
|
-
}
|
|
798
|
-
return false;
|
|
799
|
-
}
|
|
800
|
-
function stripMatchingQuotes(value) {
|
|
801
|
-
if (value.length < 2) {
|
|
802
|
-
return value;
|
|
803
|
-
}
|
|
804
|
-
const first = value[0];
|
|
805
|
-
const last = value[value.length - 1];
|
|
806
|
-
return first === '"' && last === '"' || first === "'" && last === "'" ? value.slice(1, -1).trim() : value;
|
|
807
|
-
}
|
|
808
|
-
async function mapWithConcurrency(values, concurrency, map) {
|
|
809
|
-
const results = new Array(values.length);
|
|
810
|
-
let nextIndex = 0;
|
|
811
|
-
async function worker() {
|
|
812
|
-
while (nextIndex < values.length) {
|
|
813
|
-
const index = nextIndex;
|
|
814
|
-
nextIndex += 1;
|
|
815
|
-
const value = values[index];
|
|
816
|
-
if (value !== void 0) {
|
|
817
|
-
results[index] = await map(value, index);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
await Promise.all(
|
|
822
|
-
Array.from({ length: Math.min(concurrency, values.length) }, async () => worker())
|
|
823
|
-
);
|
|
824
|
-
return results;
|
|
825
|
-
}
|
|
826
|
-
async function loadSharp() {
|
|
827
|
-
try {
|
|
828
|
-
return (await import("sharp")).default;
|
|
829
|
-
} catch (error) {
|
|
830
|
-
throw new Error(
|
|
831
|
-
'Local icon sprites require the optional "sharp" package. Install sharp or disable local icons.',
|
|
832
|
-
{ cause: error }
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
function resolvePackageLabel(icons, source, iconSets, mapName) {
|
|
837
|
-
let candidate;
|
|
838
|
-
if (typeof icons === "string" && iconSets?.[icons]) {
|
|
839
|
-
candidate = icons;
|
|
840
|
-
} else if (icons && typeof icons === "object" && icons.extends && iconSets?.[icons.extends]) {
|
|
841
|
-
candidate = icons.extends;
|
|
842
|
-
}
|
|
843
|
-
candidate ??= basename(source.replace(/[\\/]+$/u, "")) || mapName || "Icons";
|
|
844
|
-
const printable = Array.from(candidate.normalize("NFC")).map((character) => {
|
|
845
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
846
|
-
return codePoint < 32 || codePoint >= 127 && codePoint <= 159 ? "" : character;
|
|
847
|
-
}).join("").trim();
|
|
848
|
-
const bounded = Array.from(printable || "Icons").slice(0, 64).join("");
|
|
849
|
-
return tileflowIconPackageLabelSchema.parse(bounded);
|
|
850
|
-
}
|
|
851
|
-
function sortMapping(mapping) {
|
|
852
|
-
if (!mapping) {
|
|
853
|
-
return void 0;
|
|
854
|
-
}
|
|
855
|
-
return Object.fromEntries(
|
|
856
|
-
Object.entries(mapping).sort(([left], [right]) => compareCodeUnits(left, right))
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
function createCatalogMappings(mapping, targetStatus) {
|
|
860
|
-
return Object.entries(mapping ?? {}).sort(([left], [right]) => compareCodeUnits(left, right)).map(([semantic, iconId]) => ({
|
|
861
|
-
iconId,
|
|
862
|
-
semantic,
|
|
863
|
-
targetStatus: typeof targetStatus === "function" ? targetStatus(iconId) : targetStatus
|
|
864
|
-
}));
|
|
865
|
-
}
|
|
866
|
-
function compareMappedFrom(left, right) {
|
|
867
|
-
return compareCodeUnits(left.map, right.map) || compareCodeUnits(left.semantic, right.semantic);
|
|
868
|
-
}
|
|
869
|
-
function atlasRectangle(entry) {
|
|
870
|
-
return { height: entry.height, width: entry.width, x: entry.x, y: entry.y };
|
|
871
|
-
}
|
|
872
|
-
function relativePortablePath(root, candidate) {
|
|
873
|
-
const pathFromRoot = relative(root, candidate);
|
|
874
|
-
if (pathFromRoot === "") {
|
|
875
|
-
return ".";
|
|
876
|
-
}
|
|
877
|
-
if (isAbsolute(pathFromRoot)) {
|
|
878
|
-
throw new Error("Icon catalog path cannot be represented relative to the working tree");
|
|
879
|
-
}
|
|
880
|
-
return pathFromRoot.split(sep).join("/");
|
|
881
|
-
}
|
|
882
|
-
function iconSourceFormat(extension) {
|
|
883
|
-
switch (extension) {
|
|
884
|
-
case ".svg":
|
|
885
|
-
return "svg";
|
|
886
|
-
case ".png":
|
|
887
|
-
return "png";
|
|
888
|
-
case ".jpg":
|
|
889
|
-
case ".jpeg":
|
|
890
|
-
return "jpeg";
|
|
891
|
-
case ".webp":
|
|
892
|
-
return "webp";
|
|
893
|
-
default:
|
|
894
|
-
throw new Error(`Unsupported icon source format: ${extension}`);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
function sourcePath(mapName) {
|
|
898
|
-
return `maps.${mapName}.icons.source`;
|
|
899
|
-
}
|
|
900
|
-
function iconPath(mapName, fileName) {
|
|
901
|
-
return `${sourcePath(mapName)}/${fileName}`;
|
|
902
|
-
}
|
|
903
|
-
function isPathInside(root, candidate) {
|
|
904
|
-
const pathFromRoot = relative(root, candidate);
|
|
905
|
-
return pathFromRoot === "" || !isAbsolute(pathFromRoot) && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`);
|
|
906
|
-
}
|
|
907
|
-
function describeFileSystemError(error, fallback) {
|
|
908
|
-
if (error && typeof error === "object" && "code" in error) {
|
|
909
|
-
const code = String(error.code ?? "");
|
|
910
|
-
if (code === "ENOENT") {
|
|
911
|
-
return fallback;
|
|
912
|
-
}
|
|
913
|
-
if (code === "EACCES" || code === "EPERM") {
|
|
914
|
-
return "Icon source is not readable";
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
return error instanceof Error && error.message ? error.message : fallback;
|
|
918
|
-
}
|
|
919
|
-
function cloneProject(project) {
|
|
920
|
-
return JSON.parse(JSON.stringify(project));
|
|
921
|
-
}
|
|
922
|
-
function isRemoteSpriteReference(value) {
|
|
923
|
-
return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:");
|
|
924
|
-
}
|
|
925
|
-
function joinUrl(base, path) {
|
|
926
|
-
const trimmedBase = base.replace(/\/+$/g, "");
|
|
927
|
-
const trimmedPath = path.replace(/^\/+/g, "");
|
|
928
|
-
return trimmedBase ? `${trimmedBase}/${trimmedPath}` : trimmedPath;
|
|
929
|
-
}
|
|
930
|
-
function uniqueStrings(values) {
|
|
931
|
-
return [...new Set(values)];
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// src/session.ts
|
|
935
|
-
import { watch } from "chokidar";
|
|
936
|
-
import { realpathSync } from "fs";
|
|
937
|
-
import { dirname, extname as extname2, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2, win32 } from "path";
|
|
938
|
-
|
|
939
|
-
// src/diagnostic-sanitization.ts
|
|
940
|
-
function sanitizeDiagnosticSecrets(value) {
|
|
941
|
-
return value.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, "$1[redacted]@").replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\btf_(?:live|cli|test)_[A-Za-z0-9_-]{8,}\b/gi, "tf_[redacted]").replace(/\b(?:sk_(?:live|test)|rk_(?:live|test)|whsec)_[A-Za-z0-9_-]+\b/g, "[redacted]").replace(
|
|
942
|
-
/([?&](?:access_token|api_key|key|password|secret|signature|token)=)[^&#\s]+/gi,
|
|
943
|
-
"$1[redacted]"
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// src/session.ts
|
|
948
|
-
var tileflowArtifactSessionSchemaVersion = 1;
|
|
949
|
-
var configExtensions = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".json", ".mjs", ".mts", ".ts", ".tsx"]);
|
|
950
|
-
var iconExtensions = /* @__PURE__ */ new Set([".jpeg", ".jpg", ".png", ".svg", ".webp"]);
|
|
951
|
-
var excludedDirectories = /* @__PURE__ */ new Set([
|
|
952
|
-
".cache",
|
|
953
|
-
".git",
|
|
954
|
-
".hg",
|
|
955
|
-
".next",
|
|
956
|
-
".nuxt",
|
|
957
|
-
".svn",
|
|
958
|
-
".svelte-kit",
|
|
959
|
-
".tileflow",
|
|
960
|
-
".turbo",
|
|
961
|
-
"build",
|
|
962
|
-
"coverage",
|
|
963
|
-
"dist",
|
|
964
|
-
"node_modules"
|
|
965
|
-
]);
|
|
966
|
-
async function createTileflowArtifactSessionWithBuilder(options, buildArtifacts) {
|
|
967
|
-
const session = new TileflowArtifactSessionImpl(options, buildArtifacts);
|
|
968
|
-
await session.start();
|
|
969
|
-
return session;
|
|
970
|
-
}
|
|
971
|
-
var TileflowArtifactSessionImpl = class {
|
|
972
|
-
#buildArtifacts;
|
|
973
|
-
#buildOptions;
|
|
974
|
-
#configPath;
|
|
975
|
-
#cwd;
|
|
976
|
-
#debounceMs;
|
|
977
|
-
#listeners = /* @__PURE__ */ new Set();
|
|
978
|
-
#ignoredPaths;
|
|
979
|
-
#watchEnabled;
|
|
980
|
-
#closed = false;
|
|
981
|
-
#debounceTimer;
|
|
982
|
-
#generation = 0;
|
|
983
|
-
#lastGoodArtifacts;
|
|
984
|
-
#lastGoodGeneration;
|
|
985
|
-
#refreshToken = 0;
|
|
986
|
-
#state = { generation: 0, status: "building" };
|
|
987
|
-
#watcher;
|
|
988
|
-
#watchedArtifactPaths = /* @__PURE__ */ new Set();
|
|
989
|
-
constructor(options, buildArtifacts) {
|
|
990
|
-
this.#cwd = resolve2(options.cwd ?? process.cwd());
|
|
991
|
-
this.#configPath = resolve2(this.#cwd, options.config ?? "tileflow.config.ts");
|
|
992
|
-
this.#debounceMs = Math.max(0, Math.min(options.debounceMs ?? 75, 1e3));
|
|
993
|
-
this.#watchEnabled = options.watch ?? false;
|
|
994
|
-
this.#ignoredPaths = (options.ignoredPaths ?? []).map((path) => resolve2(this.#cwd, path));
|
|
995
|
-
this.#buildArtifacts = buildArtifacts;
|
|
996
|
-
this.#buildOptions = {
|
|
997
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
998
|
-
config: options.config,
|
|
999
|
-
cwd: this.#cwd,
|
|
1000
|
-
styleBaseUrl: options.styleBaseUrl,
|
|
1001
|
-
tileBaseUrl: options.tileBaseUrl
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
async start() {
|
|
1005
|
-
if (this.#watchEnabled) {
|
|
1006
|
-
this.#watcher = watch(dirname(this.#configPath), {
|
|
1007
|
-
awaitWriteFinish: { pollInterval: 20, stabilityThreshold: 50 },
|
|
1008
|
-
followSymlinks: false,
|
|
1009
|
-
ignoreInitial: true,
|
|
1010
|
-
ignored: (path, stats) => this.#ignoreWatchPath(path, stats?.isDirectory())
|
|
1011
|
-
});
|
|
1012
|
-
const watcherReady = new Promise((resolveReady, rejectReady) => {
|
|
1013
|
-
const watcher = this.#watcher;
|
|
1014
|
-
const cleanup = () => {
|
|
1015
|
-
watcher.off("ready", onReady);
|
|
1016
|
-
watcher.off("error", onError);
|
|
1017
|
-
};
|
|
1018
|
-
const onReady = () => {
|
|
1019
|
-
cleanup();
|
|
1020
|
-
resolveReady();
|
|
1021
|
-
};
|
|
1022
|
-
const onError = (error) => {
|
|
1023
|
-
cleanup();
|
|
1024
|
-
rejectReady(error);
|
|
1025
|
-
};
|
|
1026
|
-
watcher.once("ready", onReady);
|
|
1027
|
-
watcher.once("error", onError);
|
|
1028
|
-
});
|
|
1029
|
-
this.#watcher.on("all", (event, path) => {
|
|
1030
|
-
if (event === "addDir" || event === "unlinkDir") return;
|
|
1031
|
-
if (!this.#shouldRefreshForWatchEvent(path)) return;
|
|
1032
|
-
this.#scheduleRefresh(path);
|
|
1033
|
-
});
|
|
1034
|
-
this.#watcher.on("error", (error) => {
|
|
1035
|
-
if (this.#closed) return;
|
|
1036
|
-
this.#refreshToken += 1;
|
|
1037
|
-
this.#publishInvalid(this.#generation + 1, error);
|
|
1038
|
-
});
|
|
1039
|
-
await watcherReady;
|
|
1040
|
-
}
|
|
1041
|
-
await this.refresh("initial");
|
|
1042
|
-
}
|
|
1043
|
-
getState() {
|
|
1044
|
-
return this.#state;
|
|
1045
|
-
}
|
|
1046
|
-
getLastGoodArtifacts() {
|
|
1047
|
-
return this.#lastGoodArtifacts;
|
|
1048
|
-
}
|
|
1049
|
-
subscribe(listener) {
|
|
1050
|
-
if (this.#closed) return () => void 0;
|
|
1051
|
-
this.#listeners.add(listener);
|
|
1052
|
-
return () => this.#listeners.delete(listener);
|
|
1053
|
-
}
|
|
1054
|
-
async refresh(_reason = "manual") {
|
|
1055
|
-
if (this.#closed) return;
|
|
1056
|
-
const generation = ++this.#generation;
|
|
1057
|
-
const token = ++this.#refreshToken;
|
|
1058
|
-
this.#publish({
|
|
1059
|
-
generation,
|
|
1060
|
-
status: "building",
|
|
1061
|
-
...this.#lastGoodGeneration === void 0 ? {} : { lastGoodGeneration: this.#lastGoodGeneration }
|
|
1062
|
-
});
|
|
1063
|
-
try {
|
|
1064
|
-
const artifacts = await this.#buildArtifacts(this.#buildOptions);
|
|
1065
|
-
if (this.#closed || token !== this.#refreshToken) return;
|
|
1066
|
-
this.#lastGoodArtifacts = artifacts;
|
|
1067
|
-
this.#lastGoodGeneration = generation;
|
|
1068
|
-
this.#updateArtifactWatchPaths(artifacts.watchPaths);
|
|
1069
|
-
this.#publish({
|
|
1070
|
-
artifacts,
|
|
1071
|
-
generation,
|
|
1072
|
-
lastGoodGeneration: generation,
|
|
1073
|
-
status: "ready"
|
|
1074
|
-
});
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
if (this.#closed || token !== this.#refreshToken) return;
|
|
1077
|
-
this.#publishInvalid(generation, error);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
async close() {
|
|
1081
|
-
if (this.#closed) return;
|
|
1082
|
-
this.#closed = true;
|
|
1083
|
-
this.#refreshToken += 1;
|
|
1084
|
-
if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
|
|
1085
|
-
this.#listeners.clear();
|
|
1086
|
-
await this.#watcher?.close();
|
|
1087
|
-
}
|
|
1088
|
-
#ignoreWatchPath(path, isDirectory) {
|
|
1089
|
-
const resolvedPath = resolve2(path);
|
|
1090
|
-
const relativePath = relative2(dirname(this.#configPath), resolvedPath);
|
|
1091
|
-
const segments = relativePath.split(sep2).filter(Boolean);
|
|
1092
|
-
if (this.#ignoredPaths.some((path2) => isSameOrInside(path2, resolvedPath))) return true;
|
|
1093
|
-
if (segments.some((segment) => excludedDirectories.has(segment))) return true;
|
|
1094
|
-
if (isDirectory !== false) return false;
|
|
1095
|
-
if (resolvedPath === this.#configPath || this.#watchedArtifactPaths.has(resolvedPath))
|
|
1096
|
-
return false;
|
|
1097
|
-
const extension = extname2(resolvedPath).toLowerCase();
|
|
1098
|
-
if (configExtensions.has(extension)) return false;
|
|
1099
|
-
if (iconExtensions.has(extension)) return false;
|
|
1100
|
-
return true;
|
|
1101
|
-
}
|
|
1102
|
-
#shouldRefreshForWatchEvent(path) {
|
|
1103
|
-
const resolvedPath = resolve2(path);
|
|
1104
|
-
if (!iconExtensions.has(extname2(resolvedPath).toLowerCase())) return true;
|
|
1105
|
-
return [...this.#watchedArtifactPaths].some((path2) => isSameOrInside(path2, resolvedPath));
|
|
1106
|
-
}
|
|
1107
|
-
#publish(state) {
|
|
1108
|
-
this.#state = state;
|
|
1109
|
-
for (const listener of this.#listeners) listener(state);
|
|
1110
|
-
}
|
|
1111
|
-
#publishInvalid(generation, error) {
|
|
1112
|
-
this.#generation = Math.max(this.#generation, generation);
|
|
1113
|
-
this.#publish({
|
|
1114
|
-
diagnostics: createTileflowArtifactDiagnostics(error, this.#cwd),
|
|
1115
|
-
generation,
|
|
1116
|
-
status: "invalid",
|
|
1117
|
-
...this.#lastGoodGeneration === void 0 ? {} : { lastGoodGeneration: this.#lastGoodGeneration }
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
|
-
#scheduleRefresh(_path) {
|
|
1121
|
-
if (this.#closed) return;
|
|
1122
|
-
if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
|
|
1123
|
-
this.#debounceTimer = setTimeout(() => {
|
|
1124
|
-
this.#debounceTimer = void 0;
|
|
1125
|
-
void this.refresh("filesystem");
|
|
1126
|
-
}, this.#debounceMs);
|
|
1127
|
-
}
|
|
1128
|
-
#updateArtifactWatchPaths(paths) {
|
|
1129
|
-
if (!this.#watcher) return;
|
|
1130
|
-
const next = new Set(paths.map((path) => resolve2(this.#cwd, path)));
|
|
1131
|
-
const previous = this.#watchedArtifactPaths;
|
|
1132
|
-
this.#watchedArtifactPaths = next;
|
|
1133
|
-
for (const path of previous) {
|
|
1134
|
-
if (!next.has(path) && !isSameOrInside(dirname(this.#configPath), path)) {
|
|
1135
|
-
void this.#watcher.unwatch(path);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
for (const path of next) {
|
|
1139
|
-
if (!previous.has(path) && !isSameOrInside(dirname(this.#configPath), path)) {
|
|
1140
|
-
this.#watcher.add(path);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
function isSameOrInside(root, candidate) {
|
|
1146
|
-
const path = relative2(resolve2(root), resolve2(candidate));
|
|
1147
|
-
return path === "" || !isAbsolute2(path) && path !== ".." && !path.startsWith(`..${sep2}`);
|
|
1148
|
-
}
|
|
1149
|
-
function createTileflowArtifactDiagnostics(error, cwd) {
|
|
1150
|
-
const inheritedCode = optionalDiagnosticField(error, "code");
|
|
1151
|
-
const inheritedPhase = optionalDiagnosticField(error, "phase") ?? optionalNestedDiagnosticField(error, "phase");
|
|
1152
|
-
const issueList = getIssueList(error);
|
|
1153
|
-
if (issueList.length > 0) {
|
|
1154
|
-
return normalizeDiagnostics(
|
|
1155
|
-
issueList.map((issue) => ({
|
|
1156
|
-
...issue.code ?? inheritedCode ? { code: issue.code ?? inheritedCode } : {},
|
|
1157
|
-
message: sanitizeMessage(issue.message, cwd),
|
|
1158
|
-
path: sanitizePath(issue.path, cwd),
|
|
1159
|
-
...issue.phase ?? inheritedPhase ? { phase: issue.phase ?? inheritedPhase } : {}
|
|
1160
|
-
}))
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
return normalizeDiagnostics([
|
|
1164
|
-
{
|
|
1165
|
-
...inheritedCode ? { code: inheritedCode } : {},
|
|
1166
|
-
message: sanitizeMessage(error instanceof Error ? error.message : "Unknown build error", cwd),
|
|
1167
|
-
path: "",
|
|
1168
|
-
...inheritedPhase ? { phase: inheritedPhase } : {}
|
|
1169
|
-
}
|
|
1170
|
-
]);
|
|
1171
|
-
}
|
|
1172
|
-
function getIssueList(error) {
|
|
1173
|
-
if (!error || typeof error !== "object") return [];
|
|
1174
|
-
const containers = [];
|
|
1175
|
-
for (const key of ["messages", "issues"]) {
|
|
1176
|
-
const value = error[key];
|
|
1177
|
-
if (Array.isArray(value)) containers.push(value);
|
|
1178
|
-
}
|
|
1179
|
-
const details = error.details;
|
|
1180
|
-
if (details && typeof details === "object") {
|
|
1181
|
-
const diagnostics = details.diagnostics;
|
|
1182
|
-
if (Array.isArray(diagnostics)) containers.push(diagnostics);
|
|
1183
|
-
}
|
|
1184
|
-
return containers.flatMap(
|
|
1185
|
-
(value) => value.flatMap((entry) => {
|
|
1186
|
-
if (!entry || typeof entry !== "object") return [];
|
|
1187
|
-
const message = entry.message;
|
|
1188
|
-
const path = entry.path;
|
|
1189
|
-
const code = optionalDiagnosticField(entry, "code");
|
|
1190
|
-
const phase = optionalDiagnosticField(entry, "phase");
|
|
1191
|
-
return typeof message === "string" ? [
|
|
1192
|
-
{
|
|
1193
|
-
...code ? { code } : {},
|
|
1194
|
-
message,
|
|
1195
|
-
path: typeof path === "string" ? path : "",
|
|
1196
|
-
...phase ? { phase } : {}
|
|
1197
|
-
}
|
|
1198
|
-
] : [];
|
|
1199
|
-
})
|
|
1200
|
-
);
|
|
1201
|
-
}
|
|
1202
|
-
function sanitizeMessage(value, cwd) {
|
|
1203
|
-
let sanitized = value;
|
|
1204
|
-
for (const root of localPathAliases(cwd)) sanitized = sanitized.replaceAll(root, ".");
|
|
1205
|
-
sanitized = sanitized.replace(/[\r\n]+/g, " ").trim();
|
|
1206
|
-
sanitized = sanitized.replace(/https?:\/\/[^\s'")<>]+/gi, (url) => {
|
|
1207
|
-
try {
|
|
1208
|
-
return new URL(url.replace(/[),.;]+$/, "")).origin;
|
|
1209
|
-
} catch {
|
|
1210
|
-
return "(resource URL)";
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
sanitized = sanitized.replace(/(^|[\s'"(])[A-Za-z]:[\\/][^\s'")]+/g, "$1(external path)");
|
|
1214
|
-
sanitized = sanitized.replace(/(^|[\s'"(])(?:\\\\|\/\/)[^\s'")]+/g, "$1(external path)");
|
|
1215
|
-
sanitized = sanitized.replace(/(^|[\s'"(])\/[^\s'")]+/g, "$1(external path)");
|
|
1216
|
-
return sanitizeDiagnosticSecrets(sanitized);
|
|
1217
|
-
}
|
|
1218
|
-
function sanitizePath(value, cwd) {
|
|
1219
|
-
if (!value) return "";
|
|
1220
|
-
if (win32.isAbsolute(value) && !isAbsolute2(value)) return "(external)";
|
|
1221
|
-
if (!isAbsolute2(value)) return value.replaceAll("\\", "/");
|
|
1222
|
-
const local = relative2(canonicalPath(cwd), canonicalPath(value)).replaceAll(sep2, "/");
|
|
1223
|
-
return local.startsWith("../") || local === ".." ? "(external)" : local || ".";
|
|
1224
|
-
}
|
|
1225
|
-
function localPathAliases(path) {
|
|
1226
|
-
return [.../* @__PURE__ */ new Set([resolve2(path), canonicalPath(path)])].sort(
|
|
1227
|
-
(left, right) => right.length - left.length
|
|
1228
|
-
);
|
|
1229
|
-
}
|
|
1230
|
-
function canonicalPath(path) {
|
|
1231
|
-
try {
|
|
1232
|
-
return realpathSync.native(resolve2(path));
|
|
1233
|
-
} catch {
|
|
1234
|
-
return resolve2(path);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
function normalizeDiagnostics(diagnostics) {
|
|
1238
|
-
const bounded = diagnostics.map((diagnostic) => ({
|
|
1239
|
-
...diagnostic.code ? { code: boundDiagnosticField(diagnostic.code) } : {},
|
|
1240
|
-
message: boundDiagnosticText(diagnostic.message, 300),
|
|
1241
|
-
path: boundDiagnosticText(diagnostic.path, 300),
|
|
1242
|
-
...diagnostic.phase ? { phase: boundDiagnosticField(diagnostic.phase) } : {}
|
|
1243
|
-
}));
|
|
1244
|
-
bounded.sort((left, right) => {
|
|
1245
|
-
const leftKey = `${left.path}\0${left.message}\0${left.code ?? ""}\0${left.phase ?? ""}`;
|
|
1246
|
-
const rightKey = `${right.path}\0${right.message}\0${right.code ?? ""}\0${right.phase ?? ""}`;
|
|
1247
|
-
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
1248
|
-
});
|
|
1249
|
-
return bounded.filter(
|
|
1250
|
-
(diagnostic, index) => index === 0 || JSON.stringify(diagnostic) !== JSON.stringify(bounded[index - 1])
|
|
1251
|
-
).slice(0, 32);
|
|
1252
|
-
}
|
|
1253
|
-
function optionalDiagnosticField(value, key) {
|
|
1254
|
-
if (!value || typeof value !== "object") return void 0;
|
|
1255
|
-
const field = value[key];
|
|
1256
|
-
return typeof field === "string" && field.trim() ? boundDiagnosticField(field) : void 0;
|
|
1257
|
-
}
|
|
1258
|
-
function optionalNestedDiagnosticField(value, key) {
|
|
1259
|
-
if (!value || typeof value !== "object") return void 0;
|
|
1260
|
-
return optionalDiagnosticField(value.details, key);
|
|
1261
|
-
}
|
|
1262
|
-
function boundDiagnosticField(value) {
|
|
1263
|
-
return boundDiagnosticText(value.replace(/[^A-Za-z0-9_-]/g, ""), 64);
|
|
1264
|
-
}
|
|
1265
|
-
function boundDiagnosticText(value, limit) {
|
|
1266
|
-
return value.length <= limit ? value : `${value.slice(0, limit - 1)}\u2026`;
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
// src/style-validation.ts
|
|
1270
|
-
import { validateStyleMin } from "@maplibre/maplibre-gl-style-spec";
|
|
1271
|
-
var TileflowStyleValidationError = class extends Error {
|
|
1272
|
-
code = "STYLE_INVALID";
|
|
1273
|
-
issues;
|
|
1274
|
-
phase = "style-validation";
|
|
1275
|
-
constructor(issues) {
|
|
1276
|
-
super("Compiled Tileflow styles are not valid MapLibre styles.");
|
|
1277
|
-
this.name = "TileflowStyleValidationError";
|
|
1278
|
-
this.issues = normalizeIssues(issues);
|
|
1279
|
-
}
|
|
1280
|
-
};
|
|
1281
|
-
var maxIssues = 32;
|
|
1282
|
-
var maxMessageLength = 300;
|
|
1283
|
-
var maxPathLength = 300;
|
|
1284
|
-
function assertValidTileflowStyle(style, map) {
|
|
1285
|
-
const issues = validateTileflowStyle(style, map);
|
|
1286
|
-
if (issues.length > 0) throw new TileflowStyleValidationError(issues);
|
|
1287
|
-
}
|
|
1288
|
-
function validateTileflowStyle(style, map) {
|
|
1289
|
-
const jsonIssues = [];
|
|
1290
|
-
collectJsonIssues(
|
|
1291
|
-
style,
|
|
1292
|
-
`maps.${sanitizePathSegment(map)}.style`,
|
|
1293
|
-
map,
|
|
1294
|
-
jsonIssues,
|
|
1295
|
-
/* @__PURE__ */ new Set()
|
|
1296
|
-
);
|
|
1297
|
-
if (jsonIssues.length > 0) return normalizeIssues(jsonIssues);
|
|
1298
|
-
const semanticIssues = validateStyleMin(style).map(
|
|
1299
|
-
(issue) => createSemanticIssue(style, map, issue.message)
|
|
1300
|
-
);
|
|
1301
|
-
return normalizeIssues(semanticIssues);
|
|
1302
|
-
}
|
|
1303
|
-
function normalizeTileflowStyleValidationIssues(issues) {
|
|
1304
|
-
return normalizeIssues(issues);
|
|
1305
|
-
}
|
|
1306
|
-
function createSemanticIssue(style, map, rawMessage) {
|
|
1307
|
-
const normalized = rawMessage.replace(/[\r\n]+/g, " ").trim();
|
|
1308
|
-
const separator = normalized.indexOf(":");
|
|
1309
|
-
const rawPath = separator >= 0 ? normalized.slice(0, separator).trim() : "";
|
|
1310
|
-
const detail = separator >= 0 ? normalized.slice(separator + 1).trim() : normalized;
|
|
1311
|
-
return {
|
|
1312
|
-
map,
|
|
1313
|
-
message: boundText(sanitizeDiagnosticSecrets(sanitizeUrls(detail)), maxMessageLength),
|
|
1314
|
-
path: boundText(stableStylePath(style, map, rawPath), maxPathLength)
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
function stableStylePath(style, map, rawPath) {
|
|
1318
|
-
const fallback = `maps.${sanitizePathSegment(map)}.style`;
|
|
1319
|
-
if (!rawPath) return fallback;
|
|
1320
|
-
const path = rawPath.replace(/layers\[(\d+)]/g, (_match, index) => {
|
|
1321
|
-
const layer = style.layers[Number(index)];
|
|
1322
|
-
const id = typeof layer?.id === "string" && layer.id ? layer.id : index;
|
|
1323
|
-
return `layers.${sanitizePathSegment(id)}`;
|
|
1324
|
-
});
|
|
1325
|
-
return `${fallback}.${path}`;
|
|
1326
|
-
}
|
|
1327
|
-
function sanitizePathSegment(value) {
|
|
1328
|
-
let sanitized = "";
|
|
1329
|
-
let replacing = false;
|
|
1330
|
-
for (const character of value) {
|
|
1331
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
1332
|
-
const isAsciiLetter = codePoint >= 65 && codePoint <= 90 || codePoint >= 97 && codePoint <= 122;
|
|
1333
|
-
const isDigit = codePoint >= 48 && codePoint <= 57;
|
|
1334
|
-
const shouldReplace = !isAsciiLetter && !isDigit && character !== "-" && character !== "_";
|
|
1335
|
-
if (shouldReplace) {
|
|
1336
|
-
if (!replacing) sanitized += "-";
|
|
1337
|
-
replacing = true;
|
|
1338
|
-
} else {
|
|
1339
|
-
sanitized += character;
|
|
1340
|
-
replacing = false;
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
return sanitized.slice(0, 128) || "(unknown)";
|
|
1344
|
-
}
|
|
1345
|
-
function collectJsonIssues(value, path, map, issues, ancestors) {
|
|
1346
|
-
if (issues.length >= maxIssues) return;
|
|
1347
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
1348
|
-
if (typeof value === "number") {
|
|
1349
|
-
if (!Number.isFinite(value)) addJsonIssue(map, path, "non-finite number", issues);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
if (typeof value !== "object") {
|
|
1353
|
-
addJsonIssue(map, path, typeof value, issues);
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
if (ancestors.has(value)) {
|
|
1357
|
-
addJsonIssue(map, path, "cyclic object", issues);
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
|
-
ancestors.add(value);
|
|
1361
|
-
if (Array.isArray(value)) {
|
|
1362
|
-
value.forEach((item, index) => {
|
|
1363
|
-
const layerId = path.endsWith(".layers") && item && typeof item === "object" && !Array.isArray(item) ? item.id : void 0;
|
|
1364
|
-
const childPath = typeof layerId === "string" && layerId ? `${path}.${sanitizePathSegment(layerId)}` : `${path}[${index}]`;
|
|
1365
|
-
collectJsonIssues(item, childPath, map, issues, ancestors);
|
|
1366
|
-
});
|
|
1367
|
-
} else {
|
|
1368
|
-
for (const [key, item] of Object.entries(value)) {
|
|
1369
|
-
collectJsonIssues(item, `${path}.${sanitizePathSegment(key)}`, map, issues, ancestors);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
ancestors.delete(value);
|
|
1373
|
-
}
|
|
1374
|
-
function addJsonIssue(map, path, type, issues) {
|
|
1375
|
-
issues.push({
|
|
1376
|
-
map,
|
|
1377
|
-
message: `Expected a recursive JSON value; found ${type}.`,
|
|
1378
|
-
path: boundText(path, maxPathLength)
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
function normalizeIssues(issues) {
|
|
1382
|
-
const normalized = issues.map((issue) => ({
|
|
1383
|
-
map: boundText(sanitizePathSegment(issue.map), 128),
|
|
1384
|
-
message: boundText(
|
|
1385
|
-
sanitizeDiagnosticSecrets(issue.message.replace(/[\r\n]+/g, " ").trim()),
|
|
1386
|
-
maxMessageLength
|
|
1387
|
-
),
|
|
1388
|
-
path: boundText(issue.path.replaceAll("\\", "/"), maxPathLength)
|
|
1389
|
-
}));
|
|
1390
|
-
normalized.sort(
|
|
1391
|
-
(left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : left.message < right.message ? -1 : left.message > right.message ? 1 : 0
|
|
1392
|
-
);
|
|
1393
|
-
return normalized.filter(
|
|
1394
|
-
(issue, index) => index === 0 || issue.path !== normalized[index - 1]?.path || issue.message !== normalized[index - 1]?.message
|
|
1395
|
-
).slice(0, maxIssues);
|
|
1396
|
-
}
|
|
1397
|
-
function sanitizeUrls(message) {
|
|
1398
|
-
return message.replace(/https?:\/\/[^\s'"<>]+/gi, (value) => {
|
|
1399
|
-
try {
|
|
1400
|
-
return new URL(value.replace(/[),.;]+$/, "")).origin;
|
|
1401
|
-
} catch {
|
|
1402
|
-
return "(resource URL)";
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
function boundText(value, limit) {
|
|
1407
|
-
return value.length <= limit ? value : `${value.slice(0, limit - 1)}\u2026`;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
// src/feature-inspection.ts
|
|
1411
|
-
import { VectorTile } from "@mapbox/vector-tile";
|
|
1412
|
-
import Pbf from "pbf";
|
|
8
|
+
createTileflowCommandFailureDocument,
|
|
9
|
+
createTileflowCommandSummary,
|
|
10
|
+
createTileflowStructuredDiagnostics,
|
|
11
|
+
serializeTileflowCommandDocument,
|
|
12
|
+
tileflowCommandDocumentSchemaVersion
|
|
13
|
+
} from "./chunk-ZRI6PNFL.js";
|
|
1413
14
|
import {
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
if (scanTruncated) break;
|
|
1495
|
-
}
|
|
1496
|
-
const uniqueFeatures = deduplicateFeatures(features).sort(compareInspectedFeatures);
|
|
1497
|
-
const limitedFeatures = uniqueFeatures.slice(0, options.limit);
|
|
1498
|
-
const metadataVersion = style.metadata?.["tileflow:tilesetVersion"];
|
|
1499
|
-
return {
|
|
1500
|
-
schemaVersion: 1,
|
|
1501
|
-
map: mapName,
|
|
1502
|
-
camera: {
|
|
1503
|
-
center: [...options.center],
|
|
1504
|
-
zoom: options.zoom,
|
|
1505
|
-
viewport: { width: options.width, height: options.height }
|
|
1506
|
-
},
|
|
1507
|
-
source: {
|
|
1508
|
-
id: source.id,
|
|
1509
|
-
origin: tileSource.origin,
|
|
1510
|
-
tileJsonPath: tileSource.tileJsonPath,
|
|
1511
|
-
tileOrigins: [...remoteOrigins].sort(compareCodeUnits2),
|
|
1512
|
-
version: typeof metadataVersion === "string" ? metadataVersion : null
|
|
1513
|
-
},
|
|
1514
|
-
sourceLayers: options.sourceLayers,
|
|
1515
|
-
properties: options.properties,
|
|
1516
|
-
tileZoom,
|
|
1517
|
-
tilesRead: tileCoordinates.length,
|
|
1518
|
-
scannedFeatures: Math.min(scannedFeatures, maxScannedFeatures),
|
|
1519
|
-
truncated: scanTruncated || uniqueFeatures.length > options.limit,
|
|
1520
|
-
features: limitedFeatures
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
function validateInspectionOptions(input) {
|
|
1524
|
-
const center = input.center;
|
|
1525
|
-
if (!Array.isArray(center) || center.length !== 2 || !Number.isFinite(center[0]) || !Number.isFinite(center[1]) || center[0] < -180 || center[0] > 180 || center[1] < -mercatorLatitudeLimit || center[1] > mercatorLatitudeLimit) {
|
|
1526
|
-
throw new Error(
|
|
1527
|
-
`Feature inspection center must be [longitude, latitude] inside Web Mercator bounds.`
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
if (!Number.isFinite(input.zoom) || input.zoom < 0 || input.zoom > 24) {
|
|
1531
|
-
throw new Error("Feature inspection zoom must be between 0 and 24.");
|
|
1532
|
-
}
|
|
1533
|
-
const width = boundedInteger(input.width ?? 512, 64, 2048, "width");
|
|
1534
|
-
const height = boundedInteger(input.height ?? 512, 64, 2048, "height");
|
|
1535
|
-
const limit = boundedInteger(input.limit ?? 200, 1, 500, "limit");
|
|
1536
|
-
const timeoutMs = boundedInteger(input.timeoutMs ?? 1e4, 100, 6e4, "timeout");
|
|
1537
|
-
const sourceLayers = normalizeNames(input.sourceLayers, 1, 12, "source layer");
|
|
1538
|
-
const properties = normalizeNames(input.properties ?? [], 0, 16, "property");
|
|
1539
|
-
return {
|
|
1540
|
-
...input,
|
|
1541
|
-
center: [center[0], center[1]],
|
|
1542
|
-
height,
|
|
1543
|
-
limit,
|
|
1544
|
-
properties,
|
|
1545
|
-
sourceLayers,
|
|
1546
|
-
timeoutMs,
|
|
1547
|
-
width
|
|
1548
|
-
};
|
|
1549
|
-
}
|
|
1550
|
-
function boundedInteger(value, minimum, maximum, label) {
|
|
1551
|
-
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
1552
|
-
throw new Error(
|
|
1553
|
-
`Feature inspection ${label} must be an integer from ${minimum} to ${maximum}.`
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
return value;
|
|
1557
|
-
}
|
|
1558
|
-
function normalizeNames(values, minimum, maximum, label) {
|
|
1559
|
-
if (!Array.isArray(values) || values.length < minimum || values.length > maximum) {
|
|
1560
|
-
throw new Error(
|
|
1561
|
-
`Feature inspection requires ${minimum}-${maximum} ${label}${maximum === 1 ? "" : "s"}.`
|
|
1562
|
-
);
|
|
1563
|
-
}
|
|
1564
|
-
const normalized = [...new Set(values)].sort(compareCodeUnits2);
|
|
1565
|
-
for (const value of normalized) {
|
|
1566
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(value)) {
|
|
1567
|
-
throw new Error(`Invalid feature inspection ${label} "${value}".`);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
return normalized;
|
|
1571
|
-
}
|
|
1572
|
-
function resolveVectorSource(style, sourceLayers) {
|
|
1573
|
-
const requested = new Set(sourceLayers);
|
|
1574
|
-
const referencedSourceIds = new Set(
|
|
1575
|
-
style.layers.filter((layer) => requested.has(String(layer["source-layer"] ?? ""))).map((layer) => layer.source).filter((sourceId) => typeof sourceId === "string")
|
|
1576
|
-
);
|
|
1577
|
-
const vectorSources = Object.entries(style.sources).filter(
|
|
1578
|
-
([, definition2]) => definition2.type === "vector"
|
|
1579
|
-
);
|
|
1580
|
-
const candidates = referencedSourceIds.size > 0 ? vectorSources.filter(([sourceId]) => referencedSourceIds.has(sourceId)) : vectorSources;
|
|
1581
|
-
if (candidates.length !== 1) {
|
|
1582
|
-
throw new Error(
|
|
1583
|
-
`Feature inspection requires exactly one vector source for the selected layers; found ${candidates.length}.`
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
const [id, definition] = candidates[0];
|
|
1587
|
-
return { id, definition };
|
|
1588
|
-
}
|
|
1589
|
-
async function resolveTileSource(definition, fetchImplementation, options, origins) {
|
|
1590
|
-
if (Array.isArray(definition.tiles) && typeof definition.tiles[0] === "string") {
|
|
1591
|
-
const template2 = safeHttpUrl(definition.tiles[0], "vector tile template");
|
|
1592
|
-
origins.add(template2.origin);
|
|
1593
|
-
return {
|
|
1594
|
-
maxzoom: finiteZoom(definition.maxzoom, 22),
|
|
1595
|
-
minzoom: finiteZoom(definition.minzoom, 0),
|
|
1596
|
-
origin: template2.origin,
|
|
1597
|
-
scheme: definition.scheme === "tms" ? "tms" : "xyz",
|
|
1598
|
-
template: template2,
|
|
1599
|
-
tileJsonPath: null
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
if (typeof definition.url !== "string") {
|
|
1603
|
-
throw new Error("Feature inspection vector source must define an HTTP(S) TileJSON URL.");
|
|
1604
|
-
}
|
|
1605
|
-
const tileJsonUrl = safeHttpUrl(definition.url, "TileJSON URL");
|
|
1606
|
-
const response = await fetchBytes(tileJsonUrl, {
|
|
1607
|
-
fetchImplementation,
|
|
1608
|
-
label: "TileJSON",
|
|
1609
|
-
limit: maxTileJsonBytes,
|
|
1610
|
-
signal: options.signal,
|
|
1611
|
-
timeoutMs: options.timeoutMs
|
|
1612
|
-
});
|
|
1613
|
-
const finalTileJsonUrl = safeHttpUrl(response.finalUrl, "TileJSON response URL");
|
|
1614
|
-
origins.add(finalTileJsonUrl.origin);
|
|
1615
|
-
let parsed;
|
|
1616
|
-
try {
|
|
1617
|
-
parsed = JSON.parse(new TextDecoder().decode(response.body));
|
|
1618
|
-
} catch {
|
|
1619
|
-
throw new Error("Feature inspection received invalid TileJSON JSON.");
|
|
1620
|
-
}
|
|
1621
|
-
const tileJson = parseTileJson(parsed);
|
|
1622
|
-
const template = safeHttpUrl(
|
|
1623
|
-
new URL(tileJson.tiles[0], finalTileJsonUrl).toString(),
|
|
1624
|
-
"vector tile template"
|
|
1625
|
-
);
|
|
1626
|
-
origins.add(template.origin);
|
|
1627
|
-
return {
|
|
1628
|
-
maxzoom: finiteZoom(tileJson.maxzoom, 22),
|
|
1629
|
-
minzoom: finiteZoom(tileJson.minzoom, 0),
|
|
1630
|
-
origin: finalTileJsonUrl.origin,
|
|
1631
|
-
scheme: tileJson.scheme === "tms" ? "tms" : "xyz",
|
|
1632
|
-
template,
|
|
1633
|
-
tileJsonPath: finalTileJsonUrl.pathname
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
function parseTileJson(value) {
|
|
1637
|
-
if (!isRecord(value) || !Array.isArray(value.tiles) || value.tiles.length === 0) {
|
|
1638
|
-
throw new Error("Feature inspection TileJSON must contain a non-empty tiles array.");
|
|
1639
|
-
}
|
|
1640
|
-
const tiles = value.tiles.filter((entry) => typeof entry === "string");
|
|
1641
|
-
if (tiles.length !== value.tiles.length || tiles.length > 8) {
|
|
1642
|
-
throw new Error("Feature inspection TileJSON tiles must contain 1-8 URL templates.");
|
|
1643
|
-
}
|
|
1644
|
-
return {
|
|
1645
|
-
tiles,
|
|
1646
|
-
...typeof value.maxzoom === "number" ? { maxzoom: value.maxzoom } : {},
|
|
1647
|
-
...typeof value.minzoom === "number" ? { minzoom: value.minzoom } : {},
|
|
1648
|
-
...typeof value.scheme === "string" ? { scheme: value.scheme } : {}
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
function finiteZoom(value, fallback) {
|
|
1652
|
-
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 24 ? value : fallback;
|
|
1653
|
-
}
|
|
1654
|
-
function resolveTileZoom(zoom, minzoom, maxzoom) {
|
|
1655
|
-
if (minzoom > maxzoom) throw new Error("Feature inspection source minzoom exceeds maxzoom.");
|
|
1656
|
-
return Math.max(minzoom, Math.min(maxzoom, Math.floor(zoom)));
|
|
1657
|
-
}
|
|
1658
|
-
function viewportBounds(center, zoom, width, height) {
|
|
1659
|
-
const centerX = longitudeToWorldX(center[0]);
|
|
1660
|
-
const centerY = latitudeToWorldY(center[1]);
|
|
1661
|
-
const scale = 512 * 2 ** zoom;
|
|
1662
|
-
const westX = centerX - width / 2 / scale;
|
|
1663
|
-
const eastX = centerX + width / 2 / scale;
|
|
1664
|
-
if (westX < 0 || eastX > 1) {
|
|
1665
|
-
throw new Error("Feature inspection viewports that cross the antimeridian are not supported.");
|
|
1666
|
-
}
|
|
1667
|
-
const northY = Math.max(0, centerY - height / 2 / scale);
|
|
1668
|
-
const southY = Math.min(1, centerY + height / 2 / scale);
|
|
1669
|
-
return [
|
|
1670
|
-
worldXToLongitude(westX),
|
|
1671
|
-
worldYToLatitude(southY),
|
|
1672
|
-
worldXToLongitude(eastX),
|
|
1673
|
-
worldYToLatitude(northY)
|
|
1674
|
-
];
|
|
1675
|
-
}
|
|
1676
|
-
function tileCover(bounds, zoom) {
|
|
1677
|
-
const count = 2 ** zoom;
|
|
1678
|
-
const minimumX = clampTile(Math.floor(longitudeToWorldX(bounds[0]) * count), count);
|
|
1679
|
-
const maximumX = clampTile(Math.floor(longitudeToWorldX(bounds[2]) * count), count);
|
|
1680
|
-
const minimumY = clampTile(Math.floor(latitudeToWorldY(bounds[3]) * count), count);
|
|
1681
|
-
const maximumY = clampTile(Math.floor(latitudeToWorldY(bounds[1]) * count), count);
|
|
1682
|
-
const coordinates = [];
|
|
1683
|
-
for (let y = minimumY; y <= maximumY; y += 1) {
|
|
1684
|
-
for (let x = minimumX; x <= maximumX; x += 1) coordinates.push({ x, y, z: zoom });
|
|
1685
|
-
}
|
|
1686
|
-
return coordinates;
|
|
1687
|
-
}
|
|
1688
|
-
function clampTile(value, count) {
|
|
1689
|
-
return Math.max(0, Math.min(count - 1, value));
|
|
1690
|
-
}
|
|
1691
|
-
function resolveTileUrl(template, coordinate, scheme) {
|
|
1692
|
-
const y = scheme === "tms" ? 2 ** coordinate.z - 1 - coordinate.y : coordinate.y;
|
|
1693
|
-
const replacements = {
|
|
1694
|
-
z: coordinate.z,
|
|
1695
|
-
x: coordinate.x,
|
|
1696
|
-
y,
|
|
1697
|
-
"-y": 2 ** coordinate.z - 1 - coordinate.y
|
|
1698
|
-
};
|
|
1699
|
-
let value = template.toString();
|
|
1700
|
-
for (const [token, replacement] of Object.entries(replacements)) {
|
|
1701
|
-
value = value.replace(
|
|
1702
|
-
new RegExp(`(?:\\{${token}\\}|%7B${token}%7D)`, "giu"),
|
|
1703
|
-
String(replacement)
|
|
1704
|
-
);
|
|
1705
|
-
}
|
|
1706
|
-
if (/(?:\{(?:z|x|-?y)\}|%7B(?:z|x|-?y)%7D)/iu.test(value)) {
|
|
1707
|
-
throw new Error("Feature inspection tile template contains unresolved coordinates.");
|
|
1708
|
-
}
|
|
1709
|
-
return new URL(value);
|
|
1710
|
-
}
|
|
1711
|
-
async function fetchBytes(url, options) {
|
|
1712
|
-
const timeout = AbortSignal.timeout(options.timeoutMs);
|
|
1713
|
-
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
1714
|
-
const response = await options.fetchImplementation(url, { signal });
|
|
1715
|
-
if (!response.ok) {
|
|
1716
|
-
throw new Error(`${options.label} request failed with HTTP ${response.status}.`);
|
|
1717
|
-
}
|
|
1718
|
-
const declaredLength = Number(response.headers.get("content-length"));
|
|
1719
|
-
if (Number.isFinite(declaredLength) && declaredLength > options.limit) {
|
|
1720
|
-
throw new Error(`${options.label} exceeds the ${options.limit}-byte limit.`);
|
|
1721
|
-
}
|
|
1722
|
-
if (!response.body) throw new Error(`${options.label} response had no body.`);
|
|
1723
|
-
const chunks = [];
|
|
1724
|
-
let length = 0;
|
|
1725
|
-
const reader = response.body.getReader();
|
|
1726
|
-
while (true) {
|
|
1727
|
-
const result = await reader.read();
|
|
1728
|
-
if (result.done) break;
|
|
1729
|
-
length += result.value.byteLength;
|
|
1730
|
-
if (length > options.limit) {
|
|
1731
|
-
await reader.cancel();
|
|
1732
|
-
throw new Error(`${options.label} exceeds the ${options.limit}-byte limit.`);
|
|
1733
|
-
}
|
|
1734
|
-
chunks.push(result.value);
|
|
1735
|
-
}
|
|
1736
|
-
const body = new Uint8Array(length);
|
|
1737
|
-
let offset = 0;
|
|
1738
|
-
for (const chunk of chunks) {
|
|
1739
|
-
body.set(chunk, offset);
|
|
1740
|
-
offset += chunk.byteLength;
|
|
1741
|
-
}
|
|
1742
|
-
return { body, finalUrl: response.url || url.toString() };
|
|
1743
|
-
}
|
|
1744
|
-
function summarizeGeometry(value) {
|
|
1745
|
-
if (!isRecord(value) || typeof value.type !== "string" || !("coordinates" in value)) return;
|
|
1746
|
-
const points = [];
|
|
1747
|
-
collectCoordinates(value.coordinates, points);
|
|
1748
|
-
if (points.length === 0) return;
|
|
1749
|
-
let west = Infinity;
|
|
1750
|
-
let south = Infinity;
|
|
1751
|
-
let east = -Infinity;
|
|
1752
|
-
let north = -Infinity;
|
|
1753
|
-
for (const [longitude, latitude] of points) {
|
|
1754
|
-
west = Math.min(west, longitude);
|
|
1755
|
-
south = Math.min(south, latitude);
|
|
1756
|
-
east = Math.max(east, longitude);
|
|
1757
|
-
north = Math.max(north, latitude);
|
|
1758
|
-
}
|
|
1759
|
-
return {
|
|
1760
|
-
type: value.type,
|
|
1761
|
-
center: [roundCoordinate((west + east) / 2), roundCoordinate((south + north) / 2)],
|
|
1762
|
-
bounds: [
|
|
1763
|
-
roundCoordinate(west),
|
|
1764
|
-
roundCoordinate(south),
|
|
1765
|
-
roundCoordinate(east),
|
|
1766
|
-
roundCoordinate(north)
|
|
1767
|
-
]
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
function collectCoordinates(value, points) {
|
|
1771
|
-
if (!Array.isArray(value)) return;
|
|
1772
|
-
if (value.length >= 2 && typeof value[0] === "number" && Number.isFinite(value[0]) && typeof value[1] === "number" && Number.isFinite(value[1])) {
|
|
1773
|
-
points.push([value[0], value[1]]);
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
for (const child of value) collectCoordinates(child, points);
|
|
1777
|
-
}
|
|
1778
|
-
function boundsIntersect(left, right) {
|
|
1779
|
-
return left[0] <= right[2] && left[2] >= right[0] && left[1] <= right[3] && left[3] >= right[1];
|
|
1780
|
-
}
|
|
1781
|
-
function projectProperties(source, properties) {
|
|
1782
|
-
const projected = {};
|
|
1783
|
-
for (const property of properties) {
|
|
1784
|
-
const value = source[property];
|
|
1785
|
-
if (typeof value === "string") projected[property] = value.slice(0, maxStringLength);
|
|
1786
|
-
else if (typeof value === "boolean") projected[property] = value;
|
|
1787
|
-
else if (typeof value === "number" && Number.isFinite(value)) projected[property] = value;
|
|
1788
|
-
}
|
|
1789
|
-
return projected;
|
|
1790
|
-
}
|
|
1791
|
-
function deduplicateFeatures(features) {
|
|
1792
|
-
const unique = /* @__PURE__ */ new Map();
|
|
1793
|
-
for (const feature of features) {
|
|
1794
|
-
const key = feature.id === null ? `${feature.sourceLayer}|${JSON.stringify(feature.geometry)}|${JSON.stringify(feature.properties)}` : `${feature.sourceLayer}|id:${feature.id}`;
|
|
1795
|
-
if (!unique.has(key)) unique.set(key, feature);
|
|
1796
|
-
}
|
|
1797
|
-
return [...unique.values()];
|
|
1798
|
-
}
|
|
1799
|
-
function compareInspectedFeatures(left, right) {
|
|
1800
|
-
const leftName = typeof left.properties.name === "string" ? left.properties.name : "";
|
|
1801
|
-
const rightName = typeof right.properties.name === "string" ? right.properties.name : "";
|
|
1802
|
-
return compareCodeUnits2(left.sourceLayer, right.sourceLayer) || Number(rightName.length > 0) - Number(leftName.length > 0) || compareCodeUnits2(leftName, rightName) || (left.id ?? Number.MAX_SAFE_INTEGER) - (right.id ?? Number.MAX_SAFE_INTEGER) || compareCodeUnits2(JSON.stringify(left), JSON.stringify(right));
|
|
1803
|
-
}
|
|
1804
|
-
function safeHttpUrl(value, label) {
|
|
1805
|
-
let parsed;
|
|
1806
|
-
try {
|
|
1807
|
-
parsed = new URL(value);
|
|
1808
|
-
} catch {
|
|
1809
|
-
throw new Error(`Feature inspection ${label} is not a valid URL.`);
|
|
1810
|
-
}
|
|
1811
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1812
|
-
throw new Error(`Feature inspection ${label} must use HTTP or HTTPS.`);
|
|
1813
|
-
}
|
|
1814
|
-
return parsed;
|
|
1815
|
-
}
|
|
1816
|
-
function safeOrigin(value) {
|
|
1817
|
-
return safeHttpUrl(value, "response URL").origin;
|
|
1818
|
-
}
|
|
1819
|
-
function longitudeToWorldX(longitude) {
|
|
1820
|
-
return (longitude + 180) / 360;
|
|
1821
|
-
}
|
|
1822
|
-
function worldXToLongitude(x) {
|
|
1823
|
-
return x * 360 - 180;
|
|
1824
|
-
}
|
|
1825
|
-
function latitudeToWorldY(latitude) {
|
|
1826
|
-
const radians = latitude * Math.PI / 180;
|
|
1827
|
-
return (1 - Math.log(Math.tan(radians) + 1 / Math.cos(radians)) / Math.PI) / 2;
|
|
1828
|
-
}
|
|
1829
|
-
function worldYToLatitude(y) {
|
|
1830
|
-
return Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180 / Math.PI;
|
|
1831
|
-
}
|
|
1832
|
-
function roundCoordinate(value) {
|
|
1833
|
-
return Math.round(value * 1e7) / 1e7;
|
|
1834
|
-
}
|
|
1835
|
-
function isRecord(value) {
|
|
1836
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
// src/index.ts
|
|
1840
|
-
var defaultTileflowApiUrl = "https://api.tileflow.dev";
|
|
1841
|
-
var defaultTileflowConfigPath = "tileflow.config.ts";
|
|
1842
|
-
var defaultTileflowManifestPath = "public/tileflow/manifest.json";
|
|
1843
|
-
var defaultTileflowTileset = "world";
|
|
1844
|
-
var TileflowPreviewSelectionError = class extends Error {
|
|
1845
|
-
code = "PREVIEW_SELECTION_INVALID";
|
|
1846
|
-
constructor(message) {
|
|
1847
|
-
super(message);
|
|
1848
|
-
this.name = "TileflowPreviewSelectionError";
|
|
1849
|
-
}
|
|
1850
|
-
};
|
|
1851
|
-
var TileflowValidationError = class extends Error {
|
|
1852
|
-
code = "CONFIG_INVALID";
|
|
1853
|
-
messages;
|
|
1854
|
-
phase = "config-validation";
|
|
1855
|
-
constructor(messages) {
|
|
1856
|
-
super("Invalid Tileflow config");
|
|
1857
|
-
this.name = "TileflowValidationError";
|
|
1858
|
-
this.messages = messages;
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1861
|
-
async function loadTileflowConfig(configPath = defaultTileflowConfigPath, options = {}) {
|
|
1862
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1863
|
-
const resolvedPath = resolve3(cwd, configPath);
|
|
1864
|
-
if (!existsSync(resolvedPath)) {
|
|
1865
|
-
throw new Error(`Config not found: ${configPath}`);
|
|
1866
|
-
}
|
|
1867
|
-
if (options.fresh) {
|
|
1868
|
-
clearTileflowLocalModuleCache(resolvedPath, cwd);
|
|
1869
|
-
}
|
|
1870
|
-
const jiti = createJiti(import.meta.url, {
|
|
1871
|
-
fsCache: false,
|
|
1872
|
-
moduleCache: !options.fresh
|
|
1873
|
-
});
|
|
1874
|
-
const imported = await jiti.import(resolvedPath, { default: true });
|
|
1875
|
-
return asTileflowProjectConfig(imported);
|
|
1876
|
-
}
|
|
1877
|
-
async function loadValidTileflowConfig(configPath = defaultTileflowConfigPath, options = {}) {
|
|
1878
|
-
const project = await loadTileflowConfig(configPath, options);
|
|
1879
|
-
assertValidTileflowConfig(project);
|
|
1880
|
-
return project;
|
|
1881
|
-
}
|
|
1882
|
-
function assertValidTileflowConfig(project) {
|
|
1883
|
-
const result = validateConfig(project);
|
|
1884
|
-
if (!result.valid) {
|
|
1885
|
-
throw new TileflowValidationError(result.messages);
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
function asTileflowProjectConfig(input) {
|
|
1889
|
-
if (input && typeof input === "object" && "maps" in input && typeof input.maps === "object") {
|
|
1890
|
-
return input;
|
|
1891
|
-
}
|
|
1892
|
-
return {
|
|
1893
|
-
tilesets: {
|
|
1894
|
-
[defaultTileflowTileset]: {
|
|
1895
|
-
id: defaultTileflowTileset,
|
|
1896
|
-
name: "World"
|
|
1897
|
-
}
|
|
1898
|
-
},
|
|
1899
|
-
maps: {
|
|
1900
|
-
main: input
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
}
|
|
1904
|
-
function getTileflowMapNames(project) {
|
|
1905
|
-
return Object.keys(project.maps);
|
|
1906
|
-
}
|
|
1907
|
-
function getFirstTileflowMapName(project) {
|
|
1908
|
-
const [mapName] = getTileflowMapNames(project);
|
|
1909
|
-
if (!mapName) {
|
|
1910
|
-
throw new Error("Tileflow config must define at least one map.");
|
|
1911
|
-
}
|
|
1912
|
-
return mapName;
|
|
1913
|
-
}
|
|
1914
|
-
function resolveTileflowPreview(project, selection = {}) {
|
|
1915
|
-
if (selection.map !== void 0 && selection.scene !== void 0) {
|
|
1916
|
-
throw new TileflowPreviewSelectionError(
|
|
1917
|
-
"Choose either a Tileflow preview map or scene, not both."
|
|
1918
|
-
);
|
|
1919
|
-
}
|
|
1920
|
-
if (selection.scene !== void 0) {
|
|
1921
|
-
const scene = Object.hasOwn(project.scenes ?? {}, selection.scene) ? project.scenes?.[selection.scene] : void 0;
|
|
1922
|
-
if (!scene) {
|
|
1923
|
-
throw new TileflowPreviewSelectionError(`Unknown Tileflow scene: ${selection.scene}`);
|
|
1924
|
-
}
|
|
1925
|
-
const normalized = normalizeTileflowCaptureScene(scene);
|
|
1926
|
-
if (normalized.target.kind === "application") {
|
|
1927
|
-
throw new TileflowPreviewSelectionError(
|
|
1928
|
-
`Tileflow scene "${selection.scene}" targets an application. Preview it through the application's development server.`
|
|
1929
|
-
);
|
|
1930
|
-
}
|
|
1931
|
-
if (!Object.hasOwn(project.maps, normalized.map)) {
|
|
1932
|
-
throw new TileflowPreviewSelectionError(
|
|
1933
|
-
`Tileflow scene "${selection.scene}" references an unknown map: ${normalized.map}`
|
|
1934
|
-
);
|
|
1935
|
-
}
|
|
1936
|
-
return {
|
|
1937
|
-
camera: normalized.camera,
|
|
1938
|
-
label: `${normalized.map} / ${selection.scene} \xB7 ${normalized.viewport.width}\xD7${normalized.viewport.height}`,
|
|
1939
|
-
mapName: normalized.map,
|
|
1940
|
-
viewport: normalized.viewport
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
|
-
const mapName = selection.map ?? getFirstTileflowMapName(project);
|
|
1944
|
-
const map = Object.hasOwn(project.maps, mapName) ? project.maps[mapName] : void 0;
|
|
1945
|
-
if (!map) {
|
|
1946
|
-
throw new TileflowPreviewSelectionError(`Unknown Tileflow map: ${mapName}`);
|
|
1947
|
-
}
|
|
1948
|
-
return {
|
|
1949
|
-
camera: {
|
|
1950
|
-
type: "center",
|
|
1951
|
-
center: map.view?.center ? [map.view.center[0], map.view.center[1]] : [0, 0],
|
|
1952
|
-
zoom: map.view?.zoom ?? 0,
|
|
1953
|
-
bearing: map.view?.bearing ?? 0,
|
|
1954
|
-
pitch: 0
|
|
1955
|
-
},
|
|
1956
|
-
label: mapName,
|
|
1957
|
-
mapName
|
|
1958
|
-
};
|
|
1959
|
-
}
|
|
1960
|
-
function getTileflowStyleMapName(path) {
|
|
1961
|
-
const fileName = path.split("/").pop() ?? "";
|
|
1962
|
-
return fileName.replace(/\.json$/, "");
|
|
1963
|
-
}
|
|
1964
|
-
function createTileflowManifest(project, options = {}) {
|
|
1965
|
-
return createManifest(project, options);
|
|
1966
|
-
}
|
|
1967
|
-
function createTileflowStyle(project, mapName, options = {}) {
|
|
1968
|
-
const style = createStyleFromProject2(project, mapName, options);
|
|
1969
|
-
assertValidTileflowStyle(style, mapName);
|
|
1970
|
-
return style;
|
|
1971
|
-
}
|
|
1972
|
-
function createTileflowStyles(project, options = {}) {
|
|
1973
|
-
const styleEntries = [];
|
|
1974
|
-
const issues = [];
|
|
1975
|
-
for (const mapName of getTileflowMapNames(project).sort(compareCodeUnits3)) {
|
|
1976
|
-
const style = createStyleFromProject2(project, mapName, options);
|
|
1977
|
-
styleEntries.push([mapName, style]);
|
|
1978
|
-
issues.push(...validateTileflowStyle(style, mapName));
|
|
1979
|
-
}
|
|
1980
|
-
if (issues.length > 0) {
|
|
1981
|
-
throw new TileflowStyleValidationError(normalizeTileflowStyleValidationIssues(issues));
|
|
1982
|
-
}
|
|
1983
|
-
return Object.fromEntries(styleEntries);
|
|
1984
|
-
}
|
|
1985
|
-
async function createTileflowBuildArtifacts(options = {}) {
|
|
1986
|
-
const project = await loadValidTileflowConfig(options.config ?? defaultTileflowConfigPath, {
|
|
1987
|
-
cwd: options.cwd,
|
|
1988
|
-
fresh: true
|
|
1989
|
-
});
|
|
1990
|
-
const prepared = await prepareTileflowProjectIcons(project, {
|
|
1991
|
-
assetBaseUrl: resolveAssetBaseUrl(options),
|
|
1992
|
-
cwd: options.cwd ?? process.cwd()
|
|
1993
|
-
});
|
|
1994
|
-
const styleOptions = { tileBaseUrl: options.tileBaseUrl };
|
|
1995
|
-
const styles = createTileflowStyles(prepared.project, styleOptions);
|
|
1996
|
-
return {
|
|
1997
|
-
assets: prepared.assets,
|
|
1998
|
-
manifest: createManifest(prepared.project, {
|
|
1999
|
-
styleBaseUrl: options.styleBaseUrl,
|
|
2000
|
-
tileBaseUrl: options.tileBaseUrl
|
|
2001
|
-
}),
|
|
2002
|
-
project: prepared.project,
|
|
2003
|
-
styles,
|
|
2004
|
-
watchPaths: prepared.watchPaths
|
|
2005
|
-
};
|
|
2006
|
-
}
|
|
2007
|
-
function createTileflowArtifactSession(options = {}) {
|
|
2008
|
-
return createTileflowArtifactSessionWithBuilder(options, createTileflowBuildArtifacts);
|
|
2009
|
-
}
|
|
2010
|
-
async function getTileflowWatchPaths(options = {}) {
|
|
2011
|
-
const cwd = options.cwd ?? process.cwd();
|
|
2012
|
-
const project = await loadValidTileflowConfig(options.config ?? defaultTileflowConfigPath, {
|
|
2013
|
-
cwd,
|
|
2014
|
-
fresh: true
|
|
2015
|
-
});
|
|
2016
|
-
return getTileflowIconWatchPaths(project, cwd);
|
|
2017
|
-
}
|
|
2018
|
-
async function writeTileflowBuildArtifacts(options) {
|
|
2019
|
-
const cwd = options.cwd ?? process.cwd();
|
|
2020
|
-
const outDir = resolve3(cwd, options.outDir);
|
|
2021
|
-
const stylesDir = resolve3(outDir, "styles");
|
|
2022
|
-
const artifacts = await createTileflowBuildArtifacts(options);
|
|
2023
|
-
await mkdir(stylesDir, { recursive: true });
|
|
2024
|
-
for (const [mapName, style] of Object.entries(artifacts.styles)) {
|
|
2025
|
-
await writeFile(
|
|
2026
|
-
resolve3(stylesDir, `${mapName}.json`),
|
|
2027
|
-
`${JSON.stringify(style, null, 2)}
|
|
2028
|
-
`,
|
|
2029
|
-
"utf8"
|
|
2030
|
-
);
|
|
2031
|
-
}
|
|
2032
|
-
for (const asset of artifacts.assets) {
|
|
2033
|
-
const outputPath = resolve3(outDir, asset.fileName);
|
|
2034
|
-
await mkdir(dirname2(outputPath), { recursive: true });
|
|
2035
|
-
await writeFile(outputPath, asset.source);
|
|
2036
|
-
}
|
|
2037
|
-
await writeFile(
|
|
2038
|
-
resolve3(outDir, "manifest.json"),
|
|
2039
|
-
`${JSON.stringify(artifacts.manifest, null, 2)}
|
|
2040
|
-
`,
|
|
2041
|
-
"utf8"
|
|
2042
|
-
);
|
|
2043
|
-
return artifacts;
|
|
2044
|
-
}
|
|
2045
|
-
function isTileflowRequestUrl(url, basePath) {
|
|
2046
|
-
const pathname = (url ?? "/").split("?")[0] ?? "/";
|
|
2047
|
-
if (!basePath) {
|
|
2048
|
-
return pathname === "/manifest.json" || pathname === "/style.json" || pathname === "/" || pathname.startsWith("/icons/") || pathname.startsWith("/styles/");
|
|
2049
|
-
}
|
|
2050
|
-
return pathname === basePath || pathname.startsWith(`${basePath}/`);
|
|
2051
|
-
}
|
|
2052
|
-
function createTileflowNodeRequest(request) {
|
|
2053
|
-
const protocol = getNodeHeader(request, "x-forwarded-proto") ?? "http";
|
|
2054
|
-
const host = getNodeHeader(request, "host") ?? "localhost";
|
|
2055
|
-
return new Request(`${protocol}://${host}${request.url ?? "/"}`, {
|
|
2056
|
-
method: request.method
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
async function writeTileflowNodeResponse(response, tileflowResponse) {
|
|
2060
|
-
response.statusCode = tileflowResponse.status;
|
|
2061
|
-
tileflowResponse.headers.forEach((value, key) => {
|
|
2062
|
-
response.setHeader(key, value);
|
|
2063
|
-
});
|
|
2064
|
-
if (!tileflowResponse.body) {
|
|
2065
|
-
response.end();
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
const reader = tileflowResponse.body.getReader();
|
|
2069
|
-
try {
|
|
2070
|
-
while (true) {
|
|
2071
|
-
const { done, value } = await reader.read();
|
|
2072
|
-
if (done) break;
|
|
2073
|
-
if (response.destroyed) {
|
|
2074
|
-
await reader.cancel();
|
|
2075
|
-
return;
|
|
2076
|
-
}
|
|
2077
|
-
response.write(Buffer.from(value));
|
|
2078
|
-
}
|
|
2079
|
-
response.end();
|
|
2080
|
-
} finally {
|
|
2081
|
-
reader.releaseLock();
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
function joinTileflowPublicUrl(publicBase, basePath) {
|
|
2085
|
-
const trimmedBase = publicBase.replace(/\/+$/g, "");
|
|
2086
|
-
const trimmedPath = basePath.replace(/^\/+|\/+$/g, "");
|
|
2087
|
-
if (!trimmedPath) {
|
|
2088
|
-
return trimmedBase || "";
|
|
2089
|
-
}
|
|
2090
|
-
if (!trimmedBase || trimmedBase === ".") {
|
|
2091
|
-
return `/${trimmedPath}`;
|
|
2092
|
-
}
|
|
2093
|
-
return `${trimmedBase}/${trimmedPath}`;
|
|
2094
|
-
}
|
|
2095
|
-
function getTileflowAssetBasePath(basePath) {
|
|
2096
|
-
return basePath.replace(/^\/+|\/+$/g, "");
|
|
2097
|
-
}
|
|
2098
|
-
function getTileflowAssetFileName(basePath, fileName) {
|
|
2099
|
-
return basePath ? `${basePath}/${fileName}` : fileName;
|
|
2100
|
-
}
|
|
2101
|
-
function createTileflowDevRequestHandler(options = {}) {
|
|
2102
|
-
const basePath = normalizeTileflowBasePath(options.basePath);
|
|
2103
|
-
const configPath = options.config ?? defaultTileflowConfigPath;
|
|
2104
|
-
const cwd = options.cwd ?? process.cwd();
|
|
2105
|
-
const tileBaseUrl = options.tileBaseUrl ?? defaultTileflowApiUrl;
|
|
2106
|
-
return async function handleTileflowDevRequest(request) {
|
|
2107
|
-
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
2108
|
-
return jsonResponse({ error: "Method not allowed" }, 405);
|
|
2109
|
-
}
|
|
2110
|
-
const url = new URL(request.url);
|
|
2111
|
-
const path = stripTileflowBasePath(url.pathname, basePath);
|
|
2112
|
-
if (path === null) {
|
|
2113
|
-
return jsonResponse({ error: "Not found" }, 404);
|
|
2114
|
-
}
|
|
2115
|
-
try {
|
|
2116
|
-
if (path === "/__events") {
|
|
2117
|
-
return options.session ? eventStreamResponse(options.session) : jsonResponse({ error: "Live events require a development session." }, 404);
|
|
2118
|
-
}
|
|
2119
|
-
if (path === "/__runtime/maplibre-gl.js") {
|
|
2120
|
-
return textAssetResponse(getLocalMapLibreAsset("js"), "text/javascript; charset=utf-8");
|
|
2121
|
-
}
|
|
2122
|
-
if (path === "/__runtime/maplibre-gl.css") {
|
|
2123
|
-
return textAssetResponse(getLocalMapLibreAsset("css"), "text/css; charset=utf-8");
|
|
2124
|
-
}
|
|
2125
|
-
let artifacts;
|
|
2126
|
-
let state;
|
|
2127
|
-
if (options.session) {
|
|
2128
|
-
artifacts = getSessionArtifacts(options.session);
|
|
2129
|
-
state = options.session.getState();
|
|
2130
|
-
} else {
|
|
2131
|
-
artifacts = await createTileflowBuildArtifacts({
|
|
2132
|
-
assetBaseUrl: `${url.origin}${basePath}`,
|
|
2133
|
-
config: configPath,
|
|
2134
|
-
cwd,
|
|
2135
|
-
styleBaseUrl: options.styleBaseUrl ?? `${url.origin}${basePath}`,
|
|
2136
|
-
tileBaseUrl
|
|
2137
|
-
});
|
|
2138
|
-
state = {
|
|
2139
|
-
artifacts,
|
|
2140
|
-
generation: 1,
|
|
2141
|
-
lastGoodGeneration: 1,
|
|
2142
|
-
status: "ready"
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2145
|
-
if (path === "/__status") {
|
|
2146
|
-
return jsonResponse(createTileflowArtifactStatus(state));
|
|
2147
|
-
}
|
|
2148
|
-
if (path === "/" || path === "") {
|
|
2149
|
-
const preview = artifacts ? resolveTileflowPreview(artifacts.project, { map: options.map, scene: options.scene }) : void 0;
|
|
2150
|
-
return htmlResponse(previewHtml(preview, basePath, state));
|
|
2151
|
-
}
|
|
2152
|
-
if (!artifacts) return unavailableArtifactsResponse(state);
|
|
2153
|
-
if (path === "/manifest.json") {
|
|
2154
|
-
return jsonResponse(artifacts.manifest);
|
|
2155
|
-
}
|
|
2156
|
-
if (path === "/style.json") {
|
|
2157
|
-
return jsonResponse(artifacts.styles[getFirstTileflowMapName(artifacts.project)]);
|
|
2158
|
-
}
|
|
2159
|
-
if (path.startsWith("/styles/")) {
|
|
2160
|
-
const mapName = getTileflowStyleMapName(path);
|
|
2161
|
-
if (!Object.hasOwn(artifacts.styles, mapName)) {
|
|
2162
|
-
return jsonResponse({ error: `Unknown map: ${mapName}` }, 404);
|
|
2163
|
-
}
|
|
2164
|
-
return jsonResponse(artifacts.styles[mapName]);
|
|
2165
|
-
}
|
|
2166
|
-
if (path.startsWith("/icons/")) {
|
|
2167
|
-
const assetName = path.replace(/^\/+/, "");
|
|
2168
|
-
const asset = artifacts.assets.find((candidate) => candidate.fileName === assetName);
|
|
2169
|
-
if (!asset) {
|
|
2170
|
-
return jsonResponse({ error: `Unknown icon asset: ${assetName}` }, 404);
|
|
2171
|
-
}
|
|
2172
|
-
return assetResponse(asset);
|
|
2173
|
-
}
|
|
2174
|
-
return jsonResponse({ error: "Not found" }, 404);
|
|
2175
|
-
} catch (error) {
|
|
2176
|
-
options.onError?.(error);
|
|
2177
|
-
return tileflowErrorResponse(error);
|
|
2178
|
-
}
|
|
2179
|
-
};
|
|
2180
|
-
}
|
|
2181
|
-
function createTileflowArtifactStatus(state) {
|
|
2182
|
-
return {
|
|
2183
|
-
schemaVersion: tileflowArtifactSessionSchemaVersion,
|
|
2184
|
-
generation: state.generation,
|
|
2185
|
-
status: state.status,
|
|
2186
|
-
..."lastGoodGeneration" in state && state.lastGoodGeneration !== void 0 ? { lastGoodGeneration: state.lastGoodGeneration } : {},
|
|
2187
|
-
...state.status === "invalid" ? { diagnostics: state.diagnostics } : {}
|
|
2188
|
-
};
|
|
2189
|
-
}
|
|
2190
|
-
function getSessionArtifacts(session) {
|
|
2191
|
-
const state = session.getState();
|
|
2192
|
-
return state.status === "ready" ? state.artifacts : session.getLastGoodArtifacts();
|
|
2193
|
-
}
|
|
2194
|
-
function unavailableArtifactsResponse(state) {
|
|
2195
|
-
return jsonResponse(
|
|
2196
|
-
{
|
|
2197
|
-
error: "No valid Tileflow artifact generation is available.",
|
|
2198
|
-
...createTileflowArtifactStatus(state)
|
|
2199
|
-
},
|
|
2200
|
-
409
|
|
2201
|
-
);
|
|
2202
|
-
}
|
|
2203
|
-
function eventStreamResponse(session) {
|
|
2204
|
-
const encoder = new TextEncoder();
|
|
2205
|
-
let unsubscribe = () => {
|
|
2206
|
-
};
|
|
2207
|
-
const stream = new ReadableStream({
|
|
2208
|
-
start(controller) {
|
|
2209
|
-
const enqueue = (state) => {
|
|
2210
|
-
const status = createTileflowArtifactStatus(state);
|
|
2211
|
-
controller.enqueue(
|
|
2212
|
-
encoder.encode(`event: ${status.status}
|
|
2213
|
-
data: ${JSON.stringify(status)}
|
|
2214
|
-
|
|
2215
|
-
`)
|
|
2216
|
-
);
|
|
2217
|
-
};
|
|
2218
|
-
enqueue(session.getState());
|
|
2219
|
-
unsubscribe = session.subscribe(enqueue);
|
|
2220
|
-
},
|
|
2221
|
-
cancel() {
|
|
2222
|
-
unsubscribe();
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
return new Response(stream, {
|
|
2226
|
-
headers: {
|
|
2227
|
-
"Access-Control-Allow-Origin": "*",
|
|
2228
|
-
"Cache-Control": "no-cache, no-transform",
|
|
2229
|
-
Connection: "keep-alive",
|
|
2230
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
2231
|
-
"X-Accel-Buffering": "no"
|
|
2232
|
-
}
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
function getNodeHeader(request, name) {
|
|
2236
|
-
const value = request.headers[name];
|
|
2237
|
-
return Array.isArray(value) ? value[0] : value;
|
|
2238
|
-
}
|
|
2239
|
-
function normalizeTileflowBasePath(value = "") {
|
|
2240
|
-
const trimmed = value.trim().replace(/^\/+|\/+$/g, "");
|
|
2241
|
-
return trimmed ? `/${trimmed}` : "";
|
|
2242
|
-
}
|
|
2243
|
-
function stripTileflowBasePath(pathname, basePath) {
|
|
2244
|
-
if (!basePath) {
|
|
2245
|
-
return pathname;
|
|
2246
|
-
}
|
|
2247
|
-
if (pathname === basePath) {
|
|
2248
|
-
return "/";
|
|
2249
|
-
}
|
|
2250
|
-
if (pathname.startsWith(`${basePath}/`)) {
|
|
2251
|
-
return pathname.slice(basePath.length);
|
|
2252
|
-
}
|
|
2253
|
-
return null;
|
|
2254
|
-
}
|
|
2255
|
-
function tileflowErrorResponse(error) {
|
|
2256
|
-
if (error instanceof TileflowPreviewSelectionError) {
|
|
2257
|
-
return jsonResponse({ error: error.message }, 400);
|
|
2258
|
-
}
|
|
2259
|
-
if (error instanceof TileflowStyleValidationError) {
|
|
2260
|
-
return jsonResponse(
|
|
2261
|
-
{
|
|
2262
|
-
error: error.message,
|
|
2263
|
-
issues: error.issues
|
|
2264
|
-
},
|
|
2265
|
-
400
|
|
2266
|
-
);
|
|
2267
|
-
}
|
|
2268
|
-
if (error instanceof TileflowValidationError) {
|
|
2269
|
-
return jsonResponse(
|
|
2270
|
-
{
|
|
2271
|
-
error: error.message,
|
|
2272
|
-
messages: error.messages
|
|
2273
|
-
},
|
|
2274
|
-
400
|
|
2275
|
-
);
|
|
2276
|
-
}
|
|
2277
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2278
|
-
return jsonResponse({ error: message }, 500);
|
|
2279
|
-
}
|
|
2280
|
-
function jsonResponse(body, status = 200) {
|
|
2281
|
-
return new Response(`${JSON.stringify(body, null, 2)}
|
|
2282
|
-
`, {
|
|
2283
|
-
headers: {
|
|
2284
|
-
"Access-Control-Allow-Origin": "*",
|
|
2285
|
-
"Cache-Control": "no-store",
|
|
2286
|
-
"Content-Type": "application/json; charset=utf-8"
|
|
2287
|
-
},
|
|
2288
|
-
status
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
|
-
function htmlResponse(body, status = 200) {
|
|
2292
|
-
return new Response(body, {
|
|
2293
|
-
headers: {
|
|
2294
|
-
"Access-Control-Allow-Origin": "*",
|
|
2295
|
-
"Cache-Control": "no-store",
|
|
2296
|
-
"Content-Type": "text/html; charset=utf-8"
|
|
2297
|
-
},
|
|
2298
|
-
status
|
|
2299
|
-
});
|
|
2300
|
-
}
|
|
2301
|
-
function assetResponse(asset) {
|
|
2302
|
-
const body = typeof asset.source === "string" ? asset.source : Buffer.from(asset.source);
|
|
2303
|
-
return new Response(body, {
|
|
2304
|
-
headers: {
|
|
2305
|
-
"Access-Control-Allow-Origin": "*",
|
|
2306
|
-
"Cache-Control": "no-store",
|
|
2307
|
-
"Content-Type": asset.contentType
|
|
2308
|
-
}
|
|
2309
|
-
});
|
|
2310
|
-
}
|
|
2311
|
-
function resolveAssetBaseUrl(options) {
|
|
2312
|
-
if (options.assetBaseUrl !== void 0) {
|
|
2313
|
-
return options.assetBaseUrl;
|
|
2314
|
-
}
|
|
2315
|
-
if (options.styleBaseUrl && !isRelativePublicUrl(options.styleBaseUrl)) {
|
|
2316
|
-
return options.styleBaseUrl;
|
|
2317
|
-
}
|
|
2318
|
-
return "..";
|
|
2319
|
-
}
|
|
2320
|
-
function isRelativePublicUrl(value) {
|
|
2321
|
-
return !(value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:"));
|
|
2322
|
-
}
|
|
2323
|
-
function previewHtml(preview, basePath, state) {
|
|
2324
|
-
const styleUrl = preview ? `${basePath}/styles/${preview.mapName}.json` : void 0;
|
|
2325
|
-
const initialStatus = createTileflowArtifactStatus(state);
|
|
2326
|
-
const mapOptions = preview ? previewMapOptions(preview) : void 0;
|
|
2327
|
-
const viewportCss = preview?.viewport ? `
|
|
2328
|
-
html, body { min-height: 100%; margin: 0; }
|
|
2329
|
-
body {
|
|
2330
|
-
display: grid;
|
|
2331
|
-
place-items: center;
|
|
2332
|
-
overflow: auto;
|
|
2333
|
-
background: #E8E5DE;
|
|
2334
|
-
}
|
|
2335
|
-
#map {
|
|
2336
|
-
width: ${preview.viewport.width}px;
|
|
2337
|
-
height: ${preview.viewport.height}px;
|
|
2338
|
-
box-shadow: 0 24px 80px rgba(37, 34, 29, 0.18);
|
|
2339
|
-
}` : "html, body, #map { height: 100%; margin: 0; }";
|
|
2340
|
-
return `<!doctype html>
|
|
2341
|
-
<html lang="en">
|
|
2342
|
-
<head>
|
|
2343
|
-
<meta charset="utf-8" />
|
|
2344
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2345
|
-
<title>Tileflow Preview</title>
|
|
2346
|
-
<link rel="stylesheet" href="${basePath}/__runtime/maplibre-gl.css" />
|
|
2347
|
-
<style>
|
|
2348
|
-
${viewportCss}
|
|
2349
|
-
body { font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
2350
|
-
.badge {
|
|
2351
|
-
position: fixed;
|
|
2352
|
-
left: 16px;
|
|
2353
|
-
top: 16px;
|
|
2354
|
-
z-index: 1;
|
|
2355
|
-
border-radius: 8px;
|
|
2356
|
-
background: rgba(246, 247, 243, 0.92);
|
|
2357
|
-
border: 1px solid rgba(60, 64, 67, 0.12);
|
|
2358
|
-
color: #3C4043;
|
|
2359
|
-
padding: 10px 12px;
|
|
2360
|
-
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12);
|
|
2361
|
-
}
|
|
2362
|
-
.status {
|
|
2363
|
-
position: fixed;
|
|
2364
|
-
left: 16px;
|
|
2365
|
-
right: 16px;
|
|
2366
|
-
bottom: 16px;
|
|
2367
|
-
z-index: 2;
|
|
2368
|
-
display: none;
|
|
2369
|
-
max-width: 720px;
|
|
2370
|
-
border: 1px solid rgba(160, 46, 46, 0.35);
|
|
2371
|
-
border-radius: 8px;
|
|
2372
|
-
background: rgba(255, 244, 244, 0.96);
|
|
2373
|
-
color: #702020;
|
|
2374
|
-
padding: 12px;
|
|
2375
|
-
white-space: pre-wrap;
|
|
2376
|
-
}
|
|
2377
|
-
</style>
|
|
2378
|
-
</head>
|
|
2379
|
-
<body>
|
|
2380
|
-
<div class="badge" id="badge">Tileflow preview</div>
|
|
2381
|
-
<div class="status" id="status" role="status"></div>
|
|
2382
|
-
<div id="map"></div>
|
|
2383
|
-
<script src="${basePath}/__runtime/maplibre-gl.js"></script>
|
|
2384
|
-
<script>
|
|
2385
|
-
const initialStatus = ${JSON.stringify(initialStatus)};
|
|
2386
|
-
const initialGeneration = initialStatus.generation;
|
|
2387
|
-
const badge = document.getElementById("badge");
|
|
2388
|
-
const status = document.getElementById("status");
|
|
2389
|
-
const previewLabel = ${JSON.stringify(preview?.label)};
|
|
2390
|
-
const styleUrl = ${JSON.stringify(styleUrl)};
|
|
2391
|
-
const previewMapOptions = ${JSON.stringify(mapOptions)};
|
|
2392
|
-
|
|
2393
|
-
if (styleUrl) {
|
|
2394
|
-
const map = new maplibregl.Map({
|
|
2395
|
-
container: "map",
|
|
2396
|
-
style: styleUrl,
|
|
2397
|
-
...previewMapOptions
|
|
2398
|
-
});
|
|
2399
|
-
map.addControl(new maplibregl.NavigationControl(), "top-right");
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
function applyStatus(next) {
|
|
2403
|
-
badge.textContent = ["Tileflow preview", previewLabel, next.status].filter(Boolean).join(" \xB7 ");
|
|
2404
|
-
if (next.status === "invalid") {
|
|
2405
|
-
const diagnostics = next.diagnostics || [];
|
|
2406
|
-
status.textContent = diagnostics.map((item) =>
|
|
2407
|
-
(item.path ? item.path + ": " : "") + item.message
|
|
2408
|
-
).join("\\n") || "Tileflow config is invalid.";
|
|
2409
|
-
status.style.display = "block";
|
|
2410
|
-
return;
|
|
2411
|
-
}
|
|
2412
|
-
status.style.display = "none";
|
|
2413
|
-
if (next.status === "ready" && next.generation > initialGeneration) {
|
|
2414
|
-
location.reload();
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
applyStatus(initialStatus);
|
|
2419
|
-
const events = new EventSource(${JSON.stringify(`${basePath}/__events`)});
|
|
2420
|
-
for (const eventName of ["building", "ready", "invalid"]) {
|
|
2421
|
-
events.addEventListener(eventName, (event) => applyStatus(JSON.parse(event.data)));
|
|
2422
|
-
}
|
|
2423
|
-
</script>
|
|
2424
|
-
</body>
|
|
2425
|
-
</html>`;
|
|
2426
|
-
}
|
|
2427
|
-
function previewMapOptions(preview) {
|
|
2428
|
-
if (preview.camera.type === "center") {
|
|
2429
|
-
return {
|
|
2430
|
-
bearing: preview.camera.bearing,
|
|
2431
|
-
center: preview.camera.center,
|
|
2432
|
-
pitch: preview.camera.pitch,
|
|
2433
|
-
zoom: preview.camera.zoom
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2436
|
-
const [west, south, east, north] = preview.camera.bounds;
|
|
2437
|
-
return {
|
|
2438
|
-
bearing: preview.camera.bearing,
|
|
2439
|
-
bounds: [
|
|
2440
|
-
[west, south],
|
|
2441
|
-
[east, north]
|
|
2442
|
-
],
|
|
2443
|
-
fitBoundsOptions: { padding: preview.camera.padding },
|
|
2444
|
-
pitch: preview.camera.pitch
|
|
2445
|
-
};
|
|
2446
|
-
}
|
|
2447
|
-
var localRequire = createRequire(import.meta.url);
|
|
2448
|
-
var localMapLibreJavaScript;
|
|
2449
|
-
var localMapLibreStylesheet;
|
|
2450
|
-
function getLocalMapLibreAsset(kind) {
|
|
2451
|
-
if (kind === "js" && localMapLibreJavaScript !== void 0) return localMapLibreJavaScript;
|
|
2452
|
-
if (kind === "css" && localMapLibreStylesheet !== void 0) return localMapLibreStylesheet;
|
|
2453
|
-
const packagePath = localRequire.resolve("maplibre-gl/package.json");
|
|
2454
|
-
const source = readFileSync(
|
|
2455
|
-
join(dirname2(packagePath), "dist", kind === "js" ? "maplibre-gl.js" : "maplibre-gl.css"),
|
|
2456
|
-
"utf8"
|
|
2457
|
-
);
|
|
2458
|
-
if (kind === "js") localMapLibreJavaScript = source;
|
|
2459
|
-
else localMapLibreStylesheet = source;
|
|
2460
|
-
return source;
|
|
2461
|
-
}
|
|
2462
|
-
function textAssetResponse(body, contentType) {
|
|
2463
|
-
return new Response(body, {
|
|
2464
|
-
headers: {
|
|
2465
|
-
"Access-Control-Allow-Origin": "*",
|
|
2466
|
-
"Cache-Control": "no-store",
|
|
2467
|
-
"Content-Type": contentType
|
|
2468
|
-
}
|
|
2469
|
-
});
|
|
2470
|
-
}
|
|
2471
|
-
function clearTileflowLocalModuleCache(configPath, cwd) {
|
|
2472
|
-
const roots = [canonicalPath2(cwd), canonicalPath2(dirname2(resolve3(configPath)))];
|
|
2473
|
-
for (const cachePath of Object.keys(localRequire.cache)) {
|
|
2474
|
-
if (cachePath.split(sep3).includes("node_modules")) continue;
|
|
2475
|
-
if (roots.some((root) => isPathWithin(root, canonicalPath2(cachePath)))) {
|
|
2476
|
-
delete localRequire.cache[cachePath];
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
function canonicalPath2(path) {
|
|
2481
|
-
try {
|
|
2482
|
-
return realpathSync2.native(resolve3(path));
|
|
2483
|
-
} catch {
|
|
2484
|
-
return resolve3(path);
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
function isPathWithin(root, candidate) {
|
|
2488
|
-
const path = relative3(resolve3(root), resolve3(candidate));
|
|
2489
|
-
return path === "" || !isAbsolute3(path) && path !== ".." && !path.startsWith(`..${sep3}`);
|
|
2490
|
-
}
|
|
15
|
+
TileflowPreviewSelectionError,
|
|
16
|
+
normalizeTileflowLandmarkManifest,
|
|
17
|
+
readBoundedTileflowJsonResponse,
|
|
18
|
+
readBoundedTileflowResponse,
|
|
19
|
+
renderTileflowPreviewHtml,
|
|
20
|
+
resolveTileflowPreview
|
|
21
|
+
} from "./chunk-GWUMIHL6.js";
|
|
22
|
+
import {
|
|
23
|
+
createTileflowArtifactStatus,
|
|
24
|
+
createTileflowComparisonRequestHandler,
|
|
25
|
+
createTileflowDevRequestHandler,
|
|
26
|
+
createTileflowNodeRequest,
|
|
27
|
+
getTileflowRequestPath,
|
|
28
|
+
getTileflowStyleInspectionSelection,
|
|
29
|
+
getTileflowStyleSelection,
|
|
30
|
+
isTileflowRequestUrl,
|
|
31
|
+
tileflowComparisonSchemaVersion,
|
|
32
|
+
writeTileflowNodeResponse
|
|
33
|
+
} from "./chunk-OEABXOJH.js";
|
|
34
|
+
import {
|
|
35
|
+
TileflowHostedManifestOverwriteError,
|
|
36
|
+
assertTileflowSelfHostedManifestTarget,
|
|
37
|
+
createTileflowArtifactDiagnostics,
|
|
38
|
+
createTileflowArtifactPlan,
|
|
39
|
+
createTileflowArtifactSession,
|
|
40
|
+
createTileflowBuildArtifacts,
|
|
41
|
+
createTileflowBuildProvenance,
|
|
42
|
+
createTileflowManifest,
|
|
43
|
+
createTileflowStyle,
|
|
44
|
+
createTileflowStyles,
|
|
45
|
+
getTileflowArtifactFiles,
|
|
46
|
+
getTileflowWatchPaths,
|
|
47
|
+
isTileflowArtifactInputPath,
|
|
48
|
+
refreshTileflowArtifactSession,
|
|
49
|
+
tileflowArtifactInventoryFileName,
|
|
50
|
+
tileflowArtifactPlanSchemaVersion,
|
|
51
|
+
tileflowArtifactSessionSchemaVersion,
|
|
52
|
+
writeTileflowArtifactPlan,
|
|
53
|
+
writeTileflowBuildArtifacts
|
|
54
|
+
} from "./chunk-O7PE6FB2.js";
|
|
55
|
+
import {
|
|
56
|
+
TileflowFontCompilationError,
|
|
57
|
+
bindTileflowStyleFontBundle,
|
|
58
|
+
getTileflowAssetBasePath,
|
|
59
|
+
getTileflowAssetFileName,
|
|
60
|
+
getTileflowFontWatchPaths,
|
|
61
|
+
joinTileflowPublicUrl,
|
|
62
|
+
normalizeTileflowBasePath,
|
|
63
|
+
prepareTileflowStyleFonts,
|
|
64
|
+
replaceTileflowStyleFontSources,
|
|
65
|
+
resolveTileflowArtifactPublicUrls
|
|
66
|
+
} from "./chunk-VEB43KDS.js";
|
|
67
|
+
import {
|
|
68
|
+
TileflowIconCompilationError,
|
|
69
|
+
compileTileflowIconPackages,
|
|
70
|
+
getTileflowIconWatchPaths,
|
|
71
|
+
iconFileExtensions,
|
|
72
|
+
inspectTileflowIconCatalogs,
|
|
73
|
+
prepareTileflowCatalogIcons
|
|
74
|
+
} from "./chunk-BALG4DSG.js";
|
|
75
|
+
import "./chunk-BL3FK4GO.js";
|
|
76
|
+
import {
|
|
77
|
+
TileflowStyleValidationError,
|
|
78
|
+
assertValidTileflowStyle,
|
|
79
|
+
validateTileflowStyle
|
|
80
|
+
} from "./chunk-KDFTE4Q5.js";
|
|
81
|
+
import {
|
|
82
|
+
TileflowValidationError,
|
|
83
|
+
assertValidTileflowConfig,
|
|
84
|
+
defaultTileflowApiUrl,
|
|
85
|
+
defaultTileflowConfigPath,
|
|
86
|
+
defaultTileflowManifestPath,
|
|
87
|
+
getFirstTileflowMapName,
|
|
88
|
+
getTileflowMapNames,
|
|
89
|
+
loadTileflowConfig,
|
|
90
|
+
loadTileflowConfigWithInputs,
|
|
91
|
+
loadValidTileflowConfig,
|
|
92
|
+
loadValidTileflowConfigWithInputs
|
|
93
|
+
} from "./chunk-BKHIZ7H2.js";
|
|
94
|
+
import "./chunk-D4OE4ITL.js";
|
|
2491
95
|
export {
|
|
96
|
+
TileflowConfigInspectionError,
|
|
97
|
+
TileflowFontCompilationError,
|
|
98
|
+
TileflowHostedManifestOverwriteError,
|
|
2492
99
|
TileflowIconCompilationError,
|
|
2493
100
|
TileflowPreviewSelectionError,
|
|
2494
101
|
TileflowStyleValidationError,
|
|
2495
102
|
TileflowValidationError,
|
|
2496
|
-
|
|
103
|
+
assertTileflowSelfHostedManifestTarget,
|
|
2497
104
|
assertValidTileflowConfig,
|
|
2498
105
|
assertValidTileflowStyle,
|
|
106
|
+
bindTileflowStyleFontBundle,
|
|
2499
107
|
compileTileflowIconPackages,
|
|
2500
108
|
createTileflowArtifactDiagnostics,
|
|
109
|
+
createTileflowArtifactPlan,
|
|
2501
110
|
createTileflowArtifactSession,
|
|
2502
111
|
createTileflowArtifactStatus,
|
|
2503
112
|
createTileflowBuildArtifacts,
|
|
113
|
+
createTileflowBuildProvenance,
|
|
114
|
+
createTileflowCommandFailureDocument,
|
|
115
|
+
createTileflowCommandSummary,
|
|
116
|
+
createTileflowComparisonRequestHandler,
|
|
2504
117
|
createTileflowDevRequestHandler,
|
|
2505
118
|
createTileflowManifest,
|
|
2506
119
|
createTileflowNodeRequest,
|
|
120
|
+
createTileflowStructuredDiagnostics,
|
|
2507
121
|
createTileflowStyle,
|
|
2508
122
|
createTileflowStyles,
|
|
2509
123
|
defaultTileflowApiUrl,
|
|
2510
124
|
defaultTileflowConfigPath,
|
|
2511
125
|
defaultTileflowManifestPath,
|
|
2512
|
-
defaultTileflowTileset,
|
|
2513
126
|
getFirstTileflowMapName,
|
|
127
|
+
getTileflowArtifactFiles,
|
|
2514
128
|
getTileflowAssetBasePath,
|
|
2515
129
|
getTileflowAssetFileName,
|
|
130
|
+
getTileflowFontWatchPaths,
|
|
131
|
+
getTileflowIconWatchPaths,
|
|
2516
132
|
getTileflowMapNames,
|
|
2517
|
-
|
|
133
|
+
getTileflowRequestPath,
|
|
134
|
+
getTileflowStyleInspectionSelection,
|
|
135
|
+
getTileflowStyleSelection,
|
|
2518
136
|
getTileflowWatchPaths,
|
|
2519
137
|
iconFileExtensions,
|
|
138
|
+
inspectLoadedTileflowConfig,
|
|
139
|
+
inspectTileflowConfig,
|
|
2520
140
|
inspectTileflowFeatures,
|
|
2521
141
|
inspectTileflowIconCatalogs,
|
|
142
|
+
isTileflowArtifactInputPath,
|
|
2522
143
|
isTileflowRequestUrl,
|
|
2523
144
|
joinTileflowPublicUrl,
|
|
2524
145
|
loadTileflowConfig,
|
|
146
|
+
loadTileflowConfigWithInputs,
|
|
2525
147
|
loadValidTileflowConfig,
|
|
148
|
+
loadValidTileflowConfigWithInputs,
|
|
2526
149
|
normalizeTileflowBasePath,
|
|
2527
|
-
|
|
150
|
+
normalizeTileflowLandmarkManifest,
|
|
151
|
+
prepareTileflowCatalogIcons,
|
|
152
|
+
prepareTileflowStyleFonts,
|
|
153
|
+
readBoundedTileflowJsonResponse,
|
|
154
|
+
readBoundedTileflowResponse,
|
|
155
|
+
refreshTileflowArtifactSession,
|
|
156
|
+
renderTileflowPreviewHtml,
|
|
157
|
+
replaceTileflowStyleFontSources,
|
|
158
|
+
resolveTileflowArtifactPublicUrls,
|
|
2528
159
|
resolveTileflowPreview,
|
|
160
|
+
serializeTileflowCommandDocument,
|
|
161
|
+
tileflowArtifactInventoryFileName,
|
|
162
|
+
tileflowArtifactPlanSchemaVersion,
|
|
2529
163
|
tileflowArtifactSessionSchemaVersion,
|
|
164
|
+
tileflowCommandDocumentSchemaVersion,
|
|
165
|
+
tileflowComparisonSchemaVersion,
|
|
2530
166
|
validateTileflowStyle,
|
|
167
|
+
writeTileflowArtifactPlan,
|
|
2531
168
|
writeTileflowBuildArtifacts,
|
|
2532
169
|
writeTileflowNodeResponse
|
|
2533
170
|
};
|