@tokenoftrust/storefront-runner 2.0.49 → 2.0.51
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.51",
|
|
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,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ident, parse, string as cssString, tokenize, tokenNames, url as cssUrl } from "css-tree";
|
|
2
2
|
import { parseFragment, type DefaultTreeAdapterMap } from "parse5";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -394,64 +394,46 @@ function rememberCssLocator(refs: ExtractedReference[], locator: string): boolea
|
|
|
394
394
|
return true;
|
|
395
395
|
}
|
|
396
396
|
|
|
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;
|
|
397
|
+
type CssScanNode = {
|
|
398
|
+
type: string;
|
|
399
|
+
name?: string;
|
|
400
|
+
value?: string;
|
|
401
|
+
property?: string;
|
|
402
|
+
children?: CssScanNode[];
|
|
403
|
+
block?: CssScanNode | null;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
function cssChildren(node: CssScanNode): CssScanNode[] {
|
|
407
|
+
return node.children ?? [];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function rawEscapedUrlReferences(raw: string, refs: ExtractedReference[]): void {
|
|
411
|
+
const tokens: Array<{ name: string; start: number; end: number }> = [];
|
|
412
|
+
tokenize(raw, (type, start, end) => tokens.push({ name: tokenNames[type] ?? "", start, end }));
|
|
413
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
414
|
+
const token = tokens[index]!;
|
|
415
|
+
const rawName = raw.slice(token.start, token.end - 1);
|
|
416
|
+
if (token.name !== "function-token" || !rawName.includes("\\") || ident.decode(rawName).toLowerCase() !== "url") continue;
|
|
417
|
+
let depth = 1;
|
|
418
|
+
for (let next = index + 1; next < tokens.length; next++) {
|
|
419
|
+
const nextToken = tokens[next]!;
|
|
420
|
+
if (nextToken.name === "function-token" || nextToken.name === "(-token") depth++;
|
|
421
|
+
if (nextToken.name === ")-token") depth--;
|
|
422
|
+
if (depth !== 0) continue;
|
|
423
|
+
const argument = raw.slice(token.end, nextToken.start).trim();
|
|
424
|
+
const locator = argument.startsWith('"') || argument.startsWith("'")
|
|
425
|
+
? cssString.decode(argument)
|
|
426
|
+
: cssUrl.decode(`url(${argument})`);
|
|
427
|
+
rememberCssLocator(refs, locator);
|
|
428
|
+
index = next;
|
|
429
|
+
break;
|
|
436
430
|
}
|
|
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
431
|
}
|
|
449
432
|
}
|
|
450
433
|
|
|
451
434
|
function deferredFunctionName(name: string): "var" | "env" | "cross-fade" | null {
|
|
452
|
-
const normalized =
|
|
435
|
+
const normalized = name.toLowerCase();
|
|
453
436
|
if (normalized === "var" || normalized === "env") return normalized;
|
|
454
|
-
// -webkit-cross-fade is cross-fade. Any vendor prefix stays unresolved.
|
|
455
437
|
if (normalized === "cross-fade" || /^-[a-z]+-cross-fade$/.test(normalized)) return "cross-fade";
|
|
456
438
|
return null;
|
|
457
439
|
}
|
|
@@ -460,170 +442,150 @@ function rememberDeferred(refs: ExtractedReference[], name: "var" | "env" | "cro
|
|
|
460
442
|
refs.push({ locator: `css-function:${name}`, surface: "css-url", deferredFunction: name });
|
|
461
443
|
}
|
|
462
444
|
|
|
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
445
|
function adoptCssReferences(refs: ExtractedReference[], cssRefs: readonly ExtractedReference[], afterBase: boolean): void {
|
|
492
446
|
for (const ref of cssRefs) refs.push(afterBase ? { ...ref, afterBase: true } : ref);
|
|
493
447
|
}
|
|
494
448
|
|
|
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
|
-
|
|
449
|
+
/**
|
|
450
|
+
* CSS extraction uses a JavaScript parser because this module is imported by
|
|
451
|
+
* Worker routes. A native CSS binding cannot be evaluated by workerd, which
|
|
452
|
+
* would prevent every route sharing its bundle chunk from loading.
|
|
453
|
+
*/
|
|
504
454
|
function cssReferences(css: string, kind: "stylesheet" | "declaration"): ExtractedReference[] {
|
|
505
455
|
if (!css) return [];
|
|
506
456
|
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);
|
|
457
|
+
const parseValue = (value: string): CssScanNode | null => {
|
|
458
|
+
try {
|
|
459
|
+
const parsed = parse(value, { context: "value", list: false }) as unknown as CssScanNode;
|
|
460
|
+
return parsed.type === "Value" ? parsed : null;
|
|
461
|
+
} catch {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
523
464
|
};
|
|
524
|
-
const noteDeferred = (name: "var" | "env" | "cross-fade"): void => {
|
|
525
|
-
if (
|
|
465
|
+
const noteDeferred = (name: "var" | "env" | "cross-fade", imageValue: boolean): void => {
|
|
466
|
+
if (imageValue) rememberDeferred(refs, name);
|
|
526
467
|
};
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return;
|
|
468
|
+
|
|
469
|
+
// image() resolves one source. A discarded non-image argument does not consume it.
|
|
470
|
+
const scanNestedDeferred = (node: CssScanNode): void => {
|
|
471
|
+
if (node.type !== "Function") {
|
|
472
|
+
for (const child of cssChildren(node)) scanNestedDeferred(child);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const deferred = deferredFunctionName(node.name ?? "");
|
|
476
|
+
if (deferred) {
|
|
477
|
+
noteDeferred(deferred, true);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
for (const child of cssChildren(node)) scanNestedDeferred(child);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const firstImageSource = (nodes: CssScanNode[]): boolean => {
|
|
484
|
+
let chosen = false;
|
|
485
|
+
for (const node of nodes) {
|
|
486
|
+
if (chosen) {
|
|
487
|
+
scanNestedDeferred(node);
|
|
488
|
+
continue;
|
|
549
489
|
}
|
|
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);
|
|
490
|
+
if (node.type === "Url" || node.type === "String") {
|
|
491
|
+
if (rememberCssLocator(refs, node.value ?? "")) chosen = true;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (node.type === "Raw") {
|
|
495
|
+
const parsed = parseValue(node.value ?? "");
|
|
496
|
+
if (parsed && firstImageSource(cssChildren(parsed))) chosen = true;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (node.type !== "Function") continue;
|
|
500
|
+
const name = (node.name ?? "").toLowerCase();
|
|
501
|
+
const deferred = deferredFunctionName(name);
|
|
583
502
|
if (deferred) {
|
|
584
|
-
noteDeferred(deferred);
|
|
585
|
-
|
|
586
|
-
|
|
503
|
+
noteDeferred(deferred, true);
|
|
504
|
+
chosen = true;
|
|
505
|
+
continue;
|
|
587
506
|
}
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
507
|
+
if (name === "type" || name === "format") continue;
|
|
508
|
+
if (firstImageSource(cssChildren(node))) chosen = true;
|
|
509
|
+
}
|
|
510
|
+
return chosen;
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const imageSetSources = (nodes: CssScanNode[]): void => {
|
|
514
|
+
let option: CssScanNode[] = [];
|
|
515
|
+
const flush = (): void => {
|
|
516
|
+
firstImageSource(option);
|
|
517
|
+
option = [];
|
|
518
|
+
};
|
|
519
|
+
for (const node of nodes) {
|
|
520
|
+
if (node.type === "Operator" && node.value === ",") flush();
|
|
521
|
+
else option.push(node);
|
|
522
|
+
}
|
|
523
|
+
flush();
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const scanValue = (nodes: CssScanNode[], imageValue: boolean): void => {
|
|
527
|
+
for (const node of nodes) {
|
|
528
|
+
if (node.type === "Url") {
|
|
529
|
+
rememberCssLocator(refs, node.value ?? "");
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (node.type === "Raw") {
|
|
533
|
+
const parsed = parseValue(node.value ?? "");
|
|
534
|
+
if (parsed) scanValue(cssChildren(parsed), imageValue);
|
|
535
|
+
else rawEscapedUrlReferences(node.value ?? "", refs);
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (node.type !== "Function") {
|
|
539
|
+
if (node.children) scanValue(cssChildren(node), imageValue);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const name = (node.name ?? "").toLowerCase();
|
|
543
|
+
const deferred = deferredFunctionName(name);
|
|
544
|
+
if (deferred) {
|
|
545
|
+
noteDeferred(deferred, imageValue);
|
|
546
|
+
continue;
|
|
591
547
|
}
|
|
592
|
-
const name = cssFunctionName(fn.name);
|
|
593
548
|
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;
|
|
549
|
+
firstImageSource(cssChildren(node));
|
|
550
|
+
continue;
|
|
602
551
|
}
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
return;
|
|
552
|
+
if (name === "image-set" || name === "-webkit-image-set") {
|
|
553
|
+
imageSetSources(cssChildren(node));
|
|
554
|
+
continue;
|
|
607
555
|
}
|
|
608
|
-
if (name === "url"
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
556
|
+
if (name === "url") {
|
|
557
|
+
const quoted = cssChildren(node).find((child) => child.type === "String");
|
|
558
|
+
if (quoted) rememberCssLocator(refs, quoted.value ?? "");
|
|
559
|
+
continue;
|
|
611
560
|
}
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
FunctionExit() {
|
|
615
|
-
frames.pop();
|
|
616
|
-
},
|
|
561
|
+
scanValue(cssChildren(node), imageValue);
|
|
562
|
+
}
|
|
617
563
|
};
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
564
|
+
|
|
565
|
+
const scanTree = (node: CssScanNode, fontFace = false): void => {
|
|
566
|
+
if (node.type === "Atrule") {
|
|
567
|
+
if (node.block) scanTree(node.block, (node.name ?? "").toLowerCase() === "font-face");
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (node.type === "Declaration") {
|
|
571
|
+
const property = (node.property ?? "").toLowerCase();
|
|
572
|
+
if (fontFace && property === "src") return;
|
|
573
|
+
const value = node.value as unknown as CssScanNode | undefined;
|
|
574
|
+
if (value) scanValue(value.type === "Raw" ? [value] : cssChildren(value), IMAGE_VALUE_PROPERTIES.has(property));
|
|
575
|
+
return;
|
|
624
576
|
}
|
|
577
|
+
if (node.block) scanTree(node.block, fontFace);
|
|
578
|
+
for (const child of cssChildren(node)) scanTree(child, fontFace);
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
try {
|
|
582
|
+
const parsed = parse(css, {
|
|
583
|
+
context: kind === "declaration" ? "declarationList" : "stylesheet",
|
|
584
|
+
list: false,
|
|
585
|
+
}) as unknown as CssScanNode;
|
|
586
|
+
scanTree(parsed);
|
|
625
587
|
} catch {
|
|
626
|
-
|
|
588
|
+
// Invalid CSS never causes image inventory to fail open on other artifacts.
|
|
627
589
|
}
|
|
628
590
|
return refs;
|
|
629
591
|
}
|
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
|