1 | import cacache from 'cacache';
|
2 | import * as colors from 'kleur/colors';
|
3 | import PQueue from 'p-queue';
|
4 | import validatePackageName from 'validate-npm-package-name';
|
5 | import { fetchCDNResource, PIKA_CDN, RESOURCE_CACHE } from './util.js';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | async function resolveDependency(installSpecifier, packageSemver, lockfile, canRetry = true) {
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | if (!validatePackageName(installSpecifier).validForNewPackages) {
|
21 | return null;
|
22 | }
|
23 |
|
24 | let installUrl;
|
25 | let installUrlType;
|
26 | if (lockfile && lockfile.imports[installSpecifier]) {
|
27 | installUrl = lockfile.imports[installSpecifier];
|
28 | installUrlType = 'pin';
|
29 | }
|
30 | else {
|
31 | if (packageSemver === 'latest') {
|
32 | console.warn(`warn(${installSpecifier}): Not found in "dependencies". Using latest package version...`);
|
33 | }
|
34 | if (packageSemver.startsWith('npm:@reactesm') || packageSemver.startsWith('npm:@pika/react')) {
|
35 | throw new Error(`React workaround packages no longer needed! Revert to the official React & React-DOM packages.`);
|
36 | }
|
37 | if (packageSemver.includes(' ') || packageSemver.includes(':')) {
|
38 | console.warn(`warn(${installSpecifier}): Can't fetch complex semver "${packageSemver}" from remote CDN.`);
|
39 | return null;
|
40 | }
|
41 | installUrlType = 'lookup';
|
42 | installUrl = `${PIKA_CDN}/${installSpecifier}@${packageSemver}`;
|
43 | }
|
44 |
|
45 |
|
46 | if (installUrlType === 'pin') {
|
47 | const cachedResult = await cacache.get.info(RESOURCE_CACHE, installUrl).catch(() => null);
|
48 | if (cachedResult) {
|
49 | if (cachedResult.metadata) {
|
50 | const { pinnedUrl } = cachedResult.metadata;
|
51 | return pinnedUrl;
|
52 | }
|
53 | }
|
54 | }
|
55 |
|
56 | const { statusCode, headers, body } = await fetchCDNResource(installUrl);
|
57 | if (statusCode !== 200) {
|
58 | console.warn(`Failed to resolve [${statusCode}]: ${installUrl} (${body})`);
|
59 | console.warn(`Falling back to local copy...`);
|
60 | return null;
|
61 | }
|
62 | let importUrlPath = headers['x-import-url'];
|
63 | let pinnedUrlPath = headers['x-pinned-url'];
|
64 | const buildStatus = headers['x-import-status'];
|
65 | const typesUrlPath = headers['x-typescript-types'];
|
66 | const typesUrl = typesUrlPath && `${PIKA_CDN}${typesUrlPath}`;
|
67 | if (installUrlType === 'pin') {
|
68 | const pinnedUrl = installUrl;
|
69 | await cacache.put(RESOURCE_CACHE, pinnedUrl, body, {
|
70 | metadata: { pinnedUrl, typesUrl },
|
71 | });
|
72 | return pinnedUrl;
|
73 | }
|
74 | if (pinnedUrlPath) {
|
75 | const pinnedUrl = `${PIKA_CDN}${pinnedUrlPath}`;
|
76 | await cacache.put(RESOURCE_CACHE, pinnedUrl, body, {
|
77 | metadata: { pinnedUrl, typesUrl },
|
78 | });
|
79 | return pinnedUrl;
|
80 | }
|
81 | if (buildStatus === 'SUCCESS') {
|
82 | console.warn(`Failed to lookup [${statusCode}]: ${installUrl}`);
|
83 | console.warn(`Falling back to local copy...`);
|
84 | return null;
|
85 | }
|
86 | if (!canRetry || buildStatus === 'FAIL') {
|
87 | console.warn(`Failed to build: ${installSpecifier}@${packageSemver}`);
|
88 | console.warn(`Falling back to local copy...`);
|
89 | return null;
|
90 | }
|
91 | console.log(colors.cyan(`Building ${installSpecifier}@${packageSemver}... (This takes a moment, but will be cached for future use)`));
|
92 | if (!importUrlPath) {
|
93 | throw new Error('X-Import-URL header expected, but none received.');
|
94 | }
|
95 | const { statusCode: lookupStatusCode } = await fetchCDNResource(importUrlPath);
|
96 | if (lookupStatusCode !== 200) {
|
97 | throw new Error(`Unexpected response [${lookupStatusCode}]: ${PIKA_CDN}${importUrlPath}`);
|
98 | }
|
99 | return resolveDependency(installSpecifier, packageSemver, lockfile, false);
|
100 | }
|
101 | export async function resolveTargetsFromRemoteCDN(lockfile, config) {
|
102 | const downloadQueue = new PQueue({ concurrency: 16 });
|
103 | const newLockfile = { imports: {} };
|
104 | let resolutionError;
|
105 | for (const [installSpecifier, installSemver] of Object.entries(config.webDependencies)) {
|
106 | downloadQueue.add(async () => {
|
107 | try {
|
108 | const resolvedUrl = await resolveDependency(installSpecifier, installSemver, lockfile);
|
109 | if (resolvedUrl) {
|
110 | newLockfile.imports[installSpecifier] = resolvedUrl;
|
111 | }
|
112 | }
|
113 | catch (err) {
|
114 | resolutionError = resolutionError || err;
|
115 | }
|
116 | });
|
117 | }
|
118 | await downloadQueue.onIdle();
|
119 | if (resolutionError) {
|
120 | throw resolutionError;
|
121 | }
|
122 | return newLockfile;
|
123 | }
|