@vodmal/vdx-cli 0.31.0 → 0.31.1
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/README.md +1 -0
- package/bin/node-guard.cjs +43 -0
- package/bin/vdx-mcp.cjs +9 -7
- package/bin/vdx.cjs +9 -7
- package/package.json +1 -1
- package/src/gate.ts +10 -7
- package/src/hosting.ts +5 -0
package/README.md
CHANGED
|
@@ -167,6 +167,7 @@ npm run typecheck
|
|
|
167
167
|
- `src/mcp-server.ts` — MCP stdio server (9 tools)
|
|
168
168
|
- `src/defaults.ts` — bundled rubric resolution
|
|
169
169
|
- `bin/vdx.cjs`, `bin/vdx-mcp.cjs` — Node wrappers (tsx ESM loader in-process)
|
|
170
|
+
- `bin/node-guard.cjs` — under a project's older node (`.nvmrc` with fnm, nvm, mise), runs the wrapper again on the node beside its bin link
|
|
170
171
|
|
|
171
172
|
## Not implemented yet
|
|
172
173
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
6
|
+
|
|
7
|
+
// vdx needs Node 22 or newer (engines in package.json), and its bins start with
|
|
8
|
+
// `env node`: in a project that pins an older node (.nvmrc under fnm or nvm,
|
|
9
|
+
// mise) that is the project's node, and tsx stops before vdx starts. Then vdx
|
|
10
|
+
// runs again on the node its bin link sits next to — the one whose npm put it
|
|
11
|
+
// there; the project's node stays on PATH for the commands vdx runs. This file
|
|
12
|
+
// runs on that old node: nothing past Node 14 in it.
|
|
13
|
+
// True — the current node will do; false — the re-run or the error owns the exit.
|
|
14
|
+
module.exports = function newEnoughNode(entry) {
|
|
15
|
+
if (Number(process.versions.node.split('.')[0]) >= 22) return true;
|
|
16
|
+
const node = real(path.join(path.dirname(process.argv[1]), 'node'));
|
|
17
|
+
if (!node || node === real(process.execPath)) {
|
|
18
|
+
console.error(
|
|
19
|
+
`vdx needs Node.js 22 or newer; the node on PATH here is ${process.version}` +
|
|
20
|
+
' — the project may pin it (.nvmrc, .node-version, mise.toml)',
|
|
21
|
+
);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const child = spawn(node, [entry].concat(process.argv.slice(2)), { stdio: 'inherit' });
|
|
26
|
+
// Ctrl-C reaches the child from the terminal itself; a signal sent to vdx alone is passed on.
|
|
27
|
+
['SIGINT', 'SIGQUIT'].forEach((sig) => process.on(sig, () => {}));
|
|
28
|
+
['SIGTERM', 'SIGHUP'].forEach((sig) => process.on(sig, () => child.kill(sig)));
|
|
29
|
+
child.on('error', (err) => {
|
|
30
|
+
console.error(`vdx: ${node}: ${err.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
child.on('exit', (code, signal) => process.exit(signal ? 128 + os.constants.signals[signal] : code));
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function real(p) {
|
|
38
|
+
try {
|
|
39
|
+
return fs.realpathSync(p);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/bin/vdx-mcp.cjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
register();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
if (require('./node-guard.cjs')(__filename)) {
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { register } = require('tsx/esm/api');
|
|
6
|
+
register();
|
|
7
|
+
import(path.join(__dirname, '..', 'src', 'mcp-server.ts')).catch((err) => {
|
|
8
|
+
console.error(err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
}
|
package/bin/vdx.cjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
register();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
if (require('./node-guard.cjs')(__filename)) {
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { register } = require('tsx/esm/api');
|
|
6
|
+
register();
|
|
7
|
+
import(path.join(__dirname, '..', 'src', 'index.ts')).catch((err) => {
|
|
8
|
+
console.error(err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vodmal/vdx-cli",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "vdx — unified lifecycle interface (up/down/build/test/check/fix) + versioned maturity audit with drift detection. Pairs with the vdx Claude Code plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vdx",
|
package/src/gate.ts
CHANGED
|
@@ -132,15 +132,18 @@ export function pushThroughGate(t: GateTarget, d: GateDeps): void {
|
|
|
132
132
|
d.log(`✓ ${where} is at ${short}`);
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
const gate = GATE_PREFIX + t.branch;
|
|
136
|
+
// On the gate branch already (a second vdx push) and checked: the branch takes it now.
|
|
137
|
+
// A commit not pushed yet is not asked about — GitHub would only answer 422.
|
|
138
|
+
if (d.io.git(['rev-parse', '--verify', '--quiet', `refs/remotes/${t.remote}/${gate}`]) === sha) {
|
|
139
|
+
const before = ask();
|
|
140
|
+
if (!isUnknown(before) && passed(before)) {
|
|
141
|
+
d.log(`✓ ${short} has passed ${names}`);
|
|
142
|
+
toBranch();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
const gate = GATE_PREFIX + t.branch;
|
|
144
147
|
d.log(`→ git push --force ${t.remote} ${short}:${gate} — ${where} requires ${names}; CI checks the commit there`);
|
|
145
148
|
d.push(['push', '--force', t.remote, `${sha}:refs/heads/${gate}`]);
|
|
146
149
|
d.log(`→ waiting for ${names} on ${short} (asking every ${Math.round(d.pollMs / 1000)} s): ${link}`);
|
package/src/hosting.ts
CHANGED
|
@@ -228,6 +228,11 @@ export function commitChecks(
|
|
|
228
228
|
token: string | null,
|
|
229
229
|
): CheckState[] | Unknown {
|
|
230
230
|
const runs = io.get(`/repos/${repo}/commits/${sha}/check-runs?per_page=100`, token);
|
|
231
|
+
// Seconds after a push GitHub may not show the commit yet: 422 «No commit found
|
|
232
|
+
// for SHA» for 106da5f, 15 s after its push to a new branch (2026-10-18).
|
|
233
|
+
if (runs.status === 422 && /^No commit found/.test(str(at(runs.body, 'message')) ?? '')) {
|
|
234
|
+
return names.map((name) => ({ name, state: 'pending', detail: 'not on GitHub yet' }));
|
|
235
|
+
}
|
|
231
236
|
const refusedRuns = refusal(runs, repo, token);
|
|
232
237
|
if (refusedRuns) return refusedRuns;
|
|
233
238
|
// A re-run is a new check run under the same name: the newest one counts.
|