@tileflow/dev 0.1.0-alpha.40 → 0.1.0-alpha.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,6 +59,9 @@ The artifact plan exposes files and observed inputs from the same generation. St
59
59
  `styles[map][theme]`, never as an implicit default-theme alias. Keep that generation alive for the
60
60
  whole operation, then release it. Do not reconstruct its asset list from directory guesses.
61
61
 
62
+ Generated style files use compact JSON in both stable and immutable generation paths. Their
63
+ parsed contents are unchanged; format the JSON in your editor when inspecting a generated file.
64
+
62
65
  Map-owned tree models are prepared alongside icons and embedded in the styles. Custom tools that
63
66
  compile icons separately can use `prepareTileflowMapTreeModels` from `@tileflow/dev/icons` with
64
67
  the map's model directories, base directory, working directory, target and diagnostic path. Pass
@@ -82,6 +85,10 @@ await writeTileflowBuildArtifacts({
82
85
 
83
86
  Serve the complete output. The writer installs immutable generation assets before changing the
84
87
  manifest pointer, tracks its own files, and refuses to replace a hosted manifest by default.
88
+ Web output includes `families/<map>.json` and its immutable generation copy. The version-3
89
+ manifest selects that shared document; conventional theme JSON remains available for exports.
90
+ Web artifacts include semantic runtime colour programs. Native artifacts keep concrete styles.
91
+ Changing SDK/map package versions requires rebuilding and publishing the new artifacts.
85
92
  Native builds retain embedded PNG image sources as hashed files beside the styles, so their
86
93
  resource URLs remain portable without permitting data URLs in the native profile.
87
94
  `overwriteHostedManifest: true` is an explicit migration option, not a normal build setting.
package/dist/artifacts.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  usesTileflowLineBackgroundGlyphs,
33
33
  writeTileflowArtifactPlan,
34
34
  writeTileflowBuildArtifacts
35
- } from "./chunk-AVJMTXW5.js";
35
+ } from "./chunk-OQCK4AXD.js";
36
36
  import {
37
37
  getTileflowAssetBasePath,
38
38
  getTileflowAssetFileName,
@@ -57,6 +57,7 @@ import {
57
57
  createStylesFromCatalog,
58
58
  createStylesFromCatalogWithInspection,
59
59
  createTileflowMapBuildManifest,
60
+ createTileflowStyleFamily,
60
61
  tileflowMapBuildManifestFileName
61
62
  } from "@tileflow/core/build";
62
63
  import { parseTileflowRuntimeManifest } from "@tileflow/core/manifest";
@@ -67,6 +68,59 @@ import {
67
68
  tileflowNativeBuildRecordFileName,
68
69
  TileflowNativeCompatibilityError as TileflowNativeCompatibilityError4
69
70
  } from "@tileflow/core/native-profile";
71
+ import { prepareTileflowStyleFamilyForRendering } from "@tileflow/core/runtime";
72
+
73
+ // src/family-style-resources.ts
74
+ function rebaseTileflowFamilyStyle(style, mapName, themeName) {
75
+ const rebase = (value) => {
76
+ let start = 0;
77
+ let end = value.length;
78
+ while (start < end && value.charCodeAt(start) <= 32) start++;
79
+ while (end > start && value.charCodeAt(end - 1) <= 32) end--;
80
+ const reference = value.slice(start, end);
81
+ if (/^[\\/]/u.test(reference) || /^[a-z][a-z\d+.-]*:/iu.test(reference)) return value;
82
+ const target = !reference || /^[?#]/u.test(reference) ? `${themeName}.json${reference}` : reference;
83
+ return `../styles/${mapName}/${target}`;
84
+ };
85
+ const property = (value, key2) => {
86
+ if (record(value) && typeof value[key2] === "string") value[key2] = rebase(value[key2]);
87
+ };
88
+ const result = structuredClone(style);
89
+ property(result, "glyphs");
90
+ if (typeof result.sprite === "string") result.sprite = rebase(result.sprite);
91
+ else if (Array.isArray(result.sprite))
92
+ for (const sprite of result.sprite) property(sprite, "url");
93
+ const fontFaces = result["font-faces"];
94
+ if (record(fontFaces))
95
+ for (const [family, face] of Object.entries(fontFaces)) {
96
+ if (typeof face === "string") fontFaces[family] = rebase(face);
97
+ else property(face, "url");
98
+ }
99
+ for (const source of Object.values(result.sources)) {
100
+ property(source, "url");
101
+ if (source.type === "geojson") property(source, "data");
102
+ for (const key2 of ["tiles", "urls"]) {
103
+ const value = source[key2];
104
+ if (Array.isArray(value))
105
+ source[key2] = value.map(
106
+ (url) => typeof url === "string" ? rebase(url) : url
107
+ );
108
+ }
109
+ }
110
+ const fonts = result.metadata?.["tileflow:fontFaces"];
111
+ if (Array.isArray(fonts)) for (const face of fonts) property(face, "source");
112
+ for (const key2 of ["tileflow:buildings", "tileflow:vegetation"]) {
113
+ const descriptor = result.metadata?.[key2];
114
+ if (!record(descriptor)) continue;
115
+ if (Array.isArray(descriptor.models))
116
+ for (const model of descriptor.models) property(model, "url");
117
+ property(descriptor.landmarks, "url");
118
+ }
119
+ return result;
120
+ }
121
+ function record(value) {
122
+ return value !== null && typeof value === "object" && !Array.isArray(value);
123
+ }
70
124
 
71
125
  // src/image-source-assets.ts
72
126
  import { createHash } from "crypto";
@@ -1446,6 +1500,7 @@ function prepareTileflowNativeStyles(input, assets, policy = {}) {
1446
1500
  Object.defineProperty(nativeFaces, face.family, { value: face.source, enumerable: true });
1447
1501
  }
1448
1502
  delete metadata[tileflowStyleFontFacesMetadataKey];
1503
+ delete metadata["tileflow:colors"];
1449
1504
  const { projection: _projection, ...rest } = original;
1450
1505
  const style = {
1451
1506
  ...rest,
@@ -2031,8 +2086,8 @@ function optionalDiagnosticPath(value) {
2031
2086
  }
2032
2087
  function isTileflowMapResolutionDiagnostic(value, path) {
2033
2088
  if (!value || typeof value !== "object" || !path) return false;
2034
- const record = value;
2035
- return record.name === "TileflowMapResolutionError" && typeof record.code === "string" && typeof record.mapId === "string" && path.startsWith("/modules/") && !/[\p{Cc}]/u.test(path) && !/~(?:[^01]|$)/u.test(path);
2089
+ const record2 = value;
2090
+ return record2.name === "TileflowMapResolutionError" && typeof record2.code === "string" && typeof record2.mapId === "string" && path.startsWith("/modules/") && !/[\p{Cc}]/u.test(path) && !/~(?:[^01]|$)/u.test(path);
2036
2091
  }
2037
2092
  function removeSemanticResolutionPath(message, path) {
2038
2093
  if (!path) return message;
@@ -2264,11 +2319,15 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
2264
2319
  throw new TileflowLocalTilesetProductionError();
2265
2320
  }
2266
2321
  const inspected = options.inspection ? createTileflowStylesWithInspection(prepared.project, {
2322
+ runtimeColors: true,
2267
2323
  apiBaseUrl: options.apiBaseUrl,
2268
2324
  mapAssets: prepared.mapAssets
2269
2325
  }) : void 0;
2270
2326
  const compileStyles = renderer === "native" ? createStylesFromCatalog : createTileflowStyles;
2271
2327
  const compiledStyles = inspected?.styles ?? compileStyles(prepared.project, {
2328
+ // Both renderers share compilation before Native's representation-only lowering.
2329
+ // Native removes the web-only binding catalog after that common semantic pipeline.
2330
+ runtimeColors: true,
2272
2331
  apiBaseUrl: options.apiBaseUrl,
2273
2332
  mapAssets: prepared.mapAssets
2274
2333
  });
@@ -2335,7 +2394,7 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
2335
2394
  const stableManifest = renderer === "native" ? createManifest(prepared.project, {
2336
2395
  styleBaseUrl: nativePublicBase(options.styleBaseUrl ?? ".")
2337
2396
  }) : createFontAwareManifest(
2338
- createManifest(prepared.project, { styleBaseUrl: options.styleBaseUrl }),
2397
+ createManifest(prepared.project, { styleBaseUrl: options.styleBaseUrl, families: true }),
2339
2398
  styles
2340
2399
  );
2341
2400
  const inputs = {
@@ -2409,6 +2468,9 @@ function getTileflowArtifactFiles(artifacts) {
2409
2468
  return getStableTileflowArtifactFiles(artifacts);
2410
2469
  }
2411
2470
  function getStableTileflowArtifactFiles(artifacts) {
2471
+ for (const [mapName, themes] of Object.entries(artifacts.styles))
2472
+ for (const themeName of Object.keys(themes))
2473
+ assertTileflowArtifactFileName(`styles/${mapName}/${themeName}.json`);
2412
2474
  const files = [
2413
2475
  {
2414
2476
  contentType: "application/json; charset=utf-8",
@@ -2426,10 +2488,16 @@ function getStableTileflowArtifactFiles(artifacts) {
2426
2488
  ([mapName, themes]) => Object.entries(themes).map(([themeName, style]) => ({
2427
2489
  contentType: "application/json; charset=utf-8",
2428
2490
  fileName: `styles/${mapName}/${themeName}.json`,
2429
- source: `${JSON.stringify(style, null, 2)}
2491
+ source: `${JSON.stringify(style)}
2430
2492
  `
2431
2493
  }))
2432
2494
  ),
2495
+ ...!artifacts.nativeBuild ? Object.entries(artifacts.styles).map(([mapName, styles]) => ({
2496
+ contentType: "application/json; charset=utf-8",
2497
+ fileName: `families/${mapName}.json`,
2498
+ source: `${JSON.stringify(prepareArtifactStyleFamily(mapName, styles))}
2499
+ `
2500
+ })) : [],
2433
2501
  ...artifacts.assets,
2434
2502
  ...artifacts.nativeBuild ? [
2435
2503
  {
@@ -2442,6 +2510,18 @@ function getStableTileflowArtifactFiles(artifacts) {
2442
2510
  ];
2443
2511
  return validateArtifactFiles(files);
2444
2512
  }
2513
+ function prepareArtifactStyleFamily(mapName, styles) {
2514
+ return prepareTileflowStyleFamilyForRendering(
2515
+ createTileflowStyleFamily(
2516
+ Object.fromEntries(
2517
+ Object.entries(styles).map(([theme, style]) => [
2518
+ theme,
2519
+ rebaseTileflowFamilyStyle(style, mapName, theme)
2520
+ ])
2521
+ )
2522
+ )
2523
+ );
2524
+ }
2445
2525
  function validateArtifactFiles(files) {
2446
2526
  const seen = /* @__PURE__ */ new Set();
2447
2527
  for (const file of files) {
@@ -2464,6 +2544,13 @@ function createGenerationManifest(manifest, generation, assets) {
2464
2544
  mapName,
2465
2545
  {
2466
2546
  ...map,
2547
+ ...map.familyUrl ? {
2548
+ familyUrl: insertGenerationInPublicUrl(
2549
+ map.familyUrl,
2550
+ `families/${mapName}.json`,
2551
+ generation
2552
+ )
2553
+ } : {},
2467
2554
  themes: Object.fromEntries(
2468
2555
  Object.entries(map.themes).map(([themeName, theme]) => [
2469
2556
  themeName,
@@ -2517,7 +2604,7 @@ function createGenerationArtifactFiles(artifacts, generation, nativeUrls) {
2517
2604
  return {
2518
2605
  contentType: "application/json; charset=utf-8",
2519
2606
  fileName: `${prefix}/styles/${mapName}/${themeName}.json`,
2520
- source: `${JSON.stringify(generationStyle, null, 2)}
2607
+ source: `${JSON.stringify(generationStyle)}
2521
2608
  `
2522
2609
  };
2523
2610
  })
@@ -2526,7 +2613,34 @@ function createGenerationArtifactFiles(artifacts, generation, nativeUrls) {
2526
2613
  ...asset,
2527
2614
  fileName: `${prefix}/${asset.fileName}`
2528
2615
  }));
2529
- return validateArtifactFiles([...stableFiles, ...immutableStyles, ...immutableAssets]);
2616
+ const immutableFamilies = artifacts.nativeBuild ? [] : Object.keys(artifacts.styles).map((mapName) => ({
2617
+ contentType: "application/json; charset=utf-8",
2618
+ fileName: `${prefix}/families/${mapName}.json`,
2619
+ source: `${JSON.stringify(
2620
+ prepareArtifactStyleFamily(
2621
+ mapName,
2622
+ Object.fromEntries(
2623
+ Object.keys(artifacts.styles[mapName]).map((theme) => [
2624
+ theme,
2625
+ JSON.parse(
2626
+ String(
2627
+ immutableStyles.find(
2628
+ (file) => file.fileName === `${prefix}/styles/${mapName}/${theme}.json`
2629
+ ).source
2630
+ )
2631
+ )
2632
+ ])
2633
+ )
2634
+ )
2635
+ )}
2636
+ `
2637
+ }));
2638
+ return validateArtifactFiles([
2639
+ ...stableFiles,
2640
+ ...immutableStyles,
2641
+ ...immutableFamilies,
2642
+ ...immutableAssets
2643
+ ]);
2530
2644
  }
2531
2645
  function createFontAwareManifest(manifest, styles) {
2532
2646
  const maps = Object.fromEntries(
@@ -3014,12 +3128,13 @@ function assertTileflowArtifactFileName(fileName) {
3014
3128
  assertSafeManagedFileName(fileName);
3015
3129
  const logicalName = fileName.replace(/^generations\/[a-f0-9]{64}\//, "");
3016
3130
  const styleMatch = /^styles\/([^/]+)\/([^/]+)\.json$/u.exec(logicalName);
3131
+ const familyMatch = /^families\/([^/]+)\.json$/u.exec(logicalName);
3017
3132
  const iconMatch = /^icons\/([^/]+)\/sprite(?:@2x)?\.(?:json|png)$/u.exec(logicalName);
3018
3133
  const imageMatch = /^images\/([^/]+)\/[a-f0-9]{64}\.png$/u.exec(logicalName);
3019
3134
  const modelMatch = /^models\/([^/]+)\/[a-z0-9]+(?:-[a-z0-9]+)*-[a-f0-9]{16}\.glb$/u.exec(
3020
3135
  logicalName
3021
3136
  );
3022
- if (fileName === "manifest.json" || fileName === tileflowMapBuildManifestFileName || fileName === tileflowNativeBuildRecordFileName || styleMatch && tileflowMapIdSchema.safeParse(styleMatch[1]).success && tileflowThemeNameSchema.safeParse(styleMatch[2]).success || iconMatch && tileflowMapIdSchema.safeParse(iconMatch[1]).success || imageMatch && tileflowMapIdSchema.safeParse(imageMatch[1]).success || modelMatch && tileflowMapIdSchema.safeParse(modelMatch[1]).success || /^tilesets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.pmtiles$/u.test(logicalName) || /^fonts\/[a-z0-9]+(?:-[a-z0-9]+)*-[a-f0-9]{64}\.(?:otf|ttf|woff2)$/.test(logicalName) || /^fonts\/licenses\/license-[a-f0-9]{64}\.txt$/.test(logicalName)) {
3137
+ if (fileName === "manifest.json" || fileName === tileflowMapBuildManifestFileName || fileName === tileflowNativeBuildRecordFileName || styleMatch && tileflowMapIdSchema.safeParse(styleMatch[1]).success && tileflowThemeNameSchema.safeParse(styleMatch[2]).success || familyMatch && tileflowMapIdSchema.safeParse(familyMatch[1]).success || iconMatch && tileflowMapIdSchema.safeParse(iconMatch[1]).success || imageMatch && tileflowMapIdSchema.safeParse(imageMatch[1]).success || modelMatch && tileflowMapIdSchema.safeParse(modelMatch[1]).success || /^tilesets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.pmtiles$/u.test(logicalName) || /^fonts\/[a-z0-9]+(?:-[a-z0-9]+)*-[a-f0-9]{64}\.(?:otf|ttf|woff2)$/.test(logicalName) || /^fonts\/licenses\/license-[a-f0-9]{64}\.txt$/.test(logicalName)) {
3023
3138
  return;
3024
3139
  }
3025
3140
  throw new Error(`Unexpected Tileflow managed artifact file name: ${fileName}`);
package/dist/preview.js CHANGED
@@ -1306,7 +1306,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
1306
1306
  const runtimeLayerVisibility = {};
1307
1307
  globalThis.__tileflowPreviewScene = coreScene;
1308
1308
  globalThis.__tileflowRuntimeVisibility = (id, visible) => {
1309
- if (!['tileflow-buildings-3d','tileflow-trees-3d','tileflow-street-lights'].includes(id)) return undefined;
1309
+ if (!['tileflow-buildings-scene-3d','tileflow-trees-3d','tileflow-street-lights'].includes(id)) return undefined;
1310
1310
  const previewMap = globalThis.__tileflowPreviewMap;
1311
1311
  if (typeof visible === 'boolean') {
1312
1312
  runtimeLayerVisibility[id] = visible;
@@ -1320,7 +1320,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
1320
1320
  globalThis.__tileflowDescribeRuntimeLayers = () => {
1321
1321
  const {buildings, vegetation, streetLights} = coreScene.scene ?? {};
1322
1322
  return {
1323
- ...(buildings ? {"tileflow-buildings-3d": {controller:"Buildings 3D", state:{visible:runtimeLayerVisibility["tileflow-buildings-3d"] !== false, shown:coreScene.buildingsShown, scale:buildings.scale}, stats:buildings.stats}} : {}),
1323
+ ...(buildings ? {"tileflow-buildings-scene-3d": {controller:"Buildings 3D", state:{visible:runtimeLayerVisibility["tileflow-buildings-scene-3d"] !== false, shown:coreScene.buildingsShown, scale:buildings.scale}, stats:buildings.stats}} : {}),
1324
1324
  ...(vegetation ? {"tileflow-trees-3d": {controller:"Vegetation", state:{visible:runtimeLayerVisibility["tileflow-trees-3d"] !== false, representation:vegetation.representation}, stats:vegetation.stats}} : {}),
1325
1325
  ...(streetLights ? {"tileflow-street-lights": {controller:"Street lights", state:{visible:runtimeLayerVisibility["tileflow-street-lights"] !== false, mounted:true}, stats:streetLights.stats}} : {})
1326
1326
  };
@@ -1376,7 +1376,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
1376
1376
  void (threeDimensionalEnabled ? scene.show({transition: {duration, mode: animation === "fade" ? "fade" : "grow"}}) : scene.hide(instant));
1377
1377
  }
1378
1378
  // The inspector can hide the buildings alone.
1379
- const showBuildings = threeDimensionalEnabled && runtimeLayerVisibility["tileflow-buildings-3d"] !== false;
1379
+ const showBuildings = threeDimensionalEnabled && runtimeLayerVisibility["tileflow-buildings-scene-3d"] !== false;
1380
1380
  if (scene.buildings && coreScene.buildingsShown !== showBuildings) {
1381
1381
  coreScene.buildingsShown = showBuildings;
1382
1382
  void (showBuildings ? scene.buildings.show(instant) : scene.buildings.hide(instant));
@@ -1438,6 +1438,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
1438
1438
  if (toggle !== "building" && toggle !== "landmark") continue;
1439
1439
  // Core's 3D buildings stand in for the compiled extrusion where the style describes them.
1440
1440
  const replaced = toggle === "building" && reads.metadata()?.["tileflow:buildings"];
1441
+ if (replaced) continue; // Core switches the native extrusion only when its scene can draw.
1441
1442
  const visibility = this.enabled && !replaced ? "visible" : "none";
1442
1443
  if (this.map.getLayoutProperty(layer.id, "visibility") !== visibility) {
1443
1444
  this.map.setLayoutProperty(layer.id, "visibility", visibility);
@@ -1636,6 +1637,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
1636
1637
  // Core's 3D buildings and trees stand in for compiled extrusions and flat trees.
1637
1638
  const replacedByCore =
1638
1639
  threeDimensionalToggle === "building" && styleMetadata["tileflow:buildings"];
1640
+ if (replacedByCore && !hideLayer) continue;
1639
1641
  const toggleVisibility = replacedByCore
1640
1642
  ? "none"
1641
1643
  : threeDimensionalToggle === "building" || threeDimensionalToggle === "landmark"
package/dist/server.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  parseTileflowLineBackgroundGlyphPath,
6
6
  tileflowArtifactSessionSchemaVersion,
7
7
  tileflowLineBackgroundGlyphRoute
8
- } from "./chunk-AVJMTXW5.js";
8
+ } from "./chunk-OQCK4AXD.js";
9
9
  import {
10
10
  getTileflowAssetBasePath,
11
11
  getTileflowAssetFileName,
@@ -3387,6 +3387,11 @@ function createTileflowDevRequestHandler(options = {}) {
3387
3387
  }
3388
3388
  return compactJsonResponse(style);
3389
3389
  }
3390
+ if (path.startsWith("/families/")) {
3391
+ const fileName = path.slice(1);
3392
+ const asset = artifacts.files?.find((candidate) => candidate.fileName === fileName);
3393
+ return asset ? assetResponse(asset) : jsonResponse2({ error: "Unknown map family." }, 404);
3394
+ }
3390
3395
  if (path.startsWith("/icons/")) {
3391
3396
  const assetName = path.replace(/^\/+/, "");
3392
3397
  const asset = artifacts.assets.find((candidate) => candidate.fileName === assetName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/dev",
3
- "version": "0.1.0-alpha.40",
3
+ "version": "0.1.0-alpha.42",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Node development utilities for Tileflow configs and builds.",
6
6
  "type": "module",
@@ -83,7 +83,7 @@
83
83
  "dependencies": {
84
84
  "@mapbox/vector-tile": "3.0.0",
85
85
  "@maplibre/maplibre-gl-style-spec": "26.4.4",
86
- "@tileflow/core": ">=0.1.0-alpha.44 <0.1.0-beta.0",
86
+ "@tileflow/core": ">=0.1.0-alpha.46 <0.1.0-beta.0",
87
87
  "chokidar": "6.0.0",
88
88
  "fontkit": "2.0.4",
89
89
  "jiti": "^2.7.0",
@@ -95,14 +95,14 @@
95
95
  "three": "0.185.1"
96
96
  },
97
97
  "devDependencies": {
98
- "@tileflow/maps": "0.1.0-alpha.17"
98
+ "@tileflow/maps": "0.1.0-alpha.18"
99
99
  },
100
100
  "optionalDependencies": {
101
101
  "sharp": "0.35.5"
102
102
  },
103
103
  "scripts": {
104
104
  "build": "tsup src/artifacts.ts src/config.ts src/fonts.ts src/icons.ts src/inspect.ts src/preview.ts src/server.ts src/tilesets.ts src/validation.ts --format esm --dts --clean --platform node --external sharp && tsup --config tsup.inspector.config.ts",
105
- "test": "tsx --test test/*.test.ts",
105
+ "test": "tsx --test --test-concurrency=2 test/*.test.ts",
106
106
  "typecheck": "tsc -p tsconfig.json --noEmit",
107
107
  "lint": "tsc -p tsconfig.json --noEmit",
108
108
  "verify": "pnpm run typecheck && pnpm run test"