piral-cli
Version:
The standard CLI for creating and building a Piral instance or a Pilet.
530 lines (466 loc) • 20.1 kB
text/typescript
import { describe, it, expect, vitest } from 'vitest';
import { resolve } from 'path';
import { clients } from '../npm-clients';
import {
dissectPackageName,
installNpmPackage,
isMonorepoPackageRef,
installNpmDependencies,
createNpmPackage,
findNpmTarball,
findSpecificVersion,
findLatestVersion,
isLocalPackage,
makeGitUrl,
makeFilePath,
getCurrentPackageDetails,
isLinkedPackage,
combinePackageRef,
getPackageName,
getFilePackageVersion,
getGitPackageVersion,
getPackageVersion,
isGitPackage,
makeExternals,
findPackageRoot,
determineNpmClient,
} from './npm';
vitest.mock('child_process');
vitest.mock('../external', async () => ({
...((await vitest.importActual('../external')) as any),
rc() {},
ora() {
return {
succeed(msg) {
console.warn(msg);
},
warn(msg) {
console.warn(msg);
},
info(msg) {
console.info(msg);
},
fail(msg) {
console.error(msg);
},
};
},
getModulePath(root, moduleName) {
return require.resolve(moduleName, {
paths: [root],
});
},
}));
let specialCase = false;
let shouldFind = true;
let wrongCase = false;
const jsonValueString = JSON.stringify([{ name: 'npm' }]);
const jsonValueStringWrong = JSON.stringify([]);
vitest.mock('./scripts', () => ({
runCommand: (exe: string, args: Array<string>, cwd: string, output?: NodeJS.WritableStream) => {
return new Promise<void>((resolve) => {
output?.write(wrongCase ? jsonValueStringWrong : jsonValueString, () => {});
resolve();
});
},
}));
vitest.mock('fs', async () => {
const fs = (await vitest.importActual('fs')) as any;
return {
...fs,
exists: (file: string, cb: (status: boolean) => void) =>
cb(
shouldFind &&
!file.endsWith('package.json') &&
!(specialCase && (file.endsWith('lerna.json') || file.endsWith('yarn.lock'))),
),
existsSync: (file: string) => {
return true;
},
readFile: (
file: string,
type: string,
callback: (err: NodeJS.ErrnoException | undefined, data: string) => void,
) => {
if (fs.existsSync(file)) {
return fs.readFile(file, type, callback);
}
return callback(undefined, '');
},
realpathSync: () => ({}),
readFileSync: () => '',
};
});
describe('npm Module', () => {
it('findPackageRoot correctly resolves the package root of parcel-bundler', () => {
const dir = process.cwd();
const version = findPackageRoot('webpack', dir);
expect(version).toBe(resolve(dir, 'node_modules', 'webpack', 'package.json'));
});
it('findPackageRoot returns undefined for invalid package', () => {
const dir = process.cwd();
const version = findPackageRoot('foo-bar-not-exist', dir);
expect(version).toBeUndefined();
});
it('dissects a fully qualified name with latest correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), 'foo@latest', client);
expect(hadVersion).toBe(true);
expect(version).toBe('latest');
expect(name).toBe('foo');
expect(type).toBe('registry');
});
it('dissects a fully qualified name with a specific version correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), 'foo@1.2.3', client);
expect(hadVersion).toBe(true);
expect(version).toBe('1.2.3');
expect(name).toBe('foo');
expect(type).toBe('registry');
});
it('dissects a simple name correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), 'foo', client);
expect(hadVersion).toBe(false);
expect(version).toBe('latest');
expect(name).toBe('foo');
expect(type).toBe('registry');
});
it('dissects a relative file name correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName('/home/yolo', '../foo/bar', client);
expect(hadVersion).toBe(false);
expect(version).toBe('latest');
expect(name).toBe(resolve('/home/yolo', '../foo/bar'));
expect(type).toBe('file');
});
it('dissects an absolute file name correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName('/home/yolo', '/foo/bar', client);
expect(hadVersion).toBe(false);
expect(version).toBe('latest');
expect(name).toBe(resolve('/home/yolo', '/foo/bar'));
expect(type).toBe('file');
});
it('dissects a git SSH repo name correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), 'ssh://foo-bar.com/foo.git', client);
expect(hadVersion).toBe(false);
expect(version).toBe('latest');
expect(name).toBe('git+ssh://foo-bar.com/foo.git');
expect(type).toBe('git');
});
it('dissects a git HTTPS repo name correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), 'https://foo-bar.com/foo.git', client);
expect(hadVersion).toBe(false);
expect(version).toBe('latest');
expect(name).toBe('git+https://foo-bar.com/foo.git');
expect(type).toBe('git');
});
it('dissects a scoped name correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), '@foo/bar', client);
expect(hadVersion).toBe(false);
expect(version).toBe('latest');
expect(name).toBe('@foo/bar');
expect(type).toBe('registry');
});
it('dissects a scoped fully qualified name with latest correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), '@foo/bar@latest', client);
expect(hadVersion).toBe(true);
expect(version).toBe('latest');
expect(name).toBe('@foo/bar');
expect(type).toBe('registry');
});
it('dissects a scoped fully qualified name with a specific version correctly', async () => {
wrongCase = false;
const client = await determineNpmClient(process.cwd());
const [name, version, hadVersion, type] = await dissectPackageName(process.cwd(), '@foo/bar@^1.x', client);
expect(hadVersion).toBe(true);
expect(version).toBe('^1.x');
expect(name).toBe('@foo/bar');
expect(type).toBe('registry');
});
it('installs a package using the npm command line tool without a target', async () => {
wrongCase = false;
await installNpmPackage({ direct: 'npm' }, 'foo', 'latest').then((result) =>
expect(result).toEqual(jsonValueString),
);
wrongCase = true;
await installNpmPackage({ direct: 'npm' }, 'foo', 'latest').then((result) =>
expect(result).not.toEqual(jsonValueString),
);
});
it('installs a package using the npm command line tool without a version', async () => {
wrongCase = false;
await installNpmPackage({ direct: 'npm' }, 'foo').then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await installNpmPackage({ direct: 'npm' }, 'foo').then((result) => expect(result).not.toEqual(jsonValueString));
});
it('installs a package using the Yarn command line tool without a version', async () => {
wrongCase = false;
await installNpmPackage({ direct: 'yarn' }, 'foo').then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await installNpmPackage({ direct: 'yarn' }, 'foo').then((result) => expect(result).not.toEqual(jsonValueString));
});
it('installs a package using the Pnpm command line tool without a version', async () => {
wrongCase = false;
await installNpmPackage({ direct: 'pnpm' }, 'foo').then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await installNpmPackage({ direct: 'pnpm' }, 'foo').then((result) => expect(result).not.toEqual(jsonValueString));
});
it('installs a package using the npm command line tool with some flag', async () => {
wrongCase = false;
await installNpmPackage({ direct: 'pnpm' }, 'foo', '1.3', '.', '--a=b').then((result) =>
expect(result).toEqual(jsonValueString),
);
wrongCase = true;
await installNpmPackage({ direct: 'pnpm' }, 'foo', '1.3', '.', '--a=b').then((result) =>
expect(result).not.toEqual(jsonValueString),
);
});
it('detectNpm finds package-lock.json', async () => {
shouldFind = true;
await clients.npm.detectClient('test').then((result) => expect(result).toBeTruthy());
shouldFind = false;
await clients.npm.detectClient('toast').then((result) => expect(result).toBeFalsy());
shouldFind = true;
});
it('detectPnpm finds pnpm-lock.yaml', async () => {
shouldFind = true;
await clients.pnpm.detectClient('test').then((result) => expect(result).toBeTruthy());
shouldFind = false;
await clients.pnpm.detectClient('toast').then((result) => expect(result).toBeFalsy());
shouldFind = true;
});
it('detectYarn finds yarn.lock', async () => {
shouldFind = true;
await clients.yarn.detectClient('test').then((result) => expect(result).toBeTruthy());
shouldFind = false;
specialCase = true;
await clients.yarn.detectClient('toast').then((result) => expect(result).toBeFalsy());
shouldFind = true;
specialCase = false;
});
it('uses npm to verify whether a particular package is included in monorepo package', async () => {
wrongCase = false;
await isMonorepoPackageRef('npm', await determineNpmClient('./')).then((result) => expect(result).toBeTruthy());
wrongCase = true;
await isMonorepoPackageRef('npm', await determineNpmClient('./')).then((result) => expect(result).toBeFalsy());
});
it('verifies whether lerna bootstrap ran', async () => {
wrongCase = false;
await clients.lerna.installDependencies().then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await clients.lerna.installDependencies().then((result) => expect(result).not.toEqual(jsonValueString));
});
it('install dependencies with npm client', async () => {
wrongCase = false;
await installNpmDependencies({ direct: 'npm' }).then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await installNpmDependencies({ direct: 'npm' }).then((result) => expect(result).not.toEqual(jsonValueString));
});
it('install dependencies with pnpm client', async () => {
wrongCase = false;
await installNpmDependencies({ direct: 'pnpm' }).then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await installNpmDependencies({ direct: 'pnpm' }).then((result) => expect(result).not.toEqual(jsonValueString));
});
it('install dependencies with yarn client', async () => {
wrongCase = false;
await installNpmDependencies({ direct: 'yarn' }).then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await installNpmDependencies({ direct: 'yarn' }).then((result) => expect(result).not.toEqual(jsonValueString));
});
it('create npm package', async () => {
wrongCase = false;
await createNpmPackage().then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await createNpmPackage().then((result) => expect(result).not.toEqual(jsonValueString));
});
it('find npm tarball', async () => {
wrongCase = false;
await findNpmTarball('foo').then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await findNpmTarball('foo').then((result) => expect(result).not.toEqual(jsonValueString));
});
it('find latest version', async () => {
wrongCase = false;
await findLatestVersion('foo').then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await findLatestVersion('foo').then((result) => expect(result).not.toEqual(jsonValueString));
});
it('find specific version', async () => {
wrongCase = false;
await findSpecificVersion('foo', '1.0.0').then((result) => expect(result).toEqual(jsonValueString));
wrongCase = true;
await findSpecificVersion('foo', '1.0.0').then((result) => expect(result).not.toEqual(jsonValueString));
});
it('check if package from full file is local', () => {
const result = isLocalPackage('./', 'file://foo.tgz');
expect(result).toBeTruthy();
});
it('check if package from current dir is local', () => {
const result = isLocalPackage('./', './');
expect(result).toBeTruthy();
});
it('check if package from file is local', () => {
const result = isLocalPackage('./', 'foo.tgz');
expect(result).toBeTruthy();
});
it('check if package from nothing is not local', () => {
const result = isLocalPackage('./', '');
expect(result).toBeFalsy();
});
it('check if package from tilde version is not local', () => {
const result = isLocalPackage('./', '~12.2.2');
expect(result).toBeFalsy();
});
it('check if package from caret version is not local', () => {
const result = isLocalPackage('./', '^12.2.2');
expect(result).toBeFalsy();
});
it('check if package from star version is not local', () => {
const result = isLocalPackage('./', '*');
expect(result).toBeFalsy();
});
it('check if package from greater than is not local', () => {
const result = isLocalPackage('./', '>=1.0.0');
expect(result).toBeFalsy();
});
it('check if package from home dir is local', () => {
const result = isLocalPackage('./', '~/foo/bar');
expect(result).toBeTruthy();
});
it('check if package from zero is not local', () => {
const result = isLocalPackage('./', '.0');
expect(result).toBeFalsy();
});
it('check if package from absolute dir is local', () => {
const result = isLocalPackage('./', '/0');
expect(result).toBeTruthy();
});
it('check if package is coming from git', () => {
let result = isGitPackage('https://.foo.git');
expect(result).toBeTruthy();
result = isGitPackage('git+');
expect(result).toBeTruthy();
result = isGitPackage('');
expect(result).toBeFalsy();
});
it('makeGitUrl returns valid git url', () => {
let result = makeGitUrl('git+https://.foo.git');
expect(result).toEqual('git+https://.foo.git');
result = makeGitUrl('https://.foo.git');
expect(result).toEqual('git+https://.foo.git');
});
it('makeFilePath returns valid file path', () => {
let result = makeFilePath('/', 'file:/foo.tgz');
expect(result).toEqual('file:/foo.tgz');
result = makeFilePath(resolve('./test', ''), 'foo.tgz');
const path = resolve('./test', 'foo.tgz');
expect(result).toEqual(`file:${path}`);
});
it('verify whether package is linked', () => {
const target = process.cwd();
let result = isLinkedPackage('foo', 'registry', false, target);
expect(result).toBeFalsy();
result = isLinkedPackage('foo', 'file', false, target);
expect(result).toBeFalsy();
result = isLinkedPackage('foo', 'git', false, target);
expect(result).toBeFalsy();
});
it('combine package refernce', () => {
let result = combinePackageRef('foo', '1.0.0', 'registry');
expect(result).toBe('foo@1.0.0');
result = combinePackageRef('foo', '', 'registry');
expect(result).toBe('foo@latest');
result = combinePackageRef('foo', '', 'file');
expect(result).toBe('foo');
result = combinePackageRef('foo', '', 'git');
expect(result).toBe('foo');
});
it('retrieve package name', async () => {
let result = getPackageName('./', 'foo', 'file');
expect(result).toBeInstanceOf(Promise);
await result.then((message) => expect(message).toBeUndefined());
result = getPackageName('./', 'foo', 'git');
expect(result).toBeInstanceOf(Promise);
await result.then((message) => expect(message).toBeUndefined());
result = getPackageName('./', 'foo', 'registry');
expect(result).toBeInstanceOf(Promise);
await result.then((name) => expect(name).toEqual('foo'));
});
it('gets path to file package', () => {
const result = getFilePackageVersion('./test/foo.tgz', './');
expect(result).toEqual('file:test/foo.tgz');
});
it('gets path to git package', () => {
const result = getGitPackageVersion('git+https://.foo.git');
expect(result).toEqual('git+https://.foo.git');
});
it('get version of package', () => {
let result = getPackageVersion(true, 'foo', '1.0.0', 'registry', './');
expect(result).toEqual('1.0.0');
result = getPackageVersion(false, 'foo', '1.0.0', 'registry', './');
expect(result).toBeFalsy();
result = getPackageVersion(true, './foo.tgz', '', 'file', './');
expect(result).toEqual('file:foo.tgz');
result = getPackageVersion(true, 'git+https://.foo.git', '', 'git', '');
expect(result).toEqual('git+https://.foo.git');
});
it('gets path to git package', async () => {
const result = getCurrentPackageDetails('./', './foo.tgz', '', 'file://foo.tgz', './');
await result.then(([path, version]) => {
expect(path).not.toBeUndefined();
});
const result2 = getCurrentPackageDetails('./', './foo.tgz', '', 'git+https://.foo.git', './');
await result2.then(([path, version]) => {
expect(path).not.toBeUndefined();
});
const result3 = getCurrentPackageDetails('./', './foo.tgz', '1.0.0', 'latest', './');
await result3.then(([path, version]) => {
expect(path).toEqual('./foo.tgz@latest');
expect(version).toEqual('latest');
});
});
it('makeExternals without externals returns coreExternals', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, []);
expect(externals).toEqual(['react', 'react-dom', 'react-router', 'react-router-dom', 'tslib']);
});
it('makeExternals with no externals returns coreExternals', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, []);
expect(externals).toEqual(['react', 'react-dom', 'react-router', 'react-router-dom', 'tslib']);
});
it('makeExternals with exclude coreExternals returns empty set', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, ['!*']);
expect(externals).toEqual([]);
});
it('makeExternals with externals concats coreExternals', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, ['foo', 'bar']);
expect(externals).toEqual(['foo', 'bar', 'react', 'react-dom', 'react-router', 'react-router-dom', 'tslib']);
});
it('makeExternals with external duplicate only reflects coreExternals', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, ['react', 'foo']);
expect(externals).toEqual(['react', 'foo', 'react-dom', 'react-router', 'react-router-dom', 'tslib']);
});
it('makeExternals with explicit include and exclude', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, ['react', 'react-calendar', '!tslib']);
expect(externals).toEqual(['react', 'react-calendar', 'react-dom', 'react-router', 'react-router-dom']);
});
it('makeExternals with all exclude and explicit include', async () => {
const externals = await makeExternals(process.cwd(), { piral: '*' }, ['react', 'react-router-dom', '!*']);
expect(externals).toEqual(['react', 'react-router-dom']);
});
});