Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Public repo: CodeRabbit reviews every PR (jac decision 0048); the org default is request only.
inheritance: true
reviews:
auto_review:
enabled: true
drafts: false
ignore_usernames: ["dependabot[bot]", "renovate[bot]"]
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ jobs:
node-version: 22
cache: npm

- name: Check lockfile registry
# Every resolved URL must point at registry.npmjs.org.
run: npm run check:lockfile

- name: Install dependencies
run: npm ci

Expand Down
82 changes: 41 additions & 41 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
"watch": "tsc -watch -p ./",
"lint": "eslint src",
"package": "vsce package",
"check:lockfile": "node scripts/check-lockfile-registry.test.js && node scripts/check-lockfile-registry.js",
"smoke:paths": "node scripts/smoke-paths.js",
"smoke:status": "node scripts/smoke-status.js",
"smoke:picker": "node scripts/smoke-picker.js",
Expand All @@ -174,6 +175,6 @@
"typescript": "^5.x"
},
"dependencies": {
"simple-git": "^3.22.0"
"simple-git": "^4.0.2"
}
}
68 changes: 68 additions & 0 deletions scripts/check-lockfile-registry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict';
// Fails when package-lock.json resolves any package from a registry other than
// the public npm registry. Run before `npm ci` so a mirror URL never reaches CI.
// Reads both lockfile layouts: `packages` (v2, v3) and the legacy
// `dependencies` tree (v1, and v2 for older npm).
const fs = require('fs');
const path = require('path');

const ALLOWED_PREFIX = 'https://registry.npmjs.org/';

function isObject(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function checkEntry(label, entry, offenders) {
if (isObject(entry) && typeof entry.resolved === 'string' && !entry.resolved.startsWith(ALLOWED_PREFIX)) {
offenders.push(`${label}: ${entry.resolved}`);
}
}

function walkLegacy(tree, trail, offenders) {
for (const [name, entry] of Object.entries(tree)) {
const label = `${trail}${name}`;
checkEntry(label, entry, offenders);
if (isObject(entry) && isObject(entry.dependencies)) {
walkLegacy(entry.dependencies, `${label} > `, offenders);
}
}
}

// Returns { offenders }. Throws if the lockfile has neither layout.
function scanLockfile(lock) {
const hasPackages = isObject(lock) && isObject(lock.packages);
const hasLegacy = isObject(lock) && isObject(lock.dependencies);
if (!hasPackages && !hasLegacy) {
throw new Error('lockfile has neither a "packages" map nor a "dependencies" tree');
}
const offenders = [];
if (hasPackages) {
for (const [name, entry] of Object.entries(lock.packages)) {
checkEntry(name || '(root)', entry, offenders);
}
}
if (hasLegacy) {
walkLegacy(lock.dependencies, '', offenders);
}
return { offenders };
}

module.exports = { scanLockfile, ALLOWED_PREFIX };

if (require.main === module) {
const lockPath = path.join(__dirname, '..', 'package-lock.json');
let result;
try {
result = scanLockfile(JSON.parse(fs.readFileSync(lockPath, 'utf8')));
} catch (err) {
console.error(`check-lockfile-registry: cannot read ${lockPath}: ${err.message}`);
process.exit(1);
}
if (result.offenders.length > 0) {
console.error(`package-lock.json resolves ${result.offenders.length} package(s) outside ${ALLOWED_PREFIX}:`);
for (const line of result.offenders) console.error(` ${line}`);
console.error(`Fix: run "npm install --package-lock-only --registry=${ALLOWED_PREFIX}" and commit the lockfile.`);
process.exit(1);
}
console.log('check-lockfile-registry OK');
}
25 changes: 25 additions & 0 deletions scripts/check-lockfile-registry.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';
// Self-test for check-lockfile-registry.js with one v1 and one v3 fixture.
const assert = require('assert');
const { scanLockfile } = require('./check-lockfile-registry');

const npmjs = 'https://registry.npmjs.org/a/-/a-1.0.0.tgz';
const mirror = 'https://registry.example.invalid/b/-/b-1.0.0.tgz';

// v1: only the legacy `dependencies` tree; the offender is nested.
const v1Clean = { lockfileVersion: 1, dependencies: { a: { resolved: npmjs, dependencies: { b: { resolved: npmjs } } } } };
const v1Dirty = { lockfileVersion: 1, dependencies: { a: { resolved: npmjs, dependencies: { b: { resolved: mirror } } } } };
assert.deepStrictEqual(scanLockfile(v1Clean).offenders, []);
assert.deepStrictEqual(scanLockfile(v1Dirty).offenders, [`a > b: ${mirror}`]);

// v3: only the `packages` map.
const v3Clean = { lockfileVersion: 3, packages: { '': { name: 'x' }, 'node_modules/a': { resolved: npmjs } } };
const v3Dirty = { lockfileVersion: 3, packages: { '': { name: 'x' }, 'node_modules/b': { resolved: mirror } } };
assert.deepStrictEqual(scanLockfile(v3Clean).offenders, []);
assert.deepStrictEqual(scanLockfile(v3Dirty).offenders, [`node_modules/b: ${mirror}`]);

// Neither layout: the check must fail, not pass on nothing.
assert.throws(() => scanLockfile({ lockfileVersion: 3 }), /neither/);
assert.throws(() => scanLockfile(null), /neither/);

console.log('check-lockfile-registry self-test OK');
2 changes: 1 addition & 1 deletion src/services/gitService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import simpleGit, { SimpleGit } from 'simple-git';
import { simpleGit, SimpleGit } from 'simple-git';
import * as path from 'path';
import * as fs from 'fs';
import { STATUS_MAP, type FileStatus } from './fileStatus';
Expand Down
Loading