@tokenoftrust/storefront-runner 2.0.50 → 2.0.52
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.52",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"./integration/native-ga4-sandbox-asset": "./src/integration/native-ga4-sandbox-asset.mjs"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"
|
|
23
|
+
"css-tree": "2.2.1",
|
|
24
24
|
"parse5": "^7.2.1"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"generate:managed-analytics-schemas": "node --experimental-strip-types scripts/generate-managed-analytics-api-v1-schemas.mjs"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@types/css-tree": "^3.2.0",
|
|
34
35
|
"ajv": "^8.20.0",
|
|
35
36
|
"typescript": "^5.6.3",
|
|
36
37
|
"vitest": "^2.1.8"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference path="./css-tree-worker.d.ts" />
|
|
2
|
+
import { ident, parse, string as cssString, tokenize, tokenNames, url as cssUrl } from "css-tree/dist/csstree.esm";
|
|
2
3
|
import { parseFragment, type DefaultTreeAdapterMap } from "parse5";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -394,64 +395,46 @@ function rememberCssLocator(refs: ExtractedReference[], locator: string): boolea
|
|
|
394
395
|
return true;
|
|
395
396
|
}
|
|
396
397
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
function
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
locations.add(urlLocationKey(argument.value));
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
if (argument.type === "function") {
|
|
434
|
-
suppressImageFunctionUrls(argument.value.arguments, locations, walkVariableFallbacks);
|
|
435
|
-
continue;
|
|
398
|
+
type CssScanNode = {
|
|
399
|
+
type: string;
|
|
400
|
+
name?: string;
|
|
401
|
+
value?: string;
|
|
402
|
+
property?: string;
|
|
403
|
+
children?: CssScanNode[];
|
|
404
|
+
block?: CssScanNode | null;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
function cssChildren(node: CssScanNode): CssScanNode[] {
|
|
408
|
+
return node.children ?? [];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function rawEscapedUrlReferences(raw: string, refs: ExtractedReference[]): void {
|
|
412
|
+
const tokens: Array<{ name: string; start: number; end: number }> = [];
|
|
413
|
+
tokenize(raw, (type, start, end) => tokens.push({ name: tokenNames[type] ?? "", start, end }));
|
|
414
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
415
|
+
const token = tokens[index]!;
|
|
416
|
+
const rawName = raw.slice(token.start, token.end - 1);
|
|
417
|
+
if (token.name !== "function-token" || !rawName.includes("\\") || ident.decode(rawName).toLowerCase() !== "url") continue;
|
|
418
|
+
let depth = 1;
|
|
419
|
+
for (let next = index + 1; next < tokens.length; next++) {
|
|
420
|
+
const nextToken = tokens[next]!;
|
|
421
|
+
if (nextToken.name === "function-token" || nextToken.name === "(-token") depth++;
|
|
422
|
+
if (nextToken.name === ")-token") depth--;
|
|
423
|
+
if (depth !== 0) continue;
|
|
424
|
+
const argument = raw.slice(token.end, nextToken.start).trim();
|
|
425
|
+
const locator = argument.startsWith('"') || argument.startsWith("'")
|
|
426
|
+
? cssString.decode(argument)
|
|
427
|
+
: cssUrl.decode(`url(${argument})`);
|
|
428
|
+
rememberCssLocator(refs, locator);
|
|
429
|
+
index = next;
|
|
430
|
+
break;
|
|
436
431
|
}
|
|
437
|
-
// image-set suppression stays on nested functions. image() also suppresses var()/env() fallback urls.
|
|
438
|
-
if (!walkVariableFallbacks || (argument.type !== "var" && argument.type !== "env")) continue;
|
|
439
|
-
const fallback = argument.value.fallback;
|
|
440
|
-
if (fallback) suppressImageFunctionUrls(fallback, locations, true);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/** url() nodes nested in an unparsed image-set option are not that option. Direct url() arguments stay visible. */
|
|
445
|
-
function suppressNestedFunctionUrls(nodes: readonly TokenOrValue[], locations: Set<string>): void {
|
|
446
|
-
for (const argument of nodes) {
|
|
447
|
-
if (argument.type === "function") suppressImageFunctionUrls(argument.value.arguments, locations);
|
|
448
432
|
}
|
|
449
433
|
}
|
|
450
434
|
|
|
451
435
|
function deferredFunctionName(name: string): "var" | "env" | "cross-fade" | null {
|
|
452
|
-
const normalized =
|
|
436
|
+
const normalized = name.toLowerCase();
|
|
453
437
|
if (normalized === "var" || normalized === "env") return normalized;
|
|
454
|
-
// -webkit-cross-fade is cross-fade. Any vendor prefix stays unresolved.
|
|
455
438
|
if (normalized === "cross-fade" || /^-[a-z]+-cross-fade$/.test(normalized)) return "cross-fade";
|
|
456
439
|
return null;
|
|
457
440
|
}
|
|
@@ -460,170 +443,150 @@ function rememberDeferred(refs: ExtractedReference[], name: "var" | "env" | "cro
|
|
|
460
443
|
refs.push({ locator: `css-function:${name}`, surface: "css-url", deferredFunction: name });
|
|
461
444
|
}
|
|
462
445
|
|
|
463
|
-
/** One kept locator. type(), format(), and a discarded non-image do not consume it. var(), env(), and cross-fade() consume it without walking children. Nested image() keeps its own one source. */
|
|
464
|
-
function recordImageFunctionSource(refs: ExtractedReference[], nodes: readonly TokenOrValue[]): boolean {
|
|
465
|
-
for (const argument of nodes) {
|
|
466
|
-
if (argument.type === "url") {
|
|
467
|
-
if (rememberCssLocator(refs, argument.value.url)) return true;
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
if (argument.type === "token" && argument.value.type === "string") {
|
|
471
|
-
if (rememberCssLocator(refs, String(argument.value.value))) return true;
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
// The visitor records the deferred dependency. Children are not a source.
|
|
475
|
-
if (argument.type === "var" || argument.type === "env") return true;
|
|
476
|
-
if (argument.type !== "function") continue;
|
|
477
|
-
const name = cssFunctionName(argument.value.name);
|
|
478
|
-
if (name === "url") {
|
|
479
|
-
const locator = quotedUrlFunctionLocator(argument.value.arguments);
|
|
480
|
-
if (locator != null && rememberCssLocator(refs, locator)) return true;
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
// MIME tags, not image sources. Recursing would keep "image/png" and consume the one slot.
|
|
484
|
-
if (name === "type" || name === "format") continue;
|
|
485
|
-
if (deferredFunctionName(name)) return true;
|
|
486
|
-
if (recordImageFunctionSource(refs, argument.value.arguments)) return true;
|
|
487
|
-
}
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
446
|
function adoptCssReferences(refs: ExtractedReference[], cssRefs: readonly ExtractedReference[], afterBase: boolean): void {
|
|
492
447
|
for (const ref of cssRefs) refs.push(afterBase ? { ...ref, afterBase: true } : ref);
|
|
493
448
|
}
|
|
494
449
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if (image.type !== "image-set") return;
|
|
501
|
-
for (const option of image.value.options) rememberCssImage(refs, option.image);
|
|
502
|
-
}
|
|
503
|
-
|
|
450
|
+
/**
|
|
451
|
+
* CSS extraction uses a JavaScript parser because this module is imported by
|
|
452
|
+
* Worker routes. A native CSS binding cannot be evaluated by workerd, which
|
|
453
|
+
* would prevent every route sharing its bundle chunk from loading.
|
|
454
|
+
*/
|
|
504
455
|
function cssReferences(css: string, kind: "stylesheet" | "declaration"): ExtractedReference[] {
|
|
505
456
|
if (!css) return [];
|
|
506
457
|
const refs: ExtractedReference[] = [];
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
// "deferred" is var(), env(), or cross-fade(): their children are not resolved.
|
|
515
|
-
const frames: Array<"image" | "image-set" | "deferred" | "skip"> = [];
|
|
516
|
-
let valueDeferDepth = 0;
|
|
517
|
-
const propertyStack: string[] = [];
|
|
518
|
-
const deferredOpen = (): boolean => valueDeferDepth > 0 || frames.includes("deferred");
|
|
519
|
-
const inImageValue = (): boolean => {
|
|
520
|
-
if (frames.includes("image") || frames.includes("image-set")) return true;
|
|
521
|
-
const property = propertyStack.at(-1);
|
|
522
|
-
return property != null && IMAGE_VALUE_PROPERTIES.has(property);
|
|
458
|
+
const parseValue = (value: string): CssScanNode | null => {
|
|
459
|
+
try {
|
|
460
|
+
const parsed = parse(value, { context: "value", list: false }) as unknown as CssScanNode;
|
|
461
|
+
return parsed.type === "Value" ? parsed : null;
|
|
462
|
+
} catch {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
523
465
|
};
|
|
524
|
-
const noteDeferred = (name: "var" | "env" | "cross-fade"): void => {
|
|
525
|
-
if (
|
|
466
|
+
const noteDeferred = (name: "var" | "env" | "cross-fade", imageValue: boolean): void => {
|
|
467
|
+
if (imageValue) rememberDeferred(refs, name);
|
|
526
468
|
};
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return;
|
|
469
|
+
|
|
470
|
+
// image() resolves one source. A discarded non-image argument does not consume it.
|
|
471
|
+
const scanNestedDeferred = (node: CssScanNode): void => {
|
|
472
|
+
if (node.type !== "Function") {
|
|
473
|
+
for (const child of cssChildren(node)) scanNestedDeferred(child);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const deferred = deferredFunctionName(node.name ?? "");
|
|
477
|
+
if (deferred) {
|
|
478
|
+
noteDeferred(deferred, true);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
for (const child of cssChildren(node)) scanNestedDeferred(child);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const firstImageSource = (nodes: CssScanNode[]): boolean => {
|
|
485
|
+
let chosen = false;
|
|
486
|
+
for (const node of nodes) {
|
|
487
|
+
if (chosen) {
|
|
488
|
+
scanNestedDeferred(node);
|
|
489
|
+
continue;
|
|
549
490
|
}
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
Variable() {
|
|
565
|
-
noteDeferred("var");
|
|
566
|
-
valueDeferDepth++;
|
|
567
|
-
},
|
|
568
|
-
VariableExit() {
|
|
569
|
-
valueDeferDepth--;
|
|
570
|
-
},
|
|
571
|
-
EnvironmentVariable() {
|
|
572
|
-
noteDeferred("env");
|
|
573
|
-
valueDeferDepth++;
|
|
574
|
-
},
|
|
575
|
-
EnvironmentVariableExit() {
|
|
576
|
-
valueDeferDepth--;
|
|
577
|
-
},
|
|
578
|
-
// image() stays unparsed. Quoted URL()/uRl() is a nested function, not argument type "url".
|
|
579
|
-
// image-set() with a string or image() option stays unparsed too; its string options are tokens.
|
|
580
|
-
// Lowercase var() and env() are not functions; VAR()/ENV() are. cross-fade(), including a vendor prefix, is always a function.
|
|
581
|
-
Function(fn) {
|
|
582
|
-
const deferred = deferredFunctionName(fn.name);
|
|
491
|
+
if (node.type === "Url" || node.type === "String") {
|
|
492
|
+
if (rememberCssLocator(refs, node.value ?? "")) chosen = true;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (node.type === "Raw") {
|
|
496
|
+
const parsed = parseValue(node.value ?? "");
|
|
497
|
+
if (parsed && firstImageSource(cssChildren(parsed))) chosen = true;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (node.type !== "Function") continue;
|
|
501
|
+
const name = (node.name ?? "").toLowerCase();
|
|
502
|
+
const deferred = deferredFunctionName(name);
|
|
583
503
|
if (deferred) {
|
|
584
|
-
noteDeferred(deferred);
|
|
585
|
-
|
|
586
|
-
|
|
504
|
+
noteDeferred(deferred, true);
|
|
505
|
+
chosen = true;
|
|
506
|
+
continue;
|
|
587
507
|
}
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
508
|
+
if (name === "type" || name === "format") continue;
|
|
509
|
+
if (firstImageSource(cssChildren(node))) chosen = true;
|
|
510
|
+
}
|
|
511
|
+
return chosen;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const imageSetSources = (nodes: CssScanNode[]): void => {
|
|
515
|
+
let option: CssScanNode[] = [];
|
|
516
|
+
const flush = (): void => {
|
|
517
|
+
firstImageSource(option);
|
|
518
|
+
option = [];
|
|
519
|
+
};
|
|
520
|
+
for (const node of nodes) {
|
|
521
|
+
if (node.type === "Operator" && node.value === ",") flush();
|
|
522
|
+
else option.push(node);
|
|
523
|
+
}
|
|
524
|
+
flush();
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const scanValue = (nodes: CssScanNode[], imageValue: boolean): void => {
|
|
528
|
+
for (const node of nodes) {
|
|
529
|
+
if (node.type === "Url") {
|
|
530
|
+
rememberCssLocator(refs, node.value ?? "");
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (node.type === "Raw") {
|
|
534
|
+
const parsed = parseValue(node.value ?? "");
|
|
535
|
+
if (parsed) scanValue(cssChildren(parsed), imageValue);
|
|
536
|
+
else rawEscapedUrlReferences(node.value ?? "", refs);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (node.type !== "Function") {
|
|
540
|
+
if (node.children) scanValue(cssChildren(node), imageValue);
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const name = (node.name ?? "").toLowerCase();
|
|
544
|
+
const deferred = deferredFunctionName(name);
|
|
545
|
+
if (deferred) {
|
|
546
|
+
noteDeferred(deferred, imageValue);
|
|
547
|
+
continue;
|
|
591
548
|
}
|
|
592
|
-
const name = cssFunctionName(fn.name);
|
|
593
549
|
if (name === "image") {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
frames.push("image");
|
|
599
|
-
suppressImageFunctionUrls(fn.arguments, imageFunctionUrlLocations, true);
|
|
600
|
-
recordImageFunctionSource(refs, fn.arguments);
|
|
601
|
-
return;
|
|
550
|
+
firstImageSource(cssChildren(node));
|
|
551
|
+
continue;
|
|
602
552
|
}
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
return;
|
|
553
|
+
if (name === "image-set" || name === "-webkit-image-set") {
|
|
554
|
+
imageSetSources(cssChildren(node));
|
|
555
|
+
continue;
|
|
607
556
|
}
|
|
608
|
-
if (name === "url"
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
557
|
+
if (name === "url") {
|
|
558
|
+
const quoted = cssChildren(node).find((child) => child.type === "String");
|
|
559
|
+
if (quoted) rememberCssLocator(refs, quoted.value ?? "");
|
|
560
|
+
continue;
|
|
611
561
|
}
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
FunctionExit() {
|
|
615
|
-
frames.pop();
|
|
616
|
-
},
|
|
562
|
+
scanValue(cssChildren(node), imageValue);
|
|
563
|
+
}
|
|
617
564
|
};
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
565
|
+
|
|
566
|
+
const scanTree = (node: CssScanNode, fontFace = false): void => {
|
|
567
|
+
if (node.type === "Atrule") {
|
|
568
|
+
if (node.block) scanTree(node.block, (node.name ?? "").toLowerCase() === "font-face");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (node.type === "Declaration") {
|
|
572
|
+
const property = (node.property ?? "").toLowerCase();
|
|
573
|
+
if (fontFace && property === "src") return;
|
|
574
|
+
const value = node.value as unknown as CssScanNode | undefined;
|
|
575
|
+
if (value) scanValue(value.type === "Raw" ? [value] : cssChildren(value), IMAGE_VALUE_PROPERTIES.has(property));
|
|
576
|
+
return;
|
|
624
577
|
}
|
|
578
|
+
if (node.block) scanTree(node.block, fontFace);
|
|
579
|
+
for (const child of cssChildren(node)) scanTree(child, fontFace);
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
const parsed = parse(css, {
|
|
584
|
+
context: kind === "declaration" ? "declarationList" : "stylesheet",
|
|
585
|
+
list: false,
|
|
586
|
+
}) as unknown as CssScanNode;
|
|
587
|
+
scanTree(parsed);
|
|
625
588
|
} catch {
|
|
626
|
-
|
|
589
|
+
// Invalid CSS never causes image inventory to fail open on other artifacts.
|
|
627
590
|
}
|
|
628
591
|
return refs;
|
|
629
592
|
}
|
package/pnpm-lock.runner.yaml
CHANGED
|
@@ -112,13 +112,16 @@ importers:
|
|
|
112
112
|
|
|
113
113
|
packages/public-runtime:
|
|
114
114
|
dependencies:
|
|
115
|
-
|
|
116
|
-
specifier:
|
|
117
|
-
version:
|
|
115
|
+
css-tree:
|
|
116
|
+
specifier: 2.2.1
|
|
117
|
+
version: 2.2.1
|
|
118
118
|
parse5:
|
|
119
119
|
specifier: ^7.2.1
|
|
120
120
|
version: 7.3.0
|
|
121
121
|
devDependencies:
|
|
122
|
+
'@types/css-tree':
|
|
123
|
+
specifier: ^3.2.0
|
|
124
|
+
version: 3.2.0
|
|
122
125
|
ajv:
|
|
123
126
|
specifier: ^8.20.0
|
|
124
127
|
version: 8.20.0
|
|
@@ -1916,6 +1919,9 @@ packages:
|
|
|
1916
1919
|
'@tybys/wasm-util@0.10.3':
|
|
1917
1920
|
resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
|
|
1918
1921
|
|
|
1922
|
+
'@types/css-tree@3.2.0':
|
|
1923
|
+
resolution: {integrity: sha512-J5KXmk6BFIepOT7280FdFyNs4c5fh0Uee0otjKEvzchrRs38Ii9qminqc4ds0L19X8Zd/rT+brR9jkBthygjWw==}
|
|
1924
|
+
|
|
1919
1925
|
'@types/estree-jsx@1.0.5':
|
|
1920
1926
|
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
|
1921
1927
|
|
|
@@ -5201,6 +5207,8 @@ snapshots:
|
|
|
5201
5207
|
tslib: 2.8.1
|
|
5202
5208
|
optional: true
|
|
5203
5209
|
|
|
5210
|
+
'@types/css-tree@3.2.0': {}
|
|
5211
|
+
|
|
5204
5212
|
'@types/estree-jsx@1.0.5':
|
|
5205
5213
|
dependencies:
|
|
5206
5214
|
'@types/estree': 1.0.9
|