@vodmal/vdx-cli 0.13.0 → 0.13.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 +3 -0
- package/package.json +1 -1
- package/src/doctor.ts +1 -1
- package/src/index.ts +4 -2
- package/src/publish.ts +66 -6
package/README.md
CHANGED
|
@@ -50,6 +50,9 @@ vdx doctor [--format=ansi|markdown|json]
|
|
|
50
50
|
# Start your agent in a project per your profile (~/.vdx-environment.yaml or $VDX_ENVIRONMENT)
|
|
51
51
|
vdx ai [project-path] [--new] [--restart] [--detach] [--dry-run] # default: cwd
|
|
52
52
|
|
|
53
|
+
# Print the installed version
|
|
54
|
+
vdx --version
|
|
55
|
+
|
|
53
56
|
# MCP stdio server consumed by the Claude Code plugin
|
|
54
57
|
vdx-mcp --project <path>
|
|
55
58
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vodmal/vdx-cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.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/doctor.ts
CHANGED
|
@@ -162,7 +162,7 @@ function checkNpmAuth(): CheckResult {
|
|
|
162
162
|
id: 'npm-auth',
|
|
163
163
|
label: 'npm auth',
|
|
164
164
|
status: 'warning',
|
|
165
|
-
message: 'not logged in — `vdx publish`
|
|
165
|
+
message: 'not logged in — `vdx publish` refuses at pre-flight (npm-auth)',
|
|
166
166
|
remedy: 'npm login',
|
|
167
167
|
};
|
|
168
168
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
reportDoctorJson,
|
|
20
20
|
reportDoctorAnsi,
|
|
21
21
|
} from './report.ts';
|
|
22
|
-
import { runDoctor, looksLikeProject } from './doctor.ts';
|
|
22
|
+
import { runDoctor, looksLikeProject, readCliVersion } from './doctor.ts';
|
|
23
23
|
import { planInit, writeInit, renderPlanSummary } from './init.ts';
|
|
24
24
|
import {
|
|
25
25
|
planPublish,
|
|
@@ -42,6 +42,7 @@ function usage(): never {
|
|
|
42
42
|
vdx publish <patch|minor|major> [--dry-run] [--force]
|
|
43
43
|
vdx doctor [--format=ansi|markdown|json] [--json]
|
|
44
44
|
vdx ai [project_path] [--new] [--restart] [--detach] [--dry-run] (default: cwd)
|
|
45
|
+
vdx --version
|
|
45
46
|
`,
|
|
46
47
|
);
|
|
47
48
|
process.exit(1);
|
|
@@ -283,7 +284,8 @@ function cmdAi(opts: ParsedArgs): void {
|
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
const parsed = parseArgs(process.argv);
|
|
286
|
-
if (parsed.cmd === '
|
|
287
|
+
if (parsed.cmd === '--version') process.stdout.write(readCliVersion() + '\n');
|
|
288
|
+
else if (parsed.cmd === 'audit') cmdAudit(parsed);
|
|
287
289
|
else if (parsed.cmd === 'init') cmdInit(parsed);
|
|
288
290
|
else if (parsed.cmd === 'publish') cmdPublish(parsed);
|
|
289
291
|
else if (parsed.cmd === 'doctor') cmdDoctor(parsed);
|
package/src/publish.ts
CHANGED
|
@@ -79,12 +79,21 @@ function isGitWorkingTreeClean(projectRoot: string): { ok: boolean; detail: stri
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
/** Runs npm with these args and returns stdout; throws with `stderr` on a non-zero exit. */
|
|
83
|
+
export type NpmRunner = (args: string[]) => string;
|
|
84
|
+
|
|
85
|
+
// A caller's `npm run -s` passes `npm_config_loglevel=silent` down, and npm then
|
|
86
|
+
// fails with an empty stderr — no E401 or E404 left to tell the cases apart.
|
|
87
|
+
const runNpm: NpmRunner = (args) =>
|
|
88
|
+
execFileSync('npm', args, {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
91
|
+
env: { ...process.env, npm_config_loglevel: 'error' },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
function fetchPublishedVersion(packageName: string, npm: NpmRunner): string | null {
|
|
83
95
|
try {
|
|
84
|
-
const out =
|
|
85
|
-
encoding: 'utf8',
|
|
86
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
87
|
-
});
|
|
96
|
+
const out = npm(['view', packageName, 'version']);
|
|
88
97
|
return out.trim() || null;
|
|
89
98
|
} catch (e: any) {
|
|
90
99
|
const stderr = String(e?.stderr ?? e?.message ?? '');
|
|
@@ -93,6 +102,54 @@ function fetchPublishedVersion(packageName: string): string | null {
|
|
|
93
102
|
}
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
/**
|
|
106
|
+
* The registry `npm publish` sends this package to, when it is not npm's
|
|
107
|
+
* default: `publishConfig.registry`, else the scope's registry from npm config.
|
|
108
|
+
*/
|
|
109
|
+
export function publishRegistry(
|
|
110
|
+
pkg: { name?: string; publishConfig?: { registry?: string } },
|
|
111
|
+
npm: NpmRunner,
|
|
112
|
+
): string | null {
|
|
113
|
+
if (pkg.publishConfig?.registry) return pkg.publishConfig.registry;
|
|
114
|
+
const scope = pkg.name?.match(/^(@[^/]+)\//)?.[1];
|
|
115
|
+
if (!scope) return null;
|
|
116
|
+
try {
|
|
117
|
+
const value = npm(['config', 'get', `${scope}:registry`]).trim();
|
|
118
|
+
return value && value !== 'undefined' ? value : null;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Logged in where the package goes? Without it `npm publish` fails only after
|
|
126
|
+
* the bump and the pack, and with E404 on a scoped package, not E401 — an
|
|
127
|
+
* expired npm session reads as a missing package.
|
|
128
|
+
*/
|
|
129
|
+
export function checkNpmAuth(registry: string | null, npm: NpmRunner): PreflightCheck {
|
|
130
|
+
const where = registry ?? 'npm';
|
|
131
|
+
const login = registry ? `npm login --registry ${registry}` : 'npm login';
|
|
132
|
+
try {
|
|
133
|
+
const who = npm(registry ? ['whoami', '--registry', registry] : ['whoami']).trim();
|
|
134
|
+
if (who) return { name: 'npm-auth', ok: true, message: `OK (logged in to ${where} as ${who})` };
|
|
135
|
+
return { name: 'npm-auth', ok: false, message: `npm whoami answered nothing for ${where}. Run: ${login}` };
|
|
136
|
+
} catch (e: any) {
|
|
137
|
+
const stderr = String(e?.stderr ?? e?.message ?? '');
|
|
138
|
+
if (/E401|ENEEDAUTH/.test(stderr)) {
|
|
139
|
+
return {
|
|
140
|
+
name: 'npm-auth',
|
|
141
|
+
ok: false,
|
|
142
|
+
message: `not logged in to ${where} (the npm session may have expired). Run: ${login}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
name: 'npm-auth',
|
|
147
|
+
ok: false,
|
|
148
|
+
message: `npm whoami failed for ${where}: ${stderr.split('\n').find((l) => l.trim()) ?? stderr}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
96
153
|
function checkMiseOverride(projectRoot: string): boolean {
|
|
97
154
|
const miseToml = path.join(projectRoot, 'mise.toml');
|
|
98
155
|
if (!fs.existsSync(miseToml)) return false;
|
|
@@ -116,6 +173,7 @@ export function planPublish(
|
|
|
116
173
|
opts: PublishOptions,
|
|
117
174
|
audit: AuditResult,
|
|
118
175
|
ctx: Ctx,
|
|
176
|
+
npm: NpmRunner = runNpm,
|
|
119
177
|
): PublishPlan {
|
|
120
178
|
if (checkMiseOverride(opts.projectRoot)) {
|
|
121
179
|
return {
|
|
@@ -197,10 +255,12 @@ export function planPublish(
|
|
|
197
255
|
: `release-artifact at ${raLevel}, need >= L3. Add files[], main/exports, license in package.json.`,
|
|
198
256
|
});
|
|
199
257
|
|
|
258
|
+
preflight.push(checkNpmAuth(publishRegistry(pkg, npm), npm));
|
|
259
|
+
|
|
200
260
|
let published: string | null = null;
|
|
201
261
|
let registryError: string | null = null;
|
|
202
262
|
try {
|
|
203
|
-
published = fetchPublishedVersion(packageName);
|
|
263
|
+
published = fetchPublishedVersion(packageName, npm);
|
|
204
264
|
} catch (e: any) {
|
|
205
265
|
registryError = e?.message ?? String(e);
|
|
206
266
|
}
|