@expofp/config 3.28.2 → 3.29.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.
- package/README.md +6 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/load-config.js +52 -6
- package/dist/lib/normalize-fp-svg.d.ts +1 -1
- package/dist/lib/normalize-fp-svg.js +1 -1
- package/dist/lib/resource-urls.d.ts +4 -4
- package/dist/lib/resource-urls.js +26 -19
- package/dist/lib/store-ref.d.ts +40 -0
- package/dist/lib/store-ref.js +56 -0
- package/dist/lib/visit-resources.d.ts +18 -4
- package/dist/lib/visit-resources.js +19 -8
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -32,5 +32,9 @@ been retired (`?hide=`, `?centerxy=`, `?lang=`, …) are listed in `retiredKeys`
|
|
|
32
32
|
old link opens a clean plan instead of replaying its query as search text.
|
|
33
33
|
|
|
34
34
|
Also: `applyIntents` (dispatch `selectBooth` / `changeLanguage` / … to the floor plan),
|
|
35
|
-
`serializeConfigResources` (collect schema-tagged assets + payloads for offline copies
|
|
36
|
-
`
|
|
35
|
+
`serializeConfigResources` (collect schema-tagged assets + payloads for offline copies and normalized
|
|
36
|
+
expo folders), `resolveUrl` / `compactUrl` (the `$/<expo>/<hash>/<name>` ⇄
|
|
37
|
+
`https://<expo>.expofp.com/immutable/<hash>/<name>` codec of a normalized folder's store refs —
|
|
38
|
+
`loadConfig` follows the folder's pointer `manifest.json` to its immutable snapshot and expands the
|
|
39
|
+
refs, so an effective config only ever carries full URLs), and `toDebugSettings` (config schema →
|
|
40
|
+
`@expofp/debug` panel). Types re-export from `@expofp/schema`.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { type LegacyOneShot, type LegacyPrimary, type LegacyUrlQuery, parseLegac
|
|
|
6
6
|
export { loadConfig } from './lib/load-config.js';
|
|
7
7
|
export { type StorageLike } from './lib/local-storage-codec.js';
|
|
8
8
|
export { type ConfigResourceStore, serializeConfigResources, } from './lib/serialize-config-resources.js';
|
|
9
|
+
export { compactUrl, resolveUrl } from './lib/store-ref.js';
|
|
9
10
|
export { parseFromUrlTolerant } from './lib/url-codec.js';
|
|
10
11
|
export { parseIntentsFromUrl } from './lib/url-intents.js';
|
|
11
12
|
export { type Config, type Consent, type Manifest, type Options } from '@expofp/schema';
|
package/dist/index.js
CHANGED
|
@@ -7,5 +7,6 @@ export { parseDeprecatedUrlParams } from './lib/deprecated-url-params.js';
|
|
|
7
7
|
export { parseLegacyQuery, serializeSelection, splitPlatformParams, } from './lib/legacy-url.js';
|
|
8
8
|
export { loadConfig } from './lib/load-config.js';
|
|
9
9
|
export { serializeConfigResources, } from './lib/serialize-config-resources.js';
|
|
10
|
+
export { compactUrl, resolveUrl } from './lib/store-ref.js';
|
|
10
11
|
export { parseFromUrlTolerant } from './lib/url-codec.js';
|
|
11
12
|
export { parseIntentsFromUrl } from './lib/url-intents.js';
|
package/dist/lib/load-config.js
CHANGED
|
@@ -8,10 +8,11 @@ import { parseFromStorage } from './local-storage-codec.js';
|
|
|
8
8
|
import { normalizeFpSvgLayerAliases } from './normalize-fp-svg.js';
|
|
9
9
|
import { normalizeLegacyData } from './normalize-legacy-data.js';
|
|
10
10
|
import { applyRebookingData } from './rebooking.js';
|
|
11
|
+
import { resolveUrl } from './store-ref.js';
|
|
11
12
|
import { parseFromUrlTolerant } from './url-codec.js';
|
|
12
13
|
import { parseIntentsFromUrl } from './url-intents.js';
|
|
13
14
|
import { readValidateFlag } from './validate-flag.js';
|
|
14
|
-
import { documentRef, setValidateManifest, validateOrClone } from './visit-resources.js';
|
|
15
|
+
import { documentRef, isRefShaped, setValidateManifest, validateOrClone, visitResources, } from './visit-resources.js';
|
|
15
16
|
const log = debug('efp:config');
|
|
16
17
|
/**
|
|
17
18
|
* Assemble the effective config — the runtime's single, complete copy of the
|
|
@@ -42,11 +43,17 @@ export async function loadConfig(manifest, options, url, storage) {
|
|
|
42
43
|
// layers merge, so it can't come off the effective config. See readValidateFlag.
|
|
43
44
|
const validateSchema = readValidateFlag(url, storage);
|
|
44
45
|
setValidateManifest(validateSchema); // gates the referenced-document parses (visit-resources)
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
46
|
+
// A normalized expo folder's mutable `manifest.json` is a tiny pointer
|
|
47
|
+
// (`{"$ref": "$/<expo>/<hash>/manifest.json"}`) — follow the chain down to
|
|
48
|
+
// the snapshot first. Then take an OWN copy of the settled manifest
|
|
49
|
+
// (validated when the flag is on — see validateOrClone): `resolve`
|
|
50
|
+
// deep-freezes what it settles, an offline or normalized manifest carries
|
|
51
|
+
// the full event data (booths, exhibitors, …), and `normalizeLegacyData`
|
|
52
|
+
// below mutates those nested objects in place. Store refs (`$/…`) expand
|
|
53
|
+
// into their canonical immutable URLs right here — the effective config
|
|
54
|
+
// only ever carries full URLs, so no consumer sees the notation.
|
|
55
|
+
manifest = validateOrClone(ManifestSchema, await resolveManifestChain(manifest));
|
|
56
|
+
await expandManifestStoreRefs(manifest);
|
|
50
57
|
// The legacy event-data directory and the data-revision pin are INPUTS,
|
|
51
58
|
// consumed right here — the effective config carries only the refs derived
|
|
52
59
|
// from them, so neither is merged.
|
|
@@ -195,6 +202,45 @@ export async function loadConfig(manifest, options, url, storage) {
|
|
|
195
202
|
deepFreeze(config);
|
|
196
203
|
return config;
|
|
197
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Hops a manifest pointer chain may take before it must have settled. The
|
|
207
|
+
* normalized form needs exactly one (mutable pointer → immutable snapshot);
|
|
208
|
+
* the ceiling only guards against a cyclic or runaway chain.
|
|
209
|
+
*/
|
|
210
|
+
const MANIFEST_POINTER_HOPS_MAX = 3;
|
|
211
|
+
/**
|
|
212
|
+
* Follow the «pointer → snapshot» chain: a settled manifest that is itself
|
|
213
|
+
* ref-shaped is a pointer — resolve what it points at, until a document
|
|
214
|
+
* settles. A store ref (`$/…`) expands by the fixed convention before its
|
|
215
|
+
* fetch, the input ref included; a rooted local pointer resolves against
|
|
216
|
+
* the page like any rooted URL. Chain-walking lives here, not in
|
|
217
|
+
* `@expofp/resolve` — the resolver stays one-layered and domain-free.
|
|
218
|
+
*/
|
|
219
|
+
async function resolveManifestChain(input) {
|
|
220
|
+
let current = input;
|
|
221
|
+
for (let hops = 0;; hops++) {
|
|
222
|
+
if (isRefShaped(current))
|
|
223
|
+
current = { ...current, $ref: resolveUrl(current.$ref) };
|
|
224
|
+
const settled = await resolve(current);
|
|
225
|
+
if (!isRefShaped(settled))
|
|
226
|
+
return settled;
|
|
227
|
+
if (hops >= MANIFEST_POINTER_HOPS_MAX) {
|
|
228
|
+
throw new Error(`loadConfig: manifest pointer chain deeper than ${MANIFEST_POINTER_HOPS_MAX} at ${settled.$ref}`);
|
|
229
|
+
}
|
|
230
|
+
log('loadConfig', 'manifest is a pointer, following it to', settled.$ref);
|
|
231
|
+
current = { $ref: settled.$ref };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Expand every store ref (`$/…`) of a normalized manifest — asset URLs and
|
|
236
|
+
* document `$ref`s alike — into its canonical URL, via the one schema-guided
|
|
237
|
+
* walk. Passthrough for other values, so compat and offline manifests come
|
|
238
|
+
* out untouched.
|
|
239
|
+
*/
|
|
240
|
+
async function expandManifestStoreRefs(manifest) {
|
|
241
|
+
const expand = (url) => resolveUrl(url);
|
|
242
|
+
await visitResources(ManifestSchema, manifest, { asset: expand, ref: expand });
|
|
243
|
+
}
|
|
198
244
|
async function loadLegacyDataJs(baseUrl, version) {
|
|
199
245
|
// data.js is a window-assigning script like fp.svg.js — resolve() evaluates
|
|
200
246
|
// it to its globals and the expo data document rides on `__data`. The
|
|
@@ -8,7 +8,7 @@ import type { FpSvgLayerJs } from '@expofp/schema';
|
|
|
8
8
|
* (`window['__fpBooth'] = …` — the chain had every layer script clobber the
|
|
9
9
|
* shared `window.__fp`). The schemas and the floorplan know only the plain
|
|
10
10
|
* `__fp`/`__fpPaths` keys, so the loader folds the alias onto them on the
|
|
11
|
-
* payload's own copy, before the resource walk — the `svgAsset`
|
|
11
|
+
* payload's own copy, before the resource walk — the `svgAsset` URL rewrite
|
|
12
12
|
* then covers a folded drawing like a plain one. The folded values skip the
|
|
13
13
|
* (opt-in) schema validation, which ran before the fold — the same trust the
|
|
14
14
|
* untouched alias keys always had.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (`window['__fpBooth'] = …` — the chain had every layer script clobber the
|
|
8
8
|
* shared `window.__fp`). The schemas and the floorplan know only the plain
|
|
9
9
|
* `__fp`/`__fpPaths` keys, so the loader folds the alias onto them on the
|
|
10
|
-
* payload's own copy, before the resource walk — the `svgAsset`
|
|
10
|
+
* payload's own copy, before the resource walk — the `svgAsset` URL rewrite
|
|
11
11
|
* then covers a folded drawing like a plain one. The folded values skip the
|
|
12
12
|
* (opt-in) schema validation, which ran before the fold — the same trust the
|
|
13
13
|
* untouched alias keys always had.
|
|
@@ -16,10 +16,10 @@ export declare function toAbsoluteUrl(path: string, baseUrl: string): string;
|
|
|
16
16
|
export declare function isAbsoluteUrl(path: string): boolean;
|
|
17
17
|
/** The ref's own directory — the base its document's relative paths resolve against. */
|
|
18
18
|
export declare function refDirectory(ref: string): string;
|
|
19
|
-
/** Every distinct `<image>` asset
|
|
20
|
-
export declare function
|
|
21
|
-
/** Rewrite every `<image>` asset
|
|
22
|
-
export declare function
|
|
19
|
+
/** Every distinct `<image>` asset URL in one SVG document string. */
|
|
20
|
+
export declare function collectSvgImageAssets(svg: string): string[];
|
|
21
|
+
/** Rewrite every `<image>` asset URL in one SVG document string. */
|
|
22
|
+
export declare function rewriteSvgImageAssets(svg: string, rewrite: (url: string) => string): string;
|
|
23
23
|
/** Every distinct non-`data:` `url(…)` in one CSS text. */
|
|
24
24
|
export declare function collectCssUrls(css: string): string[];
|
|
25
25
|
/** Rewrite every non-`data:` `url(…)` in one CSS text. */
|
|
@@ -42,37 +42,44 @@ function execAll(regex, text) {
|
|
|
42
42
|
}
|
|
43
43
|
return matches;
|
|
44
44
|
}
|
|
45
|
-
/* ── SVG documents (`svgAsset`) — `<image>`
|
|
45
|
+
/* ── SVG documents (`svgAsset`) — an `<image>`'s URLs are asset paths ─────── */
|
|
46
46
|
const IMAGE_TAG = /<image\b[^>]*>/g;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The asset-bearing attributes of an `<image>`: the href it draws, and
|
|
49
|
+
* `data-model-3d` — the URL of the 3D model the floor plan renders *instead* of
|
|
50
|
+
* the image, whose href then never loads (floorplan's `layer-models.ts`). Both are
|
|
51
|
+
* authored relative to the drawing, so both need the same rebase; a model URL
|
|
52
|
+
* left behind resolves against the page and 404s.
|
|
53
|
+
*/
|
|
54
|
+
const ASSET_ATTR = /(\b(?:(?:xlink:)?href|data-model-3d)\s*=\s*)(?:"([^"]*)"|'([^']*)')/g;
|
|
55
|
+
/** In-document (`#id`) and empty references are not asset URLs. */
|
|
56
|
+
function isAssetUrl(url) {
|
|
57
|
+
return !!url && !url.startsWith('#');
|
|
51
58
|
}
|
|
52
|
-
/** Every distinct `<image>` asset
|
|
53
|
-
export function
|
|
54
|
-
const
|
|
59
|
+
/** Every distinct `<image>` asset URL in one SVG document string. */
|
|
60
|
+
export function collectSvgImageAssets(svg) {
|
|
61
|
+
const urls = new Set();
|
|
55
62
|
if (!svg.includes('<image'))
|
|
56
63
|
return [];
|
|
57
64
|
for (const tag of svg.match(IMAGE_TAG) ?? []) {
|
|
58
|
-
for (const match of execAll(
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
65
|
+
for (const match of execAll(ASSET_ATTR, tag)) {
|
|
66
|
+
const url = match[2] ?? match[3];
|
|
67
|
+
if (isAssetUrl(url))
|
|
68
|
+
urls.add(url);
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
|
-
return [...
|
|
71
|
+
return [...urls];
|
|
65
72
|
}
|
|
66
|
-
/** Rewrite every `<image>` asset
|
|
67
|
-
export function
|
|
73
|
+
/** Rewrite every `<image>` asset URL in one SVG document string. */
|
|
74
|
+
export function rewriteSvgImageAssets(svg, rewrite) {
|
|
68
75
|
if (!svg.includes('<image'))
|
|
69
76
|
return svg;
|
|
70
|
-
return svg.replace(IMAGE_TAG, (tag) => tag.replace(
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
77
|
+
return svg.replace(IMAGE_TAG, (tag) => tag.replace(ASSET_ATTR, (match, attr, doubleQuoted, singleQuoted) => {
|
|
78
|
+
const url = doubleQuoted ?? singleQuoted ?? '';
|
|
79
|
+
if (!isAssetUrl(url))
|
|
73
80
|
return match;
|
|
74
81
|
const quote = doubleQuoted !== undefined ? '"' : "'";
|
|
75
|
-
return `${attr}${quote}${rewrite(
|
|
82
|
+
return `${attr}${quote}${rewrite(url)}${quote}`;
|
|
76
83
|
}));
|
|
77
84
|
}
|
|
78
85
|
/* ── CSS text (`cssAsset`) — `url(…)`s are asset paths ────────────────────── */
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `$/` notation of the normalized expo form (plan G § 3) — the codec
|
|
3
|
+
* between a store ref and the canonical URL it aliases.
|
|
4
|
+
*
|
|
5
|
+
* Every object of an expo's content-addressed store lives at one URL fixed
|
|
6
|
+
* forever — `https://<expo>.expofp.com/immutable/<hash>/<name>`, the expo's
|
|
7
|
+
* production host whichever contour generated or serves the folder — and
|
|
8
|
+
* the store ref `$/<expo>/<hash>/<name>` is that URL's alias by pure prefix
|
|
9
|
+
* substitution. No document, page or base URL takes part, so a snapshot
|
|
10
|
+
* reads identically from a contour page, an integrator's host, a local
|
|
11
|
+
* `floorplan.js`, an inline copy or a Node tool. The two forms are
|
|
12
|
+
* equivalent and a creator compacts at will: shorter bytes, an explicit
|
|
13
|
+
* "store content" marker, and an alias that survives a domain move.
|
|
14
|
+
*
|
|
15
|
+
* The marker is unambiguous by generation invariant, not by URL illegality:
|
|
16
|
+
* a store only ever returns canonical URLs, and no passthrough value ever
|
|
17
|
+
* starts with `$/`. A ref left unexpanded by mistake breaks loudly — an
|
|
18
|
+
* honest 404 on `<base>/$/…` — where a `#/…` fragment would fail silently.
|
|
19
|
+
*
|
|
20
|
+
* The schema `.meta` walker (`visit-resources`) calls the codec at every
|
|
21
|
+
* URL-carrying place — manifest fields, document `$ref`s, CSS `url(…)`
|
|
22
|
+
* tokens, `<image>` hrefs of a drawing — so prose like `US$/day` is never
|
|
23
|
+
* touched, and the effective config only ever carries full URLs.
|
|
24
|
+
*/
|
|
25
|
+
/** Whether a URL-carrying value is a store ref (`$/…`). */
|
|
26
|
+
export declare function isStoreRef(value: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Expand a store ref into the canonical URL it aliases:
|
|
29
|
+
* `$/demo/ZL-InrvJgYpz1-k2/logo.png` →
|
|
30
|
+
* `https://demo.expofp.com/immutable/ZL-InrvJgYpz1-k2/logo.png`. Any other
|
|
31
|
+
* value — a full URL, a rooted or relative path, a `data:` URI — passes
|
|
32
|
+
* through untouched. A malformed `$/…` is a generation bug and throws.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveUrl(ref: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Compact a canonical store URL into its `$/` alias — the inverse of
|
|
37
|
+
* {@link resolveUrl}; any other URL passes through untouched.
|
|
38
|
+
*/
|
|
39
|
+
export declare function compactUrl(url: string): string;
|
|
40
|
+
//# sourceMappingURL=store-ref.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `$/` notation of the normalized expo form (plan G § 3) — the codec
|
|
3
|
+
* between a store ref and the canonical URL it aliases.
|
|
4
|
+
*
|
|
5
|
+
* Every object of an expo's content-addressed store lives at one URL fixed
|
|
6
|
+
* forever — `https://<expo>.expofp.com/immutable/<hash>/<name>`, the expo's
|
|
7
|
+
* production host whichever contour generated or serves the folder — and
|
|
8
|
+
* the store ref `$/<expo>/<hash>/<name>` is that URL's alias by pure prefix
|
|
9
|
+
* substitution. No document, page or base URL takes part, so a snapshot
|
|
10
|
+
* reads identically from a contour page, an integrator's host, a local
|
|
11
|
+
* `floorplan.js`, an inline copy or a Node tool. The two forms are
|
|
12
|
+
* equivalent and a creator compacts at will: shorter bytes, an explicit
|
|
13
|
+
* "store content" marker, and an alias that survives a domain move.
|
|
14
|
+
*
|
|
15
|
+
* The marker is unambiguous by generation invariant, not by URL illegality:
|
|
16
|
+
* a store only ever returns canonical URLs, and no passthrough value ever
|
|
17
|
+
* starts with `$/`. A ref left unexpanded by mistake breaks loudly — an
|
|
18
|
+
* honest 404 on `<base>/$/…` — where a `#/…` fragment would fail silently.
|
|
19
|
+
*
|
|
20
|
+
* The schema `.meta` walker (`visit-resources`) calls the codec at every
|
|
21
|
+
* URL-carrying place — manifest fields, document `$ref`s, CSS `url(…)`
|
|
22
|
+
* tokens, `<image>` hrefs of a drawing — so prose like `US$/day` is never
|
|
23
|
+
* touched, and the effective config only ever carries full URLs.
|
|
24
|
+
*/
|
|
25
|
+
const STORE_REF_PREFIX = '$/';
|
|
26
|
+
/** `$/<expo>/<key>` — the expo is one lowercase DNS label, the key is the rest verbatim. */
|
|
27
|
+
const STORE_REF = /^\$\/([a-z0-9-]+)\/(.+)$/;
|
|
28
|
+
/** The canonical store URL: the expo's production host, `/immutable/<key>`. */
|
|
29
|
+
const CANONICAL_URL = /^https:\/\/([a-z0-9-]+)\.expofp\.com\/immutable\/(.+)$/;
|
|
30
|
+
/** Whether a URL-carrying value is a store ref (`$/…`). */
|
|
31
|
+
export function isStoreRef(value) {
|
|
32
|
+
return value.startsWith(STORE_REF_PREFIX);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Expand a store ref into the canonical URL it aliases:
|
|
36
|
+
* `$/demo/ZL-InrvJgYpz1-k2/logo.png` →
|
|
37
|
+
* `https://demo.expofp.com/immutable/ZL-InrvJgYpz1-k2/logo.png`. Any other
|
|
38
|
+
* value — a full URL, a rooted or relative path, a `data:` URI — passes
|
|
39
|
+
* through untouched. A malformed `$/…` is a generation bug and throws.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveUrl(ref) {
|
|
42
|
+
if (!isStoreRef(ref))
|
|
43
|
+
return ref;
|
|
44
|
+
const match = STORE_REF.exec(ref);
|
|
45
|
+
if (!match)
|
|
46
|
+
throw new Error(`resolveUrl: malformed store ref ${JSON.stringify(ref)}`);
|
|
47
|
+
return `https://${match[1]}.expofp.com/immutable/${match[2]}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Compact a canonical store URL into its `$/` alias — the inverse of
|
|
51
|
+
* {@link resolveUrl}; any other URL passes through untouched.
|
|
52
|
+
*/
|
|
53
|
+
export function compactUrl(url) {
|
|
54
|
+
const match = CANONICAL_URL.exec(url);
|
|
55
|
+
return match ? `${STORE_REF_PREFIX}${match[1]}/${match[2]}` : url;
|
|
56
|
+
}
|
|
@@ -21,13 +21,23 @@ import { type Ref } from '@expofp/resolve';
|
|
|
21
21
|
import type * as z from 'zod';
|
|
22
22
|
export interface ResourceHandler {
|
|
23
23
|
/**
|
|
24
|
-
* One asset URL; return the URL to write in its place
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* One asset URL; return the URL to write in its place, or `undefined` to
|
|
25
|
+
* drop the field. Dropping is only meaningful for `optional` leaves — an
|
|
26
|
+
* `optionalAsset` tag marks a URL minted without an existence check, so the
|
|
27
|
+
* resource may not exist and storing it is best-effort (a store that finds
|
|
28
|
+
* it missing drops the field rather than write a dead URL; today's optional
|
|
29
|
+
* assets are all object fields, where a drop deletes the key).
|
|
27
30
|
*/
|
|
28
|
-
asset(url: string, optional?: boolean): string | Promise<string>;
|
|
31
|
+
asset(url: string, optional?: boolean): string | undefined | Promise<string | undefined>;
|
|
29
32
|
/** A ref's resolved — and internally rewritten — document; return the new ref URL. */
|
|
30
33
|
document?(refUrl: string, document: unknown): string | Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Rewrite a ref's URL in place WITHOUT resolving its document — the
|
|
36
|
+
* expansion hook for store refs (`$/…`) in a normalized manifest, where
|
|
37
|
+
* the document must stay lazy. When present it takes the whole ref
|
|
38
|
+
* branch: `document`/`documentError` do not fire on the same walk.
|
|
39
|
+
*/
|
|
40
|
+
ref?(refUrl: string): string | Promise<string>;
|
|
31
41
|
/**
|
|
32
42
|
* Tolerate a ref that failed to resolve — the field is dropped. Omitted,
|
|
33
43
|
* the failure propagates and aborts the walk; throw from here to abort
|
|
@@ -82,4 +92,8 @@ export declare function validateOrClone<T>(schema: z.ZodType, value: T): T;
|
|
|
82
92
|
export declare function documentRef<R extends Ref<unknown>>(ref: R, schema: z.ZodType, normalize?: (document: object) => void): R;
|
|
83
93
|
/** Rewrite every schema-tagged resource of `value` through `handler`, in place. */
|
|
84
94
|
export declare function visitResources(schema: z.ZodType, value: object, handler: ResourceHandler, options?: VisitResourcesOptions): Promise<void>;
|
|
95
|
+
/** Package-internal: `loadConfig`'s pointer-chain loop probes with it too. */
|
|
96
|
+
export declare function isRefShaped(value: unknown): value is {
|
|
97
|
+
$ref: string;
|
|
98
|
+
};
|
|
85
99
|
//# sourceMappingURL=visit-resources.d.ts.map
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
import { getResolved, isResolved, resolve } from '@expofp/resolve';
|
|
21
21
|
import { deepClone } from '@expofp/utils';
|
|
22
22
|
import { refSchemaOf, toJsonSchema, typeMatches } from './json-schema.js';
|
|
23
|
-
import { collectCssUrls,
|
|
23
|
+
import { collectCssUrls, collectSvgImageAssets, inlineCssImports, isAbsoluteUrl, refDirectory, rewriteCssUrls, rewriteSvgImageAssets, toAbsoluteUrl, } from './resource-urls.js';
|
|
24
|
+
import { isStoreRef, resolveUrl } from './store-ref.js';
|
|
24
25
|
import { timeValidation } from './validate-timing.js';
|
|
25
26
|
/**
|
|
26
27
|
* Gates every schema validation (`parse`) of the large expo documents —
|
|
@@ -82,7 +83,10 @@ export function documentRef(ref, schema, normalize) {
|
|
|
82
83
|
const base = refDirectory(refUrl);
|
|
83
84
|
const rebase = isAbsoluteUrl(base);
|
|
84
85
|
await visitResources(schema, document, {
|
|
85
|
-
|
|
86
|
+
// A store ref (`$/…` — normalized snapshots) expands by the fixed
|
|
87
|
+
// convention, wherever the document came from (store-ref.ts); other
|
|
88
|
+
// paths get the standard web rebase.
|
|
89
|
+
asset: (url) => (isStoreRef(url) ? resolveUrl(url) : rebase ? toAbsoluteUrl(url, base) : url),
|
|
86
90
|
});
|
|
87
91
|
return document;
|
|
88
92
|
};
|
|
@@ -105,6 +109,10 @@ async function walk(node, value, assign, handler, options) {
|
|
|
105
109
|
// Rooted refs are already local — honored verbatim, like every other
|
|
106
110
|
// rooted resource URL.
|
|
107
111
|
if (isRefShaped(value)) {
|
|
112
|
+
if (handler.ref) {
|
|
113
|
+
assign({ $ref: await handler.ref(value.$ref) });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
108
116
|
if (!handler.document || value.$ref.startsWith('/'))
|
|
109
117
|
return;
|
|
110
118
|
const refUrl = value.$ref;
|
|
@@ -138,10 +146,10 @@ async function walk(node, value, assign, handler, options) {
|
|
|
138
146
|
}
|
|
139
147
|
else if (node?.svgAsset) {
|
|
140
148
|
const rewrites = new Map();
|
|
141
|
-
for (const
|
|
142
|
-
rewrites.set(
|
|
149
|
+
for (const url of collectSvgImageAssets(value)) {
|
|
150
|
+
rewrites.set(url, await handler.asset(url));
|
|
143
151
|
}
|
|
144
|
-
assign(
|
|
152
|
+
assign(rewriteSvgImageAssets(value, (url) => rewrites.get(url) ?? url));
|
|
145
153
|
}
|
|
146
154
|
else if (node?.cssAsset) {
|
|
147
155
|
// a handler that can fetch stylesheets gets @imports spliced in first,
|
|
@@ -155,8 +163,10 @@ async function walk(node, value, assign, handler, options) {
|
|
|
155
163
|
for (const url of collectCssUrls(css)) {
|
|
156
164
|
if (external.has(url))
|
|
157
165
|
continue; // a kept @import's URL stays remote
|
|
158
|
-
//
|
|
159
|
-
|
|
166
|
+
// Only scheme-qualified CSS urls are fetchable resources (legacy
|
|
167
|
+
// parity) — plus store refs (`$/…`), which a normalized snapshot
|
|
168
|
+
// carries there and the load must expand back symmetrically.
|
|
169
|
+
if (isStoreRef(url) || /^[a-z][a-z0-9+.-]*:/i.test(url)) {
|
|
160
170
|
rewrites.set(url, await handler.asset(url));
|
|
161
171
|
}
|
|
162
172
|
}
|
|
@@ -189,7 +199,8 @@ async function walk(node, value, assign, handler, options) {
|
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
}
|
|
192
|
-
|
|
202
|
+
/** Package-internal: `loadConfig`'s pointer-chain loop probes with it too. */
|
|
203
|
+
export function isRefShaped(value) {
|
|
193
204
|
return (typeof value === 'object' && value !== null && '$ref' in value && typeof value.$ref === 'string');
|
|
194
205
|
}
|
|
195
206
|
/** The root is an object/array container — leaves rewrite inside it. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/config",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.29.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ExpoFP SDK internal: config layer and schemas",
|
|
6
6
|
"homepage": "https://developer.expofp.com/",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"debug": "^4.4.3",
|
|
30
30
|
"tslib": "^2.3.0",
|
|
31
31
|
"zod": "4.4.3",
|
|
32
|
-
"@expofp/resolve": "3.
|
|
33
|
-
"@expofp/schema": "3.
|
|
34
|
-
"@expofp/utils": "3.
|
|
32
|
+
"@expofp/resolve": "3.29.0",
|
|
33
|
+
"@expofp/schema": "3.29.0",
|
|
34
|
+
"@expofp/utils": "3.29.0"
|
|
35
35
|
}
|
|
36
36
|
}
|