@tileflow/dev 0.1.0-alpha.25 → 0.1.0-alpha.27

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,6 +2,7 @@ import {
2
2
  inspectTileflowPmtilesForLocalUse
3
3
  } from "./chunk-ONYQNLNA.js";
4
4
  import {
5
+ TileflowFontCompilationError,
5
6
  getTileflowFontWatchPaths,
6
7
  joinTileflowPublicUrl,
7
8
  prepareTileflowStyleFonts,
@@ -10,7 +11,7 @@ import {
10
11
  import {
11
12
  getTileflowIconWatchPaths,
12
13
  prepareTileflowCatalogIcons
13
- } from "./chunk-D2QWYYIF.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 = {
@@ -441,6 +1242,7 @@ var TileflowArtifactSessionImpl = class {
441
1242
  // Trusted shared Icon Set resolution settings, supplied once by the owning command.
442
1243
  ...options.icons ? { icons: options.icons } : {},
443
1244
  inspection: options.inspection,
1245
+ ...options.renderer === void 0 ? {} : { renderer: options.renderer },
444
1246
  styleBaseUrl: options.styleBaseUrl,
445
1247
  target: options.target
446
1248
  };
@@ -448,7 +1250,7 @@ var TileflowArtifactSessionImpl = class {
448
1250
  async start() {
449
1251
  if (this.#watchEnabled) {
450
1252
  this.#watcher = watch(dirname(this.#configPath), {
451
- awaitWriteFinish: { pollInterval: 20, stabilityThreshold: 50 },
1253
+ awaitWriteFinish: { pollInterval: 20, stabilityThreshold: writeStabilityMs },
452
1254
  followSymlinks: false,
453
1255
  ignoreInitial: true,
454
1256
  ignored: (path, stats) => this.#ignoreWatchPath(path, stats?.isDirectory()),
@@ -476,6 +1278,22 @@ var TileflowArtifactSessionImpl = class {
476
1278
  if (!this.#shouldRefreshForWatchEvent(path)) return;
477
1279
  this.#scheduleRefresh(path);
478
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
+ });
479
1297
  this.#watcher.on("error", (error) => {
480
1298
  if (this.#closed) return;
481
1299
  this.#refreshToken += 1;
@@ -484,6 +1302,19 @@ var TileflowArtifactSessionImpl = class {
484
1302
  await watcherReady;
485
1303
  }
486
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
+ }
487
1318
  }
488
1319
  getState() {
489
1320
  return this.#state;
@@ -577,6 +1408,7 @@ var TileflowArtifactSessionImpl = class {
577
1408
  this.#closed = true;
578
1409
  this.#refreshToken += 1;
579
1410
  if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
1411
+ if (this.#startupCheckTimer) clearTimeout(this.#startupCheckTimer);
580
1412
  this.#listeners.clear();
581
1413
  await this.#watcher?.close();
582
1414
  const retained = [...this.#artifactGenerations];
@@ -589,7 +1421,7 @@ var TileflowArtifactSessionImpl = class {
589
1421
  await Promise.all(retained.map((generation) => generation.completion));
590
1422
  }
591
1423
  #ignoreWatchPath(path, isDirectory) {
592
- const resolvedPath = resolve2(path);
1424
+ const resolvedPath = canonicalWatchPath(path);
593
1425
  const relativePath = relative(dirname(this.#configPath), resolvedPath);
594
1426
  const segments = relativePath.split(sep).filter(Boolean);
595
1427
  if (this.#ignoredPaths.some((path2) => isSameOrInside(path2, resolvedPath))) return true;
@@ -603,7 +1435,7 @@ var TileflowArtifactSessionImpl = class {
603
1435
  return true;
604
1436
  }
605
1437
  #shouldRefreshForWatchEvent(path) {
606
- const resolvedPath = resolve2(path);
1438
+ const resolvedPath = canonicalWatchPath(path);
607
1439
  const extension = extname(resolvedPath).toLowerCase();
608
1440
  if (!iconExtensions.has(extension) && !fontExtensions.has(extension)) return true;
609
1441
  return [...this.#watchedArtifactPaths].some((path2) => isSameOrInside(path2, resolvedPath));
@@ -631,9 +1463,20 @@ var TileflowArtifactSessionImpl = class {
631
1463
  }
632
1464
  #updateArtifactWatchPaths(paths) {
633
1465
  if (!this.#watcher) return;
634
- const next = new Set(paths.map((path) => resolve2(this.#cwd, path)));
1466
+ const next = new Set(paths.map((path) => canonicalWatchPath(resolve2(this.#cwd, path))));
635
1467
  const previous = this.#watchedArtifactPaths;
636
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
+ }
637
1480
  for (const path of previous) {
638
1481
  if (!next.has(path) && !isSameOrInside(dirname(this.#configPath), path)) {
639
1482
  void this.#watcher.unwatch(path);
@@ -688,7 +1531,41 @@ function isSameOrInside(root, candidate) {
688
1531
  const path = relative(resolve2(root), resolve2(candidate));
689
1532
  return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
690
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
+ }
691
1565
  function createTileflowArtifactDiagnostics(error, cwd) {
1566
+ if (error instanceof TileflowNativeCompatibilityError2) {
1567
+ return error.issues.map((issue) => ({ ...issue }));
1568
+ }
692
1569
  const inheritedCode = optionalDiagnosticField(error, "code");
693
1570
  const inheritedPath = optionalDiagnosticPath(error);
694
1571
  const inheritedPhase = optionalDiagnosticField(error, "phase") ?? optionalNestedDiagnosticField(error, "phase");
@@ -719,8 +1596,8 @@ function createTileflowArtifactDiagnostics(error, cwd) {
719
1596
  function getIssueList(error) {
720
1597
  if (!error || typeof error !== "object") return [];
721
1598
  const containers = [];
722
- for (const key of ["messages", "issues"]) {
723
- const value = error[key];
1599
+ for (const key2 of ["messages", "issues"]) {
1600
+ const value = error[key2];
724
1601
  if (Array.isArray(value)) containers.push(value);
725
1602
  }
726
1603
  const details = error.details;
@@ -811,14 +1688,14 @@ function normalizeDiagnostics(diagnostics) {
811
1688
  (diagnostic, index) => index === 0 || JSON.stringify(diagnostic) !== JSON.stringify(bounded[index - 1])
812
1689
  ).slice(0, 32);
813
1690
  }
814
- function optionalDiagnosticField(value, key) {
1691
+ function optionalDiagnosticField(value, key2) {
815
1692
  if (!value || typeof value !== "object") return void 0;
816
- const field = value[key];
1693
+ const field = value[key2];
817
1694
  return typeof field === "string" && field.trim() ? boundDiagnosticField(field) : void 0;
818
1695
  }
819
- function optionalNestedDiagnosticField(value, key) {
1696
+ function optionalNestedDiagnosticField(value, key2) {
820
1697
  if (!value || typeof value !== "object") return void 0;
821
- return optionalDiagnosticField(value.details, key);
1698
+ return optionalDiagnosticField(value.details, key2);
822
1699
  }
823
1700
  function boundDiagnosticField(value) {
824
1701
  return boundDiagnosticText(value.replace(/[^A-Za-z0-9_-]/g, ""), 64);
@@ -827,6 +1704,213 @@ function boundDiagnosticText(value, limit) {
827
1704
  return value.length <= limit ? value : `${value.slice(0, limit - 1)}\u2026`;
828
1705
  }
829
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
+
830
1914
  // src/artifacts.ts
831
1915
  var tileflowArtifactPlanSchemaVersion = 1;
832
1916
  var tileflowArtifactInventoryFileName = ".tileflow-artifacts.json";
@@ -905,31 +1989,50 @@ function assertValidTileflowStyles(project, styles) {
905
1989
  }
906
1990
  }
907
1991
  async function createTileflowArtifactPlan(prepared, options = {}) {
908
- 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)) {
909
2001
  throw new TileflowLocalTilesetProductionError();
910
2002
  }
911
2003
  const inspected = options.inspection ? createTileflowStylesWithInspection(prepared.project, {
912
2004
  apiBaseUrl: options.apiBaseUrl,
913
2005
  mapAssets: prepared.mapAssets
914
2006
  }) : void 0;
915
- const compiledStyles = inspected?.styles ?? createTileflowStyles(prepared.project, {
2007
+ const compileStyles = renderer === "native" ? createStylesFromCatalog : createTileflowStyles;
2008
+ const compiledStyles = inspected?.styles ?? compileStyles(prepared.project, {
916
2009
  apiBaseUrl: options.apiBaseUrl,
917
2010
  mapAssets: prepared.mapAssets
918
2011
  });
919
- const localTilesets = await prepareTileflowLocalTilesets(prepared.project, compiledStyles, {
920
- 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),
921
2017
  baseDirectory: prepared.baseDirectory,
922
2018
  cwd: prepared.cwd
923
2019
  });
924
2020
  try {
925
2021
  const preparedFonts = await prepareTileflowStyleFonts(prepared.project, localTilesets.styles, {
926
- assetBaseUrl: resolveAssetBaseUrl(options),
2022
+ assetBaseUrl: resolveRendererAssetBaseUrl(options),
927
2023
  baseDirectory: prepared.baseDirectory,
928
2024
  cwd: prepared.cwd,
929
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;
930
2033
  });
931
- const styles = preparedFonts.styles;
932
2034
  const assets = [...prepared.assets, ...preparedFonts.assets];
2035
+ const styles = renderer === "native" ? prepareTileflowNativeStyles(preparedFonts.styles, assets) : preparedFonts.styles;
933
2036
  const provenance = await createTileflowBuildProvenance(prepared.cwd);
934
2037
  const buildManifest = await createTileflowMapBuildManifest(
935
2038
  Object.fromEntries(
@@ -960,7 +2063,9 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
960
2063
  ),
961
2064
  { provenance }
962
2065
  );
963
- const stableManifest = createFontAwareManifest(
2066
+ const stableManifest = renderer === "native" ? createManifest(prepared.project, {
2067
+ styleBaseUrl: nativePublicBase(options.styleBaseUrl ?? ".")
2068
+ }) : createFontAwareManifest(
964
2069
  createManifest(prepared.project, { styleBaseUrl: options.styleBaseUrl }),
965
2070
  styles
966
2071
  );
@@ -977,6 +2082,12 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
977
2082
  const partial = {
978
2083
  assets,
979
2084
  buildManifest,
2085
+ ...renderer === "native" ? {
2086
+ nativeBuild: createTileflowNativeBuildRecord2(
2087
+ hashBytes(serializeCanonicalJson4(buildManifest)),
2088
+ nativeLowering.transformations
2089
+ )
2090
+ } : {},
980
2091
  dispose: localTilesets.dispose,
981
2092
  manifest: stableManifest,
982
2093
  project: prepared.project,
@@ -986,7 +2097,8 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
986
2097
  watchPaths: uniqueStrings([...inputs.files, ...inputs.directories])
987
2098
  };
988
2099
  const generation = hashArtifactGeneration(getStableTileflowArtifactFiles(partial));
989
- const manifest = createGenerationManifest(stableManifest, generation, assets);
2100
+ const generatedManifest = createGenerationManifest(stableManifest, generation, assets);
2101
+ const manifest = renderer === "native" ? parseTileflowRuntimeManifest(generatedManifest) : generatedManifest;
990
2102
  const generationArtifacts = { ...partial, manifest };
991
2103
  return {
992
2104
  ...generationArtifacts,
@@ -1002,6 +2114,7 @@ async function createTileflowArtifactPlan(prepared, options = {}) {
1002
2114
  }
1003
2115
  }
1004
2116
  async function createTileflowBuildArtifacts(options = {}) {
2117
+ resolveTileflowRenderer(options.renderer);
1005
2118
  const loaded = await loadValidTileflowConfigWithInputs(
1006
2119
  options.config ?? defaultTileflowConfigPath,
1007
2120
  {
@@ -1010,7 +2123,7 @@ async function createTileflowBuildArtifacts(options = {}) {
1010
2123
  }
1011
2124
  );
1012
2125
  const prepared = await prepareTileflowCatalogIcons(loaded.project, {
1013
- assetBaseUrl: resolveAssetBaseUrl(options),
2126
+ assetBaseUrl: resolveRendererAssetBaseUrl(options),
1014
2127
  baseDirectory: dirname2(loaded.configFile),
1015
2128
  cwd: options.cwd ?? process.cwd(),
1016
2129
  ...options.icons ? { icons: options.icons } : {}
@@ -1028,7 +2141,7 @@ function getStableTileflowArtifactFiles(artifacts) {
1028
2141
  {
1029
2142
  contentType: "application/json; charset=utf-8",
1030
2143
  fileName: tileflowMapBuildManifestFileName,
1031
- source: `${serializeCanonicalJson(artifacts.buildManifest)}
2144
+ source: `${serializeCanonicalJson4(artifacts.buildManifest)}
1032
2145
  `
1033
2146
  },
1034
2147
  {
@@ -1045,7 +2158,15 @@ function getStableTileflowArtifactFiles(artifacts) {
1045
2158
  `
1046
2159
  }))
1047
2160
  ),
1048
- ...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
+ ] : []
1049
2170
  ];
1050
2171
  return validateArtifactFiles(files);
1051
2172
  }
@@ -1109,10 +2230,13 @@ function createGenerationArtifactFiles(artifacts, generation) {
1109
2230
  // Every theme in one logical map shares the same immutable sprite closure.
1110
2231
  sprite: isRelativePublicUrl(sprite) ? sprite : insertGenerationInPublicUrl(sprite, `icons/${mapName}/sprite`, generation)
1111
2232
  } : style;
1112
- const generationStyle = replaceTileflowStyleFontSources(
2233
+ const replaceFontSources = artifacts.nativeBuild ? replaceTileflowNativeFontSources : replaceTileflowStyleFontSources;
2234
+ const generationStyle = replaceFontSources(
1113
2235
  generationStyleWithSprite,
1114
2236
  (source) => retargetLocalFontAssetUrl(source, artifacts.assets, generation)
1115
2237
  );
2238
+ if (artifacts.nativeBuild)
2239
+ assertTileflowNativeGeneratedStyle(generationStyle, mapName, themeName);
1116
2240
  return {
1117
2241
  contentType: "application/json; charset=utf-8",
1118
2242
  fileName: `${prefix}/styles/${mapName}/${themeName}.json`,
@@ -1138,7 +2262,7 @@ function createFontAwareManifest(manifest, styles) {
1138
2262
  if (!style) {
1139
2263
  throw new Error(`Missing compiled Tileflow style for ${mapName}/${themeName}.`);
1140
2264
  }
1141
- const definitions = getTileflowStyleFontFaces(style);
2265
+ const definitions = getTileflowStyleFontFaces2(style);
1142
2266
  return [themeName, { ...theme, fontFaces: definitions }];
1143
2267
  })
1144
2268
  )
@@ -1168,7 +2292,7 @@ function getMapBuildAssets(mapName, assets, fontAssets) {
1168
2292
  );
1169
2293
  }
1170
2294
  function hashBytes(source) {
1171
- 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");
1172
2296
  }
1173
2297
  async function createTileflowBuildProvenance(cwd) {
1174
2298
  const projectDirectory = resolve3(cwd);
@@ -1303,7 +2427,8 @@ async function disposeTileflowBuildArtifacts(artifacts) {
1303
2427
  async function writeTileflowArtifactPlan(artifacts, options) {
1304
2428
  assertNoLocalTilesetsForProduction(artifacts);
1305
2429
  const cwd = options.cwd ?? process.cwd();
1306
- 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);
1307
2432
  const files = getTileflowArtifactFiles(artifacts);
1308
2433
  await assertTileflowSelfHostedManifestTarget(resolve3(output.outDir, "manifest.json"), options);
1309
2434
  await commitTileflowArtifactFiles(output, files);
@@ -1590,7 +2715,7 @@ function assertTileflowArtifactFileName(fileName) {
1590
2715
  const logicalName = fileName.replace(/^generations\/[a-f0-9]{64}\//, "");
1591
2716
  const styleMatch = /^styles\/([^/]+)\/([^/]+)\.json$/u.exec(logicalName);
1592
2717
  const iconMatch = /^icons\/([^/]+)\/sprite(?:@2x)?\.(?:json|png)$/u.exec(logicalName);
1593
- 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)) {
1594
2719
  return;
1595
2720
  }
1596
2721
  throw new Error(`Unexpected Tileflow managed artifact file name: ${fileName}`);
@@ -1630,7 +2755,7 @@ async function removeManagedFile(path) {
1630
2755
  }
1631
2756
  }
1632
2757
  function hashArtifactGeneration(files) {
1633
- const hash = createHash("sha256");
2758
+ const hash = createHash3("sha256");
1634
2759
  for (const file of files) {
1635
2760
  const source = typeof file.source === "string" ? Buffer.from(file.source, "utf8") : Buffer.from(file.source);
1636
2761
  hash.update(`${file.fileName}\0${file.contentType}\0${source.byteLength}\0`, "utf8");
@@ -1638,6 +2763,13 @@ function hashArtifactGeneration(files) {
1638
2763
  }
1639
2764
  return hash.digest("hex");
1640
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
+ }
1641
2773
  function resolveAssetBaseUrl(options) {
1642
2774
  if (options.assetBaseUrl !== void 0) return options.assetBaseUrl;
1643
2775
  if (options.styleBaseUrl && !isRelativePublicUrl(options.styleBaseUrl)) {
@@ -1671,6 +2803,7 @@ export {
1671
2803
  prepareTileflowLocalTilesets,
1672
2804
  tileflowArtifactSessionSchemaVersion,
1673
2805
  createTileflowArtifactDiagnostics,
2806
+ prepareTileflowHostedNativeDeployment,
1674
2807
  tileflowArtifactPlanSchemaVersion,
1675
2808
  tileflowArtifactInventoryFileName,
1676
2809
  TileflowHostedManifestOverwriteError,