385 lines
10 KiB
JavaScript
385 lines
10 KiB
JavaScript
|
// @flow strict-local
|
||
|
|
||
|
import {MemoryFS, NodeFS, OverlayFS} from '@parcel/fs';
|
||
|
import assert from 'assert';
|
||
|
import invariant from 'assert';
|
||
|
import path from 'path';
|
||
|
import sinon from 'sinon';
|
||
|
import ThrowableDiagnostic from '@parcel/diagnostic';
|
||
|
import {loadConfig} from '@parcel/utils';
|
||
|
import WorkerFarm from '@parcel/workers';
|
||
|
import {MockPackageInstaller, NodePackageManager} from '../src';
|
||
|
|
||
|
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
|
||
|
|
||
|
function normalize(res) {
|
||
|
return {
|
||
|
...res,
|
||
|
invalidateOnFileCreate:
|
||
|
res?.invalidateOnFileCreate?.sort((a, b) => {
|
||
|
let ax =
|
||
|
a.filePath ??
|
||
|
a.glob ??
|
||
|
(a.aboveFilePath != null && a.fileName != null
|
||
|
? a.aboveFilePath + a.fileName
|
||
|
: '');
|
||
|
let bx =
|
||
|
b.filePath ??
|
||
|
b.glob ??
|
||
|
(b.aboveFilePath != null && b.fileName != null
|
||
|
? b.aboveFilePath + b.fileName
|
||
|
: '');
|
||
|
return ax < bx ? -1 : 1;
|
||
|
}) ?? [],
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function check(resolved, expected) {
|
||
|
assert.deepEqual(normalize(resolved), normalize(expected));
|
||
|
}
|
||
|
|
||
|
describe('NodePackageManager', function () {
|
||
|
let fs;
|
||
|
let packageManager;
|
||
|
let packageInstaller;
|
||
|
let workerFarm;
|
||
|
|
||
|
// These can sometimes take a lil while
|
||
|
this.timeout(20000);
|
||
|
|
||
|
beforeEach(() => {
|
||
|
workerFarm = new WorkerFarm({
|
||
|
workerPath: require.resolve('@parcel/core/src/worker.js'),
|
||
|
});
|
||
|
fs = new OverlayFS(new MemoryFS(workerFarm), new NodeFS());
|
||
|
packageInstaller = new MockPackageInstaller();
|
||
|
packageManager = new NodePackageManager(fs, '/', packageInstaller);
|
||
|
});
|
||
|
|
||
|
afterEach(async () => {
|
||
|
loadConfig.clear();
|
||
|
await workerFarm.end();
|
||
|
});
|
||
|
|
||
|
it('resolves packages that exist', async () => {
|
||
|
check(
|
||
|
await packageManager.resolve(
|
||
|
'foo',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
),
|
||
|
{
|
||
|
pkg: {
|
||
|
version: '1.1.0',
|
||
|
},
|
||
|
resolved: path.join(FIXTURES_DIR, 'has-foo/node_modules/foo/index.js'),
|
||
|
type: 1,
|
||
|
invalidateOnFileChange: new Set([
|
||
|
path.join(FIXTURES_DIR, 'has-foo/node_modules/foo/package.json'),
|
||
|
]),
|
||
|
invalidateOnFileCreate: [
|
||
|
{
|
||
|
filePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/node_modules/foo/index.ts',
|
||
|
),
|
||
|
},
|
||
|
{
|
||
|
filePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/node_modules/foo/index.tsx',
|
||
|
),
|
||
|
},
|
||
|
{
|
||
|
fileName: 'node_modules/foo',
|
||
|
aboveFilePath: path.join(FIXTURES_DIR, 'has-foo'),
|
||
|
},
|
||
|
{
|
||
|
fileName: 'tsconfig.json',
|
||
|
aboveFilePath: path.join(FIXTURES_DIR, 'has-foo'),
|
||
|
},
|
||
|
],
|
||
|
},
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('requires packages that exist', async () => {
|
||
|
assert.deepEqual(
|
||
|
await packageManager.require(
|
||
|
'foo',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
),
|
||
|
'foobar',
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it("autoinstalls packages that don't exist", async () => {
|
||
|
packageInstaller.register('a', fs, path.join(FIXTURES_DIR, 'packages/a'));
|
||
|
|
||
|
check(
|
||
|
await packageManager.resolve(
|
||
|
'a',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
{shouldAutoInstall: true},
|
||
|
),
|
||
|
{
|
||
|
pkg: {
|
||
|
name: 'a',
|
||
|
},
|
||
|
resolved: path.join(FIXTURES_DIR, 'has-foo/node_modules/a/index.js'),
|
||
|
type: 1,
|
||
|
invalidateOnFileChange: new Set([
|
||
|
path.join(FIXTURES_DIR, 'has-foo/node_modules/a/package.json'),
|
||
|
]),
|
||
|
invalidateOnFileCreate: [
|
||
|
{
|
||
|
filePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/node_modules/a/index.ts',
|
||
|
),
|
||
|
},
|
||
|
{
|
||
|
filePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/node_modules/a/index.tsx',
|
||
|
),
|
||
|
},
|
||
|
{
|
||
|
fileName: 'node_modules/a',
|
||
|
aboveFilePath: path.join(FIXTURES_DIR, 'has-foo'),
|
||
|
},
|
||
|
{
|
||
|
fileName: 'tsconfig.json',
|
||
|
aboveFilePath: path.join(FIXTURES_DIR, 'has-foo'),
|
||
|
},
|
||
|
],
|
||
|
},
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('does not autoinstall packages that are already listed in package.json', async () => {
|
||
|
packageInstaller.register('a', fs, path.join(FIXTURES_DIR, 'packages/a'));
|
||
|
|
||
|
// $FlowFixMe assert.rejects is Node 10+
|
||
|
await assert.rejects(
|
||
|
() =>
|
||
|
packageManager.resolve(
|
||
|
'a',
|
||
|
path.join(FIXTURES_DIR, 'has-a-not-yet-installed/index.js'),
|
||
|
{shouldAutoInstall: true},
|
||
|
),
|
||
|
err => {
|
||
|
invariant(err instanceof ThrowableDiagnostic);
|
||
|
assert(err.message.includes('Run your package manager'));
|
||
|
return true;
|
||
|
},
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('does not autoinstall peer dependencies that are already listed in package.json', async () => {
|
||
|
packageInstaller.register(
|
||
|
'peers',
|
||
|
fs,
|
||
|
path.join(FIXTURES_DIR, 'packages/peers'),
|
||
|
);
|
||
|
|
||
|
let spy = sinon.spy(packageInstaller, 'install');
|
||
|
await packageManager.resolve(
|
||
|
'peers',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
{shouldAutoInstall: true},
|
||
|
);
|
||
|
assert.deepEqual(spy.args, [
|
||
|
[
|
||
|
{
|
||
|
cwd: path.join(FIXTURES_DIR, 'has-foo'),
|
||
|
packagePath: path.join(FIXTURES_DIR, 'has-foo/package.json'),
|
||
|
fs,
|
||
|
saveDev: true,
|
||
|
modules: [{name: 'peers', range: undefined}],
|
||
|
},
|
||
|
],
|
||
|
]);
|
||
|
});
|
||
|
|
||
|
it('autoinstalls peer dependencies that are not listed in package.json', async () => {
|
||
|
packageInstaller.register(
|
||
|
'foo',
|
||
|
fs,
|
||
|
path.join(FIXTURES_DIR, 'packages/foo-2.0'),
|
||
|
);
|
||
|
packageInstaller.register(
|
||
|
'peers',
|
||
|
fs,
|
||
|
path.join(FIXTURES_DIR, 'packages/peers-2.0'),
|
||
|
);
|
||
|
|
||
|
let spy = sinon.spy(packageInstaller, 'install');
|
||
|
await packageManager.resolve(
|
||
|
'peers',
|
||
|
path.join(FIXTURES_DIR, 'empty/index.js'),
|
||
|
{shouldAutoInstall: true},
|
||
|
);
|
||
|
assert.deepEqual(spy.args, [
|
||
|
[
|
||
|
{
|
||
|
cwd: path.join(FIXTURES_DIR, 'empty'),
|
||
|
packagePath: path.join(FIXTURES_DIR, 'empty/package.json'),
|
||
|
fs,
|
||
|
saveDev: true,
|
||
|
modules: [{name: 'peers', range: undefined}],
|
||
|
},
|
||
|
],
|
||
|
[
|
||
|
{
|
||
|
cwd: path.join(FIXTURES_DIR, 'empty'),
|
||
|
packagePath: path.join(FIXTURES_DIR, 'empty/package.json'),
|
||
|
fs,
|
||
|
saveDev: true,
|
||
|
modules: [{name: 'foo', range: '^2.0.0'}],
|
||
|
},
|
||
|
],
|
||
|
]);
|
||
|
});
|
||
|
|
||
|
describe('range mismatch', () => {
|
||
|
it("cannot autoinstall if there's a local requirement", async () => {
|
||
|
packageManager.invalidate(
|
||
|
'foo',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
);
|
||
|
|
||
|
// $FlowFixMe assert.rejects is Node 10+
|
||
|
await assert.rejects(
|
||
|
() =>
|
||
|
packageManager.resolve(
|
||
|
'foo',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
{
|
||
|
range: '^2.0.0',
|
||
|
},
|
||
|
),
|
||
|
err => {
|
||
|
invariant(err instanceof ThrowableDiagnostic);
|
||
|
assert.equal(
|
||
|
err.message,
|
||
|
'Could not find module "foo" satisfying ^2.0.0.',
|
||
|
);
|
||
|
return true;
|
||
|
},
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it("can autoinstall into local package if there isn't a local requirement", async () => {
|
||
|
packageInstaller.register(
|
||
|
'foo',
|
||
|
fs,
|
||
|
path.join(FIXTURES_DIR, 'packages/foo-2.0'),
|
||
|
);
|
||
|
|
||
|
let spy = sinon.spy(packageInstaller, 'install');
|
||
|
check(
|
||
|
await packageManager.resolve(
|
||
|
'foo',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/subpackage/index.js'),
|
||
|
{
|
||
|
range: '^2.0.0',
|
||
|
shouldAutoInstall: true,
|
||
|
},
|
||
|
),
|
||
|
{
|
||
|
pkg: {
|
||
|
name: 'foo',
|
||
|
version: '2.0.0',
|
||
|
},
|
||
|
resolved: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/subpackage/node_modules/foo/index.js',
|
||
|
),
|
||
|
type: 1,
|
||
|
invalidateOnFileChange: new Set([
|
||
|
path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/subpackage/node_modules/foo/package.json',
|
||
|
),
|
||
|
]),
|
||
|
invalidateOnFileCreate: [
|
||
|
{
|
||
|
filePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/subpackage/node_modules/foo/index.ts',
|
||
|
),
|
||
|
},
|
||
|
{
|
||
|
filePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/subpackage/node_modules/foo/index.tsx',
|
||
|
),
|
||
|
},
|
||
|
{
|
||
|
fileName: 'node_modules/foo',
|
||
|
aboveFilePath: path.join(FIXTURES_DIR, 'has-foo/subpackage'),
|
||
|
},
|
||
|
{
|
||
|
fileName: 'tsconfig.json',
|
||
|
aboveFilePath: path.join(FIXTURES_DIR, 'has-foo/subpackage'),
|
||
|
},
|
||
|
],
|
||
|
},
|
||
|
);
|
||
|
|
||
|
assert.deepEqual(spy.args, [
|
||
|
[
|
||
|
{
|
||
|
cwd: path.join(FIXTURES_DIR, 'has-foo/subpackage'),
|
||
|
packagePath: path.join(
|
||
|
FIXTURES_DIR,
|
||
|
'has-foo/subpackage/package.json',
|
||
|
),
|
||
|
fs,
|
||
|
saveDev: true,
|
||
|
modules: [{name: 'foo', range: '^2.0.0'}],
|
||
|
},
|
||
|
],
|
||
|
]);
|
||
|
});
|
||
|
|
||
|
it("cannot autoinstall peer dependencies if there's an incompatible local requirement", async () => {
|
||
|
packageManager.invalidate(
|
||
|
'peers',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
);
|
||
|
packageInstaller.register(
|
||
|
'foo',
|
||
|
fs,
|
||
|
path.join(FIXTURES_DIR, 'packages/foo-2.0'),
|
||
|
);
|
||
|
packageInstaller.register(
|
||
|
'peers',
|
||
|
fs,
|
||
|
path.join(FIXTURES_DIR, 'packages/peers-2.0'),
|
||
|
);
|
||
|
|
||
|
// $FlowFixMe assert.rejects is Node 10+
|
||
|
await assert.rejects(
|
||
|
() =>
|
||
|
packageManager.resolve(
|
||
|
'peers',
|
||
|
path.join(FIXTURES_DIR, 'has-foo/index.js'),
|
||
|
{
|
||
|
range: '^2.0.0',
|
||
|
shouldAutoInstall: true,
|
||
|
},
|
||
|
),
|
||
|
err => {
|
||
|
assert(err instanceof ThrowableDiagnostic);
|
||
|
assert.equal(
|
||
|
err.message,
|
||
|
'Could not install the peer dependency "foo" for "peers", installed version 1.1.0 is incompatible with ^2.0.0',
|
||
|
);
|
||
|
return true;
|
||
|
},
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|