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.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "npm-diff",
"version": "1.0.2",
"version": "1.0.3",
"description": "Generate a formatted diff of npm package-lock.json changes for pull requests",
"main": "dist/index.js",
"scripts": {
"build": "ncc build src/action.ts -o dist --minify --no-cache",
"lint": "eslint .",
"format": "prettier --check \"**/*.{ts,js,json,md}\"",
"format:fix": "prettier --write \"**/*.{ts,js,json,md}\"",
"test": "node --import ts-node/register --test test/*.test.js"
"test": "node -r ts-node/register --test test/**/*.test.js"
},
"keywords": [
"npm",
Expand Down
9 changes: 7 additions & 2 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ export function run(): void {
core.debug(`Changed files: ${changedFiles.join(', ')}`);
core.debug(`Checking for: ${lockfilePath}`);

if (!changedFiles.includes(lockfilePath)) {
// Normalize path to match git diff output (no leading ./)
const normalizedPath = lockfilePath.replace(/^(\.\/)+/, '');
if (!changedFiles.includes(normalizedPath)) {
core.setOutput('has_changes', 'false');
core.setOutput('diff', `_No changes to ${lockfilePath}_`);
core.setOutput('added_count', '0');
Expand Down Expand Up @@ -219,6 +221,9 @@ export function run(): void {
core.setOutput('updated_count', changes.updated.length.toString());
}

if (require.main === module) {
if (
(typeof require !== 'undefined' && require.main === module) ||
(typeof process !== 'undefined' && process.argv[1]?.endsWith('action.ts'))
) {
run();
}
175 changes: 175 additions & 0 deletions test/functional/action.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/* eslint-disable @typescript-eslint/no-require-imports, no-undef */
const { test } = require('node:test');
const assert = require('node:assert');
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

test('Functional test: detects devDependency change in package.json', () => {
const root = process.cwd();
const tempDir = path.join(root, 'temp-functional-test-pkgjson');
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
fs.mkdirSync(tempDir);

try {
// Setup git repo
execSync('git init', { cwd: tempDir });
execSync('git config user.email "test@example.com"', { cwd: tempDir });
execSync('git config user.name "Test User"', { cwd: tempDir });

// Create initial package.json
const pkgJson = {
name: 'test-pkg',
version: '1.0.0',
dependencies: {
axios: '1.0.0',
},
devDependencies: {
vite: '^4.3.9',
},
};
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
execSync('git add package.json', { cwd: tempDir });
execSync('git commit -m "Initial commit"', { cwd: tempDir });
execSync('git branch -m main', { cwd: tempDir });

// Create a new branch and change dependencies
execSync('git checkout -b feature', { cwd: tempDir });

// 1. Upgrade vite (devDep)
pkgJson.devDependencies.vite = '^4.5.14';
// 2. Add lodash (dep)
pkgJson.dependencies.lodash = '4.17.21';
// 3. Remove axios (dep)
delete pkgJson.dependencies.axios;

fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
execSync('git add package.json', { cwd: tempDir });
execSync('git commit -m "Update dependencies"', { cwd: tempDir });

// Add remote origin to self to satisfy git merge-base origin/main
execSync('git remote add origin .', { cwd: tempDir });
execSync('git fetch origin', { cwd: tempDir });

// Prepare GitHub Action environment
const outputPath = path.join(tempDir, 'github_output');
fs.writeFileSync(outputPath, '');

const env = {
...process.env,
INPUT_PATH: 'package.json',
'INPUT_BASE-REF': 'main',
GITHUB_OUTPUT: outputPath,
GITHUB_WORKSPACE: tempDir,
};

// Run the action as a script
const actionPath = path.join(root, 'src', 'action.ts');
execSync(`node -r ts-node/register ${actionPath}`, {
cwd: tempDir,
env,
stdio: 'inherit',
});

const outputContent = fs.readFileSync(outputPath, 'utf8');
console.log('Action Outputs (package.json):\n', outputContent);

assert.match(outputContent, /has_changes[\s\S]+true/);
assert.match(outputContent, /updated_count[\s\S]+1/);
assert.match(outputContent, /added_count[\s\S]+1/);
assert.match(outputContent, /removed_count[\s\S]+1/);
assert.match(outputContent, /vite \| Upgraded \| `\^4\.3\.9` \| `\^4\.5\.14`/);
assert.match(outputContent, /lodash \| Added \| - \| `4\.17\.21`/);
assert.match(outputContent, /axios \| Removed \| `1\.0\.0` \| -/);
} finally {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
}
});

test('Functional test: detects package upgrade in package-lock.json', () => {
const root = process.cwd();
const tempDir = path.join(root, 'temp-functional-test-lockfile');
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
fs.mkdirSync(tempDir);

try {
// Setup git repo
execSync('git init', { cwd: tempDir });
execSync('git config user.email "test@example.com"', { cwd: tempDir });
execSync('git config user.name "Test User"', { cwd: tempDir });

// Create initial package-lock.json (v3)
const lockfile = {
name: 'test-pkg',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': {
name: 'test-pkg',
version: '1.0.0',
dependencies: {
axios: '^1.0.0',
},
},
'node_modules/axios': {
version: '1.0.0',
},
},
};
fs.writeFileSync(path.join(tempDir, 'package-lock.json'), JSON.stringify(lockfile, null, 2));
execSync('git add package-lock.json', { cwd: tempDir });
execSync('git commit -m "Initial commit"', { cwd: tempDir });
execSync('git branch -m main', { cwd: tempDir });

// Create a new branch and upgrade dependency
execSync('git checkout -b feature', { cwd: tempDir });
lockfile.packages['node_modules/axios'].version = '1.1.0';
lockfile.packages[''].dependencies.axios = '^1.1.0';

fs.writeFileSync(path.join(tempDir, 'package-lock.json'), JSON.stringify(lockfile, null, 2));
execSync('git add package-lock.json', { cwd: tempDir });
execSync('git commit -m "Upgrade axios"', { cwd: tempDir });

// Add remote origin to self to satisfy git merge-base origin/main
execSync('git remote add origin .', { cwd: tempDir });
execSync('git fetch origin', { cwd: tempDir });

// Prepare GitHub Action environment
const outputPath = path.join(tempDir, 'github_output');
fs.writeFileSync(outputPath, '');

const env = {
...process.env,
// INPUT_PATH defaults to package-lock.json
'INPUT_BASE-REF': 'main',
GITHUB_OUTPUT: outputPath,
GITHUB_WORKSPACE: tempDir,
};

// Run the action as a script
const actionPath = path.join(root, 'src', 'action.ts');
execSync(`node -r ts-node/register ${actionPath}`, {
cwd: tempDir,
env,
stdio: 'inherit',
});

const outputContent = fs.readFileSync(outputPath, 'utf8');
console.log('Action Outputs (package-lock.json):\n', outputContent);

assert.match(outputContent, /has_changes[\s\S]+true/);
assert.match(outputContent, /updated_count[\s\S]+1/);
assert.match(outputContent, /axios \| Upgraded \| `1\.0\.0` \| `1\.1\.0`/);
} finally {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
}
});
2 changes: 1 addition & 1 deletion test/action.test.js → test/unit/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, parseLockfile } = require('../src/action.ts');
const { comparePackages, formatMarkdown, parseLockfile } = require('../../src/action.ts');

test('comparePackages identifies added, removed and updated packages', () => {
const base = {
Expand Down