repl-sdk 0.0.0 → 1.0.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.
Files changed (61) hide show
  1. package/dist/assets/tar-worker-kdkltuRC.js +598 -0
  2. package/dist/assets/tar-worker-kdkltuRC.js.map +1 -0
  3. package/dist/codemirror-D4aIVflZ.js +110 -0
  4. package/dist/codemirror-D4aIVflZ.js.map +1 -0
  5. package/dist/gjs-CzFzkEFv.js +173 -0
  6. package/dist/gjs-CzFzkEFv.js.map +1 -0
  7. package/dist/gmd-D9OXs2v3.js +166 -0
  8. package/dist/gmd-D9OXs2v3.js.map +1 -0
  9. package/dist/hbs-CuhWjffM.js +62 -0
  10. package/dist/hbs-CuhWjffM.js.map +1 -0
  11. package/dist/index-CUWCqMoD.js +2133 -0
  12. package/dist/index-CUWCqMoD.js.map +1 -0
  13. package/dist/index.js +4 -104
  14. package/dist/index.js.map +1 -1
  15. package/dist/parse-aBKk9rfS.js +328 -0
  16. package/dist/parse-aBKk9rfS.js.map +1 -0
  17. package/dist/render-app-island-B-i8rvGi.js +61 -0
  18. package/dist/render-app-island-B-i8rvGi.js.map +1 -0
  19. package/package.json +82 -9
  20. package/src/cache.js +138 -0
  21. package/src/cdn.js +93 -0
  22. package/src/codemirror.js +161 -0
  23. package/src/compilers/ember/gjs.js +212 -0
  24. package/src/compilers/ember/gmd.js +190 -0
  25. package/src/compilers/ember/hbs.js +98 -0
  26. package/src/compilers/ember/render-app-island.js +83 -0
  27. package/src/compilers/ember.js +166 -0
  28. package/src/compilers/js.js +32 -0
  29. package/src/compilers/markdown/build-compiler.js +151 -0
  30. package/src/compilers/markdown/const.js +2 -0
  31. package/src/compilers/markdown/heading-id.js +75 -0
  32. package/src/compilers/markdown/live-code-extraction.js +198 -0
  33. package/src/compilers/markdown/parse.js +22 -0
  34. package/src/compilers/markdown/parse.test.ts +363 -0
  35. package/src/compilers/markdown/sanitize-for-glimmer.js +26 -0
  36. package/src/compilers/markdown/types.ts +21 -0
  37. package/src/compilers/markdown/utils.js +78 -0
  38. package/src/compilers/markdown.js +125 -0
  39. package/src/compilers/mermaid.js +35 -0
  40. package/src/compilers/react.js +47 -0
  41. package/src/compilers/svelte.js +116 -0
  42. package/src/compilers/vue.js +58 -0
  43. package/src/compilers.js +108 -0
  44. package/src/es-module-shim.js +53 -0
  45. package/src/index.d.ts +53 -4
  46. package/src/index.js +744 -89
  47. package/src/npm.js +58 -0
  48. package/src/request.Request.test.ts +59 -0
  49. package/src/request.js +140 -0
  50. package/src/resolve.fromImports.test.ts +35 -0
  51. package/src/resolve.fromInternalImport.test.ts +69 -0
  52. package/src/resolve.js +352 -0
  53. package/src/resolve.resolvePath.test.ts +24 -0
  54. package/src/resolve.test.ts +23 -0
  55. package/src/specifier.js +71 -0
  56. package/src/specifier.test.ts +90 -0
  57. package/src/tar-worker.js +61 -0
  58. package/src/tar.js +76 -0
  59. package/src/types.ts +335 -58
  60. package/src/utils.js +28 -1
  61. package/declarations/index.d.ts +0 -73
package/src/resolve.js ADDED
@@ -0,0 +1,352 @@
1
+ import { exports as resolveExports } from 'resolve.exports';
2
+ import { resolve as resolveImports } from 'resolve.imports';
3
+
4
+ /**
5
+ * @typedef {import('./request.js').Request} Request
6
+ */
7
+ import { assert, fakeDomain } from './utils.js';
8
+
9
+ /**
10
+ * If a package wanted, they could provide a special export condition
11
+ * targeting REPLs.
12
+ *
13
+ * This format should still be ESM.
14
+ * CJS is not supported in browsers, and I won't support CJS in this REPL.
15
+ */
16
+ const CONDITIONS = ['repl', 'module', 'browser', 'import', 'default', 'development'];
17
+
18
+ /**
19
+ * @type {Map<string, import('./types.ts').RequestAnswer>} specifier => filePath in the tgz
20
+ */
21
+ const resolveCache = new Map();
22
+
23
+ const AT = '___AT___';
24
+ const fakeProtocol = 'repl://';
25
+
26
+ /**
27
+ *
28
+ * @param {*} start packageName or packageName with file
29
+ * @param {*} target file to resolve within the packageName
30
+ * @returns
31
+ */
32
+ export function resolvePath(start, target) {
33
+ /**
34
+ * How to make the whole package name look like one segment for URL
35
+ */
36
+ const base = start.replace(/^@([^/]+)\/([^/]+)/, `${AT}$1___$2`);
37
+
38
+ const url = new URL(target, fakeProtocol + fakeDomain + base);
39
+
40
+ /**
41
+ * href omits the protocol
42
+ * (which is what we want)
43
+ */
44
+ return url.href
45
+ .replace(fakeProtocol + fakeDomain, '')
46
+ .replace(AT, '@')
47
+ .replace('___', '/')
48
+ .replace(/^\//, './');
49
+ }
50
+
51
+ /**
52
+ * @param {import('./types.ts').UntarredPackage} untarred
53
+ * @param {Request} request
54
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
55
+ */
56
+ export function resolve(untarred, request) {
57
+ let answer = undefined;
58
+
59
+ const key = request.key;
60
+
61
+ if (resolveCache.has(key)) {
62
+ return resolveCache.get(key);
63
+ }
64
+
65
+ answer ||= fromImports(untarred, request, answer);
66
+ answer ||= fromInternalImport(untarred, request, answer);
67
+ answer ||= fromExportsString(untarred, request, answer);
68
+ answer ||= fromExports(untarred, request, answer);
69
+ answer ||= fromModule(untarred, request, answer);
70
+ answer ||= fromBrowser(untarred, request, answer);
71
+ answer ||= fromMain(untarred, request, answer);
72
+ answer ||= fromIndex(untarred, request, answer);
73
+ answer ||= fromFallback(untarred, request, answer);
74
+
75
+ if (answer) {
76
+ resolveCache.set(key, answer);
77
+ }
78
+
79
+ return answer;
80
+ }
81
+
82
+ /**
83
+ * These are likely all private imports
84
+ *
85
+ * @param {import('./types.ts').UntarredPackage} untarred
86
+ * @param {Request} request
87
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
88
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
89
+ */
90
+ export function fromInternalImport(untarred, request, answer) {
91
+ if (answer) return answer;
92
+
93
+ if (!request.from) return answer;
94
+
95
+ const fromSpecifier = request.from;
96
+ const answerFrom = resolve(untarred, fromSpecifier);
97
+
98
+ if (!answerFrom) {
99
+ printError(untarred, fromSpecifier, answer);
100
+
101
+ return;
102
+ }
103
+
104
+ const inTarFile = resolvePath(
105
+ fromSpecifier.name + '/' + answerFrom.inTarFile,
106
+ request.to
107
+ ).replace(new RegExp(`^${fromSpecifier.name}/`), '');
108
+ const result = checkFile(untarred, inTarFile);
109
+
110
+ if (result) {
111
+ return createAnswer(result, request, 'internalImport');
112
+ }
113
+
114
+ // Internal imports should always exist, unless a package is just broken.
115
+ printError(untarred, request, answer);
116
+ }
117
+
118
+ /**
119
+ * @param {import('./types.ts').UntarredPackage} untarred
120
+ * @param {Request} request
121
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
122
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
123
+ */
124
+ function fromExports(untarred, request, answer) {
125
+ if (answer) return answer;
126
+
127
+ const exports = untarred.manifest.exports;
128
+
129
+ if (!(typeof exports === 'object')) return answer;
130
+
131
+ const foundArray = resolveExports(untarred.manifest, request.to, {
132
+ conditions: CONDITIONS,
133
+ });
134
+
135
+ const found = foundArray?.map((f) => checkFile(untarred, f)).find(Boolean);
136
+
137
+ if (found) {
138
+ return createAnswer(found, request, 'exports');
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @param {import('./types.ts').UntarredPackage} untarred
144
+ * @param {Request} request
145
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
146
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
147
+ */
148
+ export function fromImports(untarred, request, answer) {
149
+ if (answer) return answer;
150
+ if (!request.to.startsWith('#')) return answer;
151
+
152
+ const imports = untarred.manifest.imports;
153
+
154
+ if (!(typeof imports === 'object')) return answer;
155
+
156
+ const found = resolveImports({ content: untarred.manifest }, request.to, {
157
+ conditions: CONDITIONS,
158
+ });
159
+
160
+ if (found) {
161
+ return createAnswer(found.replace(/^\.\//, ''), request, 'imports');
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @param {import('./types.ts').UntarredPackage} untarred
167
+ * @param {Request} request
168
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
169
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
170
+ */
171
+ function fromExportsString(untarred, request, answer) {
172
+ if (answer) return answer;
173
+ if (!hasExports(untarred)) return answer;
174
+
175
+ // technically not legacy, but it's the same logic
176
+ return checkLegacyEntry(untarred, request, 'exports');
177
+ }
178
+
179
+ /**
180
+ * @param {import('./types.ts').UntarredPackage} untarred
181
+ * @param {Request} request
182
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
183
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
184
+ */
185
+ function fromModule(untarred, request, answer) {
186
+ if (answer) return answer;
187
+ if (hasExports(untarred)) return answer;
188
+
189
+ return checkLegacyEntry(untarred, request, 'module');
190
+ }
191
+
192
+ /**
193
+ * @param {import('./types.ts').UntarredPackage} untarred
194
+ * @param {Request} request
195
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
196
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
197
+ */
198
+ function fromBrowser(untarred, request, answer) {
199
+ if (answer) return answer;
200
+ if (hasExports(untarred)) return answer;
201
+
202
+ return checkLegacyEntry(untarred, request, 'browser');
203
+ }
204
+
205
+ /**
206
+ * @param {import('./types.ts').UntarredPackage} untarred
207
+ * @param {Request} request
208
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
209
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
210
+ */
211
+ function fromMain(untarred, request, answer) {
212
+ if (answer) return answer;
213
+ if (hasExports(untarred)) return answer;
214
+
215
+ return checkLegacyEntry(untarred, request, 'main');
216
+ }
217
+
218
+ /**
219
+ * @param {import('./types.ts').UntarredPackage} untarred
220
+ * @param {Request} request
221
+ * @param {string} entryName
222
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
223
+ */
224
+ function checkLegacyEntry(untarred, request, entryName) {
225
+ if (request.to !== '.') return;
226
+
227
+ const filePath = untarred.manifest[/** @type {keyof typeof untarred.manifest} */ (entryName)];
228
+
229
+ if (!filePath || typeof filePath !== 'string') return;
230
+
231
+ const result = checkFile(untarred, filePath);
232
+
233
+ if (result) {
234
+ return createAnswer(result, request, entryName);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * @param {import('./types.ts').UntarredPackage} untarred
240
+ * @param {Request} request
241
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
242
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
243
+ */
244
+ function fromIndex(untarred, request, answer) {
245
+ if (answer) return answer;
246
+ if (hasExports(untarred)) return answer;
247
+
248
+ if (request.to === '.') {
249
+ if (untarred.contents['index.js']) {
250
+ return {
251
+ inTarFile: 'index.js',
252
+ ext: 'js',
253
+ from: 'index',
254
+ };
255
+ }
256
+ }
257
+ }
258
+
259
+ /**
260
+ * @param {import('./types.ts').UntarredPackage} untarred
261
+ * @param {Request} request
262
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
263
+ * @returns {undefined | import('./types.ts').RequestAnswer} the in-tar path
264
+ */
265
+ function fromFallback(untarred, request, answer) {
266
+ if (answer) return answer;
267
+
268
+ const result = checkFile(untarred, request.to);
269
+
270
+ if (result) {
271
+ return createAnswer(result, request, 'fallback');
272
+ }
273
+ }
274
+
275
+ /**
276
+ *
277
+ * @param {import('./types.ts').UntarredPackage} untarred
278
+ * @param {string | undefined} filePath
279
+ * @returns {string | undefined} the variant
280
+ */
281
+ function checkFile(untarred, filePath) {
282
+ if (!filePath) return;
283
+
284
+ for (const prefix of ['', 'pkg/']) {
285
+ const path = prefix + filePath;
286
+ const dotless = prefix + filePath.replace(/^\.\//, '');
287
+
288
+ if (untarred.contents[path]) {
289
+ return path;
290
+ }
291
+
292
+ if (untarred.contents[dotless]) {
293
+ return dotless;
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * @param {string} filePath
300
+ */
301
+ function extName(filePath) {
302
+ return filePath.split('.').pop();
303
+ }
304
+
305
+ /**
306
+ * @param {import('./types.ts').UntarredPackage} untarred
307
+ */
308
+ function hasExports(untarred) {
309
+ return Boolean(untarred.manifest.exports);
310
+ }
311
+
312
+ /**
313
+ * @param {string} forFile
314
+ * @param {Request} request
315
+ * @param {string} fromMethod
316
+ */
317
+ function createAnswer(forFile, request, fromMethod) {
318
+ const ext = extName(forFile);
319
+
320
+ assert(
321
+ `All files must have an extension. This file (in ${request.name}) did not have an extension: ${forFile}`,
322
+ ext
323
+ );
324
+
325
+ return {
326
+ inTarFile: forFile,
327
+ ext,
328
+ from: fromMethod,
329
+ };
330
+ }
331
+
332
+ /**
333
+ * @param {import('./types.ts').UntarredPackage} untarred
334
+ * @param {Request} request
335
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
336
+ * @throws {Error}
337
+ */
338
+ export function printError(untarred, request, answer) {
339
+ const { name, exports, main, module, browser } = untarred.manifest;
340
+
341
+ console.group(`${name} file info`);
342
+ console.info(`${name} has these files: `, Object.keys(untarred.contents));
343
+ console.info(`We searched for '${request.original}'`);
344
+ console.info(`from: `, { exports, main, module, browser });
345
+ console.info(`And found: `, answer);
346
+ console.info(`The request was: `, request);
347
+ console.groupEnd();
348
+
349
+ // eslint-disable-next-line no-debugger
350
+ debugger;
351
+ throw new Error(`Could not find file for ${request.original}`);
352
+ }
@@ -0,0 +1,24 @@
1
+ import { expect as errorExpect, it } from 'vitest';
2
+
3
+ const expect = errorExpect.soft;
4
+
5
+ import { resolvePath } from './resolve.js';
6
+
7
+ function test({ from, to, expected }: { from: string; to: string; expected: string }) {
8
+ it(`import ${to} from ${from}`, () => {
9
+ expect(resolvePath(from, to)).deep.equal(expected);
10
+ });
11
+ }
12
+
13
+ test({ from: '@scope/pkgName', to: 'index.js', expected: '@scope/pkgName/index.js' });
14
+ test({ from: '@scope/pkgName/index.js', to: './foo.js', expected: '@scope/pkgName/foo.js' });
15
+ test({
16
+ from: '@scope/pkgName/folder/index.js',
17
+ to: './foo.js',
18
+ expected: '@scope/pkgName/folder/foo.js',
19
+ });
20
+ test({
21
+ from: '@scope/pkgName/folder/index.js',
22
+ to: '../foo.js',
23
+ expected: '@scope/pkgName/foo.js',
24
+ });
@@ -0,0 +1,23 @@
1
+ import { expect as errorExpect, it } from 'vitest';
2
+
3
+ import { Request } from './request.js';
4
+ import { resolve } from './resolve.js';
5
+
6
+ import type { UntarredPackage } from './types.js';
7
+
8
+ const expect = errorExpect.soft;
9
+
10
+ it('resolves the entrypoint (rehype-raw)', () => {
11
+ const untarred = {
12
+ contents: {
13
+ 'index.js': 'entry file',
14
+ },
15
+ manifest: {
16
+ exports: './index.js',
17
+ },
18
+ };
19
+ const request = Request.fromSpecifier('rehype-raw');
20
+ const answer = resolve(untarred as unknown as UntarredPackage, request);
21
+
22
+ expect(answer?.inTarFile).toBe('index.js');
23
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Parses
3
+ * @scope/pkgName
4
+ * pkgName
5
+ * @scope/pkgName@version
6
+ * pkgName@version
7
+ * @scope/pkgName@version/path
8
+ * pkgName@version/path
9
+ *
10
+ * @param {string} specifier
11
+ * @returns {{ name: string, version: string | undefined, path: string }}
12
+ */
13
+ export function parseSpecifier(specifier) {
14
+ let name = '';
15
+ let version = '';
16
+ let path = '.';
17
+
18
+ const chars = specifier.split('');
19
+
20
+ const hasScope = chars[0] === '@';
21
+
22
+ let isVersion = false;
23
+ let isPath = false;
24
+ let finishedName = false;
25
+ let slashCount = 0;
26
+
27
+ for (let i = 0; i < chars.length; i++) {
28
+ const char = chars[i];
29
+
30
+ // Enter version mode
31
+ // Goes until the end, or a slash
32
+ if (char === '@' && i > 0) {
33
+ finishedName = true;
34
+ isVersion = true;
35
+ continue;
36
+ } else if (char === '/') {
37
+ slashCount++;
38
+
39
+ if (hasScope) {
40
+ if (slashCount > 1) {
41
+ finishedName = true;
42
+ }
43
+ } else {
44
+ finishedName = true;
45
+ }
46
+
47
+ if (finishedName) {
48
+ isPath = true;
49
+ }
50
+ }
51
+
52
+ if (isVersion && char === '/') {
53
+ isVersion = false;
54
+ isPath = true;
55
+ }
56
+
57
+ if (isVersion) {
58
+ version += char;
59
+ } else if (isPath) {
60
+ path += char;
61
+ } else {
62
+ name += char;
63
+ }
64
+ }
65
+
66
+ return {
67
+ name,
68
+ version: version || undefined,
69
+ path,
70
+ };
71
+ }
@@ -0,0 +1,90 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+
3
+ import { expect as errorExpect, it } from 'vitest';
4
+
5
+ import { parseSpecifier } from './specifier.js';
6
+
7
+ const expect = errorExpect.soft;
8
+
9
+ function test(input: string, expected: { name: string; version?: string; path: string }) {
10
+ it(input, () => {
11
+ expect(parseSpecifier(input)).deep.equal(expected);
12
+ });
13
+ }
14
+
15
+ test('pkgName', {
16
+ name: 'pkgName',
17
+ version: undefined,
18
+ path: '.',
19
+ });
20
+
21
+ test('pkgName/foo', {
22
+ name: 'pkgName',
23
+ version: undefined,
24
+ path: './foo',
25
+ });
26
+
27
+ test('pkgName@beta', {
28
+ name: 'pkgName',
29
+ version: 'beta',
30
+ path: '.',
31
+ });
32
+
33
+ test('pkgName@beta/foo', {
34
+ name: 'pkgName',
35
+ version: 'beta',
36
+ path: './foo',
37
+ });
38
+
39
+ test('pkgName@5.4.3-beta.2', {
40
+ name: 'pkgName',
41
+ version: '5.4.3-beta.2',
42
+ path: '.',
43
+ });
44
+
45
+ test('pkgName@5.4.3', {
46
+ name: 'pkgName',
47
+ version: '5.4.3',
48
+ path: '.',
49
+ });
50
+
51
+ test('pkgName@5.4.3/foo', {
52
+ name: 'pkgName',
53
+ version: '5.4.3',
54
+ path: './foo',
55
+ });
56
+
57
+ test('pkgName@5.4.3/foo/bar', {
58
+ name: 'pkgName',
59
+ version: '5.4.3',
60
+ path: './foo/bar',
61
+ });
62
+
63
+ test('@scope/pkgName', {
64
+ name: '@scope/pkgName',
65
+ version: undefined,
66
+ path: '.',
67
+ });
68
+
69
+ test('@scope/pkgName/foo', {
70
+ name: '@scope/pkgName',
71
+ version: undefined,
72
+ path: './foo',
73
+ });
74
+
75
+ test('@scope/pkgName@1.0.0', {
76
+ name: '@scope/pkgName',
77
+ version: '1.0.0',
78
+ path: '.',
79
+ });
80
+
81
+ test('@scope/pkgName@1.0.0/foo', {
82
+ name: '@scope/pkgName',
83
+ version: '1.0.0',
84
+ path: './foo',
85
+ });
86
+
87
+ it('invalid', () => {
88
+ // @ts-expect-error
89
+ expect(() => parseSpecifier(undefined)).toThrow();
90
+ });
@@ -0,0 +1,61 @@
1
+ import { expose } from 'comlink';
2
+ import { parseTar } from 'tarparser';
3
+
4
+ import { cache } from './cache.js';
5
+ import { getNPMInfo, getTarUrl } from './npm.js';
6
+
7
+ const obj = { getTar };
8
+
9
+ expose(obj);
10
+
11
+ /**
12
+ * @param {string} name of the package
13
+ * @param {string} requestedVersion version or tag to fetch the package at
14
+ */
15
+ async function getTar(name, requestedVersion) {
16
+ const key = `${name}@${requestedVersion}`;
17
+ const untarred = cache.tarballs.get(key);
18
+
19
+ if (untarred) {
20
+ return untarred;
21
+ }
22
+
23
+ const contents = await cache.cachedPromise(`getTar:${key}`, async () => {
24
+ const json = await getNPMInfo(name, requestedVersion);
25
+ const tgzUrl = await getTarUrl(json, requestedVersion);
26
+
27
+ const response = await fetch(tgzUrl, {
28
+ headers: {
29
+ ACCEPT: 'application/octet-stream',
30
+ },
31
+ });
32
+
33
+ return await untar(await response.arrayBuffer());
34
+ });
35
+
36
+ const manifest = JSON.parse(contents['package.json'].text);
37
+
38
+ const info = /** @type {import('./types.ts').UntarredPackage}*/ ({ manifest, contents });
39
+
40
+ cache.tarballs.set(key, info);
41
+
42
+ return info;
43
+ }
44
+
45
+ /**
46
+ * @param {ArrayBuffer} arrayBuffer
47
+ */
48
+ async function untar(arrayBuffer) {
49
+ /**
50
+ * @type {{ [name: string]: import('tarparser').FileDescription }}
51
+ */
52
+ const contents = {};
53
+
54
+ for (const file of await parseTar(arrayBuffer)) {
55
+ if (file.type === 'file') {
56
+ contents[file.name.slice(8)] = file; // remove `package/` prefix
57
+ }
58
+ }
59
+
60
+ return contents;
61
+ }
package/src/tar.js ADDED
@@ -0,0 +1,76 @@
1
+ import { wrap } from 'comlink';
2
+
3
+ import { cache } from './cache.js';
4
+ import { Request } from './request.js';
5
+ import { printError, resolve } from './resolve.js';
6
+ import { assert, unzippedPrefix } from './utils.js';
7
+
8
+ const worker = new Worker(new URL('./tar-worker.js', import.meta.url), {
9
+ name: 'Tar & NPM Downloader Worker',
10
+ type: 'module',
11
+ });
12
+ const com = wrap(worker);
13
+
14
+ /**
15
+ * @param {string} url request URL
16
+ * @returns {Promise<undefined | { code: string, ext: string }>}
17
+ */
18
+ export async function getFromTarball(url) {
19
+ const key = url.replace(unzippedPrefix + '/', '');
20
+ const request = cache.requestCache.get(key);
21
+
22
+ assert(`Missing request for ${url}`, request);
23
+
24
+ if (cache.fileCache.has(key)) {
25
+ return cache.fileCache.get(key);
26
+ }
27
+
28
+ const data = await cache.cachedPromise(key, async () => {
29
+ const untarred = await com.getTar(request.name, request.version);
30
+ const answer = resolve(untarred, request);
31
+
32
+ if (!answer) {
33
+ throw new Error(`Could not find file for ${request.original}`);
34
+ }
35
+
36
+ return { answer, name: request.name, version: request.version };
37
+ });
38
+
39
+ const untarred = await com.getTar(request.name, request.version);
40
+
41
+ const result = getFile(untarred, key, data.answer);
42
+
43
+ assert(`Missing file for ${url}`, result);
44
+
45
+ cache.fileCache.set(key, result);
46
+
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * @param {import('./types.ts').UntarredPackage} untarred
52
+ * @param {string} key
53
+ * @param {undefined | import('./types.ts').RequestAnswer} answer
54
+ * @returns {undefined | { code: string, ext: string }}
55
+ */
56
+ function getFile(untarred, key, answer) {
57
+ const request = Request.fromRequestId(key);
58
+
59
+ if (!answer) {
60
+ printError(untarred, request, answer);
61
+
62
+ return;
63
+ }
64
+
65
+ const { inTarFile, ext } = answer;
66
+
67
+ const code = untarred.contents[inTarFile]?.text;
68
+
69
+ if (!code) {
70
+ printError(untarred, request, answer);
71
+
72
+ return;
73
+ }
74
+
75
+ return { code, ext };
76
+ }