mirror of https://github.com/garrytan/gstack.git
Merge b672aa68d1 into 2be6c06ba8
This commit is contained in:
commit
fe43639a9c
|
|
@ -31,8 +31,8 @@
|
|||
// file. No bump. Validates the VERSION pattern first.
|
||||
//
|
||||
// Contract: classify NEVER writes. write/repair mutate VERSION + package.json
|
||||
// only. No git mutation, no network. Mirrors gstack-next-version's reader/writer
|
||||
// split so /ship composes them.
|
||||
// + package-lock.json (when present) only. No git mutation, no network. Mirrors
|
||||
// gstack-next-version's reader/writer split so /ship composes them.
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
|
@ -101,6 +101,25 @@ function writePkgVersion(cwd: string, version: string): void {
|
|||
writeFileSync(pkgPath, JSON.stringify(parsed, null, 2) + "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* package-lock.json carries the version twice (top-level `version` and, in
|
||||
* lockfileVersion >= 2, `packages[""].version`). Leaving either stale after a
|
||||
* bump dirties the tree on the next `npm install`, which downstream clean-tree
|
||||
* asserts reject far from the cause. No-op when the lockfile is absent.
|
||||
*/
|
||||
function writeLockVersion(cwd: string, version: string): void {
|
||||
const lockPath = join(cwd, "package-lock.json");
|
||||
if (!existsSync(lockPath)) return;
|
||||
const raw = readFileSync(lockPath, "utf-8");
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||
parsed.version = version;
|
||||
const packages = parsed.packages as Record<string, Record<string, unknown>> | undefined;
|
||||
if (packages && typeof packages[""] === "object" && packages[""] !== null) {
|
||||
packages[""].version = version;
|
||||
}
|
||||
writeFileSync(lockPath, JSON.stringify(parsed, null, 2) + "\n");
|
||||
}
|
||||
|
||||
function baseVersion(cwd: string, base: string, versionRel: string): string {
|
||||
// Verify the base ref resolves, mirroring the Step 12 guard.
|
||||
try {
|
||||
|
|
@ -164,15 +183,22 @@ function cmdWrite(args: string[], cwd: string): void {
|
|||
if (existsSync(join(cwd, "package.json"))) {
|
||||
try {
|
||||
writePkgVersion(cwd, version!);
|
||||
writeLockVersion(cwd, version!);
|
||||
} catch {
|
||||
fail(
|
||||
"failed to update package.json. VERSION was written but package.json is now stale. " +
|
||||
"Re-run — classify will report DRIFT_STALE_PKG and repair will sync it.",
|
||||
"failed to update package.json/package-lock.json. VERSION was written but the npm " +
|
||||
"manifests are now stale. Re-run — classify will report DRIFT_STALE_PKG and repair will sync them.",
|
||||
3,
|
||||
);
|
||||
}
|
||||
}
|
||||
process.stdout.write(JSON.stringify({ wrote: version, packageJson: existsSync(join(cwd, "package.json")) }) + "\n");
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
wrote: version,
|
||||
packageJson: existsSync(join(cwd, "package.json")),
|
||||
packageLock: existsSync(join(cwd, "package-lock.json")),
|
||||
}) + "\n",
|
||||
);
|
||||
}
|
||||
|
||||
function cmdRepair(args: string[], cwd: string): void {
|
||||
|
|
@ -190,8 +216,9 @@ function cmdRepair(args: string[], cwd: string): void {
|
|||
}
|
||||
try {
|
||||
writePkgVersion(cwd, current);
|
||||
writeLockVersion(cwd, current);
|
||||
} catch {
|
||||
fail("drift repair failed — could not update package.json.", 3);
|
||||
fail("drift repair failed — could not update package.json/package-lock.json.", 3);
|
||||
}
|
||||
process.stdout.write(JSON.stringify({ repaired: current }) + "\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ describe('write (FRESH bump)', () => {
|
|||
fs.writeFileSync(path.join(dir, 'VERSION'), '1.0.0.0\n');
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x', version: '1.0.0.0', scripts: { t: 'y' } }, null, 2) + '\n');
|
||||
const out = execFileSync('bun', [BIN, 'write', '--version', '1.1.0.0'], { cwd: dir }).toString();
|
||||
expect(JSON.parse(out)).toEqual({ wrote: '1.1.0.0', packageJson: true });
|
||||
expect(JSON.parse(out)).toEqual({ wrote: '1.1.0.0', packageJson: true, packageLock: false });
|
||||
expect(fs.readFileSync(path.join(dir, 'VERSION'), 'utf-8').trim()).toBe('1.1.0.0');
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
||||
expect(pkg.version).toBe('1.1.0.0');
|
||||
|
|
@ -72,7 +72,7 @@ describe('write (FRESH bump)', () => {
|
|||
const d2 = fs.mkdtempSync(path.join(os.tmpdir(), 'vbump-noPkg-'));
|
||||
fs.writeFileSync(path.join(d2, 'VERSION'), '0.1.0.0\n');
|
||||
const out = execFileSync('bun', [BIN, 'write', '--version', '0.2.0.0'], { cwd: d2 }).toString();
|
||||
expect(JSON.parse(out)).toEqual({ wrote: '0.2.0.0', packageJson: false });
|
||||
expect(JSON.parse(out)).toEqual({ wrote: '0.2.0.0', packageJson: false, packageLock: false });
|
||||
expect(fs.readFileSync(path.join(d2, 'VERSION'), 'utf-8').trim()).toBe('0.2.0.0');
|
||||
fs.rmSync(d2, { recursive: true, force: true });
|
||||
});
|
||||
|
|
@ -100,6 +100,48 @@ describe('repair (DRIFT_STALE_PKG)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('write/repair sync package-lock.json (both version fields)', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vbump-lock-'));
|
||||
afterAll(() => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* noop */ } });
|
||||
|
||||
const lock = (v: string) => JSON.stringify({
|
||||
name: 'x', version: v, lockfileVersion: 3,
|
||||
packages: { '': { name: 'x', version: v }, 'node_modules/a': { version: '9.9.9' } },
|
||||
}, null, 2) + '\n';
|
||||
|
||||
test('write updates top-level version and packages[""].version, leaves deps alone', () => {
|
||||
fs.writeFileSync(path.join(dir, 'VERSION'), '1.0.0.0\n');
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x', version: '1.0.0.0' }, null, 2) + '\n');
|
||||
fs.writeFileSync(path.join(dir, 'package-lock.json'), lock('1.0.0.0'));
|
||||
const out = execFileSync('bun', [BIN, 'write', '--version', '1.1.0.0'], { cwd: dir }).toString();
|
||||
expect(JSON.parse(out)).toEqual({ wrote: '1.1.0.0', packageJson: true, packageLock: true });
|
||||
const l = JSON.parse(fs.readFileSync(path.join(dir, 'package-lock.json'), 'utf-8'));
|
||||
expect(l.version).toBe('1.1.0.0');
|
||||
expect(l.packages[''].version).toBe('1.1.0.0');
|
||||
expect(l.packages['node_modules/a'].version).toBe('9.9.9'); // untouched
|
||||
});
|
||||
|
||||
test('repair heals a stale lockfile alongside package.json', () => {
|
||||
fs.writeFileSync(path.join(dir, 'VERSION'), '2.0.0.0\n');
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x', version: '1.9.0.0' }, null, 2) + '\n');
|
||||
fs.writeFileSync(path.join(dir, 'package-lock.json'), lock('1.9.0.0'));
|
||||
execFileSync('bun', [BIN, 'repair'], { cwd: dir });
|
||||
const l = JSON.parse(fs.readFileSync(path.join(dir, 'package-lock.json'), 'utf-8'));
|
||||
expect(l.version).toBe('2.0.0.0');
|
||||
expect(l.packages[''].version).toBe('2.0.0.0');
|
||||
});
|
||||
|
||||
test('lockfileVersion 1 (no packages map) syncs top-level only, no crash', () => {
|
||||
fs.writeFileSync(path.join(dir, 'VERSION'), '3.0.0.0\n');
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x', version: '2.9.0.0' }, null, 2) + '\n');
|
||||
fs.writeFileSync(path.join(dir, 'package-lock.json'), JSON.stringify({ name: 'x', version: '2.9.0.0', lockfileVersion: 1 }, null, 2) + '\n');
|
||||
execFileSync('bun', [BIN, 'repair'], { cwd: dir });
|
||||
const l = JSON.parse(fs.readFileSync(path.join(dir, 'package-lock.json'), 'utf-8'));
|
||||
expect(l.version).toBe('3.0.0.0');
|
||||
expect(l.packages).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('classify (idempotency over a real git base)', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vbump-classify-'));
|
||||
afterAll(() => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* noop */ } });
|
||||
|
|
|
|||
Loading…
Reference in New Issue