@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.
- package/README.md +16 -5
- package/THIRD_PARTY_NOTICES.md +27 -9
- package/dist/artifacts.d.ts +32 -5
- package/dist/artifacts.js +4 -2
- package/dist/{chunk-MKPSQSMX.js → chunk-73YPFDPC.js} +1 -1
- package/dist/{chunk-D2QWYYIF.js → chunk-EJPVUSX4.js} +242 -22
- package/dist/{chunk-W3X2JXYS.js → chunk-QAMFAM3S.js} +1168 -35
- package/dist/{chunk-3WCEHA27.js → chunk-UCBGTEQV.js} +1 -1
- package/dist/{chunk-LNCTVFUD.js → chunk-XHJBCVBB.js} +6 -1
- package/dist/icons.d.ts +10 -2
- package/dist/icons.js +3 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +9 -5
- package/dist/inspect.js +2 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.js +3 -3
- package/dist/validation.d.ts +2 -0
- package/dist/validation.js +1 -1
- package/docs/bounded-vector-feature-inspection.md +4 -4
- package/docs/local-comparison-workbench-and-compiler-inspection.md +4 -5
- package/docs/native-artifacts.md +258 -0
- package/docs/native-icons.md +129 -0
- package/package.json +3 -3
|
@@ -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-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
723
|
-
const value = error[
|
|
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,
|
|
1691
|
+
function optionalDiagnosticField(value, key2) {
|
|
815
1692
|
if (!value || typeof value !== "object") return void 0;
|
|
816
|
-
const field = value[
|
|
1693
|
+
const field = value[key2];
|
|
817
1694
|
return typeof field === "string" && field.trim() ? boundDiagnosticField(field) : void 0;
|
|
818
1695
|
}
|
|
819
|
-
function optionalNestedDiagnosticField(value,
|
|
1696
|
+
function optionalNestedDiagnosticField(value, key2) {
|
|
820
1697
|
if (!value || typeof value !== "object") return void 0;
|
|
821
|
-
return optionalDiagnosticField(value.details,
|
|
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
|
-
|
|
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
|
|
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
|
|
920
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
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:
|
|
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: `${
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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,
|