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

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.
@@ -2,15 +2,16 @@ import {
2
2
  inspectTileflowPmtilesForLocalUse
3
3
  } from "./chunk-ONYQNLNA.js";
4
4
  import {
5
+ TileflowFontCompilationError,
5
6
  getTileflowFontWatchPaths,
6
7
  joinTileflowPublicUrl,
7
8
  prepareTileflowStyleFonts,
8
9
  replaceTileflowStyleFontSources
9
- } from "./chunk-RSBV4L2Y.js";
10
+ } from "./chunk-UTRO4IBQ.js";
10
11
  import {
11
12
  getTileflowIconWatchPaths,
12
13
  prepareTileflowCatalogIcons
13
- } from "./chunk-NUHSYLLS.js";
14
+ } from "./chunk-EJPVUSX4.js";
14
15
  import {
15
16
  TileflowStyleValidationError,
16
17
  normalizeTileflowStyleValidationIssues,
@@ -27,7 +28,7 @@ import {
27
28
  } from "./chunk-D4OE4ITL.js";
28
29
 
29
30
  // src/artifacts.ts
30
- import { createHash } from "crypto";
31
+ import { createHash as createHash3 } from "crypto";
31
32
  import { realpathSync as realpathSync2 } from "fs";
32
33
  import {
33
34
  copyFile as copyFile2,
@@ -45,8 +46,8 @@ import { createRequire } from "module";
45
46
  import { dirname as dirname2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
46
47
  import {
47
48
  compareCodeUnits as compareCodeUnits2,
48
- getTileflowStyleFontFaces,
49
- serializeCanonicalJson,
49
+ getTileflowStyleFontFaces as getTileflowStyleFontFaces2,
50
+ serializeCanonicalJson as serializeCanonicalJson4,
50
51
  tileflowMapIdSchema,
51
52
  tileflowThemeNameSchema
52
53
  } from "@tileflow/core";
@@ -59,6 +60,13 @@ import {
59
60
  tileflowMapBuildManifestFileName
60
61
  } from "@tileflow/core/build";
61
62
  import { parseTileflowRuntimeManifest } from "@tileflow/core/manifest";
63
+ import {
64
+ createTileflowNativeBuildRecord as createTileflowNativeBuildRecord2,
65
+ createTileflowNativeDiagnostic as createTileflowNativeDiagnostic4,
66
+ resolveTileflowRenderer,
67
+ tileflowNativeBuildRecordFileName,
68
+ TileflowNativeCompatibilityError as TileflowNativeCompatibilityError5
69
+ } from "@tileflow/core/native-profile";
62
70
 
63
71
  // src/local-tilesets.ts
64
72
  import { randomUUID } from "crypto";
@@ -375,14 +383,802 @@ function hasErrorCode(error, code) {
375
383
  return Boolean(error && typeof error === "object" && error.code === code);
376
384
  }
377
385
 
386
+ // src/native-artifacts.ts
387
+ import { createHash } from "crypto";
388
+ import {
389
+ convertFilter,
390
+ validateStyleMin
391
+ } from "@maplibre/maplibre-gl-style-spec";
392
+ import {
393
+ getTileflowStyleFontFaces,
394
+ serializeCanonicalJson,
395
+ tileflowStyleFontFacesMetadataKey,
396
+ tileflowIconIdPattern,
397
+ tileflowIconPackageLimits
398
+ } from "@tileflow/core";
399
+ import {
400
+ createTileflowNativeDiagnostic,
401
+ TileflowNativeCompatibilityError,
402
+ tileflowNativeProfileLimits,
403
+ validateTileflowNativeStyle,
404
+ validateTileflowNativePreparedStyle
405
+ } from "@tileflow/core/native-profile";
406
+
407
+ // src/native-lowering.ts
408
+ var nativeLoweringLimits = Object.freeze({
409
+ maximumDecisionDepth: 16,
410
+ maximumDecisionNodes: 512,
411
+ maximumPropertyBranches: 16,
412
+ maximumPathsPerProperty: 64,
413
+ maximumLayerVariants: 32,
414
+ maximumZoomIntervals: 32,
415
+ maximumLayers: 4096,
416
+ maximumDashLength: 64
417
+ });
418
+ var NativeLoweringError = class extends Error {
419
+ constructor(path, reason = "shape") {
420
+ super(
421
+ reason === "budget" ? "Native decision lowering exceeds its finite budget." : "This native decision cannot be lowered without changing its meaning."
422
+ );
423
+ this.path = path;
424
+ this.reason = reason;
425
+ this.name = "NativeLoweringError";
426
+ }
427
+ path;
428
+ reason;
429
+ };
430
+ function fail(path, reason = "shape") {
431
+ throw new NativeLoweringError(path, reason);
432
+ }
433
+ function object(value) {
434
+ return value !== null && typeof value === "object" && !Array.isArray(value);
435
+ }
436
+ function operation(value, name, length) {
437
+ return Array.isArray(value) && value[0] === name && (length === void 0 || value.length === length);
438
+ }
439
+ function finite(value) {
440
+ return typeof value === "number" && Number.isFinite(value);
441
+ }
442
+ function key(value) {
443
+ return JSON.stringify(value);
444
+ }
445
+ function negate(value) {
446
+ return typeof value === "boolean" ? !value : operation(value, "!", 2) ? value[1] : ["!", value];
447
+ }
448
+ function combine(operator, values) {
449
+ const identity = operator === "all";
450
+ const result = [];
451
+ const seen = /* @__PURE__ */ new Set();
452
+ for (const value of values.flatMap(
453
+ (item) => operation(item, operator) ? item.slice(1) : [item]
454
+ )) {
455
+ if (value === identity) continue;
456
+ if (value === !identity || seen.has(key(negate(value)))) return !identity;
457
+ const serialized = key(value);
458
+ if (!seen.has(serialized)) {
459
+ seen.add(serialized);
460
+ result.push(value);
461
+ }
462
+ }
463
+ return result.length === 0 ? identity : result.length === 1 ? result[0] : [operator, ...result];
464
+ }
465
+ function choose(when, yes, no) {
466
+ if (when === true) return yes;
467
+ if (when === false) return no;
468
+ if (key(yes) === key(no)) return yes;
469
+ if (yes === true) return combine("any", [when, no]);
470
+ if (yes === false) return combine("all", [negate(when), no]);
471
+ if (no === true) return combine("any", [negate(when), yes]);
472
+ if (no === false) return combine("all", [when, yes]);
473
+ return ["case", when, yes, no];
474
+ }
475
+ function decision(arms, fallback) {
476
+ const active = [];
477
+ for (const arm of arms) {
478
+ if (arm.when === false) continue;
479
+ if (arm.when === true) {
480
+ fallback = arm.value;
481
+ break;
482
+ }
483
+ const previous = active.at(-1);
484
+ if (previous && key(previous.value) === key(arm.value)) {
485
+ previous.when = combine("any", [previous.when, arm.when]);
486
+ } else active.push({ ...arm });
487
+ }
488
+ while (active.length && key(active.at(-1).value) === key(fallback)) active.pop();
489
+ if (!active.length) return fallback;
490
+ if (active.length === 1) return choose(active[0].when, active[0].value, fallback);
491
+ return ["case", ...active.flatMap(({ when, value }) => [when, value]), fallback];
492
+ }
493
+ var Decisions = class {
494
+ constructor(path, vector) {
495
+ this.path = path;
496
+ this.vector = vector;
497
+ }
498
+ path;
499
+ vector;
500
+ visited = 0;
501
+ cuts = /* @__PURE__ */ new Set();
502
+ tick(depth) {
503
+ if (++this.visited > nativeLoweringLimits.maximumDecisionNodes || depth > nativeLoweringLimits.maximumDecisionDepth) {
504
+ fail(this.path, "budget");
505
+ }
506
+ }
507
+ tree(value, property, depth = 0) {
508
+ this.tick(depth);
509
+ const leaf = operation(value, "literal", 2) ? value[1] : value;
510
+ if (property === "line-cap" && typeof leaf === "string" && ["butt", "round", "square"].includes(leaf)) {
511
+ return { kind: "leaf", value: leaf };
512
+ }
513
+ if (property === "line-dasharray" && Array.isArray(leaf) && leaf.length > 0 && leaf.length <= nativeLoweringLimits.maximumDashLength && leaf.every((item) => finite(item) && item >= 0)) {
514
+ return { kind: "leaf", value: [...leaf] };
515
+ }
516
+ if (operation(value, "case") && value.length >= 4 && value.length % 2 === 0) {
517
+ const arms = [];
518
+ for (let index = 1; index < value.length - 1; index += 2) {
519
+ const when = this.predicate(value[index], depth + 1);
520
+ arms.push({ when, value: this.tree(value[index + 1], property, depth + 1) });
521
+ }
522
+ return { kind: "case", arms, fallback: this.tree(value.at(-1), property, depth + 1) };
523
+ }
524
+ if (operation(value, "match") && value.length >= 5 && value.length % 2 === 1) {
525
+ this.scalar(value[1], depth + 1);
526
+ const labels = this.matchLabels(value, depth + 1);
527
+ return {
528
+ kind: "case",
529
+ arms: labels.map((when, index) => ({
530
+ when,
531
+ value: this.tree(value[index * 2 + 3], property, depth + 1)
532
+ })),
533
+ fallback: this.tree(value.at(-1), property, depth + 1)
534
+ };
535
+ }
536
+ if (operation(value, "step") && value.length >= 5 && value.length % 2 === 1) {
537
+ if (this.scalar(value[1], depth + 1) !== "number") fail(this.path);
538
+ const stops = [];
539
+ let previous = -Infinity;
540
+ for (let index = 3; index < value.length; index += 2) {
541
+ const at = value[index];
542
+ if (!finite(at) || at <= previous) fail(this.path);
543
+ previous = at;
544
+ if (operation(value[1], "zoom", 1)) this.cuts.add(Math.ceil(at));
545
+ stops.push({ at, value: this.tree(value[index + 1], property, depth + 1) });
546
+ }
547
+ return {
548
+ kind: "step",
549
+ input: value[1],
550
+ fallback: this.tree(value[2], property, depth + 1),
551
+ stops
552
+ };
553
+ }
554
+ return fail(this.path);
555
+ }
556
+ scalar(value, depth) {
557
+ this.tick(depth);
558
+ if (finite(value)) return "number";
559
+ if (value === null || typeof value === "boolean" || typeof value === "string") return "scalar";
560
+ if (operation(value, "zoom", 1)) return "number";
561
+ if (operation(value, "get", 2) && typeof value[1] === "string" && this.vector) return "scalar";
562
+ if (operation(value, "geometry-type", 1) || operation(value, "id", 1) && this.vector)
563
+ return "scalar";
564
+ if (operation(value, "to-string", 2)) {
565
+ if (operation(value[1], "zoom", 1)) fail(this.path);
566
+ this.scalar(value[1], depth + 1);
567
+ return "scalar";
568
+ }
569
+ if (operation(value, "coalesce") && value.length >= 3) {
570
+ for (const child of value.slice(1)) {
571
+ if (operation(child, "zoom", 1)) fail(this.path);
572
+ this.scalar(child, depth + 1);
573
+ }
574
+ return "scalar";
575
+ }
576
+ if (operation(value, "to-number", 3) && finite(value[2])) {
577
+ if (operation(value[1], "zoom", 1)) fail(this.path);
578
+ this.scalar(value[1], depth + 1);
579
+ return "number";
580
+ }
581
+ return fail(this.path);
582
+ }
583
+ matchLabels(value, depth) {
584
+ const seen = /* @__PURE__ */ new Set();
585
+ let labelType;
586
+ const predicates = [];
587
+ for (let index = 2; index < value.length - 1; index += 2) {
588
+ const labels = Array.isArray(value[index]) ? value[index] : [value[index]];
589
+ if (!labels.length) fail(this.path);
590
+ const terms = [];
591
+ for (const label of labels) {
592
+ this.tick(depth);
593
+ if (!(typeof label === "string" || finite(label) && Number.isInteger(label)))
594
+ fail(this.path);
595
+ if (labelType !== void 0 && labelType !== typeof label) fail(this.path);
596
+ labelType = typeof label;
597
+ const encoded = key(label);
598
+ if (seen.has(encoded)) fail(this.path);
599
+ seen.add(encoded);
600
+ if (operation(value[1], "zoom", 1)) {
601
+ if (!finite(label)) fail(this.path);
602
+ this.cuts.add(label);
603
+ this.cuts.add(label + 1);
604
+ }
605
+ terms.push(["==", value[1], label]);
606
+ }
607
+ predicates.push(combine("any", terms));
608
+ }
609
+ return predicates;
610
+ }
611
+ predicate(value, depth) {
612
+ this.tick(depth);
613
+ if (typeof value === "boolean") return value;
614
+ if (operation(value, "literal", 2) && typeof value[1] === "boolean") return value[1];
615
+ if (operation(value, "all") || operation(value, "any")) {
616
+ return combine(
617
+ value[0],
618
+ value.slice(1).map((child) => this.predicate(child, depth + 1))
619
+ );
620
+ }
621
+ if (operation(value, "!", 2)) return negate(this.predicate(value[1], depth + 1));
622
+ if (operation(value, "has", 2) && typeof value[1] === "string") return value;
623
+ if (operation(value, "match") && value.length >= 5 && value.length % 2 === 1) {
624
+ this.scalar(value[1], depth + 1);
625
+ const conditions = this.matchLabels(value, depth + 1);
626
+ let remaining = true;
627
+ const terms = [];
628
+ for (const [index, condition] of conditions.entries()) {
629
+ terms.push(combine("all", [condition, this.predicate(value[index * 2 + 3], depth + 1)]));
630
+ remaining = combine("all", [remaining, negate(condition)]);
631
+ }
632
+ terms.push(combine("all", [remaining, this.predicate(value.at(-1), depth + 1)]));
633
+ return combine("any", terms);
634
+ }
635
+ if (operation(value, "case") && value.length >= 4 && value.length % 2 === 0) {
636
+ let remaining = true;
637
+ const terms = [];
638
+ for (let index = 1; index < value.length - 1; index += 2) {
639
+ const condition = this.predicate(value[index], depth + 1);
640
+ terms.push(
641
+ combine("all", [remaining, condition, this.predicate(value[index + 1], depth + 1)])
642
+ );
643
+ remaining = combine("all", [remaining, negate(condition)]);
644
+ }
645
+ terms.push(combine("all", [remaining, this.predicate(value.at(-1), depth + 1)]));
646
+ return combine("any", terms);
647
+ }
648
+ if (operation(value, "step") && value.length >= 5 && value.length % 2 === 1) {
649
+ if (this.scalar(value[1], depth + 1) !== "number") fail(this.path);
650
+ let previous = -Infinity;
651
+ let remaining = true;
652
+ let output = this.predicate(value[2], depth + 1);
653
+ const terms = [];
654
+ for (let index = 3; index < value.length; index += 2) {
655
+ const at = value[index];
656
+ if (!finite(at) || at <= previous) fail(this.path);
657
+ previous = at;
658
+ if (operation(value[1], "zoom", 1)) this.cuts.add(Math.ceil(at));
659
+ const below = ["<", value[1], at];
660
+ terms.push(combine("all", [remaining, below, output]));
661
+ remaining = combine("all", [remaining, negate(below)]);
662
+ output = this.predicate(value[index + 1], depth + 1);
663
+ }
664
+ terms.push(combine("all", [remaining, output]));
665
+ return combine("any", terms);
666
+ }
667
+ if (operation(value, "in", 3) && operation(value[2], "literal", 2) && Array.isArray(value[2][1])) {
668
+ this.scalar(value[1], depth + 1);
669
+ if (operation(value[1], "zoom", 1)) fail(this.path);
670
+ for (const item of value[2][1]) {
671
+ this.tick(depth + 1);
672
+ if (!(item === null || typeof item === "boolean" || typeof item === "string" || finite(item)))
673
+ fail(this.path);
674
+ }
675
+ return value;
676
+ }
677
+ if (Array.isArray(value) && value.length === 3 && ["==", "!=", "<", "<=", ">", ">="].includes(String(value[0]))) {
678
+ const left = this.scalar(value[1], depth + 1);
679
+ const right = this.scalar(value[2], depth + 1);
680
+ if (!["==", "!="].includes(String(value[0])) && (left !== "number" || right !== "number"))
681
+ fail(this.path);
682
+ if (operation(value[1], "zoom", 1) || operation(value[2], "zoom", 1)) {
683
+ const constant = operation(value[1], "zoom", 1) ? value[2] : value[1];
684
+ if (!finite(constant)) fail(this.path);
685
+ this.cuts.add(Math.ceil(constant));
686
+ this.cuts.add(Math.floor(constant) + 1);
687
+ }
688
+ return value;
689
+ }
690
+ return fail(this.path);
691
+ }
692
+ atZoom(value, zoom) {
693
+ if (typeof value === "boolean") return value;
694
+ const expr = value;
695
+ if (expr[0] === "all" || expr[0] === "any")
696
+ return combine(
697
+ expr[0],
698
+ expr.slice(1).map((child) => this.atZoom(child, zoom))
699
+ );
700
+ if (expr[0] === "!") return negate(this.atZoom(expr[1], zoom));
701
+ if (operation(expr[1], "zoom", 1) || operation(expr[2], "zoom", 1)) {
702
+ const left = operation(expr[1], "zoom", 1) ? zoom : expr[1];
703
+ const right = operation(expr[2], "zoom", 1) ? zoom : expr[2];
704
+ switch (expr[0]) {
705
+ case "==":
706
+ return left === right;
707
+ case "!=":
708
+ return left !== right;
709
+ case "<":
710
+ return left < right;
711
+ case "<=":
712
+ return left <= right;
713
+ case ">":
714
+ return left > right;
715
+ case ">=":
716
+ return left >= right;
717
+ }
718
+ }
719
+ return expr;
720
+ }
721
+ outcomes(tree, zoom) {
722
+ const leaves = [];
723
+ const walk = (node, guard) => {
724
+ if (guard === false) return;
725
+ if (node.kind === "leaf") {
726
+ leaves.push({ when: guard, value: node.value });
727
+ if (leaves.length > nativeLoweringLimits.maximumPathsPerProperty) fail(this.path, "budget");
728
+ } else if (node.kind === "case") {
729
+ let remaining = guard;
730
+ for (const arm of node.arms) {
731
+ const condition = this.atZoom(arm.when, zoom);
732
+ walk(arm.value, combine("all", [remaining, condition]));
733
+ remaining = combine("all", [remaining, negate(condition)]);
734
+ }
735
+ walk(node.fallback, remaining);
736
+ } else if (operation(node.input, "zoom", 1)) {
737
+ let selected = node.fallback;
738
+ for (const stop of node.stops) {
739
+ if (zoom < stop.at) break;
740
+ selected = stop.value;
741
+ }
742
+ walk(selected, guard);
743
+ } else {
744
+ let previous = node.fallback;
745
+ let remaining = guard;
746
+ for (const stop of node.stops) {
747
+ const below = ["<", node.input, stop.at];
748
+ walk(previous, combine("all", [remaining, below]));
749
+ remaining = combine("all", [remaining, negate(below)]);
750
+ previous = stop.value;
751
+ }
752
+ walk(previous, remaining);
753
+ }
754
+ };
755
+ walk(tree, true);
756
+ const grouped = /* @__PURE__ */ new Map();
757
+ for (const outcome of leaves) {
758
+ const id = key(outcome.value);
759
+ if (!grouped.has(id)) grouped.set(id, { when: true, value: outcome.value });
760
+ }
761
+ if (grouped.size > nativeLoweringLimits.maximumPropertyBranches) fail(this.path, "budget");
762
+ return [...grouped.entries()].map(([id, outcome]) => ({
763
+ value: outcome.value,
764
+ when: this.accepts(tree, id, zoom)
765
+ }));
766
+ }
767
+ accepts(tree, outcome, zoom) {
768
+ if (tree.kind === "leaf") return key(tree.value) === outcome;
769
+ if (tree.kind === "case") {
770
+ return decision(
771
+ tree.arms.map((arm) => ({
772
+ when: this.atZoom(arm.when, zoom),
773
+ value: this.accepts(arm.value, outcome, zoom)
774
+ })),
775
+ this.accepts(tree.fallback, outcome, zoom)
776
+ );
777
+ }
778
+ if (operation(tree.input, "zoom", 1)) {
779
+ let selected = tree.fallback;
780
+ for (const stop of tree.stops) {
781
+ if (zoom < stop.at) break;
782
+ selected = stop.value;
783
+ }
784
+ return this.accepts(selected, outcome, zoom);
785
+ }
786
+ return decision(
787
+ tree.stops.map((stop, index) => ({
788
+ when: ["<", tree.input, stop.at],
789
+ value: this.accepts(
790
+ index === 0 ? tree.fallback : tree.stops[index - 1].value,
791
+ outcome,
792
+ zoom
793
+ )
794
+ })),
795
+ this.accepts(tree.stops.at(-1).value, outcome, zoom)
796
+ );
797
+ }
798
+ };
799
+ function isConstant(value, property) {
800
+ return value === void 0 || (property === "line-cap" ? typeof value === "string" : Array.isArray(value) && value.every((item) => typeof item === "number"));
801
+ }
802
+ function lowerLayer(layer, index, vector) {
803
+ if (layer.type !== "line") return;
804
+ const properties = [];
805
+ let cap;
806
+ let dash;
807
+ for (const [group, property] of [
808
+ ["layout", "line-cap"],
809
+ ["paint", "line-dasharray"]
810
+ ]) {
811
+ const value = layer[group]?.[property];
812
+ if (isConstant(value, property)) continue;
813
+ const decisions = new Decisions(`/layers/${index}/${group}/${property}`, vector);
814
+ const tree = decisions.tree(value, property);
815
+ properties.push(property);
816
+ if (property === "line-cap") cap = { decisions, tree };
817
+ else dash = { decisions, tree };
818
+ }
819
+ if (!properties.length) return;
820
+ const minimum = layer.minzoom ?? 0;
821
+ const maximum = layer.maxzoom ?? 24;
822
+ if (!finite(minimum) || !finite(maximum) || minimum < 0 || maximum > 24 || minimum >= maximum) {
823
+ fail(`/layers/${index}/minzoom`);
824
+ }
825
+ const cuts = [
826
+ .../* @__PURE__ */ new Set([
827
+ minimum,
828
+ maximum,
829
+ ...[...cap?.decisions.cuts ?? [], ...dash?.decisions.cuts ?? []].filter(
830
+ (cut) => cut > minimum && cut < maximum
831
+ )
832
+ ])
833
+ ].sort((a, b) => a - b);
834
+ if (cuts.length - 1 > nativeLoweringLimits.maximumZoomIntervals)
835
+ fail(`/layers/${index}`, "budget");
836
+ const variants = [];
837
+ const tails = /* @__PURE__ */ new Map();
838
+ for (let part = 0; part < cuts.length - 1; part++) {
839
+ const low = cuts[part];
840
+ const high = cuts[part + 1];
841
+ const caps = cap ? cap.decisions.outcomes(cap.tree, Math.floor(low)) : [{ when: true, value: void 0 }];
842
+ const dashes = dash ? dash.decisions.outcomes(dash.tree, Math.floor(low)) : [{ when: true, value: void 0 }];
843
+ for (const c of caps)
844
+ for (const d of dashes) {
845
+ const when = combine("all", [c.when, d.when]);
846
+ if (when === false) continue;
847
+ const signature = key([when, c.value, d.value]);
848
+ const tail = tails.get(signature);
849
+ if (tail && tail.maximum === low) {
850
+ tail.maximum = high;
851
+ continue;
852
+ }
853
+ const variant = {
854
+ minimum: low,
855
+ maximum: high,
856
+ when,
857
+ ...cap ? { cap: c.value } : {},
858
+ ...dash ? { dash: d.value } : {}
859
+ };
860
+ variants.push(variant);
861
+ tails.set(signature, variant);
862
+ if (variants.length > nativeLoweringLimits.maximumLayerVariants)
863
+ fail(`/layers/${index}`, "budget");
864
+ }
865
+ }
866
+ return { variants, properties };
867
+ }
868
+ function lowerNativeStyleRepresentation(input, normalizeFilter = (filter) => filter) {
869
+ if (!Array.isArray(input.layers) || input.layers.length > nativeLoweringLimits.maximumLayers)
870
+ fail("/layers", "budget");
871
+ const originalLayers = input.layers;
872
+ const reserved = /* @__PURE__ */ new Set();
873
+ for (const [index, layer] of originalLayers.entries()) {
874
+ if (!object(layer) || typeof layer.id !== "string" || reserved.has(layer.id))
875
+ fail(`/layers/${index}/id`);
876
+ reserved.add(layer.id);
877
+ }
878
+ let projection = "none";
879
+ if (input.projection !== void 0) {
880
+ if (!object(input.projection) || Object.keys(input.projection).length !== 1 || typeof input.projection.type !== "string" || !["globe", "mercator"].includes(input.projection.type))
881
+ fail("/projection");
882
+ projection = input.projection.type === "globe" ? "globe-to-mercator" : "mercator-to-implicit";
883
+ }
884
+ const layers = [];
885
+ const transformations = [];
886
+ for (const [index, layer] of originalLayers.entries()) {
887
+ const source = object(input.sources) && typeof layer.source === "string" ? input.sources[layer.source] : void 0;
888
+ const lowered = lowerLayer(layer, index, object(source) && source.type === "vector");
889
+ if (!lowered) {
890
+ layers.push(layer);
891
+ continue;
892
+ }
893
+ if (layers.length + lowered.variants.length + originalLayers.length - index - 1 > nativeLoweringLimits.maximumLayers) {
894
+ fail(`/layers/${index}`, "budget");
895
+ }
896
+ const originalFilter = layer.filter === void 0 ? void 0 : normalizeFilter(layer.filter);
897
+ const outputStart = layers.length;
898
+ for (const [branch, variant] of lowered.variants.entries()) {
899
+ const stem = `${layer.id}--native-v1-${branch}`;
900
+ let id = stem;
901
+ let collision = 0;
902
+ while (reserved.has(id)) id = `${stem}-${++collision}`;
903
+ reserved.add(id);
904
+ const next = { ...layer, id };
905
+ if (variant.minimum !== (layer.minzoom ?? 0)) next.minzoom = variant.minimum;
906
+ if (variant.maximum !== (layer.maxzoom ?? 24)) next.maxzoom = variant.maximum;
907
+ if (variant.when !== true)
908
+ next.filter = originalFilter === void 0 ? variant.when : ["all", originalFilter, variant.when];
909
+ if (variant.cap !== void 0) next.layout = { ...layer.layout, "line-cap": variant.cap };
910
+ if (variant.dash !== void 0) next.paint = { ...layer.paint, "line-dasharray": variant.dash };
911
+ layers.push(next);
912
+ }
913
+ transformations.push({
914
+ inputLayer: index,
915
+ outputStart,
916
+ outputCount: lowered.variants.length,
917
+ properties: lowered.properties
918
+ });
919
+ }
920
+ const changed = projection !== "none" || transformations.length > 0;
921
+ const output = changed ? { ...input, layers } : input;
922
+ if (changed && projection !== "none") delete output.projection;
923
+ return {
924
+ style: output,
925
+ projection,
926
+ layers: transformations,
927
+ inputLayers: originalLayers.length,
928
+ outputLayers: layers.length
929
+ };
930
+ }
931
+
932
+ // src/native-artifacts.ts
933
+ function documentUrl(map, theme) {
934
+ return `https://artifacts.invalid/native/styles/${map}/${theme}.json`;
935
+ }
936
+ function withContext(issue, map, theme) {
937
+ const prefix = `/maps/${map}/themes/${theme}/style`;
938
+ return { ...issue, path: prefix.length + issue.path.length <= 300 ? prefix + issue.path : prefix };
939
+ }
940
+ function lowerTileflowNativeCompiledStyles(input) {
941
+ const styles = {};
942
+ const transformations = [];
943
+ for (const map of Object.keys(input).sort()) {
944
+ styles[map] = {};
945
+ for (const theme of Object.keys(input[map]).sort()) {
946
+ const original = input[map][theme];
947
+ try {
948
+ const options = { documentUrl: documentUrl(map, theme), deferFontClosure: true };
949
+ const pending = validateTileflowNativeStyle(original, options);
950
+ const blockers = pending.filter(
951
+ (issue) => issue.code !== "NATIVE_UNSUPPORTED_STYLE" || !(issue.path === "/projection" || /^\/layers\/\d+\/(?:layout\/line-cap|paint\/line-dasharray)(?:\/|$)/u.test(issue.path))
952
+ );
953
+ if (blockers.length)
954
+ throw new TileflowNativeCompatibilityError(
955
+ blockers.map((issue) => withContext(issue, map, theme))
956
+ );
957
+ const syntax = validateStyleMin(JSON.parse(serializeCanonicalJson(original)));
958
+ if (syntax.length)
959
+ throw new TileflowNativeCompatibilityError(
960
+ syntax.map((issue) => {
961
+ const match = /^layers\[(\d+)\]\.(layout\.line-cap|paint\.line-dasharray)(?=[:.[]|$)/u.exec(
962
+ issue.message
963
+ );
964
+ const path = match ? `/layers/${match[1]}/${match[2].replace(".", "/")}` : issue.message.startsWith("projection") ? "/projection" : "";
965
+ return withContext(
966
+ createTileflowNativeDiagnostic("NATIVE_UNSUPPORTED_STYLE", path),
967
+ map,
968
+ theme
969
+ );
970
+ })
971
+ );
972
+ const lowered = lowerNativeStyleRepresentation(
973
+ original,
974
+ (filter) => convertFilter(structuredClone(filter))
975
+ );
976
+ const issues = validateTileflowNativePreparedStyle(lowered.style, options);
977
+ if (issues.length)
978
+ throw new TileflowNativeCompatibilityError(
979
+ issues.map((issue) => withContext(issue, map, theme))
980
+ );
981
+ styles[map][theme] = lowered.style;
982
+ transformations.push({
983
+ map,
984
+ theme,
985
+ inputStyleSha256: createHash("sha256").update(serializeCanonicalJson(original)).digest("hex"),
986
+ loweredStyleSha256: createHash("sha256").update(serializeCanonicalJson(lowered.style)).digest("hex"),
987
+ inputLayers: lowered.inputLayers,
988
+ outputLayers: lowered.outputLayers,
989
+ projection: lowered.projection,
990
+ layers: lowered.layers
991
+ });
992
+ } catch (error) {
993
+ if (!(error instanceof NativeLoweringError)) throw error;
994
+ throw new TileflowNativeCompatibilityError([
995
+ withContext(
996
+ {
997
+ ...createTileflowNativeDiagnostic("NATIVE_UNSUPPORTED_STYLE", error.path),
998
+ message: error.message,
999
+ suggestion: error.reason === "budget" ? "Reduce the decision expansion or style size; native lowering never raises its resource limits automatically." : error.path === "/projection" ? "Use a fixed globe or Mercator projection for native-v1; adaptive projections are not supported." : "Use finite case/match/step decisions with constant leaves and supported scalar predicates, or select web."
1000
+ },
1001
+ map,
1002
+ theme
1003
+ )
1004
+ ]);
1005
+ }
1006
+ }
1007
+ }
1008
+ return { styles, transformations };
1009
+ }
1010
+ function assertTileflowNativeCompiledStyles(project, styles) {
1011
+ const issues = [];
1012
+ for (const mapName of Object.keys(project.maps).sort()) {
1013
+ for (const theme of Object.keys(styles[mapName] ?? {}).sort()) {
1014
+ issues.push(
1015
+ ...validateTileflowNativePreparedStyle(styles[mapName][theme], {
1016
+ documentUrl: documentUrl(mapName, theme),
1017
+ deferFontClosure: true
1018
+ }).map((issue) => withContext(issue, mapName, theme))
1019
+ );
1020
+ }
1021
+ }
1022
+ if (issues.length) throw new TileflowNativeCompatibilityError(issues);
1023
+ }
1024
+ function prepareTileflowNativeStyles(input, assets) {
1025
+ const issues = [];
1026
+ const result = {};
1027
+ for (const map of Object.keys(input).sort()) {
1028
+ result[map] = {};
1029
+ if (!validNativeSprites(map, assets)) {
1030
+ issues.push({
1031
+ ...createTileflowNativeDiagnostic("NATIVE_UNSUPPORTED_STYLE", `/maps/${map}/icons`),
1032
+ suggestion: "Rebuild the icon sources into a complete, bounded 1x/2x sprite pair."
1033
+ });
1034
+ }
1035
+ for (const theme of Object.keys(input[map]).sort()) {
1036
+ const original = input[map][theme];
1037
+ const preflight = validateTileflowNativePreparedStyle(original, {
1038
+ documentUrl: documentUrl(map, theme),
1039
+ deferFontClosure: true
1040
+ });
1041
+ if (preflight.length) {
1042
+ issues.push(...preflight.map((issue) => withContext(issue, map, theme)));
1043
+ continue;
1044
+ }
1045
+ const metadata = { ...original.metadata };
1046
+ const faces = getTileflowStyleFontFaces(original);
1047
+ const nativeFaces = {};
1048
+ for (const face of faces) {
1049
+ const asset = assets.find(
1050
+ (item) => item.fileName.startsWith("fonts/") && (face.source === item.fileName || face.source.endsWith(`/${item.fileName}`))
1051
+ );
1052
+ if (!asset || !isNativeFontAsset(asset)) {
1053
+ issues.push(
1054
+ withContext(
1055
+ createTileflowNativeDiagnostic("NATIVE_FONT_UNAVAILABLE", "/font-faces"),
1056
+ map,
1057
+ theme
1058
+ )
1059
+ );
1060
+ continue;
1061
+ }
1062
+ if (Object.hasOwn(nativeFaces, face.family)) {
1063
+ issues.push(
1064
+ withContext(
1065
+ createTileflowNativeDiagnostic("NATIVE_FONT_UNAVAILABLE", "/font-faces"),
1066
+ map,
1067
+ theme
1068
+ )
1069
+ );
1070
+ continue;
1071
+ }
1072
+ Object.defineProperty(nativeFaces, face.family, { value: face.source, enumerable: true });
1073
+ }
1074
+ delete metadata[tileflowStyleFontFacesMetadataKey];
1075
+ const { projection: _projection, ...rest } = original;
1076
+ const style = {
1077
+ ...rest,
1078
+ metadata,
1079
+ ...faces.length ? { "font-faces": nativeFaces } : {}
1080
+ };
1081
+ issues.push(
1082
+ ...validateTileflowNativePreparedStyle(style, {
1083
+ documentUrl: documentUrl(map, theme)
1084
+ }).map((issue) => withContext(issue, map, theme))
1085
+ );
1086
+ result[map][theme] = style;
1087
+ }
1088
+ }
1089
+ if (issues.length) throw new TileflowNativeCompatibilityError(issues);
1090
+ return result;
1091
+ }
1092
+ function isNativeFontAsset(asset) {
1093
+ const bytes = typeof asset.source === "string" ? Buffer.from(asset.source) : asset.source;
1094
+ if (bytes.byteLength < 12 || bytes.byteLength > tileflowNativeProfileLimits.maximumFontBytes)
1095
+ return false;
1096
+ const signature = Buffer.from(bytes.subarray(0, 4)).toString("hex");
1097
+ if (!/\.(?:ttf|otf)$/u.test(asset.fileName) || !["00010000", "4f54544f"].includes(signature))
1098
+ return false;
1099
+ const expectedHash = /-([a-f0-9]{64})\.(?:ttf|otf)$/u.exec(asset.fileName)?.[1];
1100
+ return expectedHash === createHash("sha256").update(bytes).digest("hex");
1101
+ }
1102
+ function assertTileflowNativeGeneratedStyle(style, map, theme) {
1103
+ const issues = validateTileflowNativePreparedStyle(style, { documentUrl: documentUrl(map, theme) });
1104
+ if (issues.length) {
1105
+ throw new TileflowNativeCompatibilityError(
1106
+ issues.map((issue) => withContext(issue, map, theme))
1107
+ );
1108
+ }
1109
+ }
1110
+ function replaceTileflowNativeFontSources(style, replace) {
1111
+ const faces = style["font-faces"];
1112
+ if (!faces) return style;
1113
+ return {
1114
+ ...style,
1115
+ "font-faces": Object.fromEntries(
1116
+ Object.entries(faces).map(([name, source]) => [name, replace(source)])
1117
+ )
1118
+ };
1119
+ }
1120
+ function validNativeSprites(map, assets) {
1121
+ const prefix = `icons/${map}/`;
1122
+ const files = assets.filter((asset) => asset.fileName.startsWith(prefix));
1123
+ if (!files.length) return true;
1124
+ try {
1125
+ if (files.length !== 4) return false;
1126
+ let totalBytes = 0;
1127
+ const indexes = [];
1128
+ for (const ratio of [1, 2]) {
1129
+ const suffix = ratio === 1 ? "" : "@2x";
1130
+ const image = files.find((asset) => asset.fileName === `${prefix}sprite${suffix}.png`);
1131
+ const index = files.find((asset) => asset.fileName === `${prefix}sprite${suffix}.json`);
1132
+ if (!image || !index) return false;
1133
+ const imageBytes = Buffer.from(
1134
+ typeof image.source === "string" ? new TextEncoder().encode(image.source) : image.source
1135
+ );
1136
+ const indexBytes = Buffer.from(
1137
+ typeof index.source === "string" ? new TextEncoder().encode(index.source) : index.source
1138
+ );
1139
+ for (const bytes of [imageBytes, indexBytes]) {
1140
+ if (bytes.length > tileflowIconPackageLimits.maxGeneratedFileBytes) return false;
1141
+ totalBytes += bytes.length;
1142
+ }
1143
+ if (imageBytes.length < 33 || imageBytes.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a" || imageBytes.toString("ascii", 12, 16) !== "IHDR")
1144
+ return false;
1145
+ const width = imageBytes.readUInt32BE(16);
1146
+ const height = imageBytes.readUInt32BE(20);
1147
+ if (!width || !height || width > tileflowIconPackageLimits.maxAtlasDimension || height > tileflowIconPackageLimits.maxAtlasDimension)
1148
+ return false;
1149
+ const data = JSON.parse(indexBytes.toString("utf8"));
1150
+ if (!data || typeof data !== "object" || Array.isArray(data)) return false;
1151
+ const names = Object.keys(data).sort();
1152
+ if (!names.length || names.length > tileflowIconPackageLimits.maxIconCount) return false;
1153
+ indexes.push(names);
1154
+ for (const name of names) {
1155
+ if (!tileflowIconIdPattern.test(name)) return false;
1156
+ const entry = data[name];
1157
+ if (!entry || entry.pixelRatio !== ratio) return false;
1158
+ const { x, y, width: w, height: h } = entry;
1159
+ if (![x, y, w, h].every((value) => typeof value === "number" && Number.isSafeInteger(value)))
1160
+ return false;
1161
+ if (x < 0 || y < 0 || w <= 0 || h <= 0 || x + w > width || y + h > height)
1162
+ return false;
1163
+ }
1164
+ }
1165
+ return totalBytes <= tileflowIconPackageLimits.maxGeneratedPackageBytes && JSON.stringify(indexes[0]) === JSON.stringify(indexes[1]);
1166
+ } catch {
1167
+ return false;
1168
+ }
1169
+ }
1170
+
378
1171
  // src/session.ts
379
1172
  import { watch } from "chokidar";
380
- import { realpathSync } from "fs";
381
- import { dirname, extname, isAbsolute, relative, resolve as resolve2, sep, win32 } from "path";
1173
+ import { realpathSync, statSync } from "fs";
1174
+ import { basename as basename2, dirname, extname, isAbsolute, relative, resolve as resolve2, sep, win32 } from "path";
1175
+ import { tileflowIconMetadataFileName } from "@tileflow/core";
1176
+ import { TileflowNativeCompatibilityError as TileflowNativeCompatibilityError2 } from "@tileflow/core/native-profile";
382
1177
  var tileflowArtifactSessionSchemaVersion = 1;
383
1178
  var configExtensions = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".json", ".mjs", ".mts", ".ts", ".tsx"]);
384
1179
  var iconExtensions = /* @__PURE__ */ new Set([".jpeg", ".jpg", ".png", ".svg", ".webp"]);
385
1180
  var fontExtensions = /* @__PURE__ */ new Set([".otf", ".ttf", ".woff2"]);
1181
+ var writeStabilityMs = 50;
386
1182
  var excludedDirectories = /* @__PURE__ */ new Set([
387
1183
  ".cache",
388
1184
  ".git",
@@ -416,6 +1212,7 @@ var TileflowArtifactSessionImpl = class {
416
1212
  #closed = false;
417
1213
  #closing;
418
1214
  #debounceTimer;
1215
+ #startupCheckTimer;
419
1216
  #generation = 0;
420
1217
  #current;
421
1218
  #artifactGenerations = /* @__PURE__ */ new Set();
@@ -424,13 +1221,17 @@ var TileflowArtifactSessionImpl = class {
424
1221
  #refreshToken = 0;
425
1222
  #state = { generation: 0, status: "building" };
426
1223
  #watcher;
1224
+ #watchedArtifactDirectories = /* @__PURE__ */ new Map();
427
1225
  #watchedArtifactPaths = /* @__PURE__ */ new Set();
428
1226
  constructor(options, buildArtifacts, discoverWatchPaths) {
429
1227
  this.#cwd = resolve2(options.cwd ?? process.cwd());
430
- this.#configPath = resolve2(this.#cwd, options.config ?? "tileflow.config.ts");
1228
+ const configPath = resolve2(this.#cwd, options.config ?? "tileflow.config.ts");
1229
+ this.#configPath = resolve2(canonicalWatchPath(dirname(configPath)), basename2(configPath));
431
1230
  this.#debounceMs = Math.max(0, Math.min(options.debounceMs ?? 75, 1e3));
432
1231
  this.#watchEnabled = options.watch ?? false;
433
- this.#ignoredPaths = (options.ignoredPaths ?? []).map((path) => resolve2(this.#cwd, path));
1232
+ this.#ignoredPaths = (options.ignoredPaths ?? []).map(
1233
+ (path) => canonicalWatchPath(resolve2(this.#cwd, path))
1234
+ );
434
1235
  this.#buildArtifacts = buildArtifacts;
435
1236
  this.#discoverWatchPaths = discoverWatchPaths;
436
1237
  this.#buildOptions = {
@@ -438,7 +1239,10 @@ var TileflowArtifactSessionImpl = class {
438
1239
  assetBaseUrl: options.assetBaseUrl,
439
1240
  config: options.config,
440
1241
  cwd: this.#cwd,
1242
+ // Trusted shared Icon Set resolution settings, supplied once by the owning command.
1243
+ ...options.icons ? { icons: options.icons } : {},
441
1244
  inspection: options.inspection,
1245
+ ...options.renderer === void 0 ? {} : { renderer: options.renderer },
442
1246
  styleBaseUrl: options.styleBaseUrl,
443
1247
  target: options.target
444
1248
  };
@@ -446,7 +1250,7 @@ var TileflowArtifactSessionImpl = class {
446
1250
  async start() {
447
1251
  if (this.#watchEnabled) {
448
1252
  this.#watcher = watch(dirname(this.#configPath), {
449
- awaitWriteFinish: { pollInterval: 20, stabilityThreshold: 50 },
1253
+ awaitWriteFinish: { pollInterval: 20, stabilityThreshold: writeStabilityMs },
450
1254
  followSymlinks: false,
451
1255
  ignoreInitial: true,
452
1256
  ignored: (path, stats) => this.#ignoreWatchPath(path, stats?.isDirectory()),
@@ -474,6 +1278,22 @@ var TileflowArtifactSessionImpl = class {
474
1278
  if (!this.#shouldRefreshForWatchEvent(path)) return;
475
1279
  this.#scheduleRefresh(path);
476
1280
  });
1281
+ this.#watcher.on("raw", (event, path, details) => {
1282
+ if (event !== "rename" && event !== "change") return;
1283
+ const resolvedPath = resolveRawWatchPath(path, details);
1284
+ if (!resolvedPath || this.#ignoreWatchPath(resolvedPath, void 0)) return;
1285
+ const directoryChanged = this.#watchedArtifactDirectories.has(resolvedPath);
1286
+ const metadataChanged = event === "rename" && basename2(resolvedPath) === tileflowIconMetadataFileName && [...this.#watchedArtifactDirectories.keys()].some(
1287
+ (directory) => isSameOrInside(directory, resolvedPath)
1288
+ );
1289
+ if (!directoryChanged && !metadataChanged) return;
1290
+ if (directoryChanged && event === "change") {
1291
+ const modified = directoryModificationTime(resolvedPath);
1292
+ if (modified === this.#watchedArtifactDirectories.get(resolvedPath)) return;
1293
+ this.#watchedArtifactDirectories.set(resolvedPath, modified);
1294
+ }
1295
+ this.#scheduleRefresh(resolvedPath);
1296
+ });
477
1297
  this.#watcher.on("error", (error) => {
478
1298
  if (this.#closed) return;
479
1299
  this.#refreshToken += 1;
@@ -482,6 +1302,19 @@ var TileflowArtifactSessionImpl = class {
482
1302
  await watcherReady;
483
1303
  }
484
1304
  await this.refresh("initial");
1305
+ if (this.#watcher && !this.#closed) {
1306
+ this.#startupCheckTimer = setTimeout(() => {
1307
+ this.#startupCheckTimer = void 0;
1308
+ if (this.#closed) return;
1309
+ for (const [path, observed] of this.#watchedArtifactDirectories) {
1310
+ if (this.#ignoreWatchPath(path, true)) continue;
1311
+ const modified = directoryModificationTime(path);
1312
+ if (modified === observed) continue;
1313
+ this.#watchedArtifactDirectories.set(path, modified);
1314
+ this.#scheduleRefresh(path);
1315
+ }
1316
+ }, writeStabilityMs);
1317
+ }
485
1318
  }
486
1319
  getState() {
487
1320
  return this.#state;
@@ -575,6 +1408,7 @@ var TileflowArtifactSessionImpl = class {
575
1408
  this.#closed = true;
576
1409
  this.#refreshToken += 1;
577
1410
  if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
1411
+ if (this.#startupCheckTimer) clearTimeout(this.#startupCheckTimer);
578
1412
  this.#listeners.clear();
579
1413
  await this.#watcher?.close();
580
1414
  const retained = [...this.#artifactGenerations];
@@ -587,7 +1421,7 @@ var TileflowArtifactSessionImpl = class {
587
1421
  await Promise.all(retained.map((generation) => generation.completion));
588
1422
  }
589
1423
  #ignoreWatchPath(path, isDirectory) {
590
- const resolvedPath = resolve2(path);
1424
+ const resolvedPath = canonicalWatchPath(path);
591
1425
  const relativePath = relative(dirname(this.#configPath), resolvedPath);
592
1426
  const segments = relativePath.split(sep).filter(Boolean);
593
1427
  if (this.#ignoredPaths.some((path2) => isSameOrInside(path2, resolvedPath))) return true;
@@ -601,7 +1435,7 @@ var TileflowArtifactSessionImpl = class {
601
1435
  return true;
602
1436
  }
603
1437
  #shouldRefreshForWatchEvent(path) {
604
- const resolvedPath = resolve2(path);
1438
+ const resolvedPath = canonicalWatchPath(path);
605
1439
  const extension = extname(resolvedPath).toLowerCase();
606
1440
  if (!iconExtensions.has(extension) && !fontExtensions.has(extension)) return true;
607
1441
  return [...this.#watchedArtifactPaths].some((path2) => isSameOrInside(path2, resolvedPath));
@@ -629,9 +1463,20 @@ var TileflowArtifactSessionImpl = class {
629
1463
  }
630
1464
  #updateArtifactWatchPaths(paths) {
631
1465
  if (!this.#watcher) return;
632
- const next = new Set(paths.map((path) => resolve2(this.#cwd, path)));
1466
+ const next = new Set(paths.map((path) => canonicalWatchPath(resolve2(this.#cwd, path))));
633
1467
  const previous = this.#watchedArtifactPaths;
634
1468
  this.#watchedArtifactPaths = next;
1469
+ const previousDirectories = this.#watchedArtifactDirectories;
1470
+ this.#watchedArtifactDirectories = /* @__PURE__ */ new Map();
1471
+ for (const path of next) {
1472
+ const modified = directoryModificationTime(path);
1473
+ if (modified !== void 0) {
1474
+ this.#watchedArtifactDirectories.set(
1475
+ path,
1476
+ previousDirectories.has(path) ? previousDirectories.get(path) : modified
1477
+ );
1478
+ }
1479
+ }
635
1480
  for (const path of previous) {
636
1481
  if (!next.has(path) && !isSameOrInside(dirname(this.#configPath), path)) {
637
1482
  void this.#watcher.unwatch(path);
@@ -686,7 +1531,41 @@ function isSameOrInside(root, candidate) {
686
1531
  const path = relative(resolve2(root), resolve2(candidate));
687
1532
  return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
688
1533
  }
1534
+ function directoryModificationTime(path) {
1535
+ try {
1536
+ const stats = statSync(path);
1537
+ return stats.isDirectory() ? stats.mtimeMs : void 0;
1538
+ } catch {
1539
+ return void 0;
1540
+ }
1541
+ }
1542
+ function resolveRawWatchPath(path, details) {
1543
+ if (typeof path !== "string" || !path) return void 0;
1544
+ if (isAbsolute(path)) return canonicalWatchPath(path);
1545
+ if (!details || typeof details !== "object") return void 0;
1546
+ const watchedPath = details.watchedPath;
1547
+ if (typeof watchedPath !== "string" || !isAbsolute(watchedPath)) return void 0;
1548
+ return canonicalWatchPath(
1549
+ basename2(watchedPath) === path ? watchedPath : resolve2(watchedPath, path)
1550
+ );
1551
+ }
1552
+ function canonicalWatchPath(path) {
1553
+ const resolved = resolve2(path);
1554
+ let ancestor = resolved;
1555
+ while (true) {
1556
+ try {
1557
+ return resolve2(realpathSync.native(ancestor), relative(ancestor, resolved));
1558
+ } catch {
1559
+ const parent = dirname(ancestor);
1560
+ if (parent === ancestor) return resolved;
1561
+ ancestor = parent;
1562
+ }
1563
+ }
1564
+ }
689
1565
  function createTileflowArtifactDiagnostics(error, cwd) {
1566
+ if (error instanceof TileflowNativeCompatibilityError2) {
1567
+ return error.issues.map((issue) => ({ ...issue }));
1568
+ }
690
1569
  const inheritedCode = optionalDiagnosticField(error, "code");
691
1570
  const inheritedPath = optionalDiagnosticPath(error);
692
1571
  const inheritedPhase = optionalDiagnosticField(error, "phase") ?? optionalNestedDiagnosticField(error, "phase");
@@ -717,8 +1596,8 @@ function createTileflowArtifactDiagnostics(error, cwd) {
717
1596
  function getIssueList(error) {
718
1597
  if (!error || typeof error !== "object") return [];
719
1598
  const containers = [];
720
- for (const key of ["messages", "issues"]) {
721
- const value = error[key];
1599
+ for (const key2 of ["messages", "issues"]) {
1600
+ const value = error[key2];
722
1601
  if (Array.isArray(value)) containers.push(value);
723
1602
  }
724
1603
  const details = error.details;
@@ -809,14 +1688,14 @@ function normalizeDiagnostics(diagnostics) {
809
1688
  (diagnostic, index) => index === 0 || JSON.stringify(diagnostic) !== JSON.stringify(bounded[index - 1])
810
1689
  ).slice(0, 32);
811
1690
  }
812
- function optionalDiagnosticField(value, key) {
1691
+ function optionalDiagnosticField(value, key2) {
813
1692
  if (!value || typeof value !== "object") return void 0;
814
- const field = value[key];
1693
+ const field = value[key2];
815
1694
  return typeof field === "string" && field.trim() ? boundDiagnosticField(field) : void 0;
816
1695
  }
817
- function optionalNestedDiagnosticField(value, key) {
1696
+ function optionalNestedDiagnosticField(value, key2) {
818
1697
  if (!value || typeof value !== "object") return void 0;
819
- return optionalDiagnosticField(value.details, key);
1698
+ return optionalDiagnosticField(value.details, key2);
820
1699
  }
821
1700
  function boundDiagnosticField(value) {
822
1701
  return boundDiagnosticText(value.replace(/[^A-Za-z0-9_-]/g, ""), 64);
@@ -825,6 +1704,213 @@ function boundDiagnosticText(value, limit) {
825
1704
  return value.length <= limit ? value : `${value.slice(0, limit - 1)}\u2026`;
826
1705
  }
827
1706
 
1707
+ // src/hosted-native-artifacts.ts
1708
+ import { createHash as createHash2 } from "crypto";
1709
+ import { serializeCanonicalJson as serializeCanonicalJson3 } from "@tileflow/core";
1710
+ import {
1711
+ inferTileflowDataRequirements,
1712
+ inferTileflowSourceRequirements,
1713
+ parseTileflowRendererDeploymentArtifact
1714
+ } from "@tileflow/core/build";
1715
+ import {
1716
+ createTileflowNativeBuildRecord,
1717
+ TileflowNativeCompatibilityError as TileflowNativeCompatibilityError4,
1718
+ createTileflowNativeDiagnostic as createTileflowNativeDiagnostic3
1719
+ } from "@tileflow/core/native-profile";
1720
+
1721
+ // src/native-hosted-provenance.ts
1722
+ import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
1723
+ import {
1724
+ createTileflowNativeDiagnostic as createTileflowNativeDiagnostic2,
1725
+ TileflowNativeCompatibilityError as TileflowNativeCompatibilityError3
1726
+ } from "@tileflow/core/native-profile";
1727
+ var overlayKeys = [
1728
+ "above-water",
1729
+ "below-roads",
1730
+ "above-roads",
1731
+ "above-buildings",
1732
+ "below-labels",
1733
+ "above-labels"
1734
+ ];
1735
+ function object2(value) {
1736
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
1737
+ return value;
1738
+ }
1739
+ function identifier(layer) {
1740
+ if (typeof layer.id !== "string" || !layer.id.length) throw new Error();
1741
+ return layer.id;
1742
+ }
1743
+ function finalizeNativeHostedProvenance(input, lowered, changes) {
1744
+ try {
1745
+ const result = JSON.parse(serializeCanonicalJson2(lowered));
1746
+ const originalIndexes = /* @__PURE__ */ new Map();
1747
+ const finalIndexes = /* @__PURE__ */ new Map();
1748
+ const starts = /* @__PURE__ */ new Map();
1749
+ const transformed = /* @__PURE__ */ new Map();
1750
+ let previous = -1;
1751
+ for (const change of changes) {
1752
+ if (!Number.isSafeInteger(change.inputLayer) || change.inputLayer <= previous || change.inputLayer >= input.layers.length || !Number.isSafeInteger(change.outputStart) || !Number.isSafeInteger(change.outputCount) || change.outputCount < 1 || change.outputCount > 32 || !Array.isArray(change.properties) || !["line-cap", "line-dasharray", "line-cap,line-dasharray"].includes(
1753
+ change.properties.join(",")
1754
+ ))
1755
+ throw new Error();
1756
+ previous = change.inputLayer;
1757
+ transformed.set(change.inputLayer, change);
1758
+ }
1759
+ for (const [index, layer] of result.layers.entries()) {
1760
+ const id = identifier(layer);
1761
+ if (finalIndexes.has(id)) throw new Error();
1762
+ finalIndexes.set(id, index);
1763
+ }
1764
+ let offset = 0;
1765
+ for (const [index, layer] of input.layers.entries()) {
1766
+ const id = identifier(layer);
1767
+ if (originalIndexes.has(id)) throw new Error();
1768
+ originalIndexes.set(id, index);
1769
+ starts.set(id, offset);
1770
+ const change = transformed.get(index);
1771
+ if (change && (change.outputStart !== offset || layer.type !== "line")) throw new Error();
1772
+ const count = change?.outputCount ?? 1;
1773
+ if (offset + count > result.layers.length) throw new Error();
1774
+ for (let branch = 0; branch < count; branch++) {
1775
+ const candidate = result.layers[offset + branch];
1776
+ if (!change && identifier(candidate) !== id) throw new Error();
1777
+ if (candidate.type !== layer.type || candidate.source !== layer.source || candidate["source-layer"] !== layer["source-layer"])
1778
+ throw new Error();
1779
+ }
1780
+ offset += count;
1781
+ }
1782
+ if (offset !== result.layers.length) throw new Error();
1783
+ const metadata = result.metadata === void 0 ? void 0 : object2(result.metadata);
1784
+ const originalMetadata = input.metadata === void 0 ? void 0 : object2(input.metadata);
1785
+ if (originalMetadata?.["tileflow:overlay-placement-manifest"] !== void 0) {
1786
+ if (!metadata) throw new Error();
1787
+ const overlay = object2(originalMetadata["tileflow:overlay-placement-manifest"]);
1788
+ const anchors = object2(overlay.anchors);
1789
+ if (overlay.schemaVersion !== 1 || Object.keys(anchors).length !== overlayKeys.length || overlayKeys.some((key2) => !Object.hasOwn(anchors, key2)))
1790
+ throw new Error();
1791
+ const updated = {};
1792
+ for (const key2 of overlayKeys) {
1793
+ const anchor = anchors[key2];
1794
+ if (anchor === null) {
1795
+ updated[key2] = null;
1796
+ continue;
1797
+ }
1798
+ if (typeof anchor !== "string" || !starts.has(anchor)) throw new Error();
1799
+ updated[key2] = identifier(result.layers[starts.get(anchor)]);
1800
+ }
1801
+ metadata["tileflow:overlay-placement-manifest"] = { ...overlay, anchors: updated };
1802
+ }
1803
+ if (originalMetadata?.["tileflow:interaction-manifest"] !== void 0) {
1804
+ if (!metadata) throw new Error();
1805
+ const manifest = object2(
1806
+ JSON.parse(serializeCanonicalJson2(originalMetadata["tileflow:interaction-manifest"]))
1807
+ );
1808
+ if (manifest.version !== 2) throw new Error();
1809
+ const domains = object2(manifest.domains);
1810
+ if (domains.poi !== void 0) {
1811
+ const poi = object2(domains.poi);
1812
+ if (!Array.isArray(poi.layers) || poi.layers.length > 256) throw new Error();
1813
+ const seen = /* @__PURE__ */ new Set();
1814
+ poi.layers = poi.layers.map((value) => {
1815
+ const entry = object2(value);
1816
+ if (typeof entry.layerId !== "string" || seen.has(entry.layerId)) throw new Error();
1817
+ const index = originalIndexes.get(entry.layerId);
1818
+ if (index === void 0 || index !== entry.priority || transformed.has(index) || !finalIndexes.has(entry.layerId))
1819
+ throw new Error();
1820
+ const original = input.layers[index];
1821
+ if (original.source !== entry.source || original["source-layer"] !== entry.sourceLayer)
1822
+ throw new Error();
1823
+ seen.add(entry.layerId);
1824
+ return { ...entry, priority: finalIndexes.get(entry.layerId) };
1825
+ });
1826
+ }
1827
+ metadata["tileflow:interaction-manifest"] = manifest;
1828
+ }
1829
+ return result;
1830
+ } catch {
1831
+ throw new TileflowNativeCompatibilityError3([
1832
+ createTileflowNativeDiagnostic2("NATIVE_UNSUPPORTED_STYLE", "/metadata")
1833
+ ]);
1834
+ }
1835
+ }
1836
+
1837
+ // src/hosted-native-artifacts.ts
1838
+ var digest = (value) => createHash2("sha256").update(serializeCanonicalJson3(value)).digest("hex");
1839
+ var invalidSource = () => new TileflowNativeCompatibilityError4([
1840
+ createTileflowNativeDiagnostic3("NATIVE_UNSUPPORTED_SOURCE", "/sources")
1841
+ ]);
1842
+ async function prepareTileflowHostedNativeDeployment(input) {
1843
+ const webStyles = JSON.parse(serializeCanonicalJson3(input.styles));
1844
+ const validationStyles = JSON.parse(
1845
+ serializeCanonicalJson3(webStyles)
1846
+ );
1847
+ for (const style of Object.values(validationStyles)) {
1848
+ for (const [sourceId, binding] of Object.entries(input.teamSources)) {
1849
+ const source = style.sources[sourceId];
1850
+ if (!source || source.type !== binding.type || source.url !== `tileflow://hosted-sources/${sourceId}` || source.tiles !== void 0)
1851
+ throw invalidSource();
1852
+ source.url = `https://artifacts.invalid/tiles/${sourceId}/tiles.json`;
1853
+ }
1854
+ }
1855
+ const lowered = lowerTileflowNativeCompiledStyles({ [input.mapId]: validationStyles });
1856
+ for (const transformation of lowered.transformations) {
1857
+ const name = transformation.theme;
1858
+ lowered.styles[input.mapId][name] = finalizeNativeHostedProvenance(
1859
+ validationStyles[name],
1860
+ lowered.styles[input.mapId][name],
1861
+ transformation.layers
1862
+ );
1863
+ }
1864
+ const projected = prepareTileflowNativeStyles(lowered.styles, input.assets)[input.mapId];
1865
+ for (const [name, style] of Object.entries(projected)) {
1866
+ for (const sourceId of Object.keys(input.teamSources)) {
1867
+ const source = style.sources[sourceId];
1868
+ if (!source) throw invalidSource();
1869
+ source.url = `tileflow://hosted-sources/${sourceId}`;
1870
+ }
1871
+ if (serializeCanonicalJson3(style.sources) !== serializeCanonicalJson3(webStyles[name].sources))
1872
+ throw invalidSource();
1873
+ }
1874
+ const nativeBuild = JSON.parse(
1875
+ serializeCanonicalJson3(input.buildManifest)
1876
+ );
1877
+ const mapBuild = nativeBuild.maps[input.mapId];
1878
+ if (!mapBuild) throw invalidSource();
1879
+ for (const [name, style] of Object.entries(projected)) {
1880
+ const theme = mapBuild.themes[name];
1881
+ if (!theme) throw invalidSource();
1882
+ mapBuild.themes[name] = {
1883
+ ...theme,
1884
+ styleSha256: digest(style),
1885
+ dataRequirements: inferTileflowDataRequirements(style),
1886
+ sourceRequirements: inferTileflowSourceRequirements(style)
1887
+ };
1888
+ }
1889
+ const transformations = lowered.transformations.map((item) => ({
1890
+ ...item,
1891
+ inputStyleSha256: digest(webStyles[item.theme]),
1892
+ loweredStyleSha256: digest({
1893
+ ...lowered.styles[input.mapId][item.theme],
1894
+ sources: webStyles[item.theme].sources
1895
+ })
1896
+ }));
1897
+ return parseTileflowRendererDeploymentArtifact({
1898
+ kind: "tileflow-map-deployment",
1899
+ schemaVersion: 2,
1900
+ mapId: input.mapId,
1901
+ teamSources: input.teamSources,
1902
+ ...input.view ? { view: input.view } : {},
1903
+ renderers: {
1904
+ web: { buildManifest: input.buildManifest, styles: webStyles },
1905
+ native: {
1906
+ buildManifest: nativeBuild,
1907
+ buildRecord: createTileflowNativeBuildRecord(digest(nativeBuild), transformations),
1908
+ styles: projected
1909
+ }
1910
+ }
1911
+ });
1912
+ }
1913
+
828
1914
  // src/artifacts.ts
829
1915
  var tileflowArtifactPlanSchemaVersion = 1;
830
1916
  var tileflowArtifactInventoryFileName = ".tileflow-artifacts.json";
@@ -903,31 +1989,50 @@ function assertValidTileflowStyles(project, styles) {
903
1989
  }
904
1990
  }
905
1991
  async function createTileflowArtifactPlan(prepared, options = {}) {
906
- if (options.target === "production" && hasLocalTilesetSources(prepared.project)) {
1992
+ const renderer = resolveTileflowRenderer(options.renderer);
1993
+ if (renderer === "native" && options.inspection) {
1994
+ throw new TileflowNativeCompatibilityError5([
1995
+ createTileflowNativeDiagnostic4("NATIVE_RENDERER_UNSUPPORTED", "/inspection")
1996
+ ]);
1997
+ }
1998
+ if (renderer === "native") {
1999
+ assertTileflowNativeCompiledStyles(prepared.project, {});
2000
+ } else if (options.target === "production" && hasLocalTilesetSources(prepared.project)) {
907
2001
  throw new TileflowLocalTilesetProductionError();
908
2002
  }
909
2003
  const inspected = options.inspection ? createTileflowStylesWithInspection(prepared.project, {
910
2004
  apiBaseUrl: options.apiBaseUrl,
911
2005
  mapAssets: prepared.mapAssets
912
2006
  }) : void 0;
913
- const compiledStyles = inspected?.styles ?? createTileflowStyles(prepared.project, {
2007
+ const compileStyles = renderer === "native" ? createStylesFromCatalog : createTileflowStyles;
2008
+ const compiledStyles = inspected?.styles ?? compileStyles(prepared.project, {
914
2009
  apiBaseUrl: options.apiBaseUrl,
915
2010
  mapAssets: prepared.mapAssets
916
2011
  });
917
- const localTilesets = await prepareTileflowLocalTilesets(prepared.project, compiledStyles, {
918
- assetBaseUrl: resolveAssetBaseUrl(options),
2012
+ const nativeLowering = renderer === "native" ? lowerTileflowNativeCompiledStyles(compiledStyles) : void 0;
2013
+ const rendererStyles = nativeLowering?.styles ?? compiledStyles;
2014
+ if (renderer === "native") assertTileflowNativeCompiledStyles(prepared.project, rendererStyles);
2015
+ const localTilesets = await prepareTileflowLocalTilesets(prepared.project, rendererStyles, {
2016
+ assetBaseUrl: resolveRendererAssetBaseUrl(options),
919
2017
  baseDirectory: prepared.baseDirectory,
920
2018
  cwd: prepared.cwd
921
2019
  });
922
2020
  try {
923
2021
  const preparedFonts = await prepareTileflowStyleFonts(prepared.project, localTilesets.styles, {
924
- assetBaseUrl: resolveAssetBaseUrl(options),
2022
+ assetBaseUrl: resolveRendererAssetBaseUrl(options),
925
2023
  baseDirectory: prepared.baseDirectory,
926
2024
  cwd: prepared.cwd,
927
2025
  target: "local"
2026
+ }).catch((error) => {
2027
+ if (renderer === "native" && error instanceof TileflowFontCompilationError) {
2028
+ throw new TileflowNativeCompatibilityError5([
2029
+ createTileflowNativeDiagnostic4("NATIVE_FONT_UNAVAILABLE", "/fonts")
2030
+ ]);
2031
+ }
2032
+ throw error;
928
2033
  });
929
- const styles = preparedFonts.styles;
930
2034
  const assets = [...prepared.assets, ...preparedFonts.assets];
2035
+ const styles = renderer === "native" ? prepareTileflowNativeStyles(preparedFonts.styles, assets) : preparedFonts.styles;
931
2036
  const provenance = await createTileflowBuildProvenance(prepared.cwd);
932
2037
  const buildManifest = await createTileflowMapBuildManifest(
933
2038
  Object.fromEntries(
@@ -948,7 +2053,8 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
948
2053
  map,
949
2054
  sourceAssets: {
950
2055
  fonts: preparedFonts.sourceIdentities[mapName] ?? [],
951
- icons: prepared.mapIconSources[mapName] ?? []
2056
+ icons: prepared.mapIconSources[mapName] ?? [],
2057
+ ...prepared.mapIconCompositions[mapName] ? { iconComposition: prepared.mapIconCompositions[mapName] } : {}
952
2058
  },
953
2059
  styles: style
954
2060
  }
@@ -957,7 +2063,9 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
957
2063
  ),
958
2064
  { provenance }
959
2065
  );
960
- const stableManifest = createFontAwareManifest(
2066
+ const stableManifest = renderer === "native" ? createManifest(prepared.project, {
2067
+ styleBaseUrl: nativePublicBase(options.styleBaseUrl ?? ".")
2068
+ }) : createFontAwareManifest(
961
2069
  createManifest(prepared.project, { styleBaseUrl: options.styleBaseUrl }),
962
2070
  styles
963
2071
  );
@@ -966,12 +2074,20 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
966
2074
  [...prepared.watchPaths, ...preparedFonts.watchPaths].map(canonicalInputPath)
967
2075
  ),
968
2076
  files: uniqueStrings(
969
- [...options.inputFiles ?? [], ...localTilesets.watchPaths].map(canonicalInputPath)
2077
+ [...options.inputFiles ?? [], ...prepared.watchFiles, ...localTilesets.watchPaths].map(
2078
+ canonicalInputPath
2079
+ )
970
2080
  )
971
2081
  };
972
2082
  const partial = {
973
2083
  assets,
974
2084
  buildManifest,
2085
+ ...renderer === "native" ? {
2086
+ nativeBuild: createTileflowNativeBuildRecord2(
2087
+ hashBytes(serializeCanonicalJson4(buildManifest)),
2088
+ nativeLowering.transformations
2089
+ )
2090
+ } : {},
975
2091
  dispose: localTilesets.dispose,
976
2092
  manifest: stableManifest,
977
2093
  project: prepared.project,
@@ -981,7 +2097,8 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
981
2097
  watchPaths: uniqueStrings([...inputs.files, ...inputs.directories])
982
2098
  };
983
2099
  const generation = hashArtifactGeneration(getStableTileflowArtifactFiles(partial));
984
- const manifest = createGenerationManifest(stableManifest, generation, assets);
2100
+ const generatedManifest = createGenerationManifest(stableManifest, generation, assets);
2101
+ const manifest = renderer === "native" ? parseTileflowRuntimeManifest(generatedManifest) : generatedManifest;
985
2102
  const generationArtifacts = { ...partial, manifest };
986
2103
  return {
987
2104
  ...generationArtifacts,
@@ -997,6 +2114,7 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
997
2114
  }
998
2115
  }
999
2116
  async function createTileflowBuildArtifacts(options = {}) {
2117
+ resolveTileflowRenderer(options.renderer);
1000
2118
  const loaded = await loadValidTileflowConfigWithInputs(
1001
2119
  options.config ?? defaultTileflowConfigPath,
1002
2120
  {
@@ -1005,9 +2123,10 @@ async function createTileflowBuildArtifacts(options = {}) {
1005
2123
  }
1006
2124
  );
1007
2125
  const prepared = await prepareTileflowCatalogIcons(loaded.project, {
1008
- assetBaseUrl: resolveAssetBaseUrl(options),
2126
+ assetBaseUrl: resolveRendererAssetBaseUrl(options),
1009
2127
  baseDirectory: dirname2(loaded.configFile),
1010
- cwd: options.cwd ?? process.cwd()
2128
+ cwd: options.cwd ?? process.cwd(),
2129
+ ...options.icons ? { icons: options.icons } : {}
1011
2130
  });
1012
2131
  return await createTileflowArtifactPlan(prepared, { ...options, inputFiles: loaded.inputFiles });
1013
2132
  }
@@ -1022,7 +2141,7 @@ function getStableTileflowArtifactFiles(artifacts) {
1022
2141
  {
1023
2142
  contentType: "application/json; charset=utf-8",
1024
2143
  fileName: tileflowMapBuildManifestFileName,
1025
- source: `${serializeCanonicalJson(artifacts.buildManifest)}
2144
+ source: `${serializeCanonicalJson4(artifacts.buildManifest)}
1026
2145
  `
1027
2146
  },
1028
2147
  {
@@ -1039,7 +2158,15 @@ function getStableTileflowArtifactFiles(artifacts) {
1039
2158
  `
1040
2159
  }))
1041
2160
  ),
1042
- ...artifacts.assets
2161
+ ...artifacts.assets,
2162
+ ...artifacts.nativeBuild ? [
2163
+ {
2164
+ contentType: "application/json; charset=utf-8",
2165
+ fileName: tileflowNativeBuildRecordFileName,
2166
+ source: `${serializeCanonicalJson4(artifacts.nativeBuild)}
2167
+ `
2168
+ }
2169
+ ] : []
1043
2170
  ];
1044
2171
  return validateArtifactFiles(files);
1045
2172
  }
@@ -1103,10 +2230,13 @@ function createGenerationArtifactFiles(artifacts, generation) {
1103
2230
  // Every theme in one logical map shares the same immutable sprite closure.
1104
2231
  sprite: isRelativePublicUrl(sprite) ? sprite : insertGenerationInPublicUrl(sprite, `icons/${mapName}/sprite`, generation)
1105
2232
  } : style;
1106
- const generationStyle = replaceTileflowStyleFontSources(
2233
+ const replaceFontSources = artifacts.nativeBuild ? replaceTileflowNativeFontSources : replaceTileflowStyleFontSources;
2234
+ const generationStyle = replaceFontSources(
1107
2235
  generationStyleWithSprite,
1108
2236
  (source) => retargetLocalFontAssetUrl(source, artifacts.assets, generation)
1109
2237
  );
2238
+ if (artifacts.nativeBuild)
2239
+ assertTileflowNativeGeneratedStyle(generationStyle, mapName, themeName);
1110
2240
  return {
1111
2241
  contentType: "application/json; charset=utf-8",
1112
2242
  fileName: `${prefix}/styles/${mapName}/${themeName}.json`,
@@ -1132,7 +2262,7 @@ function createFontAwareManifest(manifest, styles) {
1132
2262
  if (!style) {
1133
2263
  throw new Error(`Missing compiled Tileflow style for ${mapName}/${themeName}.`);
1134
2264
  }
1135
- const definitions = getTileflowStyleFontFaces(style);
2265
+ const definitions = getTileflowStyleFontFaces2(style);
1136
2266
  return [themeName, { ...theme, fontFaces: definitions }];
1137
2267
  })
1138
2268
  )
@@ -1162,7 +2292,7 @@ function getMapBuildAssets(mapName, assets, fontAssets) {
1162
2292
  );
1163
2293
  }
1164
2294
  function hashBytes(source) {
1165
- return createHash("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
2295
+ return createHash3("sha256").update(typeof source === "string" ? Buffer.from(source, "utf8") : Buffer.from(source)).digest("hex");
1166
2296
  }
1167
2297
  async function createTileflowBuildProvenance(cwd) {
1168
2298
  const projectDirectory = resolve3(cwd);
@@ -1297,7 +2427,8 @@ async function disposeTileflowBuildArtifacts(artifacts) {
1297
2427
  async function writeTileflowArtifactPlan(artifacts, options) {
1298
2428
  assertNoLocalTilesetsForProduction(artifacts);
1299
2429
  const cwd = options.cwd ?? process.cwd();
1300
- const output = await prepareTileflowOutputDirectory(cwd, options.outDir);
2430
+ const outDir = artifacts.nativeBuild ? `${options.outDir.replace(/[\\/]$/u, "")}/native` : options.outDir;
2431
+ const output = await prepareTileflowOutputDirectory(cwd, outDir);
1301
2432
  const files = getTileflowArtifactFiles(artifacts);
1302
2433
  await assertTileflowSelfHostedManifestTarget(resolve3(output.outDir, "manifest.json"), options);
1303
2434
  await commitTileflowArtifactFiles(output, files);
@@ -1584,7 +2715,7 @@ function assertTileflowArtifactFileName(fileName) {
1584
2715
  const logicalName = fileName.replace(/^generations\/[a-f0-9]{64}\//, "");
1585
2716
  const styleMatch = /^styles\/([^/]+)\/([^/]+)\.json$/u.exec(logicalName);
1586
2717
  const iconMatch = /^icons\/([^/]+)\/sprite(?:@2x)?\.(?:json|png)$/u.exec(logicalName);
1587
- if (fileName === "manifest.json" || fileName === tileflowMapBuildManifestFileName || styleMatch && tileflowMapIdSchema.safeParse(styleMatch[1]).success && tileflowThemeNameSchema.safeParse(styleMatch[2]).success || iconMatch && tileflowMapIdSchema.safeParse(iconMatch[1]).success || /^tilesets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.pmtiles$/u.test(logicalName) || /^fonts\/[a-z0-9]+(?:-[a-z0-9]+)*-[a-f0-9]{64}\.(?:otf|ttf|woff2)$/.test(logicalName) || /^fonts\/licenses\/license-[a-f0-9]{64}\.txt$/.test(logicalName)) {
2718
+ 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 || /^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)) {
1588
2719
  return;
1589
2720
  }
1590
2721
  throw new Error(`Unexpected Tileflow managed artifact file name: ${fileName}`);
@@ -1624,7 +2755,7 @@ async function removeManagedFile(path) {
1624
2755
  }
1625
2756
  }
1626
2757
  function hashArtifactGeneration(files) {
1627
- const hash = createHash("sha256");
2758
+ const hash = createHash3("sha256");
1628
2759
  for (const file of files) {
1629
2760
  const source = typeof file.source === "string" ? Buffer.from(file.source, "utf8") : Buffer.from(file.source);
1630
2761
  hash.update(`${file.fileName}\0${file.contentType}\0${source.byteLength}\0`, "utf8");
@@ -1632,6 +2763,13 @@ function hashArtifactGeneration(files) {
1632
2763
  }
1633
2764
  return hash.digest("hex");
1634
2765
  }
2766
+ function resolveRendererAssetBaseUrl(options) {
2767
+ const base = resolveAssetBaseUrl(options);
2768
+ return options.renderer === "native" ? nativePublicBase(base) : base;
2769
+ }
2770
+ function nativePublicBase(base) {
2771
+ return isRelativePublicUrl(base) ? base : `${base.replace(/\/+$/u, "")}/native`;
2772
+ }
1635
2773
  function resolveAssetBaseUrl(options) {
1636
2774
  if (options.assetBaseUrl !== void 0) return options.assetBaseUrl;
1637
2775
  if (options.styleBaseUrl && !isRelativePublicUrl(options.styleBaseUrl)) {
@@ -1665,6 +2803,7 @@ export {
1665
2803
  prepareTileflowLocalTilesets,
1666
2804
  tileflowArtifactSessionSchemaVersion,
1667
2805
  createTileflowArtifactDiagnostics,
2806
+ prepareTileflowHostedNativeDeployment,
1668
2807
  tileflowArtifactPlanSchemaVersion,
1669
2808
  tileflowArtifactInventoryFileName,
1670
2809
  TileflowHostedManifestOverwriteError,