@tileflow/core 0.1.0-alpha.47 → 0.1.0-alpha.48
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 +33 -314
- package/REFERENCE.md +379 -0
- package/dist/browser.js +6796 -6795
- package/dist/build.d.ts +4 -4
- package/dist/build.js +175 -88
- package/dist/config-reference.json +245 -162
- package/dist/{icon-composition-CInvkr7s.d.ts → icon-composition-2nsUbE87.d.ts} +4 -50
- package/dist/index.d.ts +16 -6
- package/dist/index.js +281 -4
- package/dist/{manifest-types-CcD4OQkJ.d.ts → manifest-types-U7JPDNAo.d.ts} +128 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/native.d.ts +99 -1
- package/dist/native.js +242 -4
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +281 -47
- package/package.json +2 -1
package/dist/runtime.js
CHANGED
|
@@ -488,6 +488,18 @@ function planTileflowThemeBlend(styles, options = {}) {
|
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
}
|
|
491
|
+
function mixTileflowColours(from, to, t) {
|
|
492
|
+
const a = oklab(from);
|
|
493
|
+
const b = oklab(to);
|
|
494
|
+
if (!a || !b) return void 0;
|
|
495
|
+
const alpha = a[3] + (b[3] - a[3]) * t;
|
|
496
|
+
if (alpha <= 0) return "rgba(0, 0, 0, 0)";
|
|
497
|
+
const channels = a.slice(0, 3).map((value, channel) => {
|
|
498
|
+
const from2 = value * a[3];
|
|
499
|
+
return (from2 + (b[channel] * b[3] - from2) * t) / alpha;
|
|
500
|
+
});
|
|
501
|
+
return colourFromOklab([...channels, alpha]);
|
|
502
|
+
}
|
|
491
503
|
function fillLayer(layer, position, dominant, layerSwitches) {
|
|
492
504
|
const filled = { ...layer };
|
|
493
505
|
for (const group of ["layout", "paint"]) {
|
|
@@ -531,10 +543,7 @@ function mixLeaf(values, position) {
|
|
|
531
543
|
if (t <= 0 || from === to) return from;
|
|
532
544
|
if (t >= 1) return to;
|
|
533
545
|
if (typeof from === "number" && typeof to === "number") return from + (to - from) * t;
|
|
534
|
-
|
|
535
|
-
const b = oklab(String(to));
|
|
536
|
-
if (!a || !b) return t < 0.5 ? from : to;
|
|
537
|
-
return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
|
|
546
|
+
return mixTileflowColours(String(from), String(to), t) ?? (t < 0.5 ? from : to);
|
|
538
547
|
}
|
|
539
548
|
function clampPosition(position, themes) {
|
|
540
549
|
if (!Number.isFinite(position)) return 0;
|
|
@@ -3253,49 +3262,6 @@ function fail(message) {
|
|
|
3253
3262
|
throw new TypeError(`Tileflow style family: ${message}`);
|
|
3254
3263
|
}
|
|
3255
3264
|
|
|
3256
|
-
// src/style-family-loader.ts
|
|
3257
|
-
async function loadTileflowStyleFamily(url, options = {}) {
|
|
3258
|
-
const request = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3259
|
-
const response = await request(url, { credentials: "same-origin", signal: options.signal });
|
|
3260
|
-
if (!response.ok) throw new Error(`Tileflow style family request failed: ${response.status}`);
|
|
3261
|
-
const maximum = tileflowStyleFamilyLimits.documentBytes;
|
|
3262
|
-
const length = response.headers.get("content-length");
|
|
3263
|
-
if (length && Number(length) > maximum) {
|
|
3264
|
-
await response.body?.cancel();
|
|
3265
|
-
throw new TypeError("Tileflow style family is too large.");
|
|
3266
|
-
}
|
|
3267
|
-
const reader = response.body?.getReader();
|
|
3268
|
-
if (!reader) throw new TypeError("Tileflow style family response has no body.");
|
|
3269
|
-
const chunks = [];
|
|
3270
|
-
let total = 0;
|
|
3271
|
-
try {
|
|
3272
|
-
for (; ; ) {
|
|
3273
|
-
const { done, value } = await reader.read();
|
|
3274
|
-
if (done) break;
|
|
3275
|
-
total += value.byteLength;
|
|
3276
|
-
if (total > maximum) {
|
|
3277
|
-
await reader.cancel();
|
|
3278
|
-
throw new TypeError("Tileflow style family is too large.");
|
|
3279
|
-
}
|
|
3280
|
-
chunks.push(value);
|
|
3281
|
-
}
|
|
3282
|
-
} finally {
|
|
3283
|
-
reader.releaseLock();
|
|
3284
|
-
}
|
|
3285
|
-
const bytes2 = new Uint8Array(total);
|
|
3286
|
-
let offset = 0;
|
|
3287
|
-
for (const chunk of chunks) {
|
|
3288
|
-
bytes2.set(chunk, offset);
|
|
3289
|
-
offset += chunk.length;
|
|
3290
|
-
}
|
|
3291
|
-
return parseTileflowStyleFamily(
|
|
3292
|
-
JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2))
|
|
3293
|
-
);
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
// src/color-program-runtime.ts
|
|
3297
|
-
init_color_program();
|
|
3298
|
-
|
|
3299
3265
|
// src/themes/index.ts
|
|
3300
3266
|
init_color_program();
|
|
3301
3267
|
init_model();
|
|
@@ -3362,7 +3328,271 @@ function rgbToHex(r, g, b) {
|
|
|
3362
3328
|
return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
|
|
3363
3329
|
}
|
|
3364
3330
|
|
|
3331
|
+
// src/overlays.ts
|
|
3332
|
+
init_portable_identity_rules();
|
|
3333
|
+
var tileflowOverlayPlacements = [
|
|
3334
|
+
"above-water",
|
|
3335
|
+
"below-roads",
|
|
3336
|
+
"above-roads",
|
|
3337
|
+
"above-buildings",
|
|
3338
|
+
"below-labels",
|
|
3339
|
+
"above-labels"
|
|
3340
|
+
];
|
|
3341
|
+
var tileflowClusterMetadataKey = "tileflow:cluster";
|
|
3342
|
+
function geojsonSource(options = {}) {
|
|
3343
|
+
const data = options.data ?? { features: [], type: "FeatureCollection" };
|
|
3344
|
+
if (typeof data === "string") {
|
|
3345
|
+
if (!isHttpsUrl(data)) {
|
|
3346
|
+
throw new Error("Tileflow GeoJSON source URLs must be absolute https:// URLs.");
|
|
3347
|
+
}
|
|
3348
|
+
} else if (!isPlainRecord3(data) || data.type !== "FeatureCollection" || !Array.isArray(data.features)) {
|
|
3349
|
+
throw new Error("Tileflow GeoJSON source data must be a FeatureCollection or an https:// URL.");
|
|
3350
|
+
}
|
|
3351
|
+
assertClusterOptions(options);
|
|
3352
|
+
const attribution = options.attribution?.trim();
|
|
3353
|
+
return Object.freeze({
|
|
3354
|
+
...attribution ? { attribution } : {},
|
|
3355
|
+
...pickClusterOptions(options),
|
|
3356
|
+
data: typeof data === "string" ? data : Object.freeze(cloneJson2(data)),
|
|
3357
|
+
kind: "geojson"
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
function maplibreOverlay(options) {
|
|
3361
|
+
if (!isTileflowPortableId(options.source)) {
|
|
3362
|
+
throw new Error(`Invalid Tileflow overlay source ID: ${options.source}.`);
|
|
3363
|
+
}
|
|
3364
|
+
if (!tileflowOverlayPlacements.includes(options.placement)) {
|
|
3365
|
+
throw new Error(`Invalid Tileflow overlay placement: ${String(options.placement)}.`);
|
|
3366
|
+
}
|
|
3367
|
+
if (!Array.isArray(options.layers) || options.layers.length < 1) {
|
|
3368
|
+
throw new Error("Tileflow MapLibre overlays require at least one layer.");
|
|
3369
|
+
}
|
|
3370
|
+
const layers = options.layers.map((layer, index) => {
|
|
3371
|
+
if (!isPlainRecord3(layer)) {
|
|
3372
|
+
throw new Error(`Tileflow overlay layer ${index} must be a plain object.`);
|
|
3373
|
+
}
|
|
3374
|
+
if (!isTileflowPortableId(layer.id)) {
|
|
3375
|
+
throw new Error(`Invalid Tileflow overlay layer ID: ${String(layer.id)}.`);
|
|
3376
|
+
}
|
|
3377
|
+
if (layer.id === "tileflow" || layer.id.startsWith("tileflow-")) {
|
|
3378
|
+
throw new Error(`Tileflow overlay layer ID is reserved: ${layer.id}.`);
|
|
3379
|
+
}
|
|
3380
|
+
if (typeof layer.type !== "string" || !layer.type) {
|
|
3381
|
+
throw new Error(`Tileflow overlay layer ${layer.id} requires a MapLibre type.`);
|
|
3382
|
+
}
|
|
3383
|
+
if (Object.hasOwn(layer, "source")) {
|
|
3384
|
+
throw new Error(
|
|
3385
|
+
`Tileflow overlay layer ${layer.id} inherits its source from the overlay and must not declare source.`
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
return Object.freeze(cloneJson2(layer));
|
|
3389
|
+
});
|
|
3390
|
+
return Object.freeze({
|
|
3391
|
+
kind: "maplibre-overlay",
|
|
3392
|
+
layers: Object.freeze(layers),
|
|
3393
|
+
placement: options.placement,
|
|
3394
|
+
source: options.source
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
function clusterOverlay(options) {
|
|
3398
|
+
const colors = options.colors ?? ["#2563eb", "#7c3aed", "#db2777"];
|
|
3399
|
+
const radii = options.radii ?? [16, 22, 30];
|
|
3400
|
+
const [low, high] = options.steps ?? [100, 750];
|
|
3401
|
+
if (!(low > 0 && high > low) || !radii.every((radius) => radius > 0)) {
|
|
3402
|
+
throw new Error("Tileflow cluster steps must increase and radii must be positive.");
|
|
3403
|
+
}
|
|
3404
|
+
const metadata = (role) => ({
|
|
3405
|
+
[tileflowClusterMetadataKey]: {
|
|
3406
|
+
expandOnClick: options.expandOnClick ?? true,
|
|
3407
|
+
role
|
|
3408
|
+
}
|
|
3409
|
+
});
|
|
3410
|
+
const step = (values) => [
|
|
3411
|
+
"step",
|
|
3412
|
+
["get", "point_count"],
|
|
3413
|
+
values[0],
|
|
3414
|
+
low,
|
|
3415
|
+
values[1],
|
|
3416
|
+
high,
|
|
3417
|
+
values[2]
|
|
3418
|
+
];
|
|
3419
|
+
return maplibreOverlay({
|
|
3420
|
+
layers: [
|
|
3421
|
+
{
|
|
3422
|
+
filter: ["has", "point_count"],
|
|
3423
|
+
id: `${options.id}-clusters`,
|
|
3424
|
+
metadata: metadata("cluster"),
|
|
3425
|
+
paint: {
|
|
3426
|
+
"circle-color": step(colors),
|
|
3427
|
+
"circle-radius": step(radii),
|
|
3428
|
+
"circle-stroke-color": "#ffffff",
|
|
3429
|
+
"circle-stroke-width": 2
|
|
3430
|
+
},
|
|
3431
|
+
type: "circle"
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
filter: ["has", "point_count"],
|
|
3435
|
+
id: `${options.id}-cluster-count`,
|
|
3436
|
+
layout: {
|
|
3437
|
+
"text-allow-overlap": true,
|
|
3438
|
+
"text-field": ["get", "point_count_abbreviated"],
|
|
3439
|
+
...options.font ? { "text-font": [...options.font] } : {},
|
|
3440
|
+
"text-ignore-placement": true,
|
|
3441
|
+
"text-size": 13
|
|
3442
|
+
},
|
|
3443
|
+
metadata: metadata("count"),
|
|
3444
|
+
paint: { "text-color": options.textColor ?? "#ffffff" },
|
|
3445
|
+
type: "symbol"
|
|
3446
|
+
},
|
|
3447
|
+
{
|
|
3448
|
+
filter: ["!", ["has", "point_count"]],
|
|
3449
|
+
id: `${options.id}-points`,
|
|
3450
|
+
metadata: metadata("point"),
|
|
3451
|
+
paint: {
|
|
3452
|
+
"circle-color": options.pointColor ?? colors[0],
|
|
3453
|
+
"circle-radius": 6,
|
|
3454
|
+
"circle-stroke-color": "#ffffff",
|
|
3455
|
+
"circle-stroke-width": 1.5
|
|
3456
|
+
},
|
|
3457
|
+
type: "circle"
|
|
3458
|
+
}
|
|
3459
|
+
],
|
|
3460
|
+
placement: options.placement,
|
|
3461
|
+
source: options.source
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
function heatmapOverlay(options) {
|
|
3465
|
+
const colors = options.colors ?? [
|
|
3466
|
+
[0, "rgba(33, 102, 172, 0)"],
|
|
3467
|
+
[0.2, "#67a9cf"],
|
|
3468
|
+
[0.4, "#d1e5f0"],
|
|
3469
|
+
[0.6, "#fddbc7"],
|
|
3470
|
+
[0.8, "#ef8a62"],
|
|
3471
|
+
[1, "#b2182b"]
|
|
3472
|
+
];
|
|
3473
|
+
if (colors.length < 2 || colors.some(([density], index) => {
|
|
3474
|
+
const previous = colors[index - 1]?.[0] ?? -1;
|
|
3475
|
+
return !(density >= 0 && density <= 1 && density > previous);
|
|
3476
|
+
})) {
|
|
3477
|
+
throw new Error("Tileflow heatmap colors need at least two increasing densities from 0 to 1.");
|
|
3478
|
+
}
|
|
3479
|
+
const paint = {
|
|
3480
|
+
"heatmap-color": ["interpolate", ["linear"], ["heatmap-density"], ...colors.flat()],
|
|
3481
|
+
"heatmap-intensity": options.intensity ?? 1,
|
|
3482
|
+
"heatmap-opacity": options.opacity ?? 0.8,
|
|
3483
|
+
"heatmap-radius": options.radius ?? 30,
|
|
3484
|
+
"heatmap-weight": options.weight ?? 1
|
|
3485
|
+
};
|
|
3486
|
+
return maplibreOverlay({
|
|
3487
|
+
layers: [
|
|
3488
|
+
{
|
|
3489
|
+
id: options.id,
|
|
3490
|
+
...options.maxZoom === void 0 ? {} : { maxzoom: options.maxZoom },
|
|
3491
|
+
paint,
|
|
3492
|
+
...options.sourceLayer === void 0 ? {} : { "source-layer": options.sourceLayer },
|
|
3493
|
+
type: "heatmap"
|
|
3494
|
+
}
|
|
3495
|
+
],
|
|
3496
|
+
placement: options.placement,
|
|
3497
|
+
source: options.source
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
function pickClusterOptions(source) {
|
|
3501
|
+
return {
|
|
3502
|
+
...source.cluster === void 0 ? {} : { cluster: source.cluster },
|
|
3503
|
+
...source.clusterMaxZoom === void 0 ? {} : { clusterMaxZoom: source.clusterMaxZoom },
|
|
3504
|
+
...source.clusterMinPoints === void 0 ? {} : { clusterMinPoints: source.clusterMinPoints },
|
|
3505
|
+
...source.clusterProperties === void 0 ? {} : { clusterProperties: cloneJson2(source.clusterProperties) },
|
|
3506
|
+
...source.clusterRadius === void 0 ? {} : { clusterRadius: source.clusterRadius }
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
function assertClusterOptions(options) {
|
|
3510
|
+
const { cluster, clusterMaxZoom, clusterMinPoints, clusterProperties, clusterRadius } = options;
|
|
3511
|
+
if (cluster !== void 0 && typeof cluster !== "boolean") {
|
|
3512
|
+
throw new Error("Tileflow GeoJSON cluster must be a boolean.");
|
|
3513
|
+
}
|
|
3514
|
+
if (clusterRadius !== void 0 && !(Number.isFinite(clusterRadius) && clusterRadius > 0)) {
|
|
3515
|
+
throw new Error("Tileflow clusterRadius must be a positive number of pixels.");
|
|
3516
|
+
}
|
|
3517
|
+
if (clusterMaxZoom !== void 0 && !(Number.isFinite(clusterMaxZoom) && clusterMaxZoom >= 0 && clusterMaxZoom <= 24)) {
|
|
3518
|
+
throw new Error("Tileflow clusterMaxZoom must be a zoom from 0 to 24.");
|
|
3519
|
+
}
|
|
3520
|
+
if (clusterMinPoints !== void 0 && !(Number.isInteger(clusterMinPoints) && clusterMinPoints >= 2)) {
|
|
3521
|
+
throw new Error("Tileflow clusterMinPoints must be an integer of at least 2.");
|
|
3522
|
+
}
|
|
3523
|
+
if (!cluster && [clusterMaxZoom, clusterMinPoints, clusterProperties, clusterRadius].some(
|
|
3524
|
+
(value) => value !== void 0
|
|
3525
|
+
)) {
|
|
3526
|
+
throw new Error("Tileflow cluster options require cluster: true.");
|
|
3527
|
+
}
|
|
3528
|
+
for (const [name, value] of Object.entries(clusterProperties ?? {})) {
|
|
3529
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
3530
|
+
throw new Error(`Tileflow clusterProperties.${name} must be [operator, mapExpression].`);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
function isHttpsUrl(value) {
|
|
3535
|
+
try {
|
|
3536
|
+
return new URL(value).protocol === "https:";
|
|
3537
|
+
} catch {
|
|
3538
|
+
return false;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
function cloneJson2(value) {
|
|
3542
|
+
if (Array.isArray(value)) return value.map(cloneJson2);
|
|
3543
|
+
if (!isPlainRecord3(value)) return value;
|
|
3544
|
+
return Object.fromEntries(
|
|
3545
|
+
Object.entries(value).map(([key, child]) => [key, cloneJson2(child)])
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
function isPlainRecord3(value) {
|
|
3549
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3550
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3551
|
+
return prototype === Object.prototype || prototype === null;
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
// src/style-family-loader.ts
|
|
3555
|
+
async function loadTileflowStyleFamily(url, options = {}) {
|
|
3556
|
+
const request = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3557
|
+
const response = await request(url, { credentials: "same-origin", signal: options.signal });
|
|
3558
|
+
if (!response.ok) throw new Error(`Tileflow style family request failed: ${response.status}`);
|
|
3559
|
+
const maximum = tileflowStyleFamilyLimits.documentBytes;
|
|
3560
|
+
const length = response.headers.get("content-length");
|
|
3561
|
+
if (length && Number(length) > maximum) {
|
|
3562
|
+
await response.body?.cancel();
|
|
3563
|
+
throw new TypeError("Tileflow style family is too large.");
|
|
3564
|
+
}
|
|
3565
|
+
const reader = response.body?.getReader();
|
|
3566
|
+
if (!reader) throw new TypeError("Tileflow style family response has no body.");
|
|
3567
|
+
const chunks = [];
|
|
3568
|
+
let total = 0;
|
|
3569
|
+
try {
|
|
3570
|
+
for (; ; ) {
|
|
3571
|
+
const { done, value } = await reader.read();
|
|
3572
|
+
if (done) break;
|
|
3573
|
+
total += value.byteLength;
|
|
3574
|
+
if (total > maximum) {
|
|
3575
|
+
await reader.cancel();
|
|
3576
|
+
throw new TypeError("Tileflow style family is too large.");
|
|
3577
|
+
}
|
|
3578
|
+
chunks.push(value);
|
|
3579
|
+
}
|
|
3580
|
+
} finally {
|
|
3581
|
+
reader.releaseLock();
|
|
3582
|
+
}
|
|
3583
|
+
const bytes2 = new Uint8Array(total);
|
|
3584
|
+
let offset = 0;
|
|
3585
|
+
for (const chunk of chunks) {
|
|
3586
|
+
bytes2.set(chunk, offset);
|
|
3587
|
+
offset += chunk.length;
|
|
3588
|
+
}
|
|
3589
|
+
return parseTileflowStyleFamily(
|
|
3590
|
+
JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2))
|
|
3591
|
+
);
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3365
3594
|
// src/color-program-runtime.ts
|
|
3595
|
+
init_color_program();
|
|
3366
3596
|
init_model();
|
|
3367
3597
|
function applyTileflowColorOverrides(style, overrides) {
|
|
3368
3598
|
const keys = Object.keys(overrides);
|
|
@@ -4452,18 +4682,22 @@ export {
|
|
|
4452
4682
|
applyTileflowColorOverrides,
|
|
4453
4683
|
assertValidTileflowRuntimeSource,
|
|
4454
4684
|
clearTileflowManifestCache,
|
|
4685
|
+
clusterOverlay,
|
|
4455
4686
|
createTileflowSessionController,
|
|
4456
4687
|
createTileflowSessionId,
|
|
4457
4688
|
defaultTileflowManifestUrl,
|
|
4458
4689
|
defaultTileflowRuntimeView,
|
|
4459
4690
|
expandTileflowFamilyTemplate,
|
|
4691
|
+
geojsonSource,
|
|
4460
4692
|
getTileflowAnalyticsApiUrl,
|
|
4461
4693
|
getTileflowStyleFontFaces,
|
|
4694
|
+
heatmapOverlay,
|
|
4462
4695
|
inferTileflowAnalyticsFromStyleUrl,
|
|
4463
4696
|
isTileflowTimeOfDay,
|
|
4464
4697
|
loadTileflowManifest,
|
|
4465
4698
|
loadTileflowRuntimeAppearance,
|
|
4466
4699
|
loadTileflowStyleFamily,
|
|
4700
|
+
maplibreOverlay,
|
|
4467
4701
|
materializeTileflowStyleFamily,
|
|
4468
4702
|
mergeTileflowAnalytics,
|
|
4469
4703
|
nearestTileflowBlendTheme,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.48",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Typed Tileflow config and MapLibre style compiler.",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
|
+
"REFERENCE.md",
|
|
28
29
|
"LICENSE",
|
|
29
30
|
"THIRD_PARTY_NOTICES.md"
|
|
30
31
|
],
|