Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions packages/scan/src/__tests__/dependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { describe, expect, it } from 'vitest';
import { LOCKFILE_PARSERS } from '../node/dependencies';

/**
* These parsers exist because the previous ones did not, and the absence read
* as a clean scan. So the assertion that matters in every case below is the
* same: a real lockfile must not parse to an empty list.
*/

const parse = (file: string, content: string) => {
const parser = LOCKFILE_PARSERS[file];
if (!parser) throw new Error(`no parser registered for ${file}`);
return parser(content);
};

describe('pnpm-lock.yaml', () => {
it('reads v9 quoted keys', () => {
const lock = `lockfileVersion: '9.0'

importers:

.:
dependencies:
lodash:
specifier: ^4.17.21
version: 4.17.21

packages:

'@babel/code-frame@7.24.7':
resolution: {integrity: sha512-aaa}

lodash@4.17.21:
resolution: {integrity: sha512-bbb}

snapshots:

'@babel/code-frame@7.24.7': {}
`;
expect(parse('pnpm-lock.yaml', lock)).toEqual([
{ name: '@babel/code-frame', version: '7.24.7' },
{ name: 'lodash', version: '4.17.21' },
]);
});

it('reads v6 leading-slash keys and drops the peer suffix', () => {
const lock = `lockfileVersion: '6.0'

packages:

/@babel/core@7.24.7:
resolution: {integrity: sha512-aaa}

/react-dom@18.3.1(react@18.3.1):
resolution: {integrity: sha512-bbb}
`;
expect(parse('pnpm-lock.yaml', lock)).toEqual([
{ name: '@babel/core', version: '7.24.7' },
{ name: 'react-dom', version: '18.3.1' },
]);
});

it('reads v5 slash-separated versions, including underscore peer suffixes', () => {
const lock = `lockfileVersion: 5.4

packages:

/@babel/core/7.24.7:
resolution: {integrity: sha512-aaa}

/react-dom/17.0.2_react@17.0.2:
resolution: {integrity: sha512-bbb}
`;
expect(parse('pnpm-lock.yaml', lock)).toEqual([
{ name: '@babel/core', version: '7.24.7' },
{ name: 'react-dom', version: '17.0.2' },
]);
});

it('keeps underscores that belong to the package name', () => {
const lock = `lockfileVersion: '9.0'

packages:

my_package@1.2.3:
resolution: {integrity: sha512-aaa}
`;
expect(parse('pnpm-lock.yaml', lock)).toEqual([{ name: 'my_package', version: '1.2.3' }]);
});

it('ignores entries outside the packages block', () => {
const lock = `lockfileVersion: '9.0'

importers:

apps/cli:
dependencies:
commander:
specifier: ^12.0.0
version: 12.1.0
`;
expect(parse('pnpm-lock.yaml', lock)).toEqual([]);
});
});

describe('yarn.lock', () => {
it('reads the v1 dialect', () => {
const lock = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#aaa"
integrity sha512-aaa

lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#bbb"
`;
expect(parse('yarn.lock', lock)).toEqual([
{ name: '@babel/code-frame', version: '7.24.7' },
{ name: 'lodash', version: '4.17.21' },
]);
});

it('reads the berry dialect and skips its metadata block', () => {
const lock = `# This file is generated by running "yarn install" inside your project.

__metadata:
version: 8
cacheKey: 10c0

"lodash@npm:^4.17.20":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
languageName: node
`;
expect(parse('yarn.lock', lock)).toEqual([{ name: 'lodash', version: '4.17.21' }]);
});

it('skips workspace entries that resolve to no published version', () => {
const lock = `"my-app@workspace:.":
version: 0.0.0-use.local
resolution: "my-app@workspace:."
`;
expect(parse('yarn.lock', lock)).toEqual([]);
});
});

describe('Pipfile.lock', () => {
it('reads both sections and strips the == specifier', () => {
const lock = JSON.stringify({
_meta: { hash: { sha256: 'aaa' } },
default: {
requests: { version: '==2.32.3' },
'some-vcs-dep': { git: 'https://example.com/x.git', ref: 'abc123' },
},
develop: {
pytest: { version: '==8.3.2' },
},
});
expect(parse('Pipfile.lock', lock)).toEqual([
{ name: 'requests', version: '2.32.3' },
{ name: 'pytest', version: '8.3.2' },
]);
});
});

describe('requirements.txt', () => {
it('keeps PEP 440 pre- and post-release pins', () => {
const txt = `requests==2.32.3
django==5.0rc1
urllib3==2.2.2.post1
`;
expect(parse('requirements.txt', txt)).toEqual([
{ name: 'requests', version: '2.32.3' },
{ name: 'django', version: '5.0rc1' },
{ name: 'urllib3', version: '2.2.2.post1' },
]);
});

it('handles extras, markers, comments and flag lines', () => {
const txt = `# pinned for prod
-r base.txt
--index-url https://example.com/simple
celery[redis]==5.4.0
uvicorn==0.30.6 ; python_version >= "3.11"
flask # unpinned, unusable
django>=4.0
`;
expect(parse('requirements.txt', txt)).toEqual([
{ name: 'celery', version: '5.4.0' },
{ name: 'uvicorn', version: '0.30.6' },
]);
});

it('drops wildcard pins, which are ranges rather than versions', () => {
expect(parse('requirements.txt', 'django==4.2.*\n')).toEqual([]);
});
});

describe('package-lock.json', () => {
it('reads v3 nested paths down to the package name', () => {
const lock = JSON.stringify({
lockfileVersion: 3,
packages: {
'': { name: 'root', version: '1.0.0' },
'node_modules/lodash': { version: '4.17.21' },
'node_modules/a/node_modules/semver': { version: '7.6.3' },
},
});
expect(parse('package-lock.json', lock)).toEqual([
{ name: 'lodash', version: '4.17.21' },
{ name: 'semver', version: '7.6.3' },
]);
});
});
Loading
Loading