@tileflow/dev 0.1.0-alpha.4 → 0.1.0-alpha.40
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/LICENSE +201 -0
- package/README.md +257 -5
- package/THIRD_PARTY_NOTICES.md +309 -0
- package/dist/artifacts.d.ts +269 -0
- package/dist/artifacts.js +98 -0
- package/dist/chunk-AQUGMUZW.js +324 -0
- package/dist/chunk-AVJMTXW5.js +3147 -0
- package/dist/chunk-C4VPHZ3F.js +4062 -0
- package/dist/chunk-D4OE4ITL.js +13 -0
- package/dist/chunk-FOMTSCOP.js +671 -0
- package/dist/chunk-KDFTE4Q5.js +156 -0
- package/dist/chunk-ONYQNLNA.js +1108 -0
- package/dist/chunk-RTU7TD4M.js +172 -0
- package/dist/chunk-XHJBCVBB.js +241 -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-BIHpw5Zy.d.ts +417 -0
- package/dist/icons.d.ts +3 -0
- package/dist/icons.js +41 -0
- package/dist/inspect.d.ts +149 -0
- package/dist/inspect.js +805 -0
- package/dist/inspector-browser.js +16955 -0
- package/dist/inspector-observer.js +95 -0
- package/dist/preview-assets-ATLLYY72.js +103 -0
- package/dist/preview.d.ts +31 -0
- package/dist/preview.js +1972 -0
- package/dist/server.d.ts +89 -0
- package/dist/server.js +3657 -0
- package/dist/tilesets.d.ts +89 -0
- package/dist/tilesets.js +6 -0
- package/dist/validation.d.ts +65 -0
- package/dist/validation.js +24 -0
- package/package.json +77 -13
- package/ui-fonts/hubot-sans-OFL.txt +93 -0
- package/ui-fonts/hubot-sans.woff2 +0 -0
- package/ui-fonts/inter-OFL.txt +92 -0
- package/ui-fonts/inter.woff2 +0 -0
- package/ui-fonts/jetbrains-mono-OFL.txt +93 -0
- package/ui-fonts/jetbrains-mono.woff2 +0 -0
- package/ui-fonts/mona-sans-OFL.txt +93 -0
- package/ui-fonts/mona-sans.woff2 +0 -0
- package/dist/index.d.ts +0 -54
- package/dist/index.js +0 -314
package/dist/inspect.js
ADDED
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTileflowCommandSummary
|
|
3
|
+
} from "./chunk-XHJBCVBB.js";
|
|
4
|
+
import {
|
|
5
|
+
assertValidTileflowStyle,
|
|
6
|
+
sanitizeDiagnosticSecrets
|
|
7
|
+
} from "./chunk-KDFTE4Q5.js";
|
|
8
|
+
import {
|
|
9
|
+
defaultTileflowConfigPath,
|
|
10
|
+
loadValidTileflowConfigWithInputs
|
|
11
|
+
} from "./chunk-AQUGMUZW.js";
|
|
12
|
+
import "./chunk-D4OE4ITL.js";
|
|
13
|
+
|
|
14
|
+
// src/inspect.ts
|
|
15
|
+
import { isAbsolute, relative, resolve, win32 } from "path";
|
|
16
|
+
import {
|
|
17
|
+
auditTileflowMapThemeValues,
|
|
18
|
+
compareCodeUnits as compareCodeUnits2,
|
|
19
|
+
parseTileflowMap,
|
|
20
|
+
resolveTileflowTheme
|
|
21
|
+
} from "@tileflow/core";
|
|
22
|
+
|
|
23
|
+
// src/feature-inspection.ts
|
|
24
|
+
import { VectorTile } from "@mapbox/vector-tile";
|
|
25
|
+
import { PbfReader } from "pbf";
|
|
26
|
+
import { compareCodeUnits } from "@tileflow/core";
|
|
27
|
+
import {
|
|
28
|
+
createStyleFromCatalog
|
|
29
|
+
} from "@tileflow/core/build";
|
|
30
|
+
var mercatorLatitudeLimit = 85.0511287798066;
|
|
31
|
+
var maxTileJsonBytes = 1024 * 1024;
|
|
32
|
+
var maxTileBytes = 6 * 1024 * 1024;
|
|
33
|
+
var maxTotalTileBytes = 24 * 1024 * 1024;
|
|
34
|
+
var maxTiles = 32;
|
|
35
|
+
var maxScannedFeatures = 2e4;
|
|
36
|
+
var maxStringLength = 256;
|
|
37
|
+
async function inspectTileflowFeatures(project, mapName, input) {
|
|
38
|
+
const options = validateInspectionOptions(input);
|
|
39
|
+
if (!Object.hasOwn(project.maps, mapName)) {
|
|
40
|
+
const available = Object.keys(project.maps).sort(compareCodeUnits);
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Unknown map "${mapName}". Available maps: ${available.join(", ") || "(none)"}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const style = createStyleFromCatalog(project, mapName, {
|
|
46
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
47
|
+
preparedAssets: options.preparedAssets
|
|
48
|
+
});
|
|
49
|
+
assertValidTileflowStyle(style, mapName);
|
|
50
|
+
const source = resolveVectorSource(style, options.sourceLayers);
|
|
51
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
52
|
+
const remoteOrigins = /* @__PURE__ */ new Set();
|
|
53
|
+
const tileSource = await resolveTileSource(
|
|
54
|
+
source.definition,
|
|
55
|
+
fetchImplementation,
|
|
56
|
+
options,
|
|
57
|
+
remoteOrigins
|
|
58
|
+
);
|
|
59
|
+
const bounds = viewportBounds(options.center, options.zoom, options.width, options.height);
|
|
60
|
+
const tileZoom = resolveTileZoom(options.zoom, tileSource.minzoom, tileSource.maxzoom);
|
|
61
|
+
const tileCoordinates = tileCover(bounds, tileZoom);
|
|
62
|
+
if (tileCoordinates.length > maxTiles) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Feature inspection would read ${tileCoordinates.length} tiles; narrow the viewport or increase zoom (maximum ${maxTiles}).`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const features = [];
|
|
68
|
+
let scannedFeatures = 0;
|
|
69
|
+
let totalTileBytes = 0;
|
|
70
|
+
let scanTruncated = false;
|
|
71
|
+
for (const coordinate of tileCoordinates) {
|
|
72
|
+
const tileUrl = resolveTileUrl(tileSource.template, coordinate, tileSource.scheme);
|
|
73
|
+
const bytes = await fetchBytes(tileUrl, {
|
|
74
|
+
fetchImplementation,
|
|
75
|
+
label: "vector tile",
|
|
76
|
+
limit: maxTileBytes,
|
|
77
|
+
signal: options.signal,
|
|
78
|
+
timeoutMs: options.timeoutMs
|
|
79
|
+
});
|
|
80
|
+
remoteOrigins.add(safeOrigin(bytes.finalUrl));
|
|
81
|
+
totalTileBytes += bytes.body.byteLength;
|
|
82
|
+
if (totalTileBytes > maxTotalTileBytes) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Feature inspection exceeded the ${maxTotalTileBytes}-byte total vector-tile limit.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const reader = new PbfReader(bytes.body);
|
|
88
|
+
const tile = new VectorTile(reader);
|
|
89
|
+
for (const sourceLayer of options.sourceLayers) {
|
|
90
|
+
const layer = tile.layers[sourceLayer];
|
|
91
|
+
if (!layer) continue;
|
|
92
|
+
for (let index = 0; index < layer.length; index += 1) {
|
|
93
|
+
scannedFeatures += 1;
|
|
94
|
+
if (scannedFeatures > maxScannedFeatures) {
|
|
95
|
+
scanTruncated = true;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const vectorFeature = layer.feature(index);
|
|
99
|
+
const geoJsonFeature = vectorFeature.toGeoJSON(coordinate.x, coordinate.y, coordinate.z);
|
|
100
|
+
const geometry = summarizeGeometry(geoJsonFeature.geometry);
|
|
101
|
+
if (!geometry || !boundsIntersect(bounds, geometry.bounds)) continue;
|
|
102
|
+
features.push({
|
|
103
|
+
sourceLayer,
|
|
104
|
+
id: typeof vectorFeature.id === "number" && Number.isSafeInteger(vectorFeature.id) ? vectorFeature.id : null,
|
|
105
|
+
geometry,
|
|
106
|
+
properties: projectProperties(vectorFeature.properties, options.properties)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (scanTruncated) break;
|
|
110
|
+
}
|
|
111
|
+
if (scanTruncated) break;
|
|
112
|
+
}
|
|
113
|
+
const uniqueFeatures = deduplicateFeatures(features).sort(compareInspectedFeatures);
|
|
114
|
+
const limitedFeatures = uniqueFeatures.slice(0, options.limit);
|
|
115
|
+
const dataIdentity = style.metadata?.["tileflow:data"];
|
|
116
|
+
const metadataVersion = dataIdentity && typeof dataIdentity === "object" && "revision" in dataIdentity ? dataIdentity.revision : void 0;
|
|
117
|
+
return {
|
|
118
|
+
schemaVersion: 1,
|
|
119
|
+
map: mapName,
|
|
120
|
+
camera: {
|
|
121
|
+
center: [...options.center],
|
|
122
|
+
zoom: options.zoom,
|
|
123
|
+
viewport: { width: options.width, height: options.height }
|
|
124
|
+
},
|
|
125
|
+
source: {
|
|
126
|
+
id: source.id,
|
|
127
|
+
origin: tileSource.origin,
|
|
128
|
+
tileJsonPath: tileSource.tileJsonPath,
|
|
129
|
+
tileOrigins: [...remoteOrigins].sort(compareCodeUnits),
|
|
130
|
+
version: typeof metadataVersion === "string" ? metadataVersion : null
|
|
131
|
+
},
|
|
132
|
+
sourceLayers: options.sourceLayers,
|
|
133
|
+
properties: options.properties,
|
|
134
|
+
tileZoom,
|
|
135
|
+
tilesRead: tileCoordinates.length,
|
|
136
|
+
scannedFeatures: Math.min(scannedFeatures, maxScannedFeatures),
|
|
137
|
+
truncated: scanTruncated || uniqueFeatures.length > options.limit,
|
|
138
|
+
features: limitedFeatures
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function validateInspectionOptions(input) {
|
|
142
|
+
const center = input.center;
|
|
143
|
+
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) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Feature inspection center must be [longitude, latitude] inside Web Mercator bounds.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!Number.isFinite(input.zoom) || input.zoom < 0 || input.zoom > 24) {
|
|
149
|
+
throw new Error("Feature inspection zoom must be between 0 and 24.");
|
|
150
|
+
}
|
|
151
|
+
const width = boundedInteger(input.width ?? 512, 64, 2048, "width");
|
|
152
|
+
const height = boundedInteger(input.height ?? 512, 64, 2048, "height");
|
|
153
|
+
const limit = boundedInteger(input.limit ?? 200, 1, 500, "limit");
|
|
154
|
+
const timeoutMs = boundedInteger(input.timeoutMs ?? 1e4, 100, 6e4, "timeout");
|
|
155
|
+
const sourceLayers = normalizeNames(input.sourceLayers, 1, 12, "source layer");
|
|
156
|
+
const properties = normalizeNames(input.properties ?? [], 0, 16, "property");
|
|
157
|
+
return {
|
|
158
|
+
...input,
|
|
159
|
+
center: [center[0], center[1]],
|
|
160
|
+
height,
|
|
161
|
+
limit,
|
|
162
|
+
properties,
|
|
163
|
+
sourceLayers,
|
|
164
|
+
timeoutMs,
|
|
165
|
+
width
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function boundedInteger(value, minimum, maximum, label) {
|
|
169
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Feature inspection ${label} must be an integer from ${minimum} to ${maximum}.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
function normalizeNames(values, minimum, maximum, label) {
|
|
177
|
+
if (!Array.isArray(values) || values.length < minimum || values.length > maximum) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Feature inspection requires ${minimum}-${maximum} ${label}${maximum === 1 ? "" : "s"}.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const normalized = [...new Set(values)].sort(compareCodeUnits);
|
|
183
|
+
for (const value of normalized) {
|
|
184
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(value)) {
|
|
185
|
+
throw new Error(`Invalid feature inspection ${label} "${value}".`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return normalized;
|
|
189
|
+
}
|
|
190
|
+
function resolveVectorSource(style, sourceLayers) {
|
|
191
|
+
const requested = new Set(sourceLayers);
|
|
192
|
+
const referencedSourceIds = new Set(
|
|
193
|
+
style.layers.filter((layer) => requested.has(String(layer["source-layer"] ?? ""))).map((layer) => layer.source).filter((sourceId) => typeof sourceId === "string")
|
|
194
|
+
);
|
|
195
|
+
const vectorSources = Object.entries(style.sources).filter(
|
|
196
|
+
([, definition2]) => definition2.type === "vector"
|
|
197
|
+
);
|
|
198
|
+
const candidates = referencedSourceIds.size > 0 ? vectorSources.filter(([sourceId]) => referencedSourceIds.has(sourceId)) : vectorSources;
|
|
199
|
+
if (candidates.length !== 1) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Feature inspection requires exactly one vector source for the selected layers; found ${candidates.length}.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const [id, definition] = candidates[0];
|
|
205
|
+
return { id, definition };
|
|
206
|
+
}
|
|
207
|
+
async function resolveTileSource(definition, fetchImplementation, options, origins) {
|
|
208
|
+
if (Array.isArray(definition.tiles) && typeof definition.tiles[0] === "string") {
|
|
209
|
+
const template2 = safeHttpUrl(definition.tiles[0], "vector tile template");
|
|
210
|
+
origins.add(template2.origin);
|
|
211
|
+
return {
|
|
212
|
+
maxzoom: finiteZoom(definition.maxzoom, 22),
|
|
213
|
+
minzoom: finiteZoom(definition.minzoom, 0),
|
|
214
|
+
origin: template2.origin,
|
|
215
|
+
scheme: definition.scheme === "tms" ? "tms" : "xyz",
|
|
216
|
+
template: template2,
|
|
217
|
+
tileJsonPath: null
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (typeof definition.url !== "string") {
|
|
221
|
+
throw new Error("Feature inspection vector source must define an HTTP(S) TileJSON URL.");
|
|
222
|
+
}
|
|
223
|
+
const tileJsonUrl = safeHttpUrl(definition.url, "TileJSON URL");
|
|
224
|
+
const response = await fetchBytes(tileJsonUrl, {
|
|
225
|
+
fetchImplementation,
|
|
226
|
+
label: "TileJSON",
|
|
227
|
+
limit: maxTileJsonBytes,
|
|
228
|
+
signal: options.signal,
|
|
229
|
+
timeoutMs: options.timeoutMs
|
|
230
|
+
});
|
|
231
|
+
const finalTileJsonUrl = safeHttpUrl(response.finalUrl, "TileJSON response URL");
|
|
232
|
+
origins.add(finalTileJsonUrl.origin);
|
|
233
|
+
let parsed;
|
|
234
|
+
try {
|
|
235
|
+
parsed = JSON.parse(new TextDecoder().decode(response.body));
|
|
236
|
+
} catch {
|
|
237
|
+
throw new Error("Feature inspection received invalid TileJSON JSON.");
|
|
238
|
+
}
|
|
239
|
+
const tileJson = parseTileJson(parsed);
|
|
240
|
+
const template = safeHttpUrl(
|
|
241
|
+
new URL(tileJson.tiles[0], finalTileJsonUrl).toString(),
|
|
242
|
+
"vector tile template"
|
|
243
|
+
);
|
|
244
|
+
origins.add(template.origin);
|
|
245
|
+
return {
|
|
246
|
+
maxzoom: finiteZoom(tileJson.maxzoom, 22),
|
|
247
|
+
minzoom: finiteZoom(tileJson.minzoom, 0),
|
|
248
|
+
origin: finalTileJsonUrl.origin,
|
|
249
|
+
scheme: tileJson.scheme === "tms" ? "tms" : "xyz",
|
|
250
|
+
template,
|
|
251
|
+
tileJsonPath: finalTileJsonUrl.pathname
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function parseTileJson(value) {
|
|
255
|
+
if (!isRecord(value) || !Array.isArray(value.tiles) || value.tiles.length === 0) {
|
|
256
|
+
throw new Error("Feature inspection TileJSON must contain a non-empty tiles array.");
|
|
257
|
+
}
|
|
258
|
+
const tiles = value.tiles.filter((entry) => typeof entry === "string");
|
|
259
|
+
if (tiles.length !== value.tiles.length || tiles.length > 8) {
|
|
260
|
+
throw new Error("Feature inspection TileJSON tiles must contain 1-8 URL templates.");
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
tiles,
|
|
264
|
+
...typeof value.maxzoom === "number" ? { maxzoom: value.maxzoom } : {},
|
|
265
|
+
...typeof value.minzoom === "number" ? { minzoom: value.minzoom } : {},
|
|
266
|
+
...typeof value.scheme === "string" ? { scheme: value.scheme } : {}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function finiteZoom(value, fallback) {
|
|
270
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 24 ? value : fallback;
|
|
271
|
+
}
|
|
272
|
+
function resolveTileZoom(zoom, minzoom, maxzoom) {
|
|
273
|
+
if (minzoom > maxzoom) throw new Error("Feature inspection source minzoom exceeds maxzoom.");
|
|
274
|
+
return Math.max(minzoom, Math.min(maxzoom, Math.floor(zoom)));
|
|
275
|
+
}
|
|
276
|
+
function viewportBounds(center, zoom, width, height) {
|
|
277
|
+
const centerX = longitudeToWorldX(center[0]);
|
|
278
|
+
const centerY = latitudeToWorldY(center[1]);
|
|
279
|
+
const scale = 512 * 2 ** zoom;
|
|
280
|
+
const westX = centerX - width / 2 / scale;
|
|
281
|
+
const eastX = centerX + width / 2 / scale;
|
|
282
|
+
if (westX < 0 || eastX > 1) {
|
|
283
|
+
throw new Error("Feature inspection viewports that cross the antimeridian are not supported.");
|
|
284
|
+
}
|
|
285
|
+
const northY = Math.max(0, centerY - height / 2 / scale);
|
|
286
|
+
const southY = Math.min(1, centerY + height / 2 / scale);
|
|
287
|
+
return [
|
|
288
|
+
worldXToLongitude(westX),
|
|
289
|
+
worldYToLatitude(southY),
|
|
290
|
+
worldXToLongitude(eastX),
|
|
291
|
+
worldYToLatitude(northY)
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
function tileCover(bounds, zoom) {
|
|
295
|
+
const count = 2 ** zoom;
|
|
296
|
+
const minimumX = clampTile(Math.floor(longitudeToWorldX(bounds[0]) * count), count);
|
|
297
|
+
const maximumX = clampTile(Math.floor(longitudeToWorldX(bounds[2]) * count), count);
|
|
298
|
+
const minimumY = clampTile(Math.floor(latitudeToWorldY(bounds[3]) * count), count);
|
|
299
|
+
const maximumY = clampTile(Math.floor(latitudeToWorldY(bounds[1]) * count), count);
|
|
300
|
+
const coordinates = [];
|
|
301
|
+
for (let y = minimumY; y <= maximumY; y += 1) {
|
|
302
|
+
for (let x = minimumX; x <= maximumX; x += 1) coordinates.push({ x, y, z: zoom });
|
|
303
|
+
}
|
|
304
|
+
return coordinates;
|
|
305
|
+
}
|
|
306
|
+
function clampTile(value, count) {
|
|
307
|
+
return Math.max(0, Math.min(count - 1, value));
|
|
308
|
+
}
|
|
309
|
+
function resolveTileUrl(template, coordinate, scheme) {
|
|
310
|
+
const y = scheme === "tms" ? 2 ** coordinate.z - 1 - coordinate.y : coordinate.y;
|
|
311
|
+
const replacements = {
|
|
312
|
+
z: coordinate.z,
|
|
313
|
+
x: coordinate.x,
|
|
314
|
+
y,
|
|
315
|
+
"-y": 2 ** coordinate.z - 1 - coordinate.y
|
|
316
|
+
};
|
|
317
|
+
let value = template.toString();
|
|
318
|
+
for (const [token, replacement] of Object.entries(replacements)) {
|
|
319
|
+
value = value.replace(
|
|
320
|
+
new RegExp(`(?:\\{${token}\\}|%7B${token}%7D)`, "giu"),
|
|
321
|
+
String(replacement)
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (/(?:\{(?:z|x|-?y)\}|%7B(?:z|x|-?y)%7D)/iu.test(value)) {
|
|
325
|
+
throw new Error("Feature inspection tile template contains unresolved coordinates.");
|
|
326
|
+
}
|
|
327
|
+
return new URL(value);
|
|
328
|
+
}
|
|
329
|
+
async function fetchBytes(url, options) {
|
|
330
|
+
const timeout = AbortSignal.timeout(options.timeoutMs);
|
|
331
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
332
|
+
const response = await options.fetchImplementation(url, { signal });
|
|
333
|
+
if (!response.ok) {
|
|
334
|
+
throw new Error(`${options.label} request failed with HTTP ${response.status}.`);
|
|
335
|
+
}
|
|
336
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
337
|
+
if (Number.isFinite(declaredLength) && declaredLength > options.limit) {
|
|
338
|
+
throw new Error(`${options.label} exceeds the ${options.limit}-byte limit.`);
|
|
339
|
+
}
|
|
340
|
+
if (!response.body) throw new Error(`${options.label} response had no body.`);
|
|
341
|
+
const chunks = [];
|
|
342
|
+
let length = 0;
|
|
343
|
+
const reader = response.body.getReader();
|
|
344
|
+
while (true) {
|
|
345
|
+
const result = await reader.read();
|
|
346
|
+
if (result.done) break;
|
|
347
|
+
length += result.value.byteLength;
|
|
348
|
+
if (length > options.limit) {
|
|
349
|
+
await reader.cancel();
|
|
350
|
+
throw new Error(`${options.label} exceeds the ${options.limit}-byte limit.`);
|
|
351
|
+
}
|
|
352
|
+
chunks.push(result.value);
|
|
353
|
+
}
|
|
354
|
+
const body = new Uint8Array(length);
|
|
355
|
+
let offset = 0;
|
|
356
|
+
for (const chunk of chunks) {
|
|
357
|
+
body.set(chunk, offset);
|
|
358
|
+
offset += chunk.byteLength;
|
|
359
|
+
}
|
|
360
|
+
return { body, finalUrl: response.url || url.toString() };
|
|
361
|
+
}
|
|
362
|
+
function summarizeGeometry(value) {
|
|
363
|
+
if (!isRecord(value) || typeof value.type !== "string" || !("coordinates" in value)) return;
|
|
364
|
+
const points = [];
|
|
365
|
+
collectCoordinates(value.coordinates, points);
|
|
366
|
+
if (points.length === 0) return;
|
|
367
|
+
let west = Infinity;
|
|
368
|
+
let south = Infinity;
|
|
369
|
+
let east = -Infinity;
|
|
370
|
+
let north = -Infinity;
|
|
371
|
+
for (const [longitude, latitude] of points) {
|
|
372
|
+
west = Math.min(west, longitude);
|
|
373
|
+
south = Math.min(south, latitude);
|
|
374
|
+
east = Math.max(east, longitude);
|
|
375
|
+
north = Math.max(north, latitude);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
type: value.type,
|
|
379
|
+
center: [roundCoordinate((west + east) / 2), roundCoordinate((south + north) / 2)],
|
|
380
|
+
bounds: [
|
|
381
|
+
roundCoordinate(west),
|
|
382
|
+
roundCoordinate(south),
|
|
383
|
+
roundCoordinate(east),
|
|
384
|
+
roundCoordinate(north)
|
|
385
|
+
]
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function collectCoordinates(value, points) {
|
|
389
|
+
if (!Array.isArray(value)) return;
|
|
390
|
+
if (value.length >= 2 && typeof value[0] === "number" && Number.isFinite(value[0]) && typeof value[1] === "number" && Number.isFinite(value[1])) {
|
|
391
|
+
points.push([value[0], value[1]]);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
for (const child of value) collectCoordinates(child, points);
|
|
395
|
+
}
|
|
396
|
+
function boundsIntersect(left, right) {
|
|
397
|
+
return left[0] <= right[2] && left[2] >= right[0] && left[1] <= right[3] && left[3] >= right[1];
|
|
398
|
+
}
|
|
399
|
+
function projectProperties(source, properties) {
|
|
400
|
+
const projected = {};
|
|
401
|
+
for (const property of properties) {
|
|
402
|
+
const value = source[property];
|
|
403
|
+
if (typeof value === "string") projected[property] = value.slice(0, maxStringLength);
|
|
404
|
+
else if (typeof value === "boolean") projected[property] = value;
|
|
405
|
+
else if (typeof value === "number" && Number.isFinite(value)) projected[property] = value;
|
|
406
|
+
}
|
|
407
|
+
return projected;
|
|
408
|
+
}
|
|
409
|
+
function deduplicateFeatures(features) {
|
|
410
|
+
const unique = /* @__PURE__ */ new Map();
|
|
411
|
+
for (const feature of features) {
|
|
412
|
+
const key = feature.id === null ? `${feature.sourceLayer}|${JSON.stringify(feature.geometry)}|${JSON.stringify(feature.properties)}` : `${feature.sourceLayer}|id:${feature.id}`;
|
|
413
|
+
if (!unique.has(key)) unique.set(key, feature);
|
|
414
|
+
}
|
|
415
|
+
return [...unique.values()];
|
|
416
|
+
}
|
|
417
|
+
function compareInspectedFeatures(left, right) {
|
|
418
|
+
const leftName = typeof left.properties.name === "string" ? left.properties.name : "";
|
|
419
|
+
const rightName = typeof right.properties.name === "string" ? right.properties.name : "";
|
|
420
|
+
return compareCodeUnits(left.sourceLayer, right.sourceLayer) || Number(rightName.length > 0) - Number(leftName.length > 0) || compareCodeUnits(leftName, rightName) || (left.id ?? Number.MAX_SAFE_INTEGER) - (right.id ?? Number.MAX_SAFE_INTEGER) || compareCodeUnits(JSON.stringify(left), JSON.stringify(right));
|
|
421
|
+
}
|
|
422
|
+
function safeHttpUrl(value, label) {
|
|
423
|
+
let parsed;
|
|
424
|
+
try {
|
|
425
|
+
parsed = new URL(value);
|
|
426
|
+
} catch {
|
|
427
|
+
throw new Error(`Feature inspection ${label} is not a valid URL.`);
|
|
428
|
+
}
|
|
429
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
430
|
+
throw new Error(`Feature inspection ${label} must use HTTP or HTTPS.`);
|
|
431
|
+
}
|
|
432
|
+
return parsed;
|
|
433
|
+
}
|
|
434
|
+
function safeOrigin(value) {
|
|
435
|
+
return safeHttpUrl(value, "response URL").origin;
|
|
436
|
+
}
|
|
437
|
+
function longitudeToWorldX(longitude) {
|
|
438
|
+
return (longitude + 180) / 360;
|
|
439
|
+
}
|
|
440
|
+
function worldXToLongitude(x) {
|
|
441
|
+
return x * 360 - 180;
|
|
442
|
+
}
|
|
443
|
+
function latitudeToWorldY(latitude) {
|
|
444
|
+
const radians = latitude * Math.PI / 180;
|
|
445
|
+
return (1 - Math.log(Math.tan(radians) + 1 / Math.cos(radians)) / Math.PI) / 2;
|
|
446
|
+
}
|
|
447
|
+
function worldYToLatitude(y) {
|
|
448
|
+
return Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180 / Math.PI;
|
|
449
|
+
}
|
|
450
|
+
function roundCoordinate(value) {
|
|
451
|
+
return Math.round(value * 1e7) / 1e7;
|
|
452
|
+
}
|
|
453
|
+
function isRecord(value) {
|
|
454
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/inspect.ts
|
|
458
|
+
async function inspectTileflowConfig(options = {}) {
|
|
459
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
460
|
+
const loaded = await loadValidTileflowConfigWithInputs(
|
|
461
|
+
options.config ?? defaultTileflowConfigPath,
|
|
462
|
+
options
|
|
463
|
+
);
|
|
464
|
+
return inspectLoadedTileflowConfig(loaded, { cwd, map: options.map });
|
|
465
|
+
}
|
|
466
|
+
function inspectLoadedTileflowConfig(loaded, options) {
|
|
467
|
+
const mapIds = Object.keys(loaded.project.maps).sort(compareCodeUnits2);
|
|
468
|
+
if (options.map && !Object.hasOwn(loaded.project.maps, options.map)) {
|
|
469
|
+
throw new TileflowConfigInspectionError(
|
|
470
|
+
"INSPECT_MAP_NOT_FOUND",
|
|
471
|
+
`Unknown Tileflow map "${options.map}". Available maps: ${mapIds.join(", ") || "(none)"}.`,
|
|
472
|
+
"map"
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
const selectedMapIds = options.map ? [options.map] : mapIds;
|
|
476
|
+
const maps = selectedMapIds.map(
|
|
477
|
+
(mapId) => inspectResolvedMap(loaded, mapId, resolve(options.cwd))
|
|
478
|
+
);
|
|
479
|
+
const summary = createTileflowCommandSummary({
|
|
480
|
+
code: "INSPECTION_READY",
|
|
481
|
+
command: "inspect",
|
|
482
|
+
message: `Resolved Tileflow config inspection is ready (${maps.length} ${maps.length === 1 ? "map" : "maps"}).`,
|
|
483
|
+
ok: true,
|
|
484
|
+
path: "",
|
|
485
|
+
phase: "config-inspection",
|
|
486
|
+
severity: "info",
|
|
487
|
+
suggestion: "Use lineage and provenance paths to make the smallest authoring change."
|
|
488
|
+
});
|
|
489
|
+
return { ...summary, command: "inspect", ok: true, maps, diagnostics: [] };
|
|
490
|
+
}
|
|
491
|
+
var TileflowConfigInspectionError = class extends Error {
|
|
492
|
+
constructor(code, message, path) {
|
|
493
|
+
super(message);
|
|
494
|
+
this.code = code;
|
|
495
|
+
this.name = "TileflowConfigInspectionError";
|
|
496
|
+
this.issues = [{ code, message, path, phase: this.phase }];
|
|
497
|
+
}
|
|
498
|
+
code;
|
|
499
|
+
phase = "config-inspection";
|
|
500
|
+
issues;
|
|
501
|
+
};
|
|
502
|
+
function inspectResolvedMap(loaded, mapId, cwd) {
|
|
503
|
+
const sourceMap = loaded.authoringMaps[mapId];
|
|
504
|
+
const resolvedMap = loaded.project.maps[mapId];
|
|
505
|
+
if (!sourceMap || !resolvedMap) {
|
|
506
|
+
throw new TileflowConfigInspectionError(
|
|
507
|
+
"INSPECT_MAP_SOURCE_MISSING",
|
|
508
|
+
`Unable to inspect Tileflow map "${mapId}".`,
|
|
509
|
+
`maps.${mapId}`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const sourceLineage = collectAuthoringLineage(sourceMap).reverse();
|
|
513
|
+
const snapshots = sourceLineage.map((map) => parseTileflowMap(map));
|
|
514
|
+
const resolved = sanitizeInspectValue(resolvedMap, cwd);
|
|
515
|
+
if (!isRecord2(resolved)) {
|
|
516
|
+
throw new TileflowConfigInspectionError(
|
|
517
|
+
"INSPECT_RESOLVED_MAP_INVALID",
|
|
518
|
+
`Resolved Tileflow map "${mapId}" is not an object.`,
|
|
519
|
+
`maps.${mapId}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
id: sanitizeDiagnosticSecrets(mapId),
|
|
524
|
+
lineage: sourceLineage.map((map, depth) => ({
|
|
525
|
+
depth,
|
|
526
|
+
declaredPaths: collectLeafEntries(map, { cwd, excludeExtends: true }).map(
|
|
527
|
+
(entry) => entry.path
|
|
528
|
+
),
|
|
529
|
+
id: sanitizeDiagnosticSecrets(map.id),
|
|
530
|
+
version: map.version
|
|
531
|
+
})),
|
|
532
|
+
provenance: createMergeProvenance(
|
|
533
|
+
resolvedMap,
|
|
534
|
+
sourceLineage,
|
|
535
|
+
snapshots,
|
|
536
|
+
cwd
|
|
537
|
+
),
|
|
538
|
+
resolved,
|
|
539
|
+
themeContract: inspectThemeContract(resolvedMap, cwd)
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function inspectThemeContract(map, cwd) {
|
|
543
|
+
const names = Object.keys(map.themes).sort(compareCodeUnits2);
|
|
544
|
+
const defaultResolved = resolveTileflowTheme(map.themes[map.defaultTheme]);
|
|
545
|
+
const comparableDefault = {
|
|
546
|
+
colorScheme: defaultResolved.colorScheme,
|
|
547
|
+
lighting: defaultResolved.lighting,
|
|
548
|
+
tokens: defaultResolved.tokens,
|
|
549
|
+
typography: defaultResolved.typography
|
|
550
|
+
};
|
|
551
|
+
const themes = Object.fromEntries(
|
|
552
|
+
names.map((name) => {
|
|
553
|
+
const resolved = resolveTileflowTheme(map.themes[name]);
|
|
554
|
+
const comparable = {
|
|
555
|
+
colorScheme: resolved.colorScheme,
|
|
556
|
+
lighting: resolved.lighting,
|
|
557
|
+
tokens: resolved.tokens,
|
|
558
|
+
typography: resolved.typography
|
|
559
|
+
};
|
|
560
|
+
return [
|
|
561
|
+
name,
|
|
562
|
+
{
|
|
563
|
+
colorScheme: resolved.colorScheme,
|
|
564
|
+
differencesFromDefault: name === map.defaultTheme ? [] : compareThemeValues(comparableDefault, comparable, cwd),
|
|
565
|
+
id: sanitizeDiagnosticSecrets(resolved.id),
|
|
566
|
+
isDefault: name === map.defaultTheme,
|
|
567
|
+
lighting: sanitizeInspectableRecord(resolved.lighting, cwd),
|
|
568
|
+
name,
|
|
569
|
+
systemFor: ["dark", "light"].filter(
|
|
570
|
+
(scheme) => map.systemThemes?.[scheme] === name
|
|
571
|
+
),
|
|
572
|
+
tokens: sanitizeInspectValue(resolved.tokens, cwd),
|
|
573
|
+
typography: sanitizeInspectableRecord(resolved.typography, cwd),
|
|
574
|
+
version: resolved.version
|
|
575
|
+
}
|
|
576
|
+
];
|
|
577
|
+
})
|
|
578
|
+
);
|
|
579
|
+
const tokenSchema = {
|
|
580
|
+
color: Object.keys(defaultResolved.tokens.color).sort(compareCodeUnits2),
|
|
581
|
+
font: Object.keys(defaultResolved.tokens.font).sort(compareCodeUnits2),
|
|
582
|
+
image: Object.keys(defaultResolved.tokens.image).sort(compareCodeUnits2),
|
|
583
|
+
number: Object.keys(defaultResolved.tokens.number).sort(compareCodeUnits2)
|
|
584
|
+
};
|
|
585
|
+
const audit = auditTileflowMapThemeValues(map).map((diagnostic) => {
|
|
586
|
+
const value = sanitizeInspectValue(diagnostic.value, cwd);
|
|
587
|
+
return {
|
|
588
|
+
...diagnostic,
|
|
589
|
+
message: sanitizeDiagnosticSecrets(diagnostic.message),
|
|
590
|
+
...diagnostic.owner === void 0 ? {} : { owner: sanitizeDiagnosticSecrets(diagnostic.owner) },
|
|
591
|
+
path: sanitizeDiagnosticSecrets(diagnostic.path),
|
|
592
|
+
suggestion: sanitizeDiagnosticSecrets(diagnostic.suggestion),
|
|
593
|
+
...diagnostic.target === void 0 ? {} : { target: sanitizeDiagnosticSecrets(diagnostic.target) },
|
|
594
|
+
value: typeof value === "number" ? value : String(value)
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
return {
|
|
598
|
+
audit,
|
|
599
|
+
defaultTheme: map.defaultTheme,
|
|
600
|
+
...map.systemThemes ? { systemThemes: map.systemThemes } : {},
|
|
601
|
+
themes,
|
|
602
|
+
tokenSchema
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function compareThemeValues(from, to, cwd) {
|
|
606
|
+
const differences = [];
|
|
607
|
+
const visit = (left, right, segments) => {
|
|
608
|
+
if (isRecord2(left) || isRecord2(right)) {
|
|
609
|
+
const keys = /* @__PURE__ */ new Set([
|
|
610
|
+
...Object.keys(isRecord2(left) ? left : {}),
|
|
611
|
+
...Object.keys(isRecord2(right) ? right : {})
|
|
612
|
+
]);
|
|
613
|
+
for (const key of [...keys].sort(compareCodeUnits2)) {
|
|
614
|
+
visit(isRecord2(left) ? left[key] : void 0, isRecord2(right) ? right[key] : void 0, [
|
|
615
|
+
...segments,
|
|
616
|
+
key
|
|
617
|
+
]);
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (equalInspectableValues(left, right)) return;
|
|
622
|
+
differences.push({
|
|
623
|
+
from: sanitizeInspectValue(left, cwd),
|
|
624
|
+
path: formatConfigPath(segments, cwd),
|
|
625
|
+
to: sanitizeInspectValue(right, cwd)
|
|
626
|
+
});
|
|
627
|
+
};
|
|
628
|
+
visit(from, to, []);
|
|
629
|
+
return differences.sort((left, right) => compareCodeUnits2(left.path, right.path));
|
|
630
|
+
}
|
|
631
|
+
function sanitizeInspectableRecord(value, cwd) {
|
|
632
|
+
const sanitized = sanitizeInspectValue(value, cwd);
|
|
633
|
+
return isRecord2(sanitized) ? sanitized : {};
|
|
634
|
+
}
|
|
635
|
+
function collectAuthoringLineage(map) {
|
|
636
|
+
const lineage = [];
|
|
637
|
+
const seen = /* @__PURE__ */ new Set();
|
|
638
|
+
let current = map;
|
|
639
|
+
while (current && !seen.has(current) && lineage.length < 64) {
|
|
640
|
+
seen.add(current);
|
|
641
|
+
lineage.push(current);
|
|
642
|
+
current = current.extends;
|
|
643
|
+
}
|
|
644
|
+
return lineage;
|
|
645
|
+
}
|
|
646
|
+
function createMergeProvenance(resolved, lineage, snapshots, cwd) {
|
|
647
|
+
return collectLeafEntries(resolved, { cwd }).map(({ path, segments }) => {
|
|
648
|
+
let sourceIndex = 0;
|
|
649
|
+
let declared = hasPath(lineage[0], segments);
|
|
650
|
+
for (let index = 1; index < snapshots.length; index += 1) {
|
|
651
|
+
const explicitlyDeclared = hasPath(lineage[index], segments);
|
|
652
|
+
if (explicitlyDeclared || !equalInspectableValues(
|
|
653
|
+
getPath(snapshots[index - 1], segments),
|
|
654
|
+
getPath(snapshots[index], segments)
|
|
655
|
+
)) {
|
|
656
|
+
sourceIndex = index;
|
|
657
|
+
declared = explicitlyDeclared;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
declared,
|
|
662
|
+
inherited: sourceIndex < lineage.length - 1,
|
|
663
|
+
operation: sourceIndex === 0 || !hasPath(snapshots[sourceIndex - 1], segments) ? "defined" : "overridden",
|
|
664
|
+
path,
|
|
665
|
+
sourceDepth: sourceIndex,
|
|
666
|
+
sourceMap: sanitizeDiagnosticSecrets(lineage[sourceIndex].id)
|
|
667
|
+
};
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
function collectLeafEntries(value, options) {
|
|
671
|
+
const entries = [];
|
|
672
|
+
const visit = (candidate, segments) => {
|
|
673
|
+
if (isRecord2(candidate) && Object.keys(candidate).length > 0) {
|
|
674
|
+
for (const key of Object.keys(candidate).sort(compareCodeUnits2)) {
|
|
675
|
+
if (options.excludeExtends && segments.length === 0 && key === "extends") continue;
|
|
676
|
+
visit(candidate[key], [...segments, key]);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
entries.push({ path: formatConfigPath(segments, options.cwd), segments });
|
|
681
|
+
};
|
|
682
|
+
visit(value, []);
|
|
683
|
+
return entries.sort((left, right) => compareCodeUnits2(left.path, right.path));
|
|
684
|
+
}
|
|
685
|
+
function formatConfigPath(segments, cwd) {
|
|
686
|
+
return segments.map((segment, index) => {
|
|
687
|
+
const safeSegment = sanitizeInspectKey(segment, cwd);
|
|
688
|
+
return /^[A-Za-z_][A-Za-z0-9_-]*$/u.test(safeSegment) ? `${index === 0 ? "" : "."}${safeSegment}` : `[${JSON.stringify(safeSegment).slice(0, 132)}]`;
|
|
689
|
+
}).join("");
|
|
690
|
+
}
|
|
691
|
+
function getPath(value, segments) {
|
|
692
|
+
let current = value;
|
|
693
|
+
for (const segment of segments) {
|
|
694
|
+
if (!isRecord2(current) || !Object.hasOwn(current, segment)) return void 0;
|
|
695
|
+
current = current[segment];
|
|
696
|
+
}
|
|
697
|
+
return current;
|
|
698
|
+
}
|
|
699
|
+
function hasPath(value, segments) {
|
|
700
|
+
let current = value;
|
|
701
|
+
for (const segment of segments) {
|
|
702
|
+
if (!isRecord2(current) || !Object.hasOwn(current, segment)) return false;
|
|
703
|
+
current = current[segment];
|
|
704
|
+
}
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
function equalInspectableValues(left, right) {
|
|
708
|
+
return JSON.stringify(sortInspectableValue(left)) === JSON.stringify(sortInspectableValue(right));
|
|
709
|
+
}
|
|
710
|
+
function sortInspectableValue(value) {
|
|
711
|
+
if (Array.isArray(value)) return value.map(sortInspectableValue);
|
|
712
|
+
if (!isRecord2(value)) return value;
|
|
713
|
+
return Object.fromEntries(
|
|
714
|
+
Object.keys(value).sort(compareCodeUnits2).map((key) => [key, sortInspectableValue(value[key])])
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
function sanitizeTileflowInspectableValue(value, cwd) {
|
|
718
|
+
return sanitizeInspectValue(value, cwd);
|
|
719
|
+
}
|
|
720
|
+
function sanitizeInspectValue(value, cwd, seen = /* @__PURE__ */ new WeakSet(), path = []) {
|
|
721
|
+
if (typeof value === "string") return sanitizeInspectString(value, cwd, path);
|
|
722
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
723
|
+
if (Array.isArray(value)) return value.map((item) => sanitizeInspectValue(item, cwd, seen, path));
|
|
724
|
+
if (!isRecord2(value)) return null;
|
|
725
|
+
if (seen.has(value)) return "[circular]";
|
|
726
|
+
seen.add(value);
|
|
727
|
+
const result = {};
|
|
728
|
+
for (const key of Object.keys(value).sort(compareCodeUnits2)) {
|
|
729
|
+
if (value[key] === void 0) continue;
|
|
730
|
+
const safeKey = uniqueInspectKey(result, sanitizeInspectKey(key, cwd));
|
|
731
|
+
result[safeKey] = sanitizeInspectValue(value[key], cwd, seen, [...path, key]);
|
|
732
|
+
}
|
|
733
|
+
seen.delete(value);
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
function sanitizeInspectKey(value, cwd) {
|
|
737
|
+
return boundInspectString(sanitizeInspectString(value, cwd, [])) || "(empty)";
|
|
738
|
+
}
|
|
739
|
+
function uniqueInspectKey(target, key) {
|
|
740
|
+
if (!Object.hasOwn(target, key)) return key;
|
|
741
|
+
let suffix = 2;
|
|
742
|
+
while (Object.hasOwn(target, `${key}#${suffix}`)) suffix += 1;
|
|
743
|
+
return `${key}#${suffix}`;
|
|
744
|
+
}
|
|
745
|
+
function sanitizeInspectString(value, cwd, path) {
|
|
746
|
+
const normalizedCwd = resolve(cwd);
|
|
747
|
+
if (value === normalizedCwd || value.startsWith(`${normalizedCwd}/`)) {
|
|
748
|
+
const local = relative(normalizedCwd, value).replaceAll("\\", "/");
|
|
749
|
+
return sanitizeDiagnosticSecrets(local ? `./${local}` : ".");
|
|
750
|
+
}
|
|
751
|
+
if (value.startsWith("data:")) return "data:[redacted]";
|
|
752
|
+
if (/^https?:\/\//iu.test(value)) {
|
|
753
|
+
try {
|
|
754
|
+
const url = new URL(value);
|
|
755
|
+
const suffix = `${url.search ? "?[redacted]" : ""}${url.hash ? "#[redacted]" : ""}`;
|
|
756
|
+
return boundInspectString(
|
|
757
|
+
sanitizeDiagnosticSecrets(`${url.protocol}//${url.host}${sanitizeUrlPath(url)}${suffix}`)
|
|
758
|
+
);
|
|
759
|
+
} catch {
|
|
760
|
+
return "(resource URL)";
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (win32.isAbsolute(value) || value.startsWith("file:")) {
|
|
764
|
+
return "(external path)";
|
|
765
|
+
}
|
|
766
|
+
if (path.at(-1) === "url") return sanitizeRelativeResourceUrl(value);
|
|
767
|
+
if (isAbsolute(value)) {
|
|
768
|
+
return "(external path)";
|
|
769
|
+
}
|
|
770
|
+
const sanitized = sanitizeDiagnosticSecrets(value.replace(/[\r\n]+/gu, " ").trim());
|
|
771
|
+
return boundInspectString(sanitized);
|
|
772
|
+
}
|
|
773
|
+
function sanitizeUrlPath(url) {
|
|
774
|
+
try {
|
|
775
|
+
const decoded = decodeURIComponent(url.pathname);
|
|
776
|
+
if (sanitizeDiagnosticSecrets(decoded) !== decoded) return "/[redacted]";
|
|
777
|
+
} catch {
|
|
778
|
+
return "/[redacted]";
|
|
779
|
+
}
|
|
780
|
+
return sanitizeDiagnosticSecrets(url.pathname);
|
|
781
|
+
}
|
|
782
|
+
function sanitizeRelativeResourceUrl(value) {
|
|
783
|
+
const queryIndex = value.indexOf("?");
|
|
784
|
+
const hashIndex = value.indexOf("#");
|
|
785
|
+
const cutoffs = [queryIndex, hashIndex].filter((index) => index >= 0);
|
|
786
|
+
const cutoff = cutoffs.length ? Math.min(...cutoffs) : value.length;
|
|
787
|
+
const path = sanitizeDiagnosticSecrets(value.slice(0, cutoff));
|
|
788
|
+
const hasQuery = queryIndex >= 0 && (hashIndex < 0 || queryIndex < hashIndex);
|
|
789
|
+
return boundInspectString(
|
|
790
|
+
`${path}${hasQuery ? "?[redacted]" : ""}${hashIndex >= 0 ? "#[redacted]" : ""}`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
function boundInspectString(value) {
|
|
794
|
+
return value.length <= 2048 ? value : `${value.slice(0, 2047)}\u2026`;
|
|
795
|
+
}
|
|
796
|
+
function isRecord2(value) {
|
|
797
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
798
|
+
}
|
|
799
|
+
export {
|
|
800
|
+
TileflowConfigInspectionError,
|
|
801
|
+
inspectLoadedTileflowConfig,
|
|
802
|
+
inspectTileflowConfig,
|
|
803
|
+
inspectTileflowFeatures,
|
|
804
|
+
sanitizeTileflowInspectableValue
|
|
805
|
+
};
|