diff --git a/.changeset/old-onions-repair.md b/.changeset/old-onions-repair.md new file mode 100644 index 0000000000..f9a1a663e5 --- /dev/null +++ b/.changeset/old-onions-repair.md @@ -0,0 +1,5 @@ +--- +"pnpm": patch +--- + +`pnpm audit` should send the versions of workspace projects for audit. diff --git a/.changeset/shy-foxes-repair.md b/.changeset/shy-foxes-repair.md new file mode 100644 index 0000000000..2bba13bf80 --- /dev/null +++ b/.changeset/shy-foxes-repair.md @@ -0,0 +1,5 @@ +--- +"@pnpm/audit": major +--- + +New required option added: lockfileDir. diff --git a/lockfile/audit/package.json b/lockfile/audit/package.json index b4170142ae..557a391261 100644 --- a/lockfile/audit/package.json +++ b/lockfile/audit/package.json @@ -33,6 +33,7 @@ "@pnpm/audit": "workspace:*", "@pnpm/constants": "workspace:*", "@pnpm/lockfile-file": "workspace:*", + "@pnpm/test-fixtures": "workspace:*", "@types/ramda": "0.28.15", "nock": "13.2.9" }, @@ -43,6 +44,7 @@ "@pnpm/lockfile-types": "workspace:*", "@pnpm/lockfile-utils": "workspace:*", "@pnpm/lockfile-walker": "workspace:*", + "@pnpm/read-project-manifest": "workspace:*", "@pnpm/types": "workspace:*", "ramda": "npm:@pnpm/ramda@0.28.1" }, diff --git a/lockfile/audit/src/index.ts b/lockfile/audit/src/index.ts index d5942d79e5..c98b677778 100644 --- a/lockfile/audit/src/index.ts +++ b/lockfile/audit/src/index.ts @@ -14,12 +14,13 @@ export async function audit ( opts: { agentOptions?: AgentOptions include?: { [dependenciesField in DependenciesField]: boolean } + lockfileDir: string registry: string retry?: RetryTimeoutOptions timeout?: number } ) { - const auditTree = lockfileToAuditTree(lockfile, { include: opts.include }) + const auditTree = await lockfileToAuditTree(lockfile, { include: opts.include, lockfileDir: opts.lockfileDir }) const registry = opts.registry.endsWith('/') ? opts.registry : `${opts.registry}/` const auditUrl = `${registry}-/npm/v1/security/audits` const authHeaderValue = getAuthHeader(registry) diff --git a/lockfile/audit/src/lockfileToAuditTree.ts b/lockfile/audit/src/lockfileToAuditTree.ts index cc00720010..472860f0ff 100644 --- a/lockfile/audit/src/lockfileToAuditTree.ts +++ b/lockfile/audit/src/lockfileToAuditTree.ts @@ -1,7 +1,9 @@ +import path from 'path' import { Lockfile } from '@pnpm/lockfile-types' import { nameVerFromPkgSnapshot } from '@pnpm/lockfile-utils' import { lockfileWalkerGroupImporterSteps, LockfileWalkerStep } from '@pnpm/lockfile-walker' import { DependenciesField } from '@pnpm/types' +import { readProjectManifest } from '@pnpm/read-project-manifest' import mapValues from 'ramda/src/map' export interface AuditNode { @@ -19,25 +21,29 @@ export type AuditTree = AuditNode & { metadata: Object } -export function lockfileToAuditTree ( +export async function lockfileToAuditTree ( lockfile: Lockfile, - opts?: { + opts: { include?: { [dependenciesField in DependenciesField]: boolean } + lockfileDir: string } -): AuditTree { +): Promise { const importerWalkers = lockfileWalkerGroupImporterSteps(lockfile, Object.keys(lockfile.importers), { include: opts?.include }) const dependencies = {} - importerWalkers.forEach((importerWalker) => { - const importerDeps = lockfileToAuditNode(importerWalker.step) - // For some reason the registry responds with 500 if the keys in dependencies have slashes - // see issue: https://github.com/pnpm/pnpm/issues/2848 - const depName = importerWalker.importerId.replace(/\//g, '__') - dependencies[depName] = { - dependencies: importerDeps, - requires: toRequires(importerDeps), - version: '0.0.0', - } - }) + await Promise.all( + importerWalkers.map(async (importerWalker) => { + const importerDeps = lockfileToAuditNode(importerWalker.step) + // For some reason the registry responds with 500 if the keys in dependencies have slashes + // see issue: https://github.com/pnpm/pnpm/issues/2848 + const depName = importerWalker.importerId.replace(/\//g, '__') + const { manifest } = await readProjectManifest(path.join(opts.lockfileDir, importerWalker.importerId)) + dependencies[depName] = { + dependencies: importerDeps, + requires: toRequires(importerDeps), + version: manifest.version, + } + }) + ) const auditTree: AuditTree = { name: undefined, version: undefined, diff --git a/lockfile/audit/test/__fixtures__/one-project/package.json b/lockfile/audit/test/__fixtures__/one-project/package.json new file mode 100644 index 0000000000..cd6c0be32b --- /dev/null +++ b/lockfile/audit/test/__fixtures__/one-project/package.json @@ -0,0 +1,4 @@ +{ + "name": "pkg", + "version": "1.0.0" +} diff --git a/lockfile/audit/test/index.ts b/lockfile/audit/test/index.ts index 82a65693f4..7cfe2b2dd9 100644 --- a/lockfile/audit/test/index.ts +++ b/lockfile/audit/test/index.ts @@ -1,12 +1,15 @@ import { audit } from '@pnpm/audit' import { LOCKFILE_VERSION } from '@pnpm/constants' import { PnpmError } from '@pnpm/error' +import { fixtures } from '@pnpm/test-fixtures' import nock from 'nock' import { lockfileToAuditTree } from '../lib/lockfileToAuditTree' +const f = fixtures(__dirname) + describe('audit', () => { - test('lockfileToAuditTree()', () => { - expect(lockfileToAuditTree({ + test('lockfileToAuditTree()', async () => { + expect(await lockfileToAuditTree({ importers: { '.': { dependencies: { @@ -33,7 +36,7 @@ describe('audit', () => { }, }, }, - })).toEqual({ + }, { lockfileDir: f.find('one-project') })).toEqual({ name: undefined, version: undefined, @@ -59,7 +62,7 @@ describe('audit', () => { requires: { foo: '1.0.0', }, - version: '0.0.0', + version: '1.0.0', }, }, dev: false, @@ -67,7 +70,7 @@ describe('audit', () => { integrity: undefined, metadata: {}, remove: [], - requires: { '.': '0.0.0' }, + requires: { '.': '1.0.0' }, }) }) @@ -88,6 +91,7 @@ describe('audit', () => { }, getAuthHeader, { + lockfileDir: f.find('one-project'), registry, retry: { retries: 0, diff --git a/lockfile/audit/tsconfig.json b/lockfile/audit/tsconfig.json index 912e9d52fb..dd3e5e839c 100644 --- a/lockfile/audit/tsconfig.json +++ b/lockfile/audit/tsconfig.json @@ -9,6 +9,9 @@ "../../__typings__/**/*.d.ts" ], "references": [ + { + "path": "../../__utils__/test-fixtures" + }, { "path": "../../network/fetch" }, @@ -24,6 +27,9 @@ { "path": "../../packages/types" }, + { + "path": "../../pkg-manifest/read-project-manifest" + }, { "path": "../lockfile-file" }, diff --git a/lockfile/plugin-commands-audit/src/audit.ts b/lockfile/plugin-commands-audit/src/audit.ts index d60c7b72d1..34ecdce19b 100644 --- a/lockfile/plugin-commands-audit/src/audit.ts +++ b/lockfile/plugin-commands-audit/src/audit.ts @@ -130,7 +130,8 @@ export async function handler ( | 'rootProjectManifest' > ) { - const lockfile = await readWantedLockfile(opts.lockfileDir ?? opts.dir, { ignoreIncompatible: true }) + const lockfileDir = opts.lockfileDir ?? opts.dir + const lockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: true }) if (lockfile == null) { throw new PnpmError('AUDIT_NO_LOCKFILE', `No ${WANTED_LOCKFILE} found: Cannot audit a project without a lockfile`) } @@ -156,6 +157,7 @@ export async function handler ( timeout: opts.fetchTimeout, }, include, + lockfileDir, registry: opts.registries.default, retry: { factor: opts.fetchRetryFactor, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81e148eb0e..78c9f0957d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1576,6 +1576,9 @@ importers: '@pnpm/lockfile-walker': specifier: workspace:* version: link:../lockfile-walker + '@pnpm/read-project-manifest': + specifier: workspace:* + version: link:../../pkg-manifest/read-project-manifest '@pnpm/types': specifier: workspace:* version: link:../../packages/types @@ -1592,6 +1595,9 @@ importers: '@pnpm/lockfile-file': specifier: workspace:* version: link:../lockfile-file + '@pnpm/test-fixtures': + specifier: workspace:* + version: link:../../__utils__/test-fixtures '@types/ramda': specifier: 0.28.15 version: 0.28.15