Skip to content

Commit

Permalink
fix: improve pnpm version detection (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber authored Aug 9, 2023
1 parent af757d9 commit 1811668
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 36 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
},
"devDependencies": {
"@pvtnbr/eslint-config": "^0.35.0",
"@types/firstline": "^2.0.2",
"@types/node": "^20.4.9",
"clean-pkg-json": "^1.2.0",
"eslint": "^8.46.0",
"execa": "^7.2.0",
"firstline": "^2.0.2",
"fs-fixture": "^1.2.0",
"manten": "^1.0.0",
"pkgroll": "^1.11.0",
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

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

25 changes: 19 additions & 6 deletions src/ci.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { existsSync } from 'fs';
import { spawnSync } from 'child_process';
import { getPnpmVersion, type Version } from './get-pnpm-version';
import firstline from 'firstline';
import { getPnpmVersion, type LockVersion, type NodeVersion } from './get-pnpm-version.js';

export function ci() {
const parseVersionString = <Version>(versionString: string) => versionString.split('.').map(Number) as Version;

const getPnpmLockVersion = async () => {
const lockFirstLine = await firstline('pnpm-lock.yaml');
const lockFileVersion = lockFirstLine.match(/\d+\.\d+/);
if (lockFileVersion) {
return parseVersionString<LockVersion>(lockFileVersion[0]);
}
};

export const ci = async () => {
const options = {
stdio: 'inherit' as const,
shell: true,
Expand All @@ -23,14 +34,16 @@ export function ci() {
}

if (existsSync('pnpm-lock.yaml')) {
const nodeVersion = process.versions.node.split('.').map(Number) as Version;
const pnpmVersion = getPnpmVersion(nodeVersion);
const pnpmVersion = getPnpmVersion(
parseVersionString<NodeVersion>(process.versions.node),
await getPnpmLockVersion(),
);
return spawnSync(
'npx',
[`pnpm@${pnpmVersion}`, 'i', '--frozen-lockfile'],
[`pnpm${pnpmVersion}`, 'i', '--frozen-lockfile'],
options,
);
}

throw new Error('Error: No lock file (package-lock.json, yarn.lock, pnpm-lock.yaml) found');
}
};
17 changes: 10 additions & 7 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ci } from './ci';
import { ci } from './ci.js';

try {
process.exit(ci().status!);
} catch (error) {
console.error((error as any).message);
process.exit(1);
}
(async () => {
try {
const { status } = await ci();
process.exit(status!);
} catch (error) {
console.error((error as Error).message);
process.exitCode = 1;
}
})();
73 changes: 58 additions & 15 deletions src/get-pnpm-version.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
export type Version = [number, number, number];
export type LockVersion = [number, number];
export type NodeVersion = [number, number, number];

const compareSemver = (semverA: Version, semverB: Version) => (
semverA[0] - semverB[0]
const compareSemver = <Version extends number[]>(
semverA: Version,
semverB: Version,
) => (
semverA[0] - semverB[0]
|| semverA[1] - semverB[1]
|| semverA[2] - semverB[2]
);
|| 0
);

export function getPnpmVersion(nodeVersion: Version) {
// pnpm v7 requires Node v14.19 and up: https://github.com/pnpm/pnpm/blob/v7.0.0/packages/types/package.json#L8
if (compareSemver(nodeVersion, [14, 19, 0]) >= 0) {
return 'latest';
}
type PnpmVersion = {
node: NodeVersion;
lock: LockVersion;
};
const pnpmVersions: [string, PnpmVersion][] = [
['8', {
// https://github.com/pnpm/pnpm/blob/v8.0.0/packages/types/package.json#L8
node: [16, 14, 0],

// pnpm v6 requires Node v12.17 and up: https://github.com/pnpm/pnpm/releases/tag/v6.0.0
if (compareSemver(nodeVersion, [12, 17, 0]) >= 0) {
return '6';
}
// https://github.com/pnpm/pnpm/blob/v8.0.0/packages/constants/src/index.ts#L2
lock: [6, 0],
}],
['7', {
// https://github.com/pnpm/pnpm/blob/v7.0.0/packages/types/package.json#L8
node: [14, 19, 0],

return '5';
}
// https://github.com/pnpm/pnpm/blob/v7.0.0/packages/constants/src/index.ts#L2
lock: [5, 4],
}],
['6', {
// https://github.com/pnpm/pnpm/blob/v6.0.0/packages/types/package.json#L8
node: [12, 17, 0],

// https://github.com/pnpm/pnpm/blob/v6.0.0/packages/constants/src/index.ts#L2
lock: [5, 3],
}],
['5', {
// https://github.com/pnpm/pnpm/blob/v5.0.0/packages/types/package.json#L8
node: [10, 13, 0],

// https://github.com/pnpm/pnpm/blob/v5.0.0/packages/constants/src/index.ts#L2
lock: [5, 1],
}],
];

export const getPnpmVersion = (
nodeVersion: NodeVersion,
lockfileVersion?: LockVersion,
) => {
const compatibleVersion = pnpmVersions.find(([_version, { node: nodeMinimum, lock }]) => {
const isCompatibleNodeVersion = compareSemver(nodeVersion, nodeMinimum) >= 0;
const isCompatibleLockVersion = (
!lockfileVersion
|| compareSemver(lockfileVersion, lock) === 0
);
return isCompatibleNodeVersion && isCompatibleLockVersion;
});

const foundMatch = compatibleVersion;
return foundMatch ? `@${foundMatch[0]}` : '';
};
18 changes: 12 additions & 6 deletions tests/get-pnpm-version.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import assert from 'assert';
import { getPnpmVersion } from '../src/get-pnpm-version';
import { testSuite, expect } from 'manten';
import { getPnpmVersion } from '../src/get-pnpm-version.js';

assert(getPnpmVersion([14, 19, 0]) === 'latest');
export default testSuite(({ test }) => {
test('getPnpmVersion', () => {
expect(getPnpmVersion([18, 0, 0])).toBe('@8');
expect(getPnpmVersion([16, 14, 0])).toBe('@8');

assert(getPnpmVersion([14, 18, 0]) === '6');
expect(getPnpmVersion([14, 19, 0])).toBe('@7');

assert(getPnpmVersion([12, 17, 0]) === '6');
expect(getPnpmVersion([14, 18, 0])).toBe('@6');
expect(getPnpmVersion([12, 17, 0])).toBe('@6');

assert(getPnpmVersion([12, 16, 0]) === '5');
expect(getPnpmVersion([12, 16, 0])).toBe('@5');
});
});
8 changes: 6 additions & 2 deletions tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { execa } from 'execa';
const ciBinaryPath = path.resolve('dist/cli.js');
const ci = (cwd: string) => execa(ciBinaryPath, [], { cwd });

describe('ci', ({ describe }) => {
describe('ci', ({ describe, runTestSuite }) => {
describe('lock file', ({ test }) => {
test('npm', async () => {
const fixture = await createFixture('tests/fixtures/npm');
Expand All @@ -21,7 +21,9 @@ describe('ci', ({ describe }) => {
const fixture = await createFixture('tests/fixtures/pnpm');

const { stdout } = await ci(fixture.path);
expect(stdout).toMatch('Lockfile is up-to-date, resolution step is skipped');

// pnpm changed "Lockfile is up-to-date" to "Lockfile is up to date"
expect(stdout).toMatch('Lockfile is up');

await fixture.rm();
});
Expand All @@ -35,4 +37,6 @@ describe('ci', ({ describe }) => {
await fixture.rm();
});
});

runTestSuite(import('./get-pnpm-version.spec.js'));
});

0 comments on commit 1811668

Please sign in to comment.