Skip to content

Commit

Permalink
feat(js): fine-grained dep type support for publishable libs
Browse files Browse the repository at this point in the history
Allows lib package.json to define its dependencies appropriately
between dependencies and peerDependencies, while providing a
way to continue to inherit the workspace's pkg version

e.g. "foo": "[inherit]"

ISSUES CLOSED: 10550
  • Loading branch information
mckramer committed Jun 5, 2022
1 parent c0a0f16 commit 210889e
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 19 deletions.
178 changes: 177 additions & 1 deletion packages/workspace/src/utilities/buildable-libs-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { DependencyType, ProjectGraph } from '@nrwl/devkit';
import {
DependencyType,
ProjectGraph,
ProjectGraphProjectNode,
readJsonFile,
} from '@nrwl/devkit';
import { join } from 'path';
import { vol } from 'memfs';
import {
calculateProjectDependencies,
DependentBuildableProjectNode,
updateBuildableProjectPackageJsonDependencies,
updatePaths,
} from './buildable-libs-utils';

jest.mock('fs', () => require('memfs').fs);

describe('updatePaths', () => {
const deps: DependentBuildableProjectNode[] = [
{ name: '@proj/lib', node: {} as any, outputs: ['dist/libs/lib'] },
Expand Down Expand Up @@ -84,3 +94,169 @@ describe('calculateProjectDependencies', () => {
});
});
});

describe('updateBuildableProjectPackageJsonDependencies', () => {
beforeEach(() => {
vol.reset();
});

it('should add undeclared npm packages to peerDependency list', () => {
vol.fromJSON({
'./package.json': JSON.stringify({
name: '@scope/workspace',
version: '0.0.0',
dependencies: {
foo: '^1.0.0',
bar: '~2.0.0',
unused: '<= 9.9',
},
devDependencies: {
ignored: '3.3.3',
},
}),
'./dist/libs/example/package.json': JSON.stringify({
name: '@scope/example',
version: '5.0.1',
}),
});

const root = '.';
const node = libNode('example');
const dependencies: DependentBuildableProjectNode[] = [
npmDep('foo', '^1.0.0'),
npmDep('bar', '~2.0.0'),
npmDep('ignored', '3.3.3'),
];

updateBuildableProjectPackageJsonDependencies(
root,
'example',
'build',
undefined,
node,
dependencies
);

const pkg = readJsonFile(
join(root, 'dist', 'libs', 'example', 'package.json')
);
expect(pkg.dependencies).toMatchObject({
foo: '^1.0.0',
bar: '~2.0.0',
});
expect(pkg.peerDependencies).toEqual({});
expect(pkg.devDependencies).toBeUndefined();
});

it('should inherit versions for declared dependencies', () => {
vol.fromJSON({
'./package.json': JSON.stringify({
name: '@scope/workspace',
version: '0.0.0',
dependencies: {
foo: '^1.0.0',
bar: '~2.0.0',
zed: '>=4.0.0',
unused: '<= 9.9',
},
devDependencies: {
ignored: '3.3.3',
},
}),
'./dist/libs/example/package.json': JSON.stringify({
name: '@scope/example',
version: '5.0.1',
dependencies: {
foo: '[inherit]',
},
peerDependencies: {
bar: '[inherit]',
},
devDependencies: {
ignored: '[inherit]',
},
}),
'./dist/libs/other/package.json': JSON.stringify({
name: '@scope/other',
version: '5.0.2',
}),
});

const root = '.';
const node = libNode('example');
const dependencies: DependentBuildableProjectNode[] = [
npmDep('foo', '^1.0.0'),
npmDep('bar', '~2.0.0'),
npmDep('zed', '>=4.0.0'),
npmDep('ignored', '3.3.3'),
libDep('scope', 'other'),
];

updateBuildableProjectPackageJsonDependencies(
root,
'example',
'build',
undefined,
node,
dependencies,
'peerDependencies'
);

const pkg = readJsonFile(
join(root, 'dist', 'libs', 'example', 'package.json')
);
expect(pkg.dependencies).toMatchObject({
foo: '^1.0.0',
});
expect(pkg.peerDependencies).toMatchObject({
'@scope/other': '5.0.2',
bar: '~2.0.0',
});
expect(pkg.devDependencies).toMatchObject({
// devDependencies are not supported to inherit
ignored: '[inherit]',
});
});
});

function libDep(scope: string, name: string): DependentBuildableProjectNode {
return {
name: `@${scope}/${name}`,
node: libNode(name),
outputs: [`dist/libs/${name}`],
};
}

function libNode(name: string): ProjectGraphProjectNode {
return {
name,
type: 'lib',
data: {
root: `src/${name}`,
files: [],
targets: {
build: {
options: {
outputPath: `dist/libs/${name}`,
},
outputs: ['{options.outputPath}'],
},
},
},
};
}

function npmDep(name: string, version: string): DependentBuildableProjectNode {
return {
name,
node: {
name: `npm:${name}`,
type: 'npm',
data: {
packageName: name,
version,
},
},
outputs: [],
};
}
40 changes: 22 additions & 18 deletions packages/workspace/src/utilities/buildable-libs-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ export function updateBuildableProjectPackageJsonDependencies(
configurationName: string,
node: ProjectGraphProjectNode,
dependencies: DependentBuildableProjectNode[],
typeOfDependency: 'dependencies' | 'peerDependencies' = 'dependencies'
typeOfUndeclaredDependency:
| 'dependencies'
| 'peerDependencies' = 'dependencies'
) {
const outputs = getOutputsForTargetAndConfiguration(
{
Expand All @@ -309,6 +311,7 @@ export function updateBuildableProjectPackageJsonDependencies(
workspacePackageJson = readJsonFile(`${root}/package.json`);
} catch (e) {
// cannot find or invalid package.json
console.error('failed', e);
return;
}

Expand All @@ -321,13 +324,12 @@ export function updateBuildableProjectPackageJsonDependencies(
? entry.node.data.packageName
: entry.name;

if (
!hasDependency(packageJson, 'dependencies', packageName) &&
!hasDependency(packageJson, 'devDependencies', packageName) &&
!hasDependency(packageJson, 'peerDependencies', packageName)
) {
const declaredAs = whichDependency(packageJson, packageName);

// update if not declared or if dep's version should be inherited
if (!declaredAs || packageJson[declaredAs][packageName] === '[inherit]') {
const typeOfDependency = declaredAs || typeOfUndeclaredDependency;
try {
let depVersion;
if (entry.node.type === 'lib') {
const outputs = getOutputsForTargetAndConfiguration(
{
Expand All @@ -342,23 +344,16 @@ export function updateBuildableProjectPackageJsonDependencies(
);

const depPackageJsonPath = join(root, outputs[0], 'package.json');
depVersion = readJsonFile(depPackageJsonPath).version;

const depVersion = readJsonFile(depPackageJsonPath).version;
packageJson[typeOfDependency][packageName] = depVersion;
} else if (isNpmProject(entry.node)) {
// If an npm dep is part of the workspace devDependencies, do not include it the library
if (
!!workspacePackageJson.devDependencies?.[
entry.node.data.packageName
]
) {
if (!!workspacePackageJson.devDependencies?.[packageName]) {
return;
}

depVersion = entry.node.data.version;

packageJson[typeOfDependency][entry.node.data.packageName] =
depVersion;
const depVersion = entry.node.data.version;
packageJson[typeOfDependency][packageName] = depVersion;
}
updatePackageJson = true;
} catch (e) {
Expand All @@ -372,6 +367,15 @@ export function updateBuildableProjectPackageJsonDependencies(
}
}

// verify whether the package.json already specifies the dep in any host
function whichDependency(outputJson, packageName: string) {
return ['dependencies', 'devDependencies', 'peerDependencies'].find(
(depConfigName) => {
return hasDependency(outputJson, depConfigName, packageName);
}
);
}

// verify whether the package.json already specifies the dep
function hasDependency(outputJson, depConfigName: string, packageName: string) {
if (outputJson[depConfigName]) {
Expand Down

0 comments on commit 210889e

Please sign in to comment.