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.
- package/dist/assets/tar-worker-kdkltuRC.js +598 -0
- package/dist/assets/tar-worker-kdkltuRC.js.map +1 -0
- package/dist/codemirror-D4aIVflZ.js +110 -0
- package/dist/codemirror-D4aIVflZ.js.map +1 -0
- package/dist/gjs-CzFzkEFv.js +173 -0
- package/dist/gjs-CzFzkEFv.js.map +1 -0
- package/dist/gmd-D9OXs2v3.js +166 -0
- package/dist/gmd-D9OXs2v3.js.map +1 -0
- package/dist/hbs-CuhWjffM.js +62 -0
- package/dist/hbs-CuhWjffM.js.map +1 -0
- package/dist/index-CUWCqMoD.js +2133 -0
- package/dist/index-CUWCqMoD.js.map +1 -0
- package/dist/index.js +4 -104
- package/dist/index.js.map +1 -1
- package/dist/parse-aBKk9rfS.js +328 -0
- package/dist/parse-aBKk9rfS.js.map +1 -0
- package/dist/render-app-island-B-i8rvGi.js +61 -0
- package/dist/render-app-island-B-i8rvGi.js.map +1 -0
- package/package.json +82 -9
- package/src/cache.js +138 -0
- package/src/cdn.js +93 -0
- package/src/codemirror.js +161 -0
- package/src/compilers/ember/gjs.js +212 -0
- package/src/compilers/ember/gmd.js +190 -0
- package/src/compilers/ember/hbs.js +98 -0
- package/src/compilers/ember/render-app-island.js +83 -0
- package/src/compilers/ember.js +166 -0
- package/src/compilers/js.js +32 -0
- package/src/compilers/markdown/build-compiler.js +151 -0
- package/src/compilers/markdown/const.js +2 -0
- package/src/compilers/markdown/heading-id.js +75 -0
- package/src/compilers/markdown/live-code-extraction.js +198 -0
- package/src/compilers/markdown/parse.js +22 -0
- package/src/compilers/markdown/parse.test.ts +363 -0
- package/src/compilers/markdown/sanitize-for-glimmer.js +26 -0
- package/src/compilers/markdown/types.ts +21 -0
- package/src/compilers/markdown/utils.js +78 -0
- package/src/compilers/markdown.js +125 -0
- package/src/compilers/mermaid.js +35 -0
- package/src/compilers/react.js +47 -0
- package/src/compilers/svelte.js +116 -0
- package/src/compilers/vue.js +58 -0
- package/src/compilers.js +108 -0
- package/src/es-module-shim.js +53 -0
- package/src/index.d.ts +53 -4
- package/src/index.js +744 -89
- package/src/npm.js +58 -0
- package/src/request.Request.test.ts +59 -0
- package/src/request.js +140 -0
- package/src/resolve.fromImports.test.ts +35 -0
- package/src/resolve.fromInternalImport.test.ts +69 -0
- package/src/resolve.js +352 -0
- package/src/resolve.resolvePath.test.ts +24 -0
- package/src/resolve.test.ts +23 -0
- package/src/specifier.js +71 -0
- package/src/specifier.test.ts +90 -0
- package/src/tar-worker.js +61 -0
- package/src/tar.js +76 -0
- package/src/types.ts +335 -58
- package/src/utils.js +28 -1
- 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
|
+
});
|
package/src/specifier.js
ADDED
|
@@ -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
|
+
}
|