@ohos-ports/parcel-package-manager 2.16.4-beta.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/LICENSE +21 -0
- package/index.d.ts +40 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +1968 -0
- package/lib/index.js.map +1 -0
- package/package.json +73 -0
- package/src/JSONParseStream.js +39 -0
- package/src/MockPackageInstaller.js +94 -0
- package/src/NodePackageManager.js +662 -0
- package/src/Npm.js +94 -0
- package/src/Pnpm.js +194 -0
- package/src/Yarn.js +157 -0
- package/src/getCurrentPackageManager.js +17 -0
- package/src/index.js +19 -0
- package/src/installPackage.js +284 -0
- package/src/promiseFromProcess.js +19 -0
- package/src/utils.js +93 -0
- package/src/validateModuleSpecifier.js +12 -0
- package/test/NodePackageManager.test.js +387 -0
- package/test/fixtures/empty/package.json +1 -0
- package/test/fixtures/has-a-not-yet-installed/package.json +5 -0
- package/test/fixtures/has-foo/node_modules/foo/index.js +1 -0
- package/test/fixtures/has-foo/node_modules/foo/package.json +3 -0
- package/test/fixtures/has-foo/node_modules/index.js +0 -0
- package/test/fixtures/has-foo/package.json +5 -0
- package/test/fixtures/has-foo/subpackage/package.json +1 -0
- package/test/fixtures/packages/a/index.js +1 -0
- package/test/fixtures/packages/a/package.json +3 -0
- package/test/fixtures/packages/foo-2.0/index.js +1 -0
- package/test/fixtures/packages/foo-2.0/package.json +4 -0
- package/test/fixtures/packages/peers/index.js +1 -0
- package/test/fixtures/packages/peers/package.json +7 -0
- package/test/fixtures/packages/peers-2.0/index.js +1 -0
- package/test/fixtures/packages/peers-2.0/package.json +7 -0
- package/test/getCurrentPackageManager.test.js +28 -0
- package/test/validateModuleSpecifiers.test.js +38 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type {FilePath, PackageJSON} from '@parcel/types';
|
|
4
|
+
import type {
|
|
5
|
+
ModuleRequest,
|
|
6
|
+
PackageManager,
|
|
7
|
+
PackageInstaller,
|
|
8
|
+
InstallOptions,
|
|
9
|
+
} from '@parcel/types';
|
|
10
|
+
import type {FileSystem} from '@parcel/fs';
|
|
11
|
+
|
|
12
|
+
import invariant from 'assert';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import nullthrows from 'nullthrows';
|
|
15
|
+
import semver from 'semver';
|
|
16
|
+
import ThrowableDiagnostic, {
|
|
17
|
+
generateJSONCodeHighlights,
|
|
18
|
+
encodeJSONKeyComponent,
|
|
19
|
+
md,
|
|
20
|
+
} from '@parcel/diagnostic';
|
|
21
|
+
import logger from '@parcel/logger';
|
|
22
|
+
import {loadConfig, PromiseQueue, resolveConfig} from '@parcel/utils';
|
|
23
|
+
import WorkerFarm from '@parcel/workers';
|
|
24
|
+
|
|
25
|
+
import {Npm} from './Npm';
|
|
26
|
+
import {Yarn} from './Yarn';
|
|
27
|
+
import {Pnpm} from './Pnpm.js';
|
|
28
|
+
import {getConflictingLocalDependencies} from './utils';
|
|
29
|
+
import getCurrentPackageManager from './getCurrentPackageManager';
|
|
30
|
+
import validateModuleSpecifier from './validateModuleSpecifier';
|
|
31
|
+
|
|
32
|
+
async function install(
|
|
33
|
+
fs: FileSystem,
|
|
34
|
+
packageManager: PackageManager,
|
|
35
|
+
modules: Array<ModuleRequest>,
|
|
36
|
+
from: FilePath,
|
|
37
|
+
projectRoot: FilePath,
|
|
38
|
+
options: InstallOptions = {},
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
let {installPeers = true, saveDev = true, packageInstaller} = options;
|
|
41
|
+
let moduleNames = modules.map(m => m.name).join(', ');
|
|
42
|
+
|
|
43
|
+
logger.progress(`Installing ${moduleNames}...`);
|
|
44
|
+
|
|
45
|
+
let fromPkgPath = await resolveConfig(
|
|
46
|
+
fs,
|
|
47
|
+
from,
|
|
48
|
+
['package.json'],
|
|
49
|
+
projectRoot,
|
|
50
|
+
);
|
|
51
|
+
let cwd = fromPkgPath ? path.dirname(fromPkgPath) : fs.cwd();
|
|
52
|
+
|
|
53
|
+
if (!packageInstaller) {
|
|
54
|
+
packageInstaller = await determinePackageInstaller(fs, from, projectRoot);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await packageInstaller.install({
|
|
59
|
+
modules,
|
|
60
|
+
saveDev,
|
|
61
|
+
cwd,
|
|
62
|
+
packagePath: fromPkgPath,
|
|
63
|
+
fs,
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new Error(`Failed to install ${moduleNames}: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (installPeers) {
|
|
70
|
+
await Promise.all(
|
|
71
|
+
modules.map(m =>
|
|
72
|
+
installPeerDependencies(
|
|
73
|
+
fs,
|
|
74
|
+
packageManager,
|
|
75
|
+
m,
|
|
76
|
+
from,
|
|
77
|
+
projectRoot,
|
|
78
|
+
options,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function installPeerDependencies(
|
|
86
|
+
fs: FileSystem,
|
|
87
|
+
packageManager: PackageManager,
|
|
88
|
+
module: ModuleRequest,
|
|
89
|
+
from: FilePath,
|
|
90
|
+
projectRoot: FilePath,
|
|
91
|
+
options,
|
|
92
|
+
) {
|
|
93
|
+
const {resolved} = await packageManager.resolve(module.name, from);
|
|
94
|
+
const modulePkg: PackageJSON = nullthrows(
|
|
95
|
+
await loadConfig(fs, resolved, ['package.json'], projectRoot),
|
|
96
|
+
).config;
|
|
97
|
+
const peers = modulePkg.peerDependencies || {};
|
|
98
|
+
|
|
99
|
+
let modules: Array<ModuleRequest> = [];
|
|
100
|
+
for (let [name, range] of Object.entries(peers)) {
|
|
101
|
+
invariant(typeof range === 'string');
|
|
102
|
+
|
|
103
|
+
let conflicts = await getConflictingLocalDependencies(
|
|
104
|
+
fs,
|
|
105
|
+
name,
|
|
106
|
+
from,
|
|
107
|
+
projectRoot,
|
|
108
|
+
);
|
|
109
|
+
if (conflicts) {
|
|
110
|
+
let {pkg} = await packageManager.resolve(name, from);
|
|
111
|
+
invariant(pkg);
|
|
112
|
+
if (!semver.satisfies(pkg.version, range)) {
|
|
113
|
+
throw new ThrowableDiagnostic({
|
|
114
|
+
diagnostic: {
|
|
115
|
+
message: md`Could not install the peer dependency "${name}" for "${module.name}", installed version ${pkg.version} is incompatible with ${range}`,
|
|
116
|
+
origin: '@parcel/package-manager',
|
|
117
|
+
codeFrames: [
|
|
118
|
+
{
|
|
119
|
+
filePath: conflicts.filePath,
|
|
120
|
+
language: 'json',
|
|
121
|
+
code: conflicts.json,
|
|
122
|
+
codeHighlights: generateJSONCodeHighlights(
|
|
123
|
+
conflicts.json,
|
|
124
|
+
conflicts.fields.map(field => ({
|
|
125
|
+
key: `/${field}/${encodeJSONKeyComponent(name)}`,
|
|
126
|
+
type: 'key',
|
|
127
|
+
message: 'Found this conflicting local requirement.',
|
|
128
|
+
})),
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
modules.push({name, range});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (modules.length) {
|
|
142
|
+
await install(
|
|
143
|
+
fs,
|
|
144
|
+
packageManager,
|
|
145
|
+
modules,
|
|
146
|
+
from,
|
|
147
|
+
projectRoot,
|
|
148
|
+
Object.assign({}, options, {installPeers: false}),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function determinePackageInstaller(
|
|
154
|
+
fs: FileSystem,
|
|
155
|
+
filepath: FilePath,
|
|
156
|
+
projectRoot: FilePath,
|
|
157
|
+
): Promise<PackageInstaller> {
|
|
158
|
+
let configFile = await resolveConfig(
|
|
159
|
+
fs,
|
|
160
|
+
filepath,
|
|
161
|
+
['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock'],
|
|
162
|
+
projectRoot,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
let configName = configFile && path.basename(configFile);
|
|
166
|
+
|
|
167
|
+
// Always use the package manager that seems to be used in the project,
|
|
168
|
+
// falling back to a different one wouldn't update the existing lockfile.
|
|
169
|
+
if (configName === 'package-lock.json') {
|
|
170
|
+
return new Npm();
|
|
171
|
+
} else if (configName === 'pnpm-lock.yaml') {
|
|
172
|
+
return new Pnpm();
|
|
173
|
+
} else if (configName === 'yarn.lock') {
|
|
174
|
+
return new Yarn();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let currentPackageManager = getCurrentPackageManager()?.name;
|
|
178
|
+
if (currentPackageManager === 'npm') {
|
|
179
|
+
return new Npm();
|
|
180
|
+
} else if (currentPackageManager === 'yarn') {
|
|
181
|
+
return new Yarn();
|
|
182
|
+
} else if (currentPackageManager === 'pnpm') {
|
|
183
|
+
return new Pnpm();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (await Yarn.exists()) {
|
|
187
|
+
return new Yarn();
|
|
188
|
+
} else if (await Pnpm.exists()) {
|
|
189
|
+
return new Pnpm();
|
|
190
|
+
} else {
|
|
191
|
+
return new Npm();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let queue = new PromiseQueue({maxConcurrent: 1});
|
|
196
|
+
let modulesInstalling: Set<string> = new Set();
|
|
197
|
+
|
|
198
|
+
// Exported so that it may be invoked from the worker api below.
|
|
199
|
+
// Do not call this directly! This can result in concurrent package installations
|
|
200
|
+
// across multiple instances of the package manager.
|
|
201
|
+
export function _addToInstallQueue(
|
|
202
|
+
fs: FileSystem,
|
|
203
|
+
packageManager: PackageManager,
|
|
204
|
+
modules: Array<ModuleRequest>,
|
|
205
|
+
filePath: FilePath,
|
|
206
|
+
projectRoot: FilePath,
|
|
207
|
+
options?: InstallOptions,
|
|
208
|
+
): Promise<mixed> {
|
|
209
|
+
modules = modules.map(request => ({
|
|
210
|
+
name: validateModuleSpecifier(request.name),
|
|
211
|
+
range: request.range,
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
// Wrap PromiseQueue and track modules that are currently installing.
|
|
215
|
+
// If a request comes in for a module that is currently installing, don't bother
|
|
216
|
+
// enqueuing it.
|
|
217
|
+
let modulesToInstall = modules.filter(
|
|
218
|
+
m => !modulesInstalling.has(getModuleRequestKey(m)),
|
|
219
|
+
);
|
|
220
|
+
if (modulesToInstall.length) {
|
|
221
|
+
for (let m of modulesToInstall) {
|
|
222
|
+
modulesInstalling.add(getModuleRequestKey(m));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
queue
|
|
226
|
+
.add(() =>
|
|
227
|
+
install(
|
|
228
|
+
fs,
|
|
229
|
+
packageManager,
|
|
230
|
+
modulesToInstall,
|
|
231
|
+
filePath,
|
|
232
|
+
projectRoot,
|
|
233
|
+
options,
|
|
234
|
+
).then(() => {
|
|
235
|
+
for (let m of modulesToInstall) {
|
|
236
|
+
modulesInstalling.delete(getModuleRequestKey(m));
|
|
237
|
+
}
|
|
238
|
+
}),
|
|
239
|
+
)
|
|
240
|
+
.then(
|
|
241
|
+
() => {},
|
|
242
|
+
() => {},
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return queue.run();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function installPackage(
|
|
250
|
+
fs: FileSystem,
|
|
251
|
+
packageManager: PackageManager,
|
|
252
|
+
modules: Array<ModuleRequest>,
|
|
253
|
+
filePath: FilePath,
|
|
254
|
+
projectRoot: FilePath,
|
|
255
|
+
options?: InstallOptions,
|
|
256
|
+
): Promise<mixed> {
|
|
257
|
+
if (WorkerFarm.isWorker()) {
|
|
258
|
+
let workerApi = WorkerFarm.getWorkerApi();
|
|
259
|
+
// TODO this should really be `__filename` but without the rewriting.
|
|
260
|
+
let bundlePath =
|
|
261
|
+
process.env.PARCEL_BUILD_ENV === 'production' &&
|
|
262
|
+
!process.env.PARCEL_SELF_BUILD
|
|
263
|
+
? path.join(__dirname, '..', 'lib/index.js')
|
|
264
|
+
: __filename;
|
|
265
|
+
return workerApi.callMaster({
|
|
266
|
+
location: bundlePath,
|
|
267
|
+
args: [fs, packageManager, modules, filePath, projectRoot, options],
|
|
268
|
+
method: '_addToInstallQueue',
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return _addToInstallQueue(
|
|
273
|
+
fs,
|
|
274
|
+
packageManager,
|
|
275
|
+
modules,
|
|
276
|
+
filePath,
|
|
277
|
+
projectRoot,
|
|
278
|
+
options,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getModuleRequestKey(moduleRequest: ModuleRequest): string {
|
|
283
|
+
return [moduleRequest.name, moduleRequest.range].join('@');
|
|
284
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import type {ChildProcess} from 'child_process';
|
|
4
|
+
|
|
5
|
+
export default function promiseFromProcess(
|
|
6
|
+
childProcess: ChildProcess,
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
childProcess.on('error', reject);
|
|
10
|
+
childProcess.on('close', code => {
|
|
11
|
+
if (code !== 0) {
|
|
12
|
+
reject(new Error('Child process failed'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
resolve();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import type {FilePath, ModuleRequest} from '@parcel/types';
|
|
4
|
+
import type {FileSystem} from '@parcel/fs';
|
|
5
|
+
|
|
6
|
+
import invariant from 'assert';
|
|
7
|
+
import ThrowableDiagnostic from '@parcel/diagnostic';
|
|
8
|
+
import {resolveConfig} from '@parcel/utils';
|
|
9
|
+
import {exec as _exec} from 'child_process';
|
|
10
|
+
import {promisify} from 'util';
|
|
11
|
+
|
|
12
|
+
export const exec: (
|
|
13
|
+
command: string,
|
|
14
|
+
options?: child_process$execOpts,
|
|
15
|
+
) => Promise<{|stdout: string | Buffer, stderr: string | Buffer|}> = _exec
|
|
16
|
+
? promisify(_exec)
|
|
17
|
+
: // _exec is undefined in browser builds
|
|
18
|
+
_exec;
|
|
19
|
+
|
|
20
|
+
export function npmSpecifierFromModuleRequest(
|
|
21
|
+
moduleRequest: ModuleRequest,
|
|
22
|
+
): string {
|
|
23
|
+
return moduleRequest.range != null
|
|
24
|
+
? [moduleRequest.name, moduleRequest.range].join('@')
|
|
25
|
+
: moduleRequest.name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function moduleRequestsFromDependencyMap(dependencyMap: {|
|
|
29
|
+
[string]: string,
|
|
30
|
+
|}): Array<ModuleRequest> {
|
|
31
|
+
return Object.entries(dependencyMap).map(([name, range]) => {
|
|
32
|
+
invariant(typeof range === 'string');
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
range,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getConflictingLocalDependencies(
|
|
41
|
+
fs: FileSystem,
|
|
42
|
+
name: string,
|
|
43
|
+
local: FilePath,
|
|
44
|
+
projectRoot: FilePath,
|
|
45
|
+
): Promise<?{|json: string, filePath: FilePath, fields: Array<string>|}> {
|
|
46
|
+
let pkgPath = await resolveConfig(fs, local, ['package.json'], projectRoot);
|
|
47
|
+
if (pkgPath == null) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let pkgStr = await fs.readFile(pkgPath, 'utf8');
|
|
52
|
+
let pkg;
|
|
53
|
+
try {
|
|
54
|
+
pkg = JSON.parse(pkgStr);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// TODO: codeframe
|
|
57
|
+
throw new ThrowableDiagnostic({
|
|
58
|
+
diagnostic: {
|
|
59
|
+
message: 'Failed to parse package.json',
|
|
60
|
+
origin: '@parcel/package-manager',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof pkg !== 'object' || pkg == null) {
|
|
66
|
+
// TODO: codeframe
|
|
67
|
+
throw new ThrowableDiagnostic({
|
|
68
|
+
diagnostic: {
|
|
69
|
+
message: 'Expected package.json contents to be an object.',
|
|
70
|
+
origin: '@parcel/package-manager',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let fields = [];
|
|
76
|
+
for (let field of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
77
|
+
if (
|
|
78
|
+
typeof pkg[field] === 'object' &&
|
|
79
|
+
pkg[field] != null &&
|
|
80
|
+
pkg[field][name] != null
|
|
81
|
+
) {
|
|
82
|
+
fields.push(field);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (fields.length > 0) {
|
|
87
|
+
return {
|
|
88
|
+
filePath: pkgPath,
|
|
89
|
+
json: pkgStr,
|
|
90
|
+
fields,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
const MODULE_REGEX = /^((@[^/\s]+\/){0,1}([^/\s.~]+[^/\s]*)){1}(@[^/\s]+){0,1}/;
|
|
4
|
+
|
|
5
|
+
export default function validateModuleSpecifier(moduleName: string): string {
|
|
6
|
+
let matches = MODULE_REGEX.exec(moduleName);
|
|
7
|
+
if (matches) {
|
|
8
|
+
return matches[0];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return '';
|
|
12
|
+
}
|