Skip to content

Commit

Permalink
feat(core): prune lock file based on projects dependencies and packag…
Browse files Browse the repository at this point in the history
…e.json (#13317)
  • Loading branch information
meeroslav committed Dec 8, 2022
1 parent 977ff7e commit c3bc517
Show file tree
Hide file tree
Showing 15 changed files with 471 additions and 214 deletions.
21 changes: 21 additions & 0 deletions docs/generated/devkit/index.md
Expand Up @@ -171,6 +171,7 @@ It only uses language primitives and immutable objects
- [offsetFromRoot](../../devkit/index#offsetfromroot)
- [parseJson](../../devkit/index#parsejson)
- [parseTargetString](../../devkit/index#parsetargetstring)
- [pruneLockFile](../../devkit/index#prunelockfile)
- [readAllWorkspaceConfiguration](../../devkit/index#readallworkspaceconfiguration)
- [readCachedProjectGraph](../../devkit/index#readcachedprojectgraph)
- [readJson](../../devkit/index#readjson)
Expand Down Expand Up @@ -1724,6 +1725,26 @@ parseTargetString('proj:test:production'); // returns { project: "proj", target:

---

### pruneLockFile

**pruneLockFile**(`projectName`, `isProduction?`, `packageManager?`): `Promise`<`string`\>

Prune lock file based on the given project's dependencies and overrides in local package.json

#### Parameters

| Name | Type | Default value | Description |
| :--------------- | :---------------------------------------------------- | :------------ | :----------------------------------------------------------------- |
| `projectName` | `string` | `undefined` | Project to prune against |
| `isProduction` | `boolean` | `true` | Whether to include optional and dev dependencies |
| `packageManager` | [`PackageManager`](../../devkit/index#packagemanager) | `undefined` | Package manager to use (automatically detected based on lock file) |

#### Returns

`Promise`<`string`\>

---

### readAllWorkspaceConfiguration

**readAllWorkspaceConfiguration**(): [`ProjectsConfigurations`](../../devkit/index#projectsconfigurations) & [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/packages/devkit.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions packages/devkit/index.ts
Expand Up @@ -362,3 +362,8 @@ export { Hash, Hasher } from 'nx/src/hasher/hasher';
* @category Utils
*/
export { cacheDir } from 'nx/src/utils/cache-directory';

/**
* @category Package Manager
*/
export { pruneLockFile } from 'nx/src/lock-file/lock-file';
44 changes: 34 additions & 10 deletions packages/nx/src/lock-file/lock-file.ts
Expand Up @@ -28,6 +28,9 @@ import {
ProjectGraphExternalNode,
} from '../config/project-graph';
import { existsSync } from 'fs';
import { createProjectGraphAsync } from '../project-graph/project-graph';
import { createPackageJson } from '../utils/create-package-json';
import { normalizePackageJson } from './utils/pruning';

const YARN_LOCK_PATH = join(workspaceRoot, 'yarn.lock');
const NPM_LOCK_PATH = join(workspaceRoot, 'package-lock.json');
Expand Down Expand Up @@ -159,23 +162,44 @@ export function writeLockFile(
}

/**
* Prunes {@link LockFileData} based on minimal necessary set of packages
* Returns new {@link LockFileData}
* Prune lock file based on the given project's dependencies and overrides in local package.json
*
* @param projectName Project to prune against
* @param isProduction Whether to include optional and dev dependencies
* @param packageManager Package manager to use (automatically detected based on lock file)
* @returns
*/
export function pruneLockFileData(
lockFile: LockFileData,
packages: string[],
projectName?: string,
export async function pruneLockFile(
projectName: string,
isProduction = true,
packageManager: PackageManager = detectPackageManager(workspaceRoot)
): LockFileData {
): Promise<string> {
const lockFileData = parseLockFile(packageManager);
const projectGraph = await createProjectGraphAsync();

if (!projectGraph.nodes[projectName]) {
throw Error(`Project "${projectName}" was not found.`);
}

const packageJson = createPackageJson(projectName, projectGraph, {});
// cleanup irrelevant fields from the generated package.json
const normalizedPackageJson = normalizePackageJson(
packageJson,
isProduction,
projectName
);

if (packageManager === 'yarn') {
return pruneYarnLockFile(lockFile, packages, projectName);
const prunedData = pruneYarnLockFile(lockFileData, normalizedPackageJson);
return stringifyYarnLockFile(prunedData);
}
if (packageManager === 'pnpm') {
return prunePnpmLockFile(lockFile, packages, projectName);
const prunedData = prunePnpmLockFile(lockFileData, normalizedPackageJson);
return stringifyPnpmLockFile(prunedData);
}
if (packageManager === 'npm') {
return pruneNpmLockFile(lockFile, packages, projectName);
const prunedData = pruneNpmLockFile(lockFileData, normalizedPackageJson);
return stringifyNpmLockFile(prunedData);
}
throw Error(`Unknown package manager: ${packageManager}`);
}
54 changes: 32 additions & 22 deletions packages/nx/src/lock-file/npm.spec.ts
Expand Up @@ -27,6 +27,17 @@ jest.mock('nx/src/utils/workspace-root', () => ({
workspaceRoot: '/root',
}));

const TypeScriptOnlyPackage = {
name: 'test',
version: '0.0.0',
dependencies: { typescript: '4.8.4' },
};
const YargsAndDevkitPackage = {
name: 'test',
version: '0.0.0',
dependencies: { '@nrwl/devkit': '15.0.13', yargs: '17.6.2' },
};

describe('npm LockFile utility', () => {
describe('v3', () => {
const parsedLockFile = parseNpmLockFile(lockFileV3);
Expand Down Expand Up @@ -115,27 +126,28 @@ describe('npm LockFile utility', () => {
it('should prune the lock file', () => {
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['typescript']).dependencies
pruneNpmLockFile(parsedLockFile, TypeScriptOnlyPackage).dependencies
).length
).toEqual(1);
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
.dependencies
pruneNpmLockFile(parsedLockFile, YargsAndDevkitPackage).dependencies
).length
).toEqual(136);
});

it('should correctly prune lockfile with single package', () => {
expect(
stringifyNpmLockFile(pruneNpmLockFile(parsedLockFile, ['typescript']))
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, TypeScriptOnlyPackage)
)
).toEqual(lockFileV3JustTypescript);
});

it('should correctly prune lockfile with multiple packages', () => {
expect(
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
pruneNpmLockFile(parsedLockFile, YargsAndDevkitPackage)
)
).toEqual(lockFileV3YargsAndDevkitOnly);
});
Expand Down Expand Up @@ -228,28 +240,26 @@ describe('npm LockFile utility', () => {
it('should prune the lock file', () => {
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['typescript']).dependencies
pruneNpmLockFile(parsedLockFile, TypeScriptOnlyPackage).dependencies
).length
).toEqual(1);
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
.dependencies
pruneNpmLockFile(parsedLockFile, YargsAndDevkitPackage).dependencies
).length
).toEqual(136);
});

it('should correctly prune lockfile with single package', () => {
expect(
stringifyNpmLockFile(pruneNpmLockFile(parsedLockFile, ['typescript']))
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, TypeScriptOnlyPackage)
)
).toEqual(lockFileV2JustTypescript);
});

it('should correctly prune lockfile with multiple packages', () => {
const pruned = pruneNpmLockFile(parsedLockFile, [
'yargs',
'@nrwl/devkit',
]);
const pruned = pruneNpmLockFile(parsedLockFile, YargsAndDevkitPackage);
expect(stringifyNpmLockFile(pruned)).toEqual(
lockFileV2YargsAndDevkitOnly
);
Expand Down Expand Up @@ -346,29 +356,29 @@ describe('npm LockFile utility', () => {
it('should prune the lock file', () => {
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['typescript']).dependencies
pruneNpmLockFile(parsedLockFile, TypeScriptOnlyPackage).dependencies
).length
).toEqual(1);
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
.dependencies
pruneNpmLockFile(parsedLockFile, YargsAndDevkitPackage).dependencies
).length
).toEqual(136);
});

it('should correctly prune lockfile with single package', () => {
expect(
stringifyNpmLockFile(pruneNpmLockFile(parsedLockFile, ['typescript']))
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, TypeScriptOnlyPackage)
)
).toEqual(lockFileV1JustTypescript);
});

it('should correctly prune lockfile with multiple packages', () => {
expect(
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
)
).toEqual(lockFileV1YargsAndDevkitOnly);
const pruned = pruneNpmLockFile(parsedLockFile, YargsAndDevkitPackage);
expect(stringifyNpmLockFile(pruned)).toEqual(
lockFileV1YargsAndDevkitOnly
);
});
});
});
Expand Down

1 comment on commit c3bc517

@vercel
Copy link

@vercel vercel bot commented on c3bc517 Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx-five.vercel.app
nx-dev-nrwl.vercel.app
nx.dev

Please sign in to comment.