@tokenoftrust/storefront-runner 2.1.10 → 2.2.0

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.
@@ -3,7 +3,7 @@
3
3
  * <Seo> — per-page title/meta, Open Graph, Twitter card, canonical, JSON-LD.
4
4
  * (brief §8). Tenant-aware: OG image + site name come from the resolved tenant.
5
5
  */
6
- import { articleSeoMetaTags, type ArticleMeta } from "@/lib/seo/meta.js";
6
+ import { articleSeoMetaTags, resolveOgImage, type ArticleMeta } from "@/lib/seo/meta.js";
7
7
  import { withXDefault, type Alternate } from "@/lib/seo/alternates.js";
8
8
  import { safeJsonScriptContent } from "@/lib/jsonScript";
9
9
 
@@ -64,23 +64,23 @@ const {
64
64
  const { tenant, canonicalBase } = Astro.locals;
65
65
  const canonical =
66
66
  canonicalOverride ?? new URL(path, canonicalBase + "/").toString();
67
+ // Don't double-append the brand: merchant SEO titles (title_tag) often already
68
+ // end with the store name.
69
+ const fullTitle = title.includes(tenant.displayName)
70
+ ? title
71
+ : `${title} — ${tenant.displayName}`;
72
+ const image = resolveOgImage({
73
+ ogImage, themeOgImage: tenant.theme_tokens?.brand?.ogImage ?? null, canonicalBase,
74
+ });
67
75
  const articleMeta = articleSeoMetaTags({
68
76
  ogType,
69
77
  article,
70
- imageAlt,
71
- imageWidth,
72
- imageHeight,
78
+ imageAlt: image ? imageAlt : undefined,
79
+ imageWidth: image ? imageWidth : undefined,
80
+ imageHeight: image ? imageHeight : undefined,
73
81
  twitterSite,
74
82
  twitterCreator,
75
83
  });
76
- // Don't double-append the brand: merchant SEO titles (title_tag) often already
77
- // end with the store name.
78
- const fullTitle = title.includes(tenant.displayName)
79
- ? title
80
- : `${title} — ${tenant.displayName}`;
81
- const image = ogImage
82
- ? new URL(ogImage, canonicalBase + "/").toString()
83
- : new URL(tenant.theme_tokens?.brand?.ogImage ?? "/brand/northwind-og.svg", canonicalBase + "/").toString();
84
84
 
85
85
  const graphs = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
86
86
  const alternateLinks = withXDefault(alternates);
@@ -108,12 +108,12 @@ const alternateLinks = withXDefault(alternates);
108
108
  <meta property="og:title" content={fullTitle} />
109
109
  {description && <meta property="og:description" content={description} />}
110
110
  <meta property="og:url" content={canonical} />
111
- <meta property="og:image" content={image} />
111
+ {image && <meta property="og:image" content={image} />}
112
112
 
113
113
  <meta name="twitter:card" content="summary_large_image" />
114
114
  <meta name="twitter:title" content={fullTitle} />
115
115
  {description && <meta name="twitter:description" content={description} />}
116
- <meta name="twitter:image" content={image} />
116
+ {image && <meta name="twitter:image" content={image} />}
117
117
 
118
118
  {
119
119
  articleMeta.map((t) =>
@@ -34,6 +34,20 @@ export interface ArticleSeoInput {
34
34
  twitterCreator?: string;
35
35
  }
36
36
 
37
+ /**
38
+ * The og:image/twitter:image URL, or `null` when neither the page nor the
39
+ * tenant's theme declares one. Never falls back to a platform placeholder --
40
+ * a shared image path that 404s live for every tenant that hasn't set one.
41
+ */
42
+ export function resolveOgImage(input: {
43
+ ogImage?: string;
44
+ themeOgImage?: string | null;
45
+ canonicalBase: string;
46
+ }): string | null {
47
+ const declared = input.ogImage ?? input.themeOgImage ?? null;
48
+ return declared ? new URL(declared, input.canonicalBase + "/").toString() : null;
49
+ }
50
+
37
51
  /**
38
52
  * The article-only OG/Twitter meta tags. Returns `[]` for any non-article page
39
53
  * so `article:*`, `og:image:alt`, and `twitter:site`/`creator` can never leak
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.1.10",
3
+ "version": "2.2.0",
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",
@@ -43,6 +43,17 @@ export interface PlatformHostedImageConfig {
43
43
 
44
44
  export interface ImageDependencyScanOptions {
45
45
  tenantId: string;
46
+ /**
47
+ * The tenant's own public origins (e.g. `https://tokenoftrust.com`), from declared tenant
48
+ * config -- never derived from the tenant id. An absolute URL on one of these origins is
49
+ * classified by its path exactly as a root-relative locator would be (tenant-hosted,
50
+ * platform-hosted, capture-required/local, or cross-tenant), instead of being treated as a
51
+ * third-party reference. Required (empty is valid) so every caller states its origins
52
+ * explicitly; six call sites compute finding identities and must agree, or a capture and a
53
+ * later publish scanning with different origins would diverge and the publish gate would
54
+ * refuse on an identity mismatch it can't explain.
55
+ */
56
+ tenantOrigins: readonly string[];
46
57
  platformHosted?: PlatformHostedImageConfig;
47
58
  }
48
59
 
@@ -164,6 +175,8 @@ const URL_TAB_OR_NEWLINE_RE = /[\t\n\r]/g;
164
175
  const ASCII_WHITESPACE_RE = /[\t\n\f\r ]/;
165
176
  const ASCII_WHITESPACE_RUN_RE = /[\t\n\f\r ]+/;
166
177
  const MAX_VISIBLE_FINDINGS = 100;
178
+ /** Shared artifact-scan cap for both the publish gate and the preview capture endpoint. */
179
+ export const MAX_IMAGE_SCAN_ARTIFACTS = 5000;
167
180
  const MAX_PERCENT_DECODE_PASSES = 8;
168
181
  const MAX_CATALOG_WALK_ITEMS = 1_000_000;
169
182
  const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
@@ -447,12 +460,25 @@ function adoptCssReferences(refs: ExtractedReference[], cssRefs: readonly Extrac
447
460
  for (const ref of cssRefs) refs.push(afterBase ? { ...ref, afterBase: true } : ref);
448
461
  }
449
462
 
463
+ const NO_DECLARED_CUSTOM_PROPERTIES: ReadonlySet<string> = new Set();
464
+
450
465
  /**
451
466
  * CSS extraction uses a JavaScript parser because this module is imported by
452
467
  * Worker routes. A native CSS binding cannot be evaluated by workerd, which
453
468
  * would prevent every route sharing its bundle chunk from loading.
469
+ *
470
+ * `declaredCustomProperties` names every `--foo` declared anywhere in the
471
+ * scanned artifact set (see `collectDeclaredCustomProperties`). The scanner
472
+ * already scans `url(...)` wherever it appears, including inside a custom
473
+ * property's own declaration, so a `var(--foo)` reference is only deferred
474
+ * (unresolvable statically) when `--foo` is not declared anywhere — or when
475
+ * its fallback argument itself contains no scannable image source.
454
476
  */
455
- function cssReferences(css: string, kind: "stylesheet" | "declaration"): ExtractedReference[] {
477
+ function cssReferences(
478
+ css: string,
479
+ kind: "stylesheet" | "declaration",
480
+ declaredCustomProperties: ReadonlySet<string> = NO_DECLARED_CUSTOM_PROPERTIES,
481
+ ): ExtractedReference[] {
456
482
  if (!css) return [];
457
483
  const refs: ExtractedReference[] = [];
458
484
  const parseValue = (value: string): CssScanNode | null => {
@@ -467,6 +493,28 @@ function cssReferences(css: string, kind: "stylesheet" | "declaration"): Extract
467
493
  if (imageValue) rememberDeferred(refs, name);
468
494
  };
469
495
 
496
+ // `var(--foo)`'s own declaration site already captured any image inside it (scanValue's
497
+ // Url case is unconditional); a top-level `var()` reference only needs its own deferred
498
+ // marker when `--foo` is not declared anywhere AND its fallback argument (present only
499
+ // when the parse produced [Identifier, Operator, Raw]) has no scannable image source
500
+ // either. Scoped to scanValue's top-level property-value position only: a var() nested
501
+ // inside image()/image-set() keeps the existing conservative "always defer, never resolve
502
+ // its fallback" behaviour — those constructs are rare/synthetic and untouched by this rule.
503
+ const resolveVar = (node: CssScanNode, imageValue: boolean): void => {
504
+ if (!imageValue) return;
505
+ // cssChildren() is css-tree's own iterable List, not a real array (no index access,
506
+ // no .length) — spread it once to get an indexable copy for the fixed [name, ",", fallback] shape.
507
+ const children = [...cssChildren(node)];
508
+ const propertyName = children[0]?.type === "Identifier" ? children[0].name ?? "" : "";
509
+ if (propertyName && declaredCustomProperties.has(propertyName)) return;
510
+ const rawFallback = children[2];
511
+ if (rawFallback?.type === "Raw") {
512
+ const parsedFallback = parseValue(rawFallback.value ?? "");
513
+ if (parsedFallback && firstImageSource(cssChildren(parsedFallback))) return;
514
+ }
515
+ rememberDeferred(refs, "var");
516
+ };
517
+
470
518
  // image() resolves one source. A discarded non-image argument does not consume it.
471
519
  const scanNestedDeferred = (node: CssScanNode): void => {
472
520
  if (node.type !== "Function") {
@@ -481,7 +529,7 @@ function cssReferences(css: string, kind: "stylesheet" | "declaration"): Extract
481
529
  for (const child of cssChildren(node)) scanNestedDeferred(child);
482
530
  };
483
531
 
484
- const firstImageSource = (nodes: CssScanNode[]): boolean => {
532
+ function firstImageSource(nodes: CssScanNode[]): boolean {
485
533
  let chosen = false;
486
534
  for (const node of nodes) {
487
535
  if (chosen) {
@@ -509,7 +557,7 @@ function cssReferences(css: string, kind: "stylesheet" | "declaration"): Extract
509
557
  if (firstImageSource(cssChildren(node))) chosen = true;
510
558
  }
511
559
  return chosen;
512
- };
560
+ }
513
561
 
514
562
  const imageSetSources = (nodes: CssScanNode[]): void => {
515
563
  let option: CssScanNode[] = [];
@@ -541,6 +589,10 @@ function cssReferences(css: string, kind: "stylesheet" | "declaration"): Extract
541
589
  continue;
542
590
  }
543
591
  const name = (node.name ?? "").toLowerCase();
592
+ if (name === "var") {
593
+ resolveVar(node, imageValue);
594
+ continue;
595
+ }
544
596
  const deferred = deferredFunctionName(name);
545
597
  if (deferred) {
546
598
  noteDeferred(deferred, imageValue);
@@ -706,6 +758,7 @@ function collectHtmlElement(
706
758
  sourceContext: HtmlSourceContext,
707
759
  artifactPath: string,
708
760
  inheritedBase: ResolvedBase | null,
761
+ declaredCustomProperties: ReadonlySet<string>,
709
762
  ): void {
710
763
  const tag = element.tagName;
711
764
  const html = element.namespaceURI === HTML_NAMESPACE;
@@ -717,7 +770,7 @@ function collectHtmlElement(
717
770
  if (tag === "style") return;
718
771
  const styleValue = htmlAttribute(element, "style");
719
772
  const emitStyle = (): void => {
720
- if (styleValue != null) adoptCssReferences(refs, cssReferences(styleValue, "declaration"), afterBase);
773
+ if (styleValue != null) adoptCssReferences(refs, cssReferences(styleValue, "declaration", declaredCustomProperties), afterBase);
721
774
  };
722
775
  if (!html) {
723
776
  if (element.namespaceURI === SVG_NAMESPACE && (tag === "image" || tag.toLowerCase() === "feimage")) {
@@ -732,7 +785,7 @@ function collectHtmlElement(
732
785
  if (tag === "iframe") {
733
786
  const srcdoc = htmlAttribute(element, "srcdoc");
734
787
  if (srcdoc != null && !isBlankLocator(srcdoc)) {
735
- for (const nested of htmlReferences(srcdoc, artifactPath, inheritedBase)) {
788
+ for (const nested of htmlReferences(srcdoc, artifactPath, inheritedBase, declaredCustomProperties)) {
736
789
  refs.push({
737
790
  locator: nested.locator,
738
791
  surface: nested.surface,
@@ -800,7 +853,12 @@ function collectHtmlElement(
800
853
  emitStyle();
801
854
  }
802
855
 
803
- function htmlReferences(html: string, artifactPath: string, inherited: ResolvedBase | null = null): ExtractedReference[] {
856
+ function htmlReferences(
857
+ html: string,
858
+ artifactPath: string,
859
+ inherited: ResolvedBase | null = null,
860
+ declaredCustomProperties: ReadonlySet<string> = NO_DECLARED_CUSTOM_PROPERTIES,
861
+ ): ExtractedReference[] {
804
862
  const refs: ExtractedReference[] = [];
805
863
  let baseHref: string | null = null;
806
864
  let sawBase = false;
@@ -815,7 +873,7 @@ function htmlReferences(html: string, artifactPath: string, inherited: ResolvedB
815
873
  const frame = stack.pop()!;
816
874
  const { node, inTemplate, sourceContext } = frame;
817
875
  if (isHtmlText(node)) {
818
- if (frame.cssText) adoptCssReferences(refs, cssReferences(node.value, "stylesheet"), sawBase);
876
+ if (frame.cssText) adoptCssReferences(refs, cssReferences(node.value, "stylesheet", declaredCustomProperties), sawBase);
819
877
  continue;
820
878
  }
821
879
  if (node.nodeName === "#document" || node.nodeName === "#document-fragment") {
@@ -823,7 +881,7 @@ function htmlReferences(html: string, artifactPath: string, inherited: ResolvedB
823
881
  continue;
824
882
  }
825
883
  if (!isHtmlElement(node)) continue;
826
- collectHtmlElement(node, refs, inTemplate, noteBase, sawBase, sourceContext, artifactPath, enclosingBase());
884
+ collectHtmlElement(node, refs, inTemplate, noteBase, sawBase, sourceContext, artifactPath, enclosingBase(), declaredCustomProperties);
827
885
  const childContext = childSourceContext(node.tagName, node.namespaceURI);
828
886
  if (node.tagName === "template" && node.namespaceURI === HTML_NAMESPACE && "content" in node) {
829
887
  pushHtmlChildren(stack, node.content.childNodes, true, false, childContext);
@@ -1134,15 +1192,23 @@ export function classifyImageDependency(
1134
1192
  tenantId: string;
1135
1193
  artifactPath: string;
1136
1194
  platformHosted?: PlatformHostedImageConfig;
1195
+ tenantOrigins?: readonly string[];
1137
1196
  },
1138
1197
  ): Pick<ImageDependency, "canonicalLocator" | "disposition" | "reason"> {
1139
- return classifyImageDependencyWithPrepared(locator, context, preparePlatformHostedConfig(context.platformHosted));
1198
+ return classifyImageDependencyWithPrepared(
1199
+ locator, context, preparePlatformHostedConfig(context.platformHosted), prepareTenantOrigins(context.tenantOrigins ?? []),
1200
+ );
1201
+ }
1202
+
1203
+ function prepareTenantOrigins(tenantOrigins: readonly string[]): ReadonlySet<string> {
1204
+ return new Set(tenantOrigins.map(normalizedOrigin).filter((origin): origin is string => origin != null));
1140
1205
  }
1141
1206
 
1142
1207
  function classifyParsedHttpUrl(
1143
1208
  absolute: URL,
1144
- context: { tenantId: string },
1209
+ context: { tenantId: string; artifactPath: string },
1145
1210
  platformHosted: PreparedPlatformHostedImageConfig,
1211
+ tenantOrigins: ReadonlySet<string>,
1146
1212
  ): Pick<ImageDependency, "canonicalLocator" | "disposition" | "reason"> {
1147
1213
  if (!absolute.hostname || absolute.username || absolute.password) {
1148
1214
  return { canonicalLocator: "invalid:", disposition: "security-error", reason: "malformed" };
@@ -1150,6 +1216,13 @@ function classifyParsedHttpUrl(
1150
1216
  if (absolutePathEscapes(absolute.pathname)) {
1151
1217
  return { canonicalLocator: "escape:", disposition: "security-error", reason: "tenant-escape" };
1152
1218
  }
1219
+ if (tenantOrigins.has(absolute.origin) && !absolute.pathname.startsWith("//")) {
1220
+ // The tenant's own origin: classify by path exactly as a root-relative locator would be,
1221
+ // instead of the third-party fallback below. The pathname.startsWith("//") exclusion
1222
+ // guards a URL like https://tenant.example//evil.example/x.png, whose pathname would
1223
+ // otherwise re-parse as scheme-relative to a DIFFERENT origin on the recursive call.
1224
+ return classifyImageDependencyWithPrepared(`${absolute.pathname}${absolute.search}`, context, platformHosted, tenantOrigins);
1225
+ }
1153
1226
  absolute.hash = "";
1154
1227
  const canonical = absolute.toString();
1155
1228
  if (platformHosted.origins.has(absolute.origin) || platformHosted.references.has(stripQueryHash(canonical))) {
@@ -1166,6 +1239,7 @@ function classifyImageDependencyWithPrepared(
1166
1239
  locator: string,
1167
1240
  context: { tenantId: string; artifactPath: string },
1168
1241
  platformHosted: PreparedPlatformHostedImageConfig,
1242
+ tenantOrigins: ReadonlySet<string>,
1169
1243
  ): Pick<ImageDependency, "canonicalLocator" | "disposition" | "reason"> {
1170
1244
  const raw = normalizeUrlInput(locator);
1171
1245
  if (!raw || CONTROL_RE.test(raw)) {
@@ -1184,14 +1258,14 @@ function classifyImageDependencyWithPrepared(
1184
1258
  } catch {
1185
1259
  return { canonicalLocator: "invalid:", disposition: "security-error", reason: "malformed" };
1186
1260
  }
1187
- return classifyParsedHttpUrl(absolute, context, platformHosted);
1261
+ return classifyParsedHttpUrl(absolute, context, platformHosted, tenantOrigins);
1188
1262
  }
1189
1263
 
1190
1264
  if (/^[a-z][a-z0-9+.-]*:/i.test(raw)) {
1191
1265
  return { canonicalLocator: "unsafe:", disposition: "security-error", reason: "unsafe-scheme" };
1192
1266
  }
1193
1267
  const networkUrl = backslashNetworkUrl(raw, context.artifactPath);
1194
- if (networkUrl) return classifyParsedHttpUrl(networkUrl, context, platformHosted);
1268
+ if (networkUrl) return classifyParsedHttpUrl(networkUrl, context, platformHosted, tenantOrigins);
1195
1269
  const pathOnly = stripQueryHash(raw);
1196
1270
  const structural = structuralPath(pathOnly);
1197
1271
  if (structural.startsWith("/") && hasDotDotSegment(pathOnly)) {
@@ -1279,6 +1353,79 @@ export function buildImageDependencyWarningBatch(
1279
1353
  };
1280
1354
  }
1281
1355
 
1356
+ function declaredCustomPropertyNames(css: string, kind: "stylesheet" | "declaration"): Set<string> {
1357
+ const declared = new Set<string>();
1358
+ const walk = (node: CssScanNode): void => {
1359
+ if (node.type === "Declaration") {
1360
+ const property = node.property ?? "";
1361
+ if (property.startsWith("--")) declared.add(property);
1362
+ return;
1363
+ }
1364
+ if (node.block) walk(node.block);
1365
+ for (const child of cssChildren(node)) walk(child);
1366
+ };
1367
+ try {
1368
+ const parsed = parse(css, {
1369
+ context: kind === "declaration" ? "declarationList" : "stylesheet",
1370
+ list: false,
1371
+ }) as unknown as CssScanNode;
1372
+ walk(parsed);
1373
+ } catch {
1374
+ // Invalid CSS contributes no declarations; the real scan fails open the same way.
1375
+ }
1376
+ return declared;
1377
+ }
1378
+
1379
+ /** Every `style=""` attribute value (declaration context) and `<style>` text (stylesheet context). */
1380
+ function rawCssChunksInHtml(html: string): Array<{ css: string; kind: "stylesheet" | "declaration" }> {
1381
+ const chunks: Array<{ css: string; kind: "stylesheet" | "declaration" }> = [];
1382
+ const stack: HtmlNode[] = [parseFragment(html)];
1383
+ while (stack.length > 0) {
1384
+ const node = stack.pop()!;
1385
+ if (isHtmlText(node)) continue;
1386
+ if (node.nodeName === "#document" || node.nodeName === "#document-fragment") {
1387
+ for (const child of node.childNodes) stack.push(child);
1388
+ continue;
1389
+ }
1390
+ if (!isHtmlElement(node)) continue;
1391
+ const styleValue = htmlAttribute(node, "style");
1392
+ if (styleValue != null) chunks.push({ css: styleValue, kind: "declaration" });
1393
+ if (node.tagName === "style") {
1394
+ for (const child of node.childNodes) {
1395
+ if (isHtmlText(child)) chunks.push({ css: child.value, kind: "stylesheet" });
1396
+ }
1397
+ continue;
1398
+ }
1399
+ if (node.tagName === "template" && node.namespaceURI === HTML_NAMESPACE && "content" in node) {
1400
+ for (const child of node.content.childNodes) stack.push(child);
1401
+ continue;
1402
+ }
1403
+ for (const child of node.childNodes) stack.push(child);
1404
+ }
1405
+ return chunks;
1406
+ }
1407
+
1408
+ /**
1409
+ * Pre-pass: every `--foo` declared anywhere in the artifact set (CSS files, `<style>` blocks,
1410
+ * `style=""` attributes). `scanImageDependencies` threads this into every `var()` decision so a
1411
+ * reference to a declared custom property is never deferred — its image, if any, was already
1412
+ * captured at the declaration itself.
1413
+ */
1414
+ function collectDeclaredCustomProperties(artifacts: readonly ImageDependencyArtifact[]): Set<string> {
1415
+ const declared = new Set<string>();
1416
+ for (const artifact of artifacts) {
1417
+ const kind = inferKind(artifact);
1418
+ if (kind === "css") {
1419
+ for (const name of declaredCustomPropertyNames(artifact.content, "stylesheet")) declared.add(name);
1420
+ } else if (kind === "html") {
1421
+ for (const chunk of rawCssChunksInHtml(artifact.content)) {
1422
+ for (const name of declaredCustomPropertyNames(chunk.css, chunk.kind)) declared.add(name);
1423
+ }
1424
+ }
1425
+ }
1426
+ return declared;
1427
+ }
1428
+
1282
1429
  /** Scan a revision's already-read artifacts with no network or filesystem I/O. */
1283
1430
  export function scanImageDependencies(
1284
1431
  artifacts: readonly ImageDependencyArtifact[],
@@ -1286,12 +1433,14 @@ export function scanImageDependencies(
1286
1433
  ): ImageDependencyScan {
1287
1434
  const dependencies: ImageDependency[] = [];
1288
1435
  const platformHosted = preparePlatformHostedConfig(options.platformHosted);
1436
+ const tenantOrigins = prepareTenantOrigins(options.tenantOrigins);
1437
+ const declaredCustomProperties = collectDeclaredCustomProperties(artifacts);
1289
1438
  let occurrence = 0;
1290
1439
  for (const [artifactIndex, artifact] of artifacts.entries()) {
1291
1440
  const kind = inferKind(artifact);
1292
1441
  let refs: ExtractedReference[];
1293
- if (kind === "html") refs = htmlReferences(artifact.content, artifact.path);
1294
- else if (kind === "css") refs = cssReferences(artifact.content, "stylesheet");
1442
+ if (kind === "html") refs = htmlReferences(artifact.content, artifact.path, null, declaredCustomProperties);
1443
+ else if (kind === "css") refs = cssReferences(artifact.content, "stylesheet", declaredCustomProperties);
1295
1444
  else {
1296
1445
  const parsed = catalogReferences(artifact.content, artifact.path);
1297
1446
  if (parsed == null) {
@@ -1317,7 +1466,7 @@ export function scanImageDependencies(
1317
1466
  const classified = classifyImageDependencyWithPrepared(ref.classificationLocator ?? ref.locator, {
1318
1467
  tenantId: options.tenantId,
1319
1468
  artifactPath: artifact.path,
1320
- }, platformHosted);
1469
+ }, platformHosted, tenantOrigins);
1321
1470
  dependencies.push({
1322
1471
  locator: ref.locator,
1323
1472
  artifactIndex,