coderifts 8.6.2 → 8.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/README.md +6 -3
- package/dist/.build-source-sha +3 -3
- package/dist/cli.js +816 -130
- package/package.json +7 -8
- package/scripts/build.js +8 -24
- package/scripts/check-packed-install.js +62 -1
- package/scripts/lib/source-fingerprint.js +56 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderifts",
|
|
3
|
-
"version": "8.6.
|
|
3
|
+
"version": "8.6.4",
|
|
4
4
|
"description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
5
5
|
"author": "CodeRifts <hello@coderifts.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,13 +28,12 @@
|
|
|
28
28
|
"swagger",
|
|
29
29
|
"devtools"
|
|
30
30
|
],
|
|
31
|
-
"repository":
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/coderifts/app.git",
|
|
34
|
-
"directory": "packages/cli"
|
|
35
|
-
},
|
|
31
|
+
"repository": "https://coderifts.com",
|
|
36
32
|
"homepage": "https://coderifts.com",
|
|
37
|
-
"bugs":
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://coderifts.com",
|
|
35
|
+
"email": "support@coderifts.com"
|
|
36
|
+
},
|
|
38
37
|
"engines": {
|
|
39
38
|
"node": ">=20.0.0"
|
|
40
39
|
},
|
|
@@ -52,7 +51,7 @@
|
|
|
52
51
|
"cli-table3": "^0.6.4",
|
|
53
52
|
"commander": "^12.0.0",
|
|
54
53
|
"inquirer": "^8.2.6",
|
|
55
|
-
"js-yaml": "^4.3.
|
|
54
|
+
"js-yaml": "^4.3.2",
|
|
56
55
|
"openapi-diff": "^0.24.1",
|
|
57
56
|
"ora": "^5.4.1"
|
|
58
57
|
},
|
package/scripts/build.js
CHANGED
|
@@ -11,6 +11,7 @@ const path = require('path');
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const crypto = require('crypto');
|
|
13
13
|
const esbuild = require('esbuild');
|
|
14
|
+
const { sourceFingerprint } = require('./lib/source-fingerprint');
|
|
14
15
|
|
|
15
16
|
const stub = path.join(__dirname, '..', 'src', 'logger-stub.js');
|
|
16
17
|
const appLogger = path.resolve(__dirname, '..', '..', '..', 'src', 'logger.js');
|
|
@@ -52,33 +53,16 @@ esbuild.build({
|
|
|
52
53
|
// CONTENT-ADDRESSED, not timestamps: mtimes move on checkout, rebase and copy without the bytes
|
|
53
54
|
// changing, and a freshness check that cries wolf gets deleted.
|
|
54
55
|
const root = path.join(__dirname, '..');
|
|
55
|
-
const
|
|
56
|
-
const walk = (dir) => {
|
|
57
|
-
if (!fs.existsSync(dir)) return;
|
|
58
|
-
for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
59
|
-
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
60
|
-
const full = path.join(dir, e.name);
|
|
61
|
-
if (e.isDirectory()) walk(full);
|
|
62
|
-
else if (e.name.endsWith('.js')) files.push(full);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
walk(path.join(root, 'src'));
|
|
66
|
-
walk(path.join(root, 'bin'));
|
|
67
|
-
const h = crypto.createHash('sha256');
|
|
68
|
-
for (const f of files.sort()) {
|
|
69
|
-
h.update(path.relative(root, f));
|
|
70
|
-
h.update('\0');
|
|
71
|
-
h.update(fs.readFileSync(f));
|
|
72
|
-
h.update('\0');
|
|
73
|
-
}
|
|
74
|
-
const sha256 = h.digest('hex');
|
|
56
|
+
const { sha256, file_count } = sourceFingerprint(root);
|
|
75
57
|
fs.writeFileSync(path.join(root, 'dist', '.build-source-sha'), `${JSON.stringify({
|
|
76
|
-
$comment: 'GENERATED by scripts/build.js. sha256 over src
|
|
77
|
-
+ '
|
|
58
|
+
$comment: 'GENERATED by scripts/build.js. sha256 over src/** + bin/** (path + bytes), for '
|
|
59
|
+
+ 'every extension esbuild bundles — .js AND .json. scripts/lib/source-fingerprint.js is '
|
|
60
|
+
+ 'the single implementation; test/build-freshness.test.js recomputes it from there rather '
|
|
61
|
+
+ 'than from a copy. A mismatch means dist/cli.js is stale.',
|
|
78
62
|
sha256,
|
|
79
|
-
file_count
|
|
63
|
+
file_count,
|
|
80
64
|
}, null, 2)}\n`);
|
|
81
|
-
process.stdout.write(`build: source fingerprint ${sha256.slice(0, 12)}… over ${
|
|
65
|
+
process.stdout.write(`build: source fingerprint ${sha256.slice(0, 12)}… over ${file_count} files\n`);
|
|
82
66
|
}).catch((err) => {
|
|
83
67
|
console.error(err);
|
|
84
68
|
process.exit(1);
|
|
@@ -85,7 +85,68 @@ try {
|
|
|
85
85
|
fail('packed init --agents --atomic-v2 --dry-run did not mention atomic-v2 / WIRING_REQUIRED');
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
// Packed `lock --check` must actually RUN two fixtures, not merely list the command.
|
|
89
|
+
// 8.6.3 threw CORE_UNAVAILABLE because mcp-lock-drift was a dynamic require outside the bundle.
|
|
90
|
+
const fixtures = path.join(tmp, 'lock-fixtures');
|
|
91
|
+
fs.mkdirSync(fixtures);
|
|
92
|
+
const lockedManifest = {
|
|
93
|
+
tools: [
|
|
94
|
+
{ name: 'get_user', description: 'Get user', inputSchema: { type: 'object', properties: {} } },
|
|
95
|
+
{ name: 'pay', description: 'Pay', inputSchema: { type: 'object', properties: {} } },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
const liveSame = JSON.parse(JSON.stringify(lockedManifest));
|
|
99
|
+
const liveDrift = {
|
|
100
|
+
tools: [
|
|
101
|
+
{ name: 'get_user', description: 'Get user', inputSchema: { type: 'object', properties: {} } },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
const lockedPath = path.join(fixtures, 'locked.json');
|
|
105
|
+
const samePath = path.join(fixtures, 'live-same.json');
|
|
106
|
+
const driftPath = path.join(fixtures, 'live-drift.json');
|
|
107
|
+
fs.writeFileSync(lockedPath, `${JSON.stringify(lockedManifest)}\n`);
|
|
108
|
+
fs.writeFileSync(samePath, `${JSON.stringify(liveSame)}\n`);
|
|
109
|
+
fs.writeFileSync(driftPath, `${JSON.stringify(liveDrift)}\n`);
|
|
110
|
+
|
|
111
|
+
const lockOk = run(bin, [
|
|
112
|
+
'lock', '--check', '--json',
|
|
113
|
+
'--locked-manifest', lockedPath,
|
|
114
|
+
'--live-manifest', samePath,
|
|
115
|
+
], { cwd: installDir, env: { ...process.env, NO_COLOR: '1' } });
|
|
116
|
+
if (lockOk.status !== 0) {
|
|
117
|
+
fail(`packed lock --check (identical fixtures) exited ${lockOk.status}:\n${lockOk.stderr}\n${lockOk.stdout}`);
|
|
118
|
+
}
|
|
119
|
+
if (/Cannot load mcp-lock-drift core/i.test(`${lockOk.stdout}\n${lockOk.stderr}`)) {
|
|
120
|
+
fail('packed lock --check still cannot load mcp-lock-drift core');
|
|
121
|
+
}
|
|
122
|
+
let okReport;
|
|
123
|
+
try { okReport = JSON.parse(lockOk.stdout); } catch (e) {
|
|
124
|
+
fail(`packed lock --check identical fixtures did not print JSON: ${e.message}\n${lockOk.stdout}`);
|
|
125
|
+
}
|
|
126
|
+
if (okReport.drift !== false) {
|
|
127
|
+
fail(`packed lock --check identical fixtures expected drift=false, got ${JSON.stringify(okReport)}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const lockDrift = run(bin, [
|
|
131
|
+
'lock', '--check', '--json',
|
|
132
|
+
'--locked-manifest', lockedPath,
|
|
133
|
+
'--live-manifest', driftPath,
|
|
134
|
+
], { cwd: installDir, env: { ...process.env, NO_COLOR: '1' } });
|
|
135
|
+
if (lockDrift.status !== 3) {
|
|
136
|
+
fail(`packed lock --check (drift fixture) exited ${lockDrift.status}, expected 3:\n${lockDrift.stderr}\n${lockDrift.stdout}`);
|
|
137
|
+
}
|
|
138
|
+
if (/Cannot load mcp-lock-drift core/i.test(`${lockDrift.stdout}\n${lockDrift.stderr}`)) {
|
|
139
|
+
fail('packed lock --check drift fixture still cannot load mcp-lock-drift core');
|
|
140
|
+
}
|
|
141
|
+
let driftReport;
|
|
142
|
+
try { driftReport = JSON.parse(lockDrift.stdout); } catch (e) {
|
|
143
|
+
fail(`packed lock --check drift fixture did not print JSON: ${e.message}\n${lockDrift.stdout}`);
|
|
144
|
+
}
|
|
145
|
+
if (driftReport.drift !== true) {
|
|
146
|
+
fail(`packed lock --check drift fixture expected drift=true, got ${JSON.stringify(driftReport)}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
process.stdout.write('packed install smoke: OK (atomic-v2 dry-run + lock --check two fixtures)\n');
|
|
89
150
|
} finally {
|
|
90
151
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
91
152
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The source fingerprint dist/cli.js is stamped with, and the one test/build-freshness.test.js
|
|
5
|
+
* recomputes. ONE implementation, required by both.
|
|
6
|
+
*
|
|
7
|
+
* WHY IT LIVES HERE. The walk used to exist twice — once in scripts/build.js and once inside the
|
|
8
|
+
* test, whose comment said the copy was "kept in step by the assertions below". No assertion
|
|
9
|
+
* compared them, so "kept in step" was a hope. MEASURED 2026-09-12: it had already failed in one
|
|
10
|
+
* direction and was about to fail in the other. A freshness gate whose recomputation is a COPY of
|
|
11
|
+
* the thing it checks can drift from it silently, which is the one failure a freshness gate must
|
|
12
|
+
* not have.
|
|
13
|
+
*
|
|
14
|
+
* .json COUNTS. esbuild inlines src/generated/gate-pin.json into dist/cli.js, so it is shipped
|
|
15
|
+
* runtime rather than configuration beside it. With a .js-only walk the pin moved from v0.9.3 to
|
|
16
|
+
* v0.9.4 while the marker stayed green and dist kept naming v0.9.3 — the marker's own comment
|
|
17
|
+
* promises that a mismatch means dist is stale, and for a bundled JSON it did not deliver.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const crypto = require('crypto');
|
|
23
|
+
|
|
24
|
+
/** Extensions that end up INSIDE the bundle. Anything here changes the shipped runtime. */
|
|
25
|
+
const BUNDLED_EXT = Object.freeze(['.js', '.json']);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* sha256 over src/** + bin/** (path + bytes), content-addressed — never mtime: mtimes move on
|
|
29
|
+
* checkout, rebase and copy with no byte change, and a freshness check that cries wolf gets
|
|
30
|
+
* deleted.
|
|
31
|
+
* @param {string} root package root
|
|
32
|
+
*/
|
|
33
|
+
function sourceFingerprint(root) {
|
|
34
|
+
const files = [];
|
|
35
|
+
const walk = (dir) => {
|
|
36
|
+
if (!fs.existsSync(dir)) return;
|
|
37
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
38
|
+
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
39
|
+
const full = path.join(dir, e.name);
|
|
40
|
+
if (e.isDirectory()) walk(full);
|
|
41
|
+
else if (BUNDLED_EXT.some((x) => e.name.endsWith(x))) files.push(full);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
walk(path.join(root, 'src'));
|
|
45
|
+
walk(path.join(root, 'bin'));
|
|
46
|
+
const h = crypto.createHash('sha256');
|
|
47
|
+
for (const f of files.sort()) {
|
|
48
|
+
h.update(path.relative(root, f));
|
|
49
|
+
h.update('\0');
|
|
50
|
+
h.update(fs.readFileSync(f));
|
|
51
|
+
h.update('\0');
|
|
52
|
+
}
|
|
53
|
+
return { sha256: h.digest('hex'), file_count: files.length };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { sourceFingerprint, BUNDLED_EXT };
|