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
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "npm-diff",
"version": "1.0.0",
"version": "1.0.2",
"description": "Generate a formatted diff of npm package-lock.json changes for pull requests",
"main": "dist/index.js",
"scripts": {
Expand Down
29 changes: 25 additions & 4 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ interface PackageInfo {
}

interface LockfileData {
packages?: Record<string, PackageInfo>;
dependencies?: Record<string, PackageInfo>;
packages?: Record<string, PackageInfo | string>;
dependencies?: Record<string, PackageInfo | string>;
devDependencies?: Record<string, PackageInfo | string>;
optionalDependencies?: Record<string, PackageInfo | string>;
}

export interface PackageChange {
Expand All @@ -31,14 +33,33 @@ export function parseLockfile(
try {
const content = contentStr || fs.readFileSync(path, 'utf8');
const data: LockfileData = JSON.parse(content);
const packages = data.packages || data.dependencies || {};
let packages: Record<string, PackageInfo | string> = {};

if (data.packages) {
packages = data.packages;
} else {
packages = {
...(data.dependencies || {}),
...(data.devDependencies || {}),
...(data.optionalDependencies || {}),
};
}

// Remove the root package entry if it exists to avoid comparing it
if (packages['']) {
delete packages[''];
}

return packages;
const normalized: Record<string, PackageInfo> = {};
for (const [name, info] of Object.entries(packages)) {
if (typeof info === 'string') {
normalized[name] = { version: info };
} else if (info && typeof info === 'object') {
normalized[name] = info as PackageInfo;
}
}

return normalized;
} catch {
return {};
}
Expand Down
42 changes: 41 additions & 1 deletion test/action.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-require-imports, no-undef */
const { test } = require('node:test');
const assert = require('node:assert');
const { comparePackages, formatMarkdown } = require('../src/action.ts');
const { comparePackages, formatMarkdown, parseLockfile } = require('../src/action.ts');

test('comparePackages identifies added, removed and updated packages', () => {
const base = {
Expand Down Expand Up @@ -56,3 +56,43 @@ test('formatMarkdown formats changes as a table', () => {
assert.match(markdown, /r \| Removed \| `1.0.0` \| - \|/);
assert.match(markdown, /u \| Upgraded \| `1.0.0` \| `1.1.0` \|/);
});

test('parseLockfile handles package.json and devDependencies', () => {
const content = JSON.stringify({
dependencies: { 'prod-pkg': '1.0.0' },
devDependencies: { 'dev-pkg': '2.0.0' },
optionalDependencies: { 'opt-pkg': '3.0.0' },
});

const packages = parseLockfile('package.json', content);

assert.strictEqual(packages['prod-pkg'].version, '1.0.0');
assert.strictEqual(packages['dev-pkg'].version, '2.0.0');
assert.strictEqual(packages['opt-pkg'].version, '3.0.0');
});

test('parseLockfile handles lockfile v3 packages', () => {
const content = JSON.stringify({
packages: {
'': { name: 'root', version: '1.0.0' },
'node_modules/pkg': { version: '1.1.0' },
},
});

const packages = parseLockfile('package-lock.json', content);

assert.strictEqual(packages[''], undefined);
assert.strictEqual(packages['node_modules/pkg'].version, '1.1.0');
});

test('parseLockfile handles lockfile v1 dependencies', () => {
const content = JSON.stringify({
dependencies: {
pkg: { version: '1.1.0' },
},
});

const packages = parseLockfile('package-lock.json', content);

assert.strictEqual(packages['pkg'].version, '1.1.0');
});