nx/packages/js/src/utils/find-npm-dependencies.spec.ts

749 lines
16 KiB
TypeScript

import 'nx/src/internal-testing-utils/mock-fs';
import { vol } from 'memfs';
import { findNpmDependencies } from './find-npm-dependencies';
jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'),
workspaceRoot: '/root',
}));
jest.mock('nx/src/utils/workspace-root', () => ({
workspaceRoot: '/root',
}));
describe('findNpmDependencies', () => {
const nxJson = {
targetDefaults: {
build: {
inputs: [
'{projectRoot}/**/*',
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
],
},
},
};
afterEach(() => {
vol.reset();
});
it('should pick up external npm dependencies and their versions', () => {
vol.fromJSON(
{
'./nx.json': JSON.stringify(nxJson),
},
'/root'
);
const libWithExternalDeps = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: { build: {} },
},
};
const projectGraph = {
nodes: {
'my-lib': libWithExternalDeps,
},
externalNodes: {
'npm:foo': {
name: 'npm:foo' as const,
type: 'npm' as const,
data: {
packageName: 'foo',
version: '1.0.0',
},
},
},
dependencies: {},
};
const projectFileMap = {
'my-lib': [
{
file: 'libs/my-lib/index.ts',
hash: '123',
deps: ['npm:foo'],
},
],
};
const results = findNpmDependencies(
'/root',
libWithExternalDeps,
projectGraph,
projectFileMap,
'build'
);
expect(results).toEqual({
foo: '1.0.0',
});
});
it('should pick up helper npm dependencies if required', () => {
vol.fromJSON(
{
'./nx.json': JSON.stringify(nxJson),
'./libs/my-lib/tsconfig.json': JSON.stringify({
compilerOptions: {
importHelpers: true,
},
}),
'./libs/my-lib/.swcrc': JSON.stringify({
jsc: {
externalHelpers: true,
},
}),
},
'/root'
);
const libWithHelpers = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build1: {
executor: '@nx/js:tsc',
options: {
tsConfig: 'libs/my-lib/tsconfig.json',
},
},
build2: {
executor: '@nx/js:swc',
options: {},
},
},
},
};
const projectGraph = {
nodes: {
'my-lib': libWithHelpers,
},
externalNodes: {
'npm:tslib': {
name: 'npm:tslib' as const,
type: 'npm' as const,
data: {
packageName: 'tslib',
version: '2.6.0',
},
},
'npm:@swc/helpers': {
name: 'npm:@swc/helpers' as const,
type: 'npm' as const,
data: {
packageName: '@swc/helpers',
version: '0.5.0',
},
},
},
dependencies: {},
};
const projectFileMap = {
'my-lib': [],
};
expect(
findNpmDependencies(
'/root',
libWithHelpers,
projectGraph,
projectFileMap,
'build1'
)
).toEqual({
tslib: '2.6.0',
});
expect(
findNpmDependencies(
'/root',
libWithHelpers,
projectGraph,
projectFileMap,
'build2'
)
).toEqual({
'@swc/helpers': '0.5.0',
});
});
it.each`
fileName
${'tsconfig.base.json'}
${'tsconfig.json'}
`(
'should pick up helper npm dependencies when using tsc and run-commands',
({ fileName }) => {
vol.fromJSON(
{
[`./${fileName}`]: JSON.stringify({
compilerOptions: { importHelpers: true },
}),
'./nx.json': JSON.stringify(nxJson),
'./libs/my-lib/tsconfig.json': JSON.stringify({
compilerOptions: {
importHelpers: true,
},
}),
},
'/root'
);
const lib = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build: {
executor: 'nx:run-commands',
options: {
command: 'tsc --build tsconfig.lib.json --pretty --verbose',
},
},
},
},
};
const projectGraph = {
nodes: {
'my-lib': lib,
},
externalNodes: {
'npm:tslib': {
name: 'npm:tslib' as const,
type: 'npm' as const,
data: {
packageName: 'tslib',
version: '2.6.0',
},
},
},
dependencies: {},
};
const projectFileMap = {
'my-lib': [],
};
expect(
findNpmDependencies('/root', lib, projectGraph, projectFileMap, 'build')
).toEqual({
tslib: '2.6.0',
});
}
);
it('should handle missing ts/swc helper packages from externalNodes', () => {
vol.fromJSON(
{
'./nx.json': JSON.stringify(nxJson),
'./libs/my-lib/tsconfig.json': JSON.stringify({
compilerOptions: {
importHelpers: true,
},
}),
'./libs/my-lib/.swcrc': JSON.stringify({
jsc: {
externalHelpers: true,
},
}),
},
'/root'
);
const libWithHelpers = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build1: {
executor: '@nx/js:tsc',
options: {
tsConfig: 'libs/my-lib/tsconfig.json',
},
},
build2: {
executor: '@nx/js:swc',
options: {},
},
},
},
};
const projectGraph = {
nodes: {
'my-lib': libWithHelpers,
},
externalNodes: {},
dependencies: {},
};
const projectFileMap = {
'my-lib': [],
};
expect(
findNpmDependencies(
'/root',
libWithHelpers,
projectGraph,
projectFileMap,
'build1'
)
).toEqual({});
expect(
findNpmDependencies(
'/root',
libWithHelpers,
projectGraph,
projectFileMap,
'build2'
)
).toEqual({});
});
it('should not pick up helper npm dependencies if not required', () => {
vol.fromJSON(
{
'./libs/my-lib/tsconfig.json': JSON.stringify({
compilerOptions: {
importHelpers: false,
},
}),
'./libs/my-lib/.swcrc': JSON.stringify({
jsc: {
externalHelpers: false,
},
}),
},
'/root'
);
const libWithInlinedHelpers = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build1: {
executor: '@nx/js:tsc',
options: {
tsConfig: 'libs/my-lib/tsconfig.json',
},
},
build2: {
executor: '@nx/js:swc',
options: {},
},
},
},
};
const projectGraph = {
nodes: {
'my-lib': libWithInlinedHelpers,
},
externalNodes: {
'npm:tslib': {
name: 'npm:tslib' as const,
type: 'npm' as const,
data: {
packageName: 'tslib',
version: '2.6.0',
},
},
'npm:@swc/helpers': {
name: 'npm:@swc/helpers' as const,
type: 'npm' as const,
data: {
packageName: '@swc/helpers',
version: '0.5.0',
},
},
},
dependencies: {},
};
const projectFileMap = {
'my-lib': [],
};
const results = findNpmDependencies(
'/root',
libWithInlinedHelpers,
projectGraph,
projectFileMap,
'build'
);
expect(results).toEqual({});
});
it('should pick up helper npm dependencies from runtimeHelpers', () => {
const libWithUnknownExecutor = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build: { executor: '@my-org/some-package:build' },
},
},
};
const projectGraph = {
nodes: {
'my-lib': libWithUnknownExecutor,
},
externalNodes: {
'npm:tslib': {
name: 'npm:tslib' as const,
type: 'npm' as const,
data: { packageName: 'tslib', version: '2.6.0' },
},
'npm:@swc/helpers': {
name: 'npm:@swc/helpers' as const,
type: 'npm' as const,
data: { packageName: '@swc/helpers', version: '0.5.0' },
},
},
dependencies: {},
};
const projectFileMap = { 'my-lib': [] };
const results = findNpmDependencies(
'/root',
libWithUnknownExecutor,
projectGraph,
projectFileMap,
'build',
{ runtimeHelpers: ['tslib'] }
);
expect(results).toStrictEqual({ tslib: '2.6.0' });
});
it('should not duplicate helper npm dependencies when additionally set in runtimeHelpers', () => {
vol.fromJSON(
{
'./nx.json': JSON.stringify(nxJson),
'./libs/my-lib/tsconfig.json': JSON.stringify({
compilerOptions: { importHelpers: true },
}),
},
'/root'
);
const libWithUnknownExecutor = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: {
build: {
executor: '@nx/js:tsc',
options: { tsConfig: 'libs/my-lib/tsconfig.json' },
},
},
},
};
const projectGraph = {
nodes: {
'my-lib': libWithUnknownExecutor,
},
externalNodes: {
'npm:tslib': {
name: 'npm:tslib' as const,
type: 'npm' as const,
data: { packageName: 'tslib', version: '2.6.0' },
},
'npm:@swc/helpers': {
name: 'npm:@swc/helpers' as const,
type: 'npm' as const,
data: { packageName: '@swc/helpers', version: '0.5.0' },
},
},
dependencies: {},
};
const projectFileMap = { 'my-lib': [] };
const results = findNpmDependencies(
'/root',
libWithUnknownExecutor,
projectGraph,
projectFileMap,
'build',
{ runtimeHelpers: ['tslib'] }
);
expect(results).toStrictEqual({ tslib: '2.6.0' });
});
it('should support recursive collection of dependencies', () => {
vol.fromJSON(
{
'./nx.json': JSON.stringify(nxJson),
},
'/root'
);
const parentLib = {
name: 'parent',
type: 'lib' as const,
data: {
root: 'libs/parent',
targets: { build: {} },
},
};
const projectGraph = {
nodes: {
parent: parentLib,
child1: {
name: 'child1',
type: 'lib' as const,
data: {
root: 'libs/child1',
targets: { build: {} },
},
},
child2: {
name: 'child2',
type: 'lib' as const,
data: {
root: 'libs/child2',
targets: { build: {} },
},
},
},
externalNodes: {
'npm:foo': {
name: 'npm:foo' as const,
type: 'npm' as const,
data: {
packageName: 'foo',
version: '1.0.0',
},
},
},
dependencies: {
parent: [
{
type: 'static',
source: 'parent',
target: 'child1',
},
],
child1: [
{
type: 'static',
source: 'child1',
target: 'child2',
},
],
child2: [
{
type: 'static',
source: 'child2',
target: 'npm:foo',
},
],
},
};
const projectFileMap = {
parent: [{ file: 'libs/parent/index.ts', hash: '123', deps: ['child1'] }],
child1: [{ file: 'libs/child1/index.ts', hash: '123', deps: ['child2'] }],
child2: [
{ file: 'libs/child2/index.ts', hash: '123', deps: ['npm:foo'] },
],
};
const results = findNpmDependencies(
'/root',
parentLib,
projectGraph,
projectFileMap,
'build',
{
includeTransitiveDependencies: true,
}
);
expect(results).toEqual({
foo: '1.0.0',
});
});
it('should find workspace dependencies', () => {
vol.fromJSON(
{
'./libs/lib3/package.json': JSON.stringify({
name: '@acme/lib3',
version: '0.0.1',
}),
'./nx.json': JSON.stringify(nxJson),
},
'/root'
);
const lib1 = {
name: 'lib1',
type: 'lib' as const,
data: {
root: 'libs/lib1',
targets: { build: {} },
},
};
const lib2 = {
name: 'lib2',
type: 'lib' as const,
data: {
root: 'libs/lib2',
targets: { build: {} },
},
};
const lib3 = {
name: 'lib3',
type: 'lib' as const,
data: {
root: 'libs/lib3',
targets: { build: {} },
},
};
const projectGraph = {
nodes: {
lib1: lib1,
lib2: lib2,
lib3: lib3,
},
externalNodes: {},
dependencies: {},
};
const projectFileMap = {
lib1: [{ file: 'libs/lib1/index.ts', hash: '123', deps: ['lib3'] }],
lib2: [{ file: 'libs/lib1/index.ts', hash: '123', deps: ['lib3'] }],
lib3: [],
};
expect(
findNpmDependencies('/root', lib1, projectGraph, projectFileMap, 'build')
).toEqual({
'@acme/lib3': '0.0.1',
});
expect(
findNpmDependencies('/root', lib2, projectGraph, projectFileMap, 'build')
).toEqual({
'@acme/lib3': '0.0.1',
});
});
it('should support local path for workspace dependencies', () => {
vol.fromJSON(
{
'./libs/c/package.json': JSON.stringify({
name: '@acme/c',
version: '0.0.1',
}),
'./nx.json': JSON.stringify(nxJson),
},
'/root'
);
const a = {
name: 'a',
type: 'lib' as const,
data: {
root: 'libs/a',
targets: { build: {} },
},
};
const b = {
name: 'b',
type: 'lib' as const,
data: {
root: 'libs/b',
targets: { build: {} },
},
};
const c = {
name: 'c',
type: 'lib' as const,
data: {
root: 'libs/c',
targets: { build: {} },
},
};
const projectGraph = {
nodes: {
a: a,
b: b,
c: c,
},
externalNodes: {},
dependencies: {},
};
const projectFileMap = {
a: [{ file: 'libs/a/index.ts', hash: '123', deps: ['c'] }],
b: [{ file: 'libs/a/index.ts', hash: '123', deps: ['c'] }],
c: [],
};
expect(
findNpmDependencies('/root', a, projectGraph, projectFileMap, 'build', {
useLocalPathsForWorkspaceDependencies: true,
})
).toEqual({
'@acme/c': 'file:../c',
});
expect(
findNpmDependencies('/root', b, projectGraph, projectFileMap, 'build', {
useLocalPathsForWorkspaceDependencies: true,
})
).toEqual({
'@acme/c': 'file:../c',
});
});
it('should support ignoring extra file patterns in addition to task input', () => {
vol.fromJSON(
{
'./nx.json': JSON.stringify(nxJson),
},
'/root'
);
const lib = {
name: 'my-lib',
type: 'lib' as const,
data: {
root: 'libs/my-lib',
targets: { build: {} },
},
};
const projectGraph = {
nodes: {
'my-lib': lib,
},
externalNodes: {
'npm:foo': {
name: 'npm:foo' as const,
type: 'npm' as const,
data: {
packageName: 'foo',
version: '1.0.0',
},
},
},
dependencies: {},
};
const projectFileMap = {
'my-lib': [
{
file: 'libs/my-lib/vite.config.ts',
hash: '123',
deps: ['npm:foo'],
},
],
};
const results = findNpmDependencies(
'/root',
lib,
projectGraph,
projectFileMap,
'build',
{ ignoredFiles: ['{projectRoot}/vite.config.ts'] }
);
expect(results).toEqual({});
});
});