@expofp/loader 1.0.54 → 1.0.57
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/dist/{index.js → bundle/bundle.js} +34 -32
- package/dist/bundle/bundle.js.map +1 -0
- package/dist/{downloadZip-D1Leqczj.js → bundle/downloadZip-D2QfL_V_.js} +2 -2
- package/dist/bundle/downloadZip-D2QfL_V_.js.map +1 -0
- package/dist/{makeOffline-DdJq2uGI.js → bundle/makeOffline-CBs7cwrM.js} +2 -2
- package/dist/bundle/makeOffline-CBs7cwrM.js.map +1 -0
- package/dist/{makeOfflineBundle-CdE5LdlF.js → bundle/makeOfflineBundle-BpFIeDmK.js} +3 -3
- package/dist/bundle/makeOfflineBundle-BpFIeDmK.js.map +1 -0
- package/dist/{importJson.d.ts → esm/importJson.d.ts} +1 -1
- package/dist/esm/importJson.js +52 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/loadAndWaitGlobal.js +157 -0
- package/dist/{mutateManifest.d.ts → esm/mutateManifest.d.ts} +1 -1
- package/dist/esm/mutateManifest.js +10 -0
- package/dist/esm/offline/downloadZip copy.js +89 -0
- package/dist/esm/offline/downloadZip.js +18 -0
- package/dist/esm/offline/hashString.js +16 -0
- package/dist/esm/offline/index.js +29 -0
- package/dist/{offline → esm/offline}/makeOffline.d.ts +1 -1
- package/dist/esm/offline/makeOffline.js +134 -0
- package/dist/esm/offline/makeOfflineBundle copy.js +92 -0
- package/dist/esm/offline/makeOfflineBundle.js +64 -0
- package/dist/esm/offline/slugify.js +61 -0
- package/dist/esm/offline/tools.js +69 -0
- package/dist/{resolve.d.ts → esm/resolve.d.ts} +1 -1
- package/dist/esm/resolve.js +331 -0
- package/dist/esm/resolvers/_OLD_expoResolver.js +49 -0
- package/dist/esm/resolvers/assetResolver.js +26 -0
- package/dist/{resolvers → esm/resolvers}/bundleAssetsResolver.d.ts +1 -1
- package/dist/esm/resolvers/bundleAssetsResolver.js +20 -0
- package/dist/{resolvers → esm/resolvers}/expoRuntimeBranchResolver.d.ts +1 -1
- package/dist/esm/resolvers/expoRuntimeBranchResolver.js +20 -0
- package/dist/{resolvers → esm/resolvers}/expoRuntimeGetBranchResolver.d.ts +1 -1
- package/dist/esm/resolvers/expoRuntimeGetBranchResolver.js +14 -0
- package/dist/{resolvers → esm/resolvers}/expoRuntimeResolver.d.ts +1 -1
- package/dist/esm/resolvers/expoRuntimeResolver.js +38 -0
- package/dist/{resolvers → esm/resolvers}/httpResolver.d.ts +1 -1
- package/dist/esm/resolvers/httpResolver.js +14 -0
- package/dist/{resolvers → esm/resolvers}/index.d.ts +1 -1
- package/dist/esm/resolvers/index.js +18 -0
- package/dist/{resolvers → esm/resolvers}/legacyAssetUrlsResolver.d.ts +1 -1
- package/dist/esm/resolvers/legacyAssetUrlsResolver.js +103 -0
- package/dist/{resolvers → esm/resolvers}/legacyDataResolver.d.ts +1 -1
- package/dist/esm/resolvers/legacyDataResolver.js +17 -0
- package/dist/esm/returnCachedRef.js +12 -0
- package/dist/esm/shared.js +270 -0
- package/dist/{types.d.ts → esm/types.d.ts} +1 -1
- package/dist/esm/types.js +1 -0
- package/package.json +14 -11
- package/dist/downloadZip-D1Leqczj.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/makeOffline-DdJq2uGI.js.map +0 -1
- package/dist/makeOfflineBundle-CdE5LdlF.js.map +0 -1
- /package/dist/{index.d.ts → esm/index.d.ts} +0 -0
- /package/dist/{loadAndWaitGlobal.d.ts → esm/loadAndWaitGlobal.d.ts} +0 -0
- /package/dist/{offline → esm/offline}/downloadZip copy.d.ts +0 -0
- /package/dist/{offline → esm/offline}/downloadZip.d.ts +0 -0
- /package/dist/{offline → esm/offline}/hashString.d.ts +0 -0
- /package/dist/{offline → esm/offline}/index.d.ts +0 -0
- /package/dist/{offline → esm/offline}/makeOfflineBundle copy.d.ts +0 -0
- /package/dist/{offline → esm/offline}/makeOfflineBundle.d.ts +0 -0
- /package/dist/{offline → esm/offline}/slugify.d.ts +0 -0
- /package/dist/{offline → esm/offline}/tools.d.ts +0 -0
- /package/dist/{resolvers → esm/resolvers}/_OLD_expoResolver.d.ts +0 -0
- /package/dist/{resolvers → esm/resolvers}/assetResolver.d.ts +0 -0
- /package/dist/{returnCachedRef.d.ts → esm/returnCachedRef.d.ts} +0 -0
- /package/dist/{shared.d.ts → esm/shared.d.ts} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// import { gunzipSync } from 'fflate';
|
|
2
|
+
// import untar from 'js-untar';
|
|
3
|
+
// import { log } from '../shared';
|
|
4
|
+
// import { makeOffline } from './makeOffline';
|
|
5
|
+
export {};
|
|
6
|
+
// export async function* makeOfflineBundle(
|
|
7
|
+
// manifest: unknown
|
|
8
|
+
// ): AsyncGenerator<{ path: string; data: ArrayBuffer }> {
|
|
9
|
+
// log('makeOfflineBundle', manifest);
|
|
10
|
+
// const offlineData = await makeOffline(manifest);
|
|
11
|
+
// for await (const file of offlineData.files) {
|
|
12
|
+
// if ('data' in file) {
|
|
13
|
+
// const jsonString = JSON.stringify(file.data, null, 2);
|
|
14
|
+
// yield {
|
|
15
|
+
// path: file.targetFilePath,
|
|
16
|
+
// data: new TextEncoder().encode(jsonString).buffer,
|
|
17
|
+
// };
|
|
18
|
+
// } else if ('url' in file) {
|
|
19
|
+
// const blob = await downloadFile(file.url);
|
|
20
|
+
// yield {
|
|
21
|
+
// path: file.targetFilePath,
|
|
22
|
+
// data: await blob.arrayBuffer(),
|
|
23
|
+
// };
|
|
24
|
+
// }
|
|
25
|
+
// }
|
|
26
|
+
// yield* generateJsLoaderFiles();
|
|
27
|
+
// const html = getIndexHtml(offlineData.manifest);
|
|
28
|
+
// yield {
|
|
29
|
+
// path: 'index.html',
|
|
30
|
+
// data: new TextEncoder().encode(html).buffer,
|
|
31
|
+
// };
|
|
32
|
+
// }
|
|
33
|
+
// const MAX_CONCURRENT_DOWNLOADS = 10;
|
|
34
|
+
// const queue = new Set<Promise<Blob>>();
|
|
35
|
+
// // have a queue to limit concurrent downloads
|
|
36
|
+
// async function downloadFile(url: string): Promise<Blob> {
|
|
37
|
+
// while (queue.size >= MAX_CONCURRENT_DOWNLOADS) {
|
|
38
|
+
// await Promise.race(queue);
|
|
39
|
+
// }
|
|
40
|
+
// const downloadPromise = (async () => {
|
|
41
|
+
// log('Fetching file for zip:', url);
|
|
42
|
+
// const response = await fetch(url);
|
|
43
|
+
// if (!response.ok) {
|
|
44
|
+
// throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
45
|
+
// }
|
|
46
|
+
// return await response.blob();
|
|
47
|
+
// })();
|
|
48
|
+
// queue.add(downloadPromise);
|
|
49
|
+
// try {
|
|
50
|
+
// return await downloadPromise;
|
|
51
|
+
// } finally {
|
|
52
|
+
// queue.delete(downloadPromise);
|
|
53
|
+
// }
|
|
54
|
+
// }
|
|
55
|
+
// async function* generateJsLoaderFiles(): AsyncGenerator<{ path: string; data: ArrayBuffer }> {
|
|
56
|
+
// const packageInfo = 'https://registry.npmjs.org/@expofp/js-loader';
|
|
57
|
+
// const response = await fetch(packageInfo);
|
|
58
|
+
// if (!response.ok) {
|
|
59
|
+
// throw new Error(`Failed to fetch ${packageInfo}: ${response.status} ${response.statusText}`);
|
|
60
|
+
// }
|
|
61
|
+
// const data = await response.json();
|
|
62
|
+
// const latestVersion = data['dist-tags'].latest;
|
|
63
|
+
// const tarballUrl = data.versions[latestVersion].dist.tarball;
|
|
64
|
+
// const tgzResponse = await fetch(tarballUrl);
|
|
65
|
+
// if (!tgzResponse.ok) {
|
|
66
|
+
// throw new Error(
|
|
67
|
+
// `Failed to fetch ${tarballUrl}: ${tgzResponse.status} ${tgzResponse.statusText}`
|
|
68
|
+
// );
|
|
69
|
+
// }
|
|
70
|
+
// const tgzArrayBuffer = await tgzResponse.arrayBuffer();
|
|
71
|
+
// // use fflate to convert tgz to tar
|
|
72
|
+
// const tarFile = gunzipSync(new Uint8Array(tgzArrayBuffer));
|
|
73
|
+
// const files = await untar(tarFile.buffer as ArrayBuffer);
|
|
74
|
+
// console.info('Extracted tar file from tgz, size:', files);
|
|
75
|
+
// // const arrayBuffer = await tgzResponse.arrayBuffer();
|
|
76
|
+
// // const files = await untar(arrayBuffer);
|
|
77
|
+
// for (const file of files) {
|
|
78
|
+
// console.info('Adding js-loader file to zip:', file.name);
|
|
79
|
+
// yield { path: 'efp-js-loader/' + file.name, data: file.buffer };
|
|
80
|
+
// }
|
|
81
|
+
// }
|
|
82
|
+
// function getIndexHtml(manifest: unknown) {
|
|
83
|
+
// const html = `
|
|
84
|
+
// <!DOCTYPE html>
|
|
85
|
+
// <script type="module">
|
|
86
|
+
// import { load } from './efp-js-loader/dist/loader.js';
|
|
87
|
+
// await load(${JSON.stringify(manifest)});
|
|
88
|
+
// console.info('🚀 FloorPlan loaded', floorplan);
|
|
89
|
+
// </script>
|
|
90
|
+
// `;
|
|
91
|
+
// return html;
|
|
92
|
+
// }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { log } from '../shared';
|
|
2
|
+
import { makeOffline } from './makeOffline';
|
|
3
|
+
import { resolve } from '..';
|
|
4
|
+
export async function* makeOfflineBundle(manifest) {
|
|
5
|
+
log('makeOfflineBundle', manifest);
|
|
6
|
+
const offlineData = await makeOffline(manifest);
|
|
7
|
+
for await (const file of offlineData.files) {
|
|
8
|
+
if ('data' in file) {
|
|
9
|
+
const jsonString = JSON.stringify(file.data, null, 2);
|
|
10
|
+
yield {
|
|
11
|
+
path: file.targetFilePath,
|
|
12
|
+
data: new TextEncoder().encode(jsonString).buffer,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
else if ('url' in file) {
|
|
16
|
+
const blob = await downloadFile(file.url);
|
|
17
|
+
yield {
|
|
18
|
+
path: file.targetFilePath,
|
|
19
|
+
data: await blob.arrayBuffer(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// yield* generateJsLoaderFiles();
|
|
24
|
+
const html = await getIndexHtml(offlineData.manifest);
|
|
25
|
+
yield {
|
|
26
|
+
path: 'index.html',
|
|
27
|
+
data: new TextEncoder().encode(html).buffer,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const MAX_CONCURRENT_DOWNLOADS = 10;
|
|
31
|
+
const queue = new Set();
|
|
32
|
+
// have a queue to limit concurrent downloads
|
|
33
|
+
async function downloadFile(url) {
|
|
34
|
+
while (queue.size >= MAX_CONCURRENT_DOWNLOADS) {
|
|
35
|
+
await Promise.race(queue);
|
|
36
|
+
}
|
|
37
|
+
const downloadPromise = (async () => {
|
|
38
|
+
log('Fetching file for zip:', url);
|
|
39
|
+
const response = await fetch(url);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
return await response.blob();
|
|
44
|
+
})();
|
|
45
|
+
queue.add(downloadPromise);
|
|
46
|
+
try {
|
|
47
|
+
return await downloadPromise;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
queue.delete(downloadPromise);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function getIndexHtml(manifest) {
|
|
54
|
+
const entryPoint = await resolve(manifest, '/runtime/entry');
|
|
55
|
+
const html = `
|
|
56
|
+
<!DOCTYPE html>
|
|
57
|
+
<script type="module">
|
|
58
|
+
import { load } from ${JSON.stringify(entryPoint)};
|
|
59
|
+
await load(${JSON.stringify(manifest)});
|
|
60
|
+
console.info('🚀 loaded');
|
|
61
|
+
</script>
|
|
62
|
+
`;
|
|
63
|
+
return html;
|
|
64
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Module-level caches
|
|
2
|
+
const inputToSlug = new Map();
|
|
3
|
+
const slugToInput = new Map();
|
|
4
|
+
const WINDOWS_RESERVED = new Set([
|
|
5
|
+
"con", "prn", "aux", "nul",
|
|
6
|
+
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
|
|
7
|
+
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
|
|
8
|
+
]);
|
|
9
|
+
function makeBaseSlug(input) {
|
|
10
|
+
let slug = input
|
|
11
|
+
.normalize("NFKD")
|
|
12
|
+
// All non letters/numbers → "-"
|
|
13
|
+
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
|
|
14
|
+
// Collapse multiple "-"
|
|
15
|
+
.replace(/-+/g, "-")
|
|
16
|
+
// Trim "-" from start/end
|
|
17
|
+
.replace(/^-|-$/g, "")
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
// Strip forbidden Windows characters just in case
|
|
20
|
+
slug = slug.replace(/[<>:"/\\|?*]/g, "");
|
|
21
|
+
// Windows forbids trailing space/period
|
|
22
|
+
slug = slug.replace(/[. ]+$/g, "");
|
|
23
|
+
// Fallback if everything was stripped
|
|
24
|
+
if (!slug)
|
|
25
|
+
slug = "file";
|
|
26
|
+
// Avoid bare reserved device names
|
|
27
|
+
if (WINDOWS_RESERVED.has(slug)) {
|
|
28
|
+
slug += "-file";
|
|
29
|
+
}
|
|
30
|
+
return slug;
|
|
31
|
+
}
|
|
32
|
+
export function slugifyFsUnique(input) {
|
|
33
|
+
// If we've seen this exact input before, return the same slug
|
|
34
|
+
const existing = inputToSlug.get(input);
|
|
35
|
+
if (existing)
|
|
36
|
+
return existing;
|
|
37
|
+
const base = makeBaseSlug(input);
|
|
38
|
+
let candidate = base;
|
|
39
|
+
let counter = 2;
|
|
40
|
+
while (true) {
|
|
41
|
+
const existingInput = slugToInput.get(candidate);
|
|
42
|
+
if (!existingInput) {
|
|
43
|
+
// Free slug → claim it for this input
|
|
44
|
+
slugToInput.set(candidate, input);
|
|
45
|
+
inputToSlug.set(input, candidate);
|
|
46
|
+
return candidate;
|
|
47
|
+
}
|
|
48
|
+
if (existingInput === input) {
|
|
49
|
+
// Same input somehow (super defensive)
|
|
50
|
+
inputToSlug.set(input, candidate);
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
// Collision: same slug already used by different input → add suffix
|
|
54
|
+
candidate = `${base}-${counter++}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// // Optional: to reset between runs/tests
|
|
58
|
+
// export function resetSlugCache() {
|
|
59
|
+
// inputToSlug.clear();
|
|
60
|
+
// slugToInput.clear();
|
|
61
|
+
// }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { hashString } from './hashString';
|
|
2
|
+
import { slugifyFsUnique } from './slugify';
|
|
3
|
+
export function makeUniqueJsonTargetPathFromString(str, namespace = '') {
|
|
4
|
+
// const hash = hashString(str);
|
|
5
|
+
let result = slugifyFsUnique(str); // + '-' + hash;
|
|
6
|
+
if (namespace) {
|
|
7
|
+
result = `${slugifyFsUnique(namespace)}$${result}`;
|
|
8
|
+
}
|
|
9
|
+
if (result.endsWith('/')) {
|
|
10
|
+
result += 'index.json';
|
|
11
|
+
}
|
|
12
|
+
else if (!result.endsWith('.json')) {
|
|
13
|
+
result += '.json';
|
|
14
|
+
}
|
|
15
|
+
return './' + result;
|
|
16
|
+
// handle directory case
|
|
17
|
+
}
|
|
18
|
+
export function makeTargetPathFromUrl(url, prefix = '') {
|
|
19
|
+
// https://example.com/dir1/dir2/a.js => "{prefix}{origin-slug}/dir1/dir2/a.js";
|
|
20
|
+
// https://example.com/dir1/dir2/a.js?params => "{prefix}{origin-slug}/dir1/dir2/a{paramsmd5hash}.js";
|
|
21
|
+
// use slugify.ts
|
|
22
|
+
try {
|
|
23
|
+
new URL(url);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
debugger;
|
|
27
|
+
}
|
|
28
|
+
const urlObj = new URL(url);
|
|
29
|
+
const origin = `${urlObj.protocol}//${urlObj.host}`;
|
|
30
|
+
const originSlug = slugifyFsUnique(origin);
|
|
31
|
+
let pathname = urlObj.pathname;
|
|
32
|
+
let search = urlObj.search;
|
|
33
|
+
// if path doesn't end with extension, throw
|
|
34
|
+
if (!pathname.match(/\.[^\/]+$/)) {
|
|
35
|
+
throw new Error(`Cannot make target path from URL without file extension: ${url}`);
|
|
36
|
+
}
|
|
37
|
+
const extension = pathname.substring(pathname.lastIndexOf('.'));
|
|
38
|
+
let pathnameWithoutExtension = pathname.substring(0, pathname.lastIndexOf('.'));
|
|
39
|
+
// check the pathname contains only valid fs characters
|
|
40
|
+
const invalidPathnameChars = pathnameWithoutExtension.match(/[^a-zA-Z0-9\-._\/]/g);
|
|
41
|
+
if (invalidPathnameChars) {
|
|
42
|
+
const fixedPathnameWithoutExtension = slugifyFsUnique(pathnameWithoutExtension);
|
|
43
|
+
console.warn(`Warning: pathname contains invalid filesystem characters (${[
|
|
44
|
+
...new Set(invalidPathnameChars),
|
|
45
|
+
].join(', ')}), slugifying it: ${pathnameWithoutExtension}${extension} => ${fixedPathnameWithoutExtension}${extension}`);
|
|
46
|
+
pathnameWithoutExtension = fixedPathnameWithoutExtension;
|
|
47
|
+
}
|
|
48
|
+
pathname = pathnameWithoutExtension + extension;
|
|
49
|
+
if (pathname.length > 120) {
|
|
50
|
+
console.warn(`Warning: pathname is too long (${pathname.length} characters), truncating to 150 characters: ${pathname}`);
|
|
51
|
+
pathname = pathname.substring(0, 120 - extension.length) + extension;
|
|
52
|
+
}
|
|
53
|
+
if (search) {
|
|
54
|
+
// create a hash from search params
|
|
55
|
+
const hash = hashString(search);
|
|
56
|
+
const dotIndex = pathname.lastIndexOf('.');
|
|
57
|
+
if (dotIndex !== -1) {
|
|
58
|
+
pathname = `${pathname.slice(0, dotIndex)}.${hash}${pathname.slice(dotIndex)}`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
pathname = `${pathname}${hash}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// // handle directory case
|
|
65
|
+
// if (pathname.endsWith('/')) {
|
|
66
|
+
// pathname += '__index.json';
|
|
67
|
+
// }
|
|
68
|
+
return `./${prefix}${originSlug}${pathname}`;
|
|
69
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { resolvers } from './resolvers';
|
|
2
|
+
import { createMergedObjectWithOverridenNonRefProps, deepFreeze, log, replaceObjectFields, } from './shared';
|
|
3
|
+
const globalRefCache = new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a value from an object using a JSON Pointer RFC 6901.
|
|
6
|
+
*
|
|
7
|
+
* @param object - The object to resolve the value from
|
|
8
|
+
* @param jsonPointer - A JSON Pointer string that must start with '/'
|
|
9
|
+
* @param options - Optional context for resolution, including cache and fetch callback
|
|
10
|
+
* @returns A promise that resolves to the value at the specified JSON Pointer path
|
|
11
|
+
* @throws {Error} If the JSON Pointer is not valid or if a $ref cannot be resolved
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const obj = { foo: { bar: 'baz' } };
|
|
16
|
+
* const result = await resolve(obj, '/foo/bar');
|
|
17
|
+
* // result === 'baz'
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export async function resolve(object, jsonPointer, options) {
|
|
21
|
+
log('Resolving:', jsonPointer, 'of', object, options);
|
|
22
|
+
if (typeof window !== 'undefined') {
|
|
23
|
+
window['__lastResolveObject'] = object;
|
|
24
|
+
}
|
|
25
|
+
const resolveContext = {
|
|
26
|
+
importCallback: options?.importCallback,
|
|
27
|
+
refCache: options?.refCache || globalRefCache,
|
|
28
|
+
forceFetch: !!options?.forceFetch,
|
|
29
|
+
signal: options?.signal || null,
|
|
30
|
+
};
|
|
31
|
+
if (typeof jsonPointer !== 'string') {
|
|
32
|
+
throw new Error(`Invalid JSON Pointer (not a string): ${jsonPointer}`);
|
|
33
|
+
}
|
|
34
|
+
if (!jsonPointer.startsWith('/')) {
|
|
35
|
+
throw new Error(`Invalid JSON Pointer: ${jsonPointer}`);
|
|
36
|
+
}
|
|
37
|
+
if (jsonPointer.length > 1 && jsonPointer.endsWith('/')) {
|
|
38
|
+
throw new Error(`Invalid JSON Pointer: ${jsonPointer}`);
|
|
39
|
+
}
|
|
40
|
+
// if (jsonPointer === '/') {
|
|
41
|
+
// const result = await resolveObject(object, resolveContext);
|
|
42
|
+
// if (options?.mutate) {
|
|
43
|
+
// replaceObjectFields(object, result);
|
|
44
|
+
// }
|
|
45
|
+
// return result;
|
|
46
|
+
// }
|
|
47
|
+
let parts;
|
|
48
|
+
if (jsonPointer === '/') {
|
|
49
|
+
parts = [];
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
parts = jsonPointer.substring(1).split('/');
|
|
53
|
+
}
|
|
54
|
+
if (parts.some((part) => part.length === 0))
|
|
55
|
+
throw new Error(`Invalid JSON Pointer (empty part): ${jsonPointer}`);
|
|
56
|
+
if (options?.mutate) {
|
|
57
|
+
// prepend parts with 'root'
|
|
58
|
+
parts.unshift('root');
|
|
59
|
+
const target = { root: object };
|
|
60
|
+
await resovlePartsRecursiveMutate(target, parts, 0, resolveContext);
|
|
61
|
+
replaceObjectFields(object, target.root);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return await resovlePartsRecursive(object, parts, 0, resolveContext);
|
|
65
|
+
}
|
|
66
|
+
// prepend parts with 'root'
|
|
67
|
+
// parts.unshift('root');
|
|
68
|
+
// // if (parts.some((part) => part.length === 0))
|
|
69
|
+
// // throw new Error(`Invalid JSON Pointer (empty part): ${jsonPointer}`);
|
|
70
|
+
// const target = { root: object };
|
|
71
|
+
// const value = await resovlePartsRecursiveMutate(
|
|
72
|
+
// target,
|
|
73
|
+
// // 'root',
|
|
74
|
+
// parts,
|
|
75
|
+
// 0,
|
|
76
|
+
// resolveContext
|
|
77
|
+
// );
|
|
78
|
+
// if (options?.mutate) {
|
|
79
|
+
// replaceObjectFields(object, target.root);
|
|
80
|
+
// }
|
|
81
|
+
// log(`Resolved ${jsonPointer}:`, value);
|
|
82
|
+
// return value;
|
|
83
|
+
}
|
|
84
|
+
// TODO: use library for JSON Pointer resolution
|
|
85
|
+
async function resovlePartsRecursiveMutate(node,
|
|
86
|
+
// key: string,
|
|
87
|
+
parts, partsIndex, context) {
|
|
88
|
+
const key = parts[partsIndex];
|
|
89
|
+
// console.warn('in', key);
|
|
90
|
+
await resolveObjectMutate(node, key, context);
|
|
91
|
+
if (partsIndex == parts.length - 1)
|
|
92
|
+
return;
|
|
93
|
+
if (node[key] === null || typeof node[key] !== 'object') {
|
|
94
|
+
// console.error('Cannot resolve path, encountered non-object:', node, key);
|
|
95
|
+
throw new Error(`Cannot resolve path, encountered non-object at part '${parts.slice(1).join('/')}', index ${partsIndex - 1}'`);
|
|
96
|
+
}
|
|
97
|
+
return await resovlePartsRecursiveMutate(node[key], parts, partsIndex + 1, context);
|
|
98
|
+
}
|
|
99
|
+
async function resolveObjectMutate(node, key, context) {
|
|
100
|
+
log('resolveObjectMutate:', node, key);
|
|
101
|
+
let obj = node[key];
|
|
102
|
+
const result = await resolveObject(obj, context);
|
|
103
|
+
if (result !== undefined)
|
|
104
|
+
node[key] = result;
|
|
105
|
+
}
|
|
106
|
+
// TODO: use library for JSON Pointer resolution
|
|
107
|
+
async function resovlePartsRecursive(object, parts, index, context) {
|
|
108
|
+
// should return undefined if last part not found
|
|
109
|
+
// if not last part found - throw error
|
|
110
|
+
// do not resolve object if its children are not needed (needed part exist as key)
|
|
111
|
+
if (index === parts.length) {
|
|
112
|
+
// console.warn('Resolving last part:', object);
|
|
113
|
+
return await resolveObject(object, context);
|
|
114
|
+
}
|
|
115
|
+
if (object === null || typeof object !== 'object') {
|
|
116
|
+
throw new Error(`Cannot resolve path, encountered non-object at part '${parts.join('/')}', index ${index}'`);
|
|
117
|
+
}
|
|
118
|
+
const part = parts[index];
|
|
119
|
+
let child = object[part];
|
|
120
|
+
if (child === undefined) {
|
|
121
|
+
// console.warn('Resolving, part not found during resolve:', part, object);
|
|
122
|
+
const resolvedObject = await resolveObject(object, context);
|
|
123
|
+
child = resolvedObject[part];
|
|
124
|
+
}
|
|
125
|
+
return await resovlePartsRecursive(child, parts, index + 1, context);
|
|
126
|
+
}
|
|
127
|
+
async function resolveObject(object, context) {
|
|
128
|
+
log('resolveObject:', object);
|
|
129
|
+
let obj = object;
|
|
130
|
+
do {
|
|
131
|
+
if (typeof obj !== 'object' || obj === null || !('$ref' in obj)) {
|
|
132
|
+
return obj;
|
|
133
|
+
}
|
|
134
|
+
const resolversToUse = resolvers.filter((r) => canResolve(r, obj.$ref));
|
|
135
|
+
if (resolversToUse.length === 0) {
|
|
136
|
+
if (obj.$ref) {
|
|
137
|
+
throw new Error(`No resolver found for ref: ${obj.$ref}`);
|
|
138
|
+
}
|
|
139
|
+
deepFreeze(obj);
|
|
140
|
+
return obj;
|
|
141
|
+
}
|
|
142
|
+
if (resolversToUse.length > 1) {
|
|
143
|
+
throw new Error(`Multiple resolvers can resolve ref: ${obj.$ref}`);
|
|
144
|
+
}
|
|
145
|
+
const data = await resolverResolve(resolversToUse[0], obj.$ref, context);
|
|
146
|
+
obj = createMergedObjectWithOverridenNonRefProps(data, obj);
|
|
147
|
+
} while (true);
|
|
148
|
+
}
|
|
149
|
+
export async function resolverResolve(resolver, ref, context) {
|
|
150
|
+
if (!canResolve(resolver, ref)) {
|
|
151
|
+
throw new Error(`Unexpected ref: ${ref}`);
|
|
152
|
+
}
|
|
153
|
+
return resolver.resolveRef(ref, context);
|
|
154
|
+
}
|
|
155
|
+
export function canResolve(resolver, ref) {
|
|
156
|
+
if (resolver.canResolve) {
|
|
157
|
+
return resolver.canResolve(ref);
|
|
158
|
+
}
|
|
159
|
+
if (resolver.schema) {
|
|
160
|
+
return canResolveRefSchema(ref, resolver.schema);
|
|
161
|
+
}
|
|
162
|
+
throw new Error('Resolver is missing canResolve method and schema property');
|
|
163
|
+
}
|
|
164
|
+
export function canResolveRefSchema(ref, prefixBase) {
|
|
165
|
+
const prefixes = [`${prefixBase}+`, `${prefixBase}:`];
|
|
166
|
+
for (const prefix of prefixes) {
|
|
167
|
+
if (ref.startsWith(prefix)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
export function parseRefValue(ref) {
|
|
174
|
+
// look for anything before ":" or before +https:// or before +http://
|
|
175
|
+
// https://www -> https://www
|
|
176
|
+
// so some+base+https://www -> https://www
|
|
177
|
+
// base+https://www -> https://www
|
|
178
|
+
// base:abc -> abc
|
|
179
|
+
// "some-base" can contain only non-:
|
|
180
|
+
if (ref.startsWith('http://') || ref.startsWith('https://')) {
|
|
181
|
+
return ref;
|
|
182
|
+
}
|
|
183
|
+
// if it is something+http:// or something+https://
|
|
184
|
+
const plusHttpIndex = ref.indexOf('+http://');
|
|
185
|
+
const plusHttpsIndex = ref.indexOf('+https://');
|
|
186
|
+
if (plusHttpIndex !== -1) {
|
|
187
|
+
return ref.substring(plusHttpIndex + 1);
|
|
188
|
+
}
|
|
189
|
+
if (plusHttpsIndex !== -1) {
|
|
190
|
+
return ref.substring(plusHttpsIndex + 1);
|
|
191
|
+
}
|
|
192
|
+
// if it is something:abc
|
|
193
|
+
const colonIndex = ref.indexOf(':');
|
|
194
|
+
if (colonIndex !== -1) {
|
|
195
|
+
return ref.substring(colonIndex + 1);
|
|
196
|
+
}
|
|
197
|
+
throw new Error(`Error getting URL from: ${ref}, no valid prefix found`);
|
|
198
|
+
}
|
|
199
|
+
// export function parseRefValue(ref: string): string {
|
|
200
|
+
// // look for anything before ":" or before +https:// or before +http://
|
|
201
|
+
// // https://www -> https://www
|
|
202
|
+
// // so some+base+https://www -> https://www
|
|
203
|
+
// // base+https://www -> https://www
|
|
204
|
+
// // base:abc -> abc
|
|
205
|
+
// // "some-base" can contain only non-:
|
|
206
|
+
// if (ref.startsWith('http://') || ref.startsWith('https://')) {
|
|
207
|
+
// return ref;
|
|
208
|
+
// }
|
|
209
|
+
// // if it is something+http:// or something+https://
|
|
210
|
+
// const plusHttpIndex = ref.indexOf('+http://');
|
|
211
|
+
// const plusHttpsIndex = ref.indexOf('+https://');
|
|
212
|
+
// if (plusHttpIndex !== -1) {
|
|
213
|
+
// return ref.substring(plusHttpIndex + 1);
|
|
214
|
+
// }
|
|
215
|
+
// if (plusHttpsIndex !== -1) {
|
|
216
|
+
// return ref.substring(plusHttpsIndex + 1);
|
|
217
|
+
// }
|
|
218
|
+
// // if it is something:abc
|
|
219
|
+
// const colonIndex = ref.indexOf(':');
|
|
220
|
+
// if (colonIndex !== -1) {
|
|
221
|
+
// return ref.substring(colonIndex + 1);
|
|
222
|
+
// }
|
|
223
|
+
// throw new Error(`Error getting URL from: ${ref}, no valid prefix found`);
|
|
224
|
+
// // const prefixBase = ref.split('+')[0].split(':')[0];
|
|
225
|
+
// // if (!prefixBase) {
|
|
226
|
+
// // throw new Error(`Invalid ref, missing prefix: ${ref}`);
|
|
227
|
+
// // }
|
|
228
|
+
// // return ref.substring(prefixBase.length + 1);
|
|
229
|
+
// // const prefixes = [`${prefixBase}+`, `${prefixBase}:`];
|
|
230
|
+
// // for (const prefix of prefixes) {
|
|
231
|
+
// // if (ref.startsWith(prefix)) {
|
|
232
|
+
// // const url = ref.substring(prefix.length);
|
|
233
|
+
// // if (!url) {
|
|
234
|
+
// // throw new Error(`Invalid ref, missing URL after prefix "${prefix}": ${ref}`);
|
|
235
|
+
// // }
|
|
236
|
+
// // return url;
|
|
237
|
+
// // }
|
|
238
|
+
// // }
|
|
239
|
+
// // throw new Error(
|
|
240
|
+
// // `Error getting URL from: ${ref}, none of the prefixes matched: ${prefixes.join(', ')}`
|
|
241
|
+
// // );
|
|
242
|
+
// }
|
|
243
|
+
// export function parseRefValue(ref: string, prefixBase: string): string {
|
|
244
|
+
// const prefixes = [`${prefixBase}+`, `${prefixBase}:`];
|
|
245
|
+
// for (const prefix of prefixes) {
|
|
246
|
+
// if (ref.startsWith(prefix)) {
|
|
247
|
+
// const url = ref.substring(prefix.length);
|
|
248
|
+
// if (!url) {
|
|
249
|
+
// throw new Error(`Invalid ref, missing URL after prefix "${prefix}": ${ref}`);
|
|
250
|
+
// }
|
|
251
|
+
// return url;
|
|
252
|
+
// }
|
|
253
|
+
// }
|
|
254
|
+
// throw new Error(
|
|
255
|
+
// `Error getting URL from: ${ref}, none of the prefixes matched: ${prefixes.join(', ')}`
|
|
256
|
+
// );
|
|
257
|
+
// }
|
|
258
|
+
// const importJsonNative = new Function('url', 'return import(url, { with: { type: "json" } });') as (
|
|
259
|
+
// url: string
|
|
260
|
+
// ) => Promise<any>;
|
|
261
|
+
if (typeof window !== 'undefined') {
|
|
262
|
+
window['__debugResolve'] = async function debugResolve(objectOrRef, refOrContext, context) {
|
|
263
|
+
let object;
|
|
264
|
+
let ref;
|
|
265
|
+
let resolveContext;
|
|
266
|
+
if (typeof objectOrRef === 'string') {
|
|
267
|
+
object = window['__lastResolveObject'];
|
|
268
|
+
ref = objectOrRef;
|
|
269
|
+
resolveContext = refOrContext;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
object = objectOrRef;
|
|
273
|
+
ref = refOrContext;
|
|
274
|
+
resolveContext = context;
|
|
275
|
+
}
|
|
276
|
+
return await resolve(object, ref, resolveContext);
|
|
277
|
+
};
|
|
278
|
+
// (window as any)['__dd'] = async function dd() {
|
|
279
|
+
// const url = 'https://demo.expofp.com/manifest.json';
|
|
280
|
+
// // const imp = import;
|
|
281
|
+
// const zz = (await importJsonNative(url)).default;
|
|
282
|
+
// console.warn('debugResolve demo manifest:', zz);
|
|
283
|
+
// // let object: any;
|
|
284
|
+
// // let ref: string;
|
|
285
|
+
// // let resolveContext: ResolveOptions | undefined;
|
|
286
|
+
// // if (typeof objectOrRef === 'string') {
|
|
287
|
+
// // object = (window as any)['__lastResolveObject'];
|
|
288
|
+
// // ref = objectOrRef;
|
|
289
|
+
// // resolveContext = refOrContext;
|
|
290
|
+
// // } else {
|
|
291
|
+
// // object = objectOrRef;
|
|
292
|
+
// // ref = refOrContext;
|
|
293
|
+
// // resolveContext = context;
|
|
294
|
+
// // }
|
|
295
|
+
// // return await resolve(object, ref, resolveContext);
|
|
296
|
+
// };
|
|
297
|
+
}
|
|
298
|
+
// if (index === parts.length - 1) {
|
|
299
|
+
// return await resolveObject(object, context);
|
|
300
|
+
// }
|
|
301
|
+
// if (object === null || typeof object !== 'object') {
|
|
302
|
+
// throw new Error(
|
|
303
|
+
// `Cannot resolve path, encountered non-object at part '${parts.join('/')}', index ${index}'`
|
|
304
|
+
// );
|
|
305
|
+
// }
|
|
306
|
+
// if (!(parts[0] in object)) {
|
|
307
|
+
// return undefined;
|
|
308
|
+
// }
|
|
309
|
+
// let partIndex = 0;
|
|
310
|
+
// let currentObject = await resolveObject(object, context);
|
|
311
|
+
// while (partIndex < parts.length) {
|
|
312
|
+
// const part = parts[partIndex];
|
|
313
|
+
// if (typeof currentObject !== 'object' || currentObject === null) {
|
|
314
|
+
// throw new Error(
|
|
315
|
+
// `Cannot resolve path, encountered non-object at part '${parts.join(
|
|
316
|
+
// '/'
|
|
317
|
+
// )}, index ${partIndex}'`
|
|
318
|
+
// );
|
|
319
|
+
// }
|
|
320
|
+
// // console.info(
|
|
321
|
+
// // 'resolvePath currentObject:',
|
|
322
|
+
// // part,
|
|
323
|
+
// // JSON.stringify(currentObject, null, 2),
|
|
324
|
+
// // currentObject[part]
|
|
325
|
+
// // );
|
|
326
|
+
// // debugger;
|
|
327
|
+
// const child = currentObject[part];
|
|
328
|
+
// currentObject = await resolveObject(child, context);
|
|
329
|
+
// partIndex++;
|
|
330
|
+
// }
|
|
331
|
+
// return currentObject;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// import { parseRefValue } from '../resolve';
|
|
2
|
+
// import { log } from '../shared';
|
|
3
|
+
// import type { Resolver } from '../types';
|
|
4
|
+
// import { httpResolver } from './httpResolver';
|
|
5
|
+
export {};
|
|
6
|
+
// // const SCHEMA = 'expo';
|
|
7
|
+
// export const expoResolver: Resolver = {
|
|
8
|
+
// // canResolve(ref: string) {
|
|
9
|
+
// // return canResolveRefSchema(ref, SCHEMA);
|
|
10
|
+
// // },
|
|
11
|
+
// schema: 'expo',
|
|
12
|
+
// async resolveRef(ref: string) {
|
|
13
|
+
// log('expoResolver resolveRef:', ref);
|
|
14
|
+
// // if (!this.canResolve(ref)) throw new Error(`Unexpected ref in httpResolver: ${ref}`);
|
|
15
|
+
// const expo = parseRefValue(ref);
|
|
16
|
+
// // try to resolve {$ref: `https://${expo}.expofp.com/manifest.json`}
|
|
17
|
+
// try {
|
|
18
|
+
// return await httpResolver.resolveRef(`https://${expo}.expofp.com/manifest.json`);
|
|
19
|
+
// } catch (error) {
|
|
20
|
+
// log(`Falling back to legacyDataUrlBase (no manifest for '${expo}')`);
|
|
21
|
+
// }
|
|
22
|
+
// // ET: fallback, remove in the future, when all expos support manifest.json
|
|
23
|
+
// // and throw instead
|
|
24
|
+
// const data = {
|
|
25
|
+
// expo,
|
|
26
|
+
// runtime: { $ref: `expo-runtime-get-branch:${expo}` },
|
|
27
|
+
// legacyData: { $ref: `legacy-data:https://${expo}.expofp.com/data/` },
|
|
28
|
+
// };
|
|
29
|
+
// return data;
|
|
30
|
+
// },
|
|
31
|
+
// // async *makeOfflineRef(ref: string) {
|
|
32
|
+
// // log('expoRuntimeBranchResolver makeOfflineRef:', ref);
|
|
33
|
+
// // if (!this.canResolve(ref)) throw new Error(`Unexpected ref: ${ref}`);
|
|
34
|
+
// // const value = parseRefValue(ref);
|
|
35
|
+
// // const refData = deepClone(await this.resolveRef(ref));
|
|
36
|
+
// // const targetFilePath = makeUniqueJsonTargetPathFromString(value, SCHEMA_PREFIX_BASE);
|
|
37
|
+
// // const data = yield* makeOfflineInternal(refData);
|
|
38
|
+
// // yield { data: data, targetFilePath };
|
|
39
|
+
// // return { $ref: targetFilePath, [STOP_RESOLVING]: true };
|
|
40
|
+
// // },
|
|
41
|
+
// // async *makeOfflineRef2(ref: string) {
|
|
42
|
+
// // const refData = deepClone(await this.resolveRef(ref));
|
|
43
|
+
// // const targetFilePath = makeUniqueJsonTargetPathFromString(ref);
|
|
44
|
+
// // const data = yield* makeOfflineInternal(refData);
|
|
45
|
+
// // yield { data, targetFilePath };
|
|
46
|
+
// // return targetFilePath;
|
|
47
|
+
// // },
|
|
48
|
+
// offlineMethod: 'resolveRef',
|
|
49
|
+
// };
|