@tokenoftrust/storefront-runner 2.2.85 → 2.2.87
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/agency-kit/tools/lib/redirect-declaration.mjs +627 -0
- package/apps/storefront/src/config/declaredTenantConfig.ts +94 -0
- package/apps/storefront/src/middleware/renderChain.ts +14 -3
- package/package.json +1 -1
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/src/declared-tenant-config.d.mts +22 -0
- package/packages/public-runtime/src/declared-tenant-config.mjs +150 -0
- package/packages/public-runtime/src/publication-redirects.d.mts +23 -4
- package/packages/public-runtime/src/publication-redirects.mjs +4 -378
- package/packages/public-runtime/src/static-bundle.ts +59 -11
- package/packages/public-runtime/tests/fixtures/redirect-match-cases.json +34 -0
- package/scripts/dev/port-lock.mjs +109 -0
- package/scripts/dev/standalone-graft-plan.mjs +35 -0
- package/scripts/tot-dev.mjs +49 -20
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The render-time read of a tenant's declared platform config (`.tot/config.json`
|
|
3
|
+
* → siteType/compliance/capabilities/features; see
|
|
4
|
+
* `@tot/public-runtime/declared-tenant-config`). Every render applies it the same
|
|
5
|
+
* way, on top of the resolved tenant record:
|
|
6
|
+
*
|
|
7
|
+
* 1. the checkout's own file in the build tree — what `tot dev` grafts in and
|
|
8
|
+
* what the static-publish build materializes from the tenant repo;
|
|
9
|
+
* 2. the per-environment artifact the reconcile derived from the same file at the
|
|
10
|
+
* reconciled commit — what the hosted Worker serves preview and live from.
|
|
11
|
+
*
|
|
12
|
+
* Both are the same file at the same commit, so a tenant renders identically on
|
|
13
|
+
* every surface; a field honored by one is honored by all.
|
|
14
|
+
*/
|
|
15
|
+
import {
|
|
16
|
+
applyDeclaredTenantConfig,
|
|
17
|
+
declaredTenantConfig,
|
|
18
|
+
declaredTenantConfigArtifactPath,
|
|
19
|
+
type DeclaredTenantConfig,
|
|
20
|
+
} from "@tot/public-runtime/declared-tenant-config";
|
|
21
|
+
import {
|
|
22
|
+
readPublishedArtifact,
|
|
23
|
+
readVersionArtifact,
|
|
24
|
+
tenantIdFromPath,
|
|
25
|
+
type CustomizationEnv,
|
|
26
|
+
type TenantConfig,
|
|
27
|
+
} from "@tot/public-runtime";
|
|
28
|
+
|
|
29
|
+
// The checkout's own `.tot/config.json`, read eagerly (tiny JSON). From src/config/
|
|
30
|
+
// the repo root is four levels up; both tenant tiers are globbed, and the explicit
|
|
31
|
+
// `.tot` literal matches the dotdir.
|
|
32
|
+
const checkoutConfigFiles = import.meta.glob<Record<string, unknown>>(
|
|
33
|
+
["../../../../tenants/*/.tot/config.json", "../../../../tenants/e2e/*/.tot/config.json"],
|
|
34
|
+
{ eager: true, import: "default" },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/** dir id → that checkout's parsed `.tot/config.json`. */
|
|
38
|
+
export function checkoutTotConfigs(): Map<string, Record<string, unknown>> {
|
|
39
|
+
const map = new Map<string, Record<string, unknown>>();
|
|
40
|
+
for (const [key, config] of Object.entries(checkoutConfigFiles)) {
|
|
41
|
+
const id = tenantIdFromPath(key);
|
|
42
|
+
if (id && config && typeof config === "object") map.set(id, config);
|
|
43
|
+
}
|
|
44
|
+
return map;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The declaration in the checkout's own `.tot/config.json`, or `{}` when it has none. */
|
|
48
|
+
export function checkoutDeclaredTenantConfig(tenantId: string): DeclaredTenantConfig {
|
|
49
|
+
return declaredTenantConfig(checkoutConfigFiles[`../../../../tenants/${tenantId}/.tot/config.json`]
|
|
50
|
+
?? checkoutConfigFiles[`../../../../tenants/e2e/${tenantId}/.tot/config.json`]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The declaration the reconcile published for this environment, or `{}`. */
|
|
54
|
+
export async function publishedDeclaredTenantConfig(
|
|
55
|
+
kvGet: (key: string) => Promise<string | null>,
|
|
56
|
+
tenantId: string,
|
|
57
|
+
env: CustomizationEnv,
|
|
58
|
+
): Promise<DeclaredTenantConfig> {
|
|
59
|
+
const raw = await readPublishedArtifact(kvGet, tenantId, env, declaredTenantConfigArtifactPath(tenantId));
|
|
60
|
+
if (!raw) return {};
|
|
61
|
+
try {
|
|
62
|
+
return declaredTenantConfig(JSON.parse(raw));
|
|
63
|
+
} catch {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The declaration pinned to one immutable version (a candidate under review), or `{}`. */
|
|
69
|
+
export async function versionDeclaredTenantConfig(
|
|
70
|
+
kvGet: (key: string) => Promise<string | null>,
|
|
71
|
+
tenantId: string,
|
|
72
|
+
versionId: string,
|
|
73
|
+
): Promise<DeclaredTenantConfig> {
|
|
74
|
+
const raw = await readVersionArtifact(kvGet, tenantId, versionId, declaredTenantConfigArtifactPath(tenantId));
|
|
75
|
+
if (!raw) return {};
|
|
76
|
+
try {
|
|
77
|
+
return declaredTenantConfig(JSON.parse(raw));
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The tenant as it renders in `env`: its record with every declaration applied. */
|
|
84
|
+
export async function withDeclaredTenantConfig(
|
|
85
|
+
tenant: TenantConfig,
|
|
86
|
+
kvGet: (key: string) => Promise<string | null>,
|
|
87
|
+
env: CustomizationEnv,
|
|
88
|
+
): Promise<TenantConfig> {
|
|
89
|
+
const fromCheckout = applyDeclaredTenantConfig(tenant, checkoutDeclaredTenantConfig(tenant.tenant_id));
|
|
90
|
+
return applyDeclaredTenantConfig(
|
|
91
|
+
fromCheckout,
|
|
92
|
+
await publishedDeclaredTenantConfig(kvGet, tenant.tenant_id, env),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -97,6 +97,7 @@ import {
|
|
|
97
97
|
import { recordActivity } from "@/lib/activity/recordActivity";
|
|
98
98
|
import { middlewareRewriteTargetV1 } from "./routeBranch.js";
|
|
99
99
|
import { declaredTenantOrigins } from "@/lib/tenantOrigins";
|
|
100
|
+
import { withDeclaredTenantConfig } from "@/config/declaredTenantConfig";
|
|
100
101
|
import {
|
|
101
102
|
applyInternalContentResponsePolicy,
|
|
102
103
|
internalContentGrantMatchesPlan,
|
|
@@ -147,7 +148,7 @@ export interface RenderChainInput {
|
|
|
147
148
|
export const TENANT_ORIGINS_CAPTURE_HEADER = "x-tot-tenant-origins";
|
|
148
149
|
|
|
149
150
|
export async function runRenderChain(input: RenderChainInput): Promise<Response> {
|
|
150
|
-
const { context, next, host, url, tenant, basePath, renderPath, cache, kv } = input;
|
|
151
|
+
const { context, next, host, url, tenant: tenantRecord, basePath, renderPath, cache, kv } = input;
|
|
151
152
|
const { request, locals } = context;
|
|
152
153
|
const isInternalPublication = input.publicationRoute.action !== "fail-closed" &&
|
|
153
154
|
input.publicationRoute.manifest.visibility === "internal";
|
|
@@ -177,7 +178,7 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
177
178
|
// pin can only ever name its own version; a mismatched/expired/absent token
|
|
178
179
|
// falls through to the normal channel-resolved read below, same fail-closed
|
|
179
180
|
// posture as the `__preview` token.
|
|
180
|
-
const candidatePin = await resolveCandidateContentPin(url,
|
|
181
|
+
const candidatePin = await resolveCandidateContentPin(url, tenantRecord.tenant_id);
|
|
181
182
|
// Per-developer AI-edit draft: the signed
|
|
182
183
|
// tot_dev_draft cookie's mere PRESENCE opts the request out of the shared page
|
|
183
184
|
// cache below (a draft view must never be cached publicly); it's verified —
|
|
@@ -192,7 +193,7 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
192
193
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
193
194
|
const customization = await resolveCustomizationEnv({
|
|
194
195
|
isPreviewHost,
|
|
195
|
-
tenantId:
|
|
196
|
+
tenantId: tenantRecord.tenant_id,
|
|
196
197
|
token: previewToken,
|
|
197
198
|
secret: previewSecret,
|
|
198
199
|
nowSeconds,
|
|
@@ -230,6 +231,16 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
230
231
|
if (isInternalPublication) applyInternalContentResponsePolicy(response.headers);
|
|
231
232
|
return response;
|
|
232
233
|
}
|
|
234
|
+
|
|
235
|
+
// The tenant as it renders in THIS environment: its record with the declared
|
|
236
|
+
// `.tot/config.json` fields applied (compliance, siteType, capabilities,
|
|
237
|
+
// features). Everything below — static bundle, notices, gates, features,
|
|
238
|
+
// capabilities — reads this, never the bare record.
|
|
239
|
+
const tenant = await withDeclaredTenantConfig(
|
|
240
|
+
tenantRecord,
|
|
241
|
+
kv ? (key: string) => kv.get(key) : async () => null,
|
|
242
|
+
customization.env,
|
|
243
|
+
);
|
|
233
244
|
const publicationTimestamp = publicationSource.status === "selected"
|
|
234
245
|
? publicationSource.publicationTimestamp
|
|
235
246
|
: exactPublicationTimestamp(await readEnv("TOT_PUBLICATION_TIMESTAMP"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.87",
|
|
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",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"./durable-assets": "./src/durable-assets.ts",
|
|
20
20
|
"./retention-holds": "./src/retention-holds.ts",
|
|
21
21
|
"./tenant-embed-defaults": "./src/tenant-embed-defaults.mjs",
|
|
22
|
+
"./declared-tenant-config": "./src/declared-tenant-config.mjs",
|
|
22
23
|
"./integration/lifecycle-host-asset": "./src/integration/lifecycle-host-asset.mjs",
|
|
23
24
|
"./integration/native-ga4-sandbox-asset": "./src/integration/native-ga4-sandbox-asset.mjs",
|
|
24
25
|
"./integration/visitor-region": "./src/integration/visitor-region.mjs"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Types for the dependency-free declared-tenant-config module (declared-tenant-config.mjs). */
|
|
2
|
+
import type { TenantConfig } from "./tenant.js";
|
|
3
|
+
|
|
4
|
+
export type DeclaredTenantConfigField = "siteType" | "compliance" | "capabilities" | "features";
|
|
5
|
+
export type DeclaredTenantConfig = Partial<Pick<TenantConfig, DeclaredTenantConfigField>>;
|
|
6
|
+
|
|
7
|
+
export const DECLARED_TENANT_CONFIG_FIELDS: readonly DeclaredTenantConfigField[];
|
|
8
|
+
export const TOT_CONFIG_KEYS: readonly string[];
|
|
9
|
+
export const COMPLIANCE_KEYS: readonly string[];
|
|
10
|
+
export const FEATURE_KEYS: readonly string[];
|
|
11
|
+
export const SITE_TYPES_LIST: readonly string[];
|
|
12
|
+
export function declaredTenantConfig(config: unknown): DeclaredTenantConfig;
|
|
13
|
+
export function isEmptyDeclaredTenantConfig(declared: DeclaredTenantConfig | null | undefined): boolean;
|
|
14
|
+
export function applyDeclaredTenantConfig<T extends Partial<Pick<TenantConfig, DeclaredTenantConfigField>>>(
|
|
15
|
+
tenant: T,
|
|
16
|
+
declared: DeclaredTenantConfig | null | undefined,
|
|
17
|
+
): T;
|
|
18
|
+
export function readDeclaredTenantConfigAt(
|
|
19
|
+
source: { readFile(path: string, ref: string): Promise<string | null> },
|
|
20
|
+
ref: string,
|
|
21
|
+
): Promise<DeclaredTenantConfig>;
|
|
22
|
+
export function declaredTenantConfigArtifactPath(tenantId: string): string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tenant's declared platform config: the `.tot/config.json` fields that become
|
|
3
|
+
* TenantConfig fields on every surface that renders the tenant.
|
|
4
|
+
*
|
|
5
|
+
* `.tot/config.json` is the ONE declared source for these fields. Every reader goes
|
|
6
|
+
* through this module:
|
|
7
|
+
* - `tot dev` and the static-publish build read the checkout's own file from the
|
|
8
|
+
* build tree (apps/storefront/src/config/resolver.ts `declaredConfigFor`);
|
|
9
|
+
* - the hosted Worker reads the per-environment artifact the reconcile derives from
|
|
10
|
+
* the same file at the reconciled commit (`declaredTenantConfigArtifactPath`);
|
|
11
|
+
* - `tot validate` refuses any top-level key not in `TOT_CONFIG_KEYS`, so a field
|
|
12
|
+
* no surface reads cannot pass validation.
|
|
13
|
+
* All of them apply it with `applyDeclaredTenantConfig`, so a field honored on one
|
|
14
|
+
* surface is honored on all of them.
|
|
15
|
+
*
|
|
16
|
+
* Plain JS on purpose (same reason as embed-catalog.mjs): plain-`node` tooling and
|
|
17
|
+
* the CLI's parity test read it alongside the TypeScript runtime.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** `.tot/config.json` keys that become TenantConfig fields. */
|
|
21
|
+
export const DECLARED_TENANT_CONFIG_FIELDS = Object.freeze([
|
|
22
|
+
"siteType",
|
|
23
|
+
"compliance",
|
|
24
|
+
"capabilities",
|
|
25
|
+
"features",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Every top-level `.tot/config.json` key the platform reads: the reconcile mapping
|
|
30
|
+
* keys, the CLI's host hint, provenance markers (`sample` from `tot dev --sample`,
|
|
31
|
+
* `seed` from the tot-mcp repo seed), an editor's `$schema`, and the declared
|
|
32
|
+
* TenantConfig fields.
|
|
33
|
+
*/
|
|
34
|
+
export const TOT_CONFIG_KEYS = Object.freeze([
|
|
35
|
+
"$schema",
|
|
36
|
+
"tenant",
|
|
37
|
+
"scope",
|
|
38
|
+
"mappings",
|
|
39
|
+
"hostPlatform",
|
|
40
|
+
"sample",
|
|
41
|
+
"seed",
|
|
42
|
+
...DECLARED_TENANT_CONFIG_FIELDS,
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/** Every `compliance` key the platform renders or enforces (ComplianceConfig, tenant.ts). */
|
|
46
|
+
export const COMPLIANCE_KEYS = Object.freeze([
|
|
47
|
+
"ruleProfile",
|
|
48
|
+
"minAge",
|
|
49
|
+
"nicotineWarning",
|
|
50
|
+
"shippingRestriction",
|
|
51
|
+
"pactAct",
|
|
52
|
+
"adultSignature",
|
|
53
|
+
"prop65",
|
|
54
|
+
"purchaseLimit",
|
|
55
|
+
"stateEligibility",
|
|
56
|
+
"exciseTax",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/** Every `features` key (TenantFeatures, tenant.ts). */
|
|
60
|
+
export const FEATURE_KEYS = Object.freeze([
|
|
61
|
+
"catalog",
|
|
62
|
+
"productSearch",
|
|
63
|
+
"savedItems",
|
|
64
|
+
"quickView",
|
|
65
|
+
"newsletter",
|
|
66
|
+
"commerceSitemap",
|
|
67
|
+
"productJsonLd",
|
|
68
|
+
"ageGate",
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
/** Every `siteType` value (SiteType, tenant.ts). */
|
|
72
|
+
export const SITE_TYPES_LIST = Object.freeze(["commerce", "marketing"]);
|
|
73
|
+
|
|
74
|
+
const SITE_TYPES = new Set(SITE_TYPES_LIST);
|
|
75
|
+
|
|
76
|
+
const isPlainObject = (value) =>
|
|
77
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The declared TenantConfig fields from a parsed `.tot/config.json`. A field whose
|
|
81
|
+
* value has the wrong shape is omitted, never coerced (`tot validate` reports it).
|
|
82
|
+
*
|
|
83
|
+
* @param {unknown} config
|
|
84
|
+
*/
|
|
85
|
+
export function declaredTenantConfig(config) {
|
|
86
|
+
/** @type {Record<string, unknown>} */
|
|
87
|
+
const out = {};
|
|
88
|
+
if (!isPlainObject(config)) return out;
|
|
89
|
+
const { siteType, compliance, capabilities, features } = /** @type {Record<string, unknown>} */ (config);
|
|
90
|
+
if (typeof siteType === "string" && SITE_TYPES.has(siteType)) out.siteType = siteType;
|
|
91
|
+
if (isPlainObject(compliance)) out.compliance = compliance;
|
|
92
|
+
if (isPlainObject(capabilities)) out.capabilities = capabilities;
|
|
93
|
+
if (isPlainObject(features)) out.features = features;
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** True when the declaration sets nothing. */
|
|
98
|
+
export function isEmptyDeclaredTenantConfig(declared) {
|
|
99
|
+
return !declared || Object.keys(declared).length === 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Overlay a declaration onto a resolved tenant. `siteType` is replaced; the object
|
|
104
|
+
* fields merge key by key, so a declaration adds to — and can set individual keys
|
|
105
|
+
* of — what the platform record already carries, but never erases a key it omits.
|
|
106
|
+
*
|
|
107
|
+
* @template T
|
|
108
|
+
* @param {T} tenant
|
|
109
|
+
* @param {Record<string, unknown> | null | undefined} declared
|
|
110
|
+
* @returns {T}
|
|
111
|
+
*/
|
|
112
|
+
export function applyDeclaredTenantConfig(tenant, declared) {
|
|
113
|
+
if (isEmptyDeclaredTenantConfig(declared)) return tenant;
|
|
114
|
+
const out = /** @type {Record<string, unknown>} */ ({ ...tenant });
|
|
115
|
+
for (const field of DECLARED_TENANT_CONFIG_FIELDS) {
|
|
116
|
+
const value = declared[field];
|
|
117
|
+
if (value === undefined) continue;
|
|
118
|
+
out[field] = isPlainObject(value) && isPlainObject(out[field])
|
|
119
|
+
? { ...out[field], ...value }
|
|
120
|
+
: value;
|
|
121
|
+
}
|
|
122
|
+
return /** @type {T} */ (out);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The declaration a tenant repo commits at `ref`, read through any source with the
|
|
127
|
+
* reconcile's `readFile(path, ref)` shape. `{}` when the file is absent or unreadable.
|
|
128
|
+
*
|
|
129
|
+
* @param {{ readFile(path: string, ref: string): Promise<string | null> }} source
|
|
130
|
+
* @param {string} ref
|
|
131
|
+
*/
|
|
132
|
+
export async function readDeclaredTenantConfigAt(source, ref) {
|
|
133
|
+
try {
|
|
134
|
+
const raw = await source.readFile(".tot/config.json", ref);
|
|
135
|
+
return raw ? declaredTenantConfig(JSON.parse(raw)) : {};
|
|
136
|
+
} catch {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Artifact path of the declaration the reconcile derives by reading `.tot/config.json`,
|
|
143
|
+
* in the same colocated `tenants/<id>/…` space as theme/capabilities so it is
|
|
144
|
+
* pinned per version and resolved per environment like every other artifact.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} tenantId
|
|
147
|
+
*/
|
|
148
|
+
export function declaredTenantConfigArtifactPath(tenantId) {
|
|
149
|
+
return `tenants/${tenantId}/tenant-config.json`;
|
|
150
|
+
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
/** Types for the
|
|
1
|
+
/** Types for the redirect contract (agency-kit/tools/lib/redirect-declaration.mjs), re-exported here. */
|
|
2
2
|
|
|
3
3
|
export const REDIRECT_STATUS: 308;
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
4
|
+
export const KVS_STORE_LIMIT_BYTES: number;
|
|
5
|
+
export const KVS_KEY_LIMIT_BYTES: number;
|
|
6
|
+
export const KVS_VALUE_LIMIT_BYTES: number;
|
|
7
|
+
export const KVS_RESERVED_BYTES: number;
|
|
6
8
|
export const REDIRECT_MAP_BUDGET_BYTES: number;
|
|
9
|
+
export const REDIRECT_KEY_OVERHEAD_BYTES: number;
|
|
10
|
+
export const REDIRECT_RULE_MAX_DEPTHS: number;
|
|
7
11
|
export const COLLECTION_REDIRECTS_PATH: RegExp;
|
|
8
12
|
export const SITE_REDIRECTS_PATH: "content/redirects.json";
|
|
9
13
|
export const WHOLE_SITE_DELIVERY_CONTRACT: string;
|
|
10
14
|
|
|
11
15
|
export interface DeclaredRedirect {
|
|
16
|
+
/** An exact request path, or a `/<dir>/*` rule. */
|
|
12
17
|
from: string;
|
|
13
18
|
to: string;
|
|
14
19
|
/** Which declaration produced it, named in any refusal. */
|
|
@@ -21,9 +26,24 @@ export interface RedirectSet {
|
|
|
21
26
|
violations: string[];
|
|
22
27
|
bytes: number;
|
|
23
28
|
budget: number;
|
|
29
|
+
/** How many entries are `/<dir>/*` rules. */
|
|
30
|
+
rules: number;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
export function normalizeRedirectPath(raw: string): string | null;
|
|
34
|
+
export function normalizeRedirectSource(raw: string): string | null;
|
|
35
|
+
export function ruleDirectory(raw: string): string | null;
|
|
36
|
+
export function isRedirectRule(from: string): boolean;
|
|
37
|
+
export function redirectRequestPath(pathname: string): string;
|
|
38
|
+
export function ruleLocation(to: string, capture: string): string | null;
|
|
39
|
+
export function resolveRedirect(
|
|
40
|
+
redirects: readonly { from: string; to: string }[],
|
|
41
|
+
pathname: string,
|
|
42
|
+
): { from: string; to: string; location: string } | null;
|
|
43
|
+
export function redirectResolver(
|
|
44
|
+
entries: readonly { from: string; to: string }[],
|
|
45
|
+
): (pathname: string) => { from: string; to: string; location: string } | null;
|
|
46
|
+
export function redirectRuleDepths(redirects: readonly { from: string }[]): number[];
|
|
27
47
|
export function redirectsFromCollection(
|
|
28
48
|
collection: string,
|
|
29
49
|
rows: Record<string, unknown>,
|
|
@@ -38,7 +58,6 @@ export function redirectsFromAliases(
|
|
|
38
58
|
source?: string,
|
|
39
59
|
): DeclaredRedirect[];
|
|
40
60
|
export function buildRedirectSet(declared: DeclaredRedirect[], budget?: number): RedirectSet;
|
|
41
|
-
export function serializeRedirectMap(redirects: { from: string; to: string }[]): string;
|
|
42
61
|
export function aliasesFromRedirectSet(
|
|
43
62
|
set: RedirectSet,
|
|
44
63
|
): { from: string; behavior: "redirect-308"; to: string }[];
|