@base_bit/vmg-cli 0.0.3 → 0.0.5
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 +43 -1
- package/bin/npm-update.mjs +199 -0
- package/bin/vmg.mjs +57 -15
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -32,6 +32,48 @@ absolute private writable base; `vmg doctor --json` reports the resource locatio
|
|
|
32
32
|
A damaged cache is an error: stop processes using the affected hash, remove only
|
|
33
33
|
that hash directory and retry. Older hashes are retained across upgrades.
|
|
34
34
|
|
|
35
|
-
Upgrade with `npm install -g @base_bit/vmg-cli@latest
|
|
35
|
+
Upgrade with `vmg update` (or `npm install -g @base_bit/vmg-cli@latest`); uninstall with
|
|
36
36
|
`npm uninstall -g @base_bit/vmg-cli`. Cached runtime resources remain on disk.
|
|
37
37
|
The independently distributed native binary continues to work without npm.
|
|
38
|
+
|
|
39
|
+
## Updates
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
vmg update --check # Query latest without installing
|
|
43
|
+
vmg update # Upgrade a confirmed npm global installation
|
|
44
|
+
vmg update --check --json # Structured result for scripts
|
|
45
|
+
vmg update --timeout-seconds 300
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The npm launcher queries the configured npm registry in global mode (including
|
|
49
|
+
user/global npm configuration and environment settings, excluding project
|
|
50
|
+
`.npmrc`). It compares semantic versions against `latest`, never downgrades, and
|
|
51
|
+
installs the exact checked version with its matching platform package. Automatic
|
|
52
|
+
installation requires the current package to be directly inside the active npm
|
|
53
|
+
global root with a matching prefix; linked packages and detected pnpm/yarn layouts
|
|
54
|
+
are excluded. If a different Node/npm installation is active, switch back to the
|
|
55
|
+
one that owns VMG. Project-local, npx and other package-manager installs receive
|
|
56
|
+
manual instructions. Standalone binaries must be replaced manually.
|
|
57
|
+
|
|
58
|
+
Updates retain the npm prefix, enable optional dependencies and disable install
|
|
59
|
+
scripts. Success means the entry package, platform package and native executable
|
|
60
|
+
all report the requested version. No administrator escalation is attempted. npm
|
|
61
|
+
installation is not an atomic transaction: errors, interruption or timeout can
|
|
62
|
+
leave an incomplete installation; follow the reported reinstall instruction.
|
|
63
|
+
Concurrent updates to the same installation wait for a lock within the timeout.
|
|
64
|
+
A stale lock error names the file to remove after confirming no update is running.
|
|
65
|
+
Old runtime caches are retained.
|
|
66
|
+
|
|
67
|
+
Ordinary interactive commands check in parallel at most once every **12 hours**,
|
|
68
|
+
with a three-second limit. Checks stop when the command exits. Notices go to
|
|
69
|
+
stderr and show the current/latest versions. Network or cache failures are silent
|
|
70
|
+
and do not change the command's result. Checks are disabled for non-interactive
|
|
71
|
+
runs, CI, `--json`, help and version output. Set `VMG_NO_UPDATE_NOTIFIER=1` to opt
|
|
72
|
+
out. Attempts and results are cached separately under `vmg/updates` in the user's
|
|
73
|
+
cache directory (on macOS, `~/Library/Caches`, or absolute `XDG_CACHE_HOME`).
|
|
74
|
+
|
|
75
|
+
Explicit `update --check` bypasses that cache and reports registry errors. Its
|
|
76
|
+
successful exit status is 0 even when an update is available. `--json` returns
|
|
77
|
+
`ok`, `currentVersion`, `latestVersion`, `updateAvailable` and `updated`; successful
|
|
78
|
+
installation additionally includes `installedVersion`. Errors and unsupported
|
|
79
|
+
installation attempts exit nonzero; npm logs never mix into JSON stdout.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFile, realpath, mkdir, open, rename, rm, access } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve, isAbsolute } from 'node:path';
|
|
5
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
export const packageName = '@base_bit/vmg-cli';
|
|
8
|
+
export const checkInterval = 12 * 60 * 60 * 1000;
|
|
9
|
+
const shellQuote = value => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
10
|
+
const repair = 'npm install -g @base_bit/vmg-cli@latest --include=optional --ignore-scripts';
|
|
11
|
+
const readJson = async path => JSON.parse(await readFile(path, 'utf8'));
|
|
12
|
+
const exists = async path => access(path).then(() => true, () => false);
|
|
13
|
+
|
|
14
|
+
function versionParts(value) {
|
|
15
|
+
if (typeof value !== 'string') throw new Error('Invalid npm version');
|
|
16
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value);
|
|
17
|
+
if (!match || match[4]?.split('.').some(x => /^\d+$/.test(x) && x.length > 1 && x[0] === '0')) throw new Error(`Invalid npm version: ${value}`);
|
|
18
|
+
return { core: match.slice(1, 4).map(BigInt), pre: match[4]?.split('.') };
|
|
19
|
+
}
|
|
20
|
+
export function compareVersions(a, b) {
|
|
21
|
+
const left = versionParts(a), right = versionParts(b);
|
|
22
|
+
for (let i = 0; i < 3; i++) if (left.core[i] !== right.core[i]) return left.core[i] > right.core[i] ? 1 : -1;
|
|
23
|
+
if (!left.pre || !right.pre) return left.pre ? -1 : right.pre ? 1 : 0;
|
|
24
|
+
for (let i = 0; i < Math.max(left.pre.length, right.pre.length); i++) {
|
|
25
|
+
const x = left.pre[i], y = right.pre[i];
|
|
26
|
+
if (x === y) continue;
|
|
27
|
+
if (x === undefined || y === undefined) return x === undefined ? -1 : 1;
|
|
28
|
+
const nx = /^\d+$/.test(x), ny = /^\d+$/.test(y);
|
|
29
|
+
if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
30
|
+
if (nx !== ny) return nx ? -1 : 1;
|
|
31
|
+
return x > y ? 1 : -1;
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse only the update command. Every other command keeps Rust's parser.
|
|
37
|
+
export function parseUpdate(args) {
|
|
38
|
+
let command, check = false, json = false, timeout = 120, invalid;
|
|
39
|
+
for (let i = 0; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
if (arg === '--json') json = true;
|
|
42
|
+
else if (arg === '--timeout-seconds' || arg.startsWith('--timeout-seconds=')) {
|
|
43
|
+
const value = arg.includes('=') ? arg.slice(arg.indexOf('=') + 1) : args[++i];
|
|
44
|
+
if (!/^\d+$/.test(value ?? '') || +value < 1 || +value > 3600) invalid = 'timeout-seconds must be between 1 and 3600';
|
|
45
|
+
else timeout = +value;
|
|
46
|
+
} else if (!command && !arg.startsWith('-')) command = arg;
|
|
47
|
+
else if (arg === '--check' && command === 'update') check = true;
|
|
48
|
+
else if (arg === '--help' || arg === '-h') return null;
|
|
49
|
+
else invalid = `Unexpected argument: ${arg}`;
|
|
50
|
+
}
|
|
51
|
+
if (command !== 'update') return null;
|
|
52
|
+
if (invalid) throw new Error(invalid);
|
|
53
|
+
return { check, json, timeout };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Own the npm process group; capture logs so --json remains a single result.
|
|
57
|
+
export function runCommand(program, args, { signal, cwd = homedir(), env = process.env } = {}) {
|
|
58
|
+
signal?.throwIfAborted();
|
|
59
|
+
return new Promise((resolveResult, reject) => {
|
|
60
|
+
const child = spawn(program, args, { cwd, env, shell: false, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
61
|
+
let stdout = '', stderr = '', failure, escalation;
|
|
62
|
+
const kill = sig => { try { process.kill(-child.pid, sig); } catch { child.kill(sig); } };
|
|
63
|
+
const stop = () => {
|
|
64
|
+
failure ??= signal?.reason ?? new Error('Command cancelled');
|
|
65
|
+
kill(failure.signal ?? 'SIGTERM');
|
|
66
|
+
escalation ??= setTimeout(() => kill('SIGKILL'), 1000);
|
|
67
|
+
};
|
|
68
|
+
const collect = stream => bytes => {
|
|
69
|
+
if (stdout.length + stderr.length + bytes.length > 2 * 1024 * 1024) { failure = new Error('npm output exceeded limit'); stop(); return; }
|
|
70
|
+
if (stream === 'stdout') stdout += bytes; else stderr += bytes;
|
|
71
|
+
};
|
|
72
|
+
child.stdout.on('data', collect('stdout')); child.stderr.on('data', collect('stderr'));
|
|
73
|
+
signal?.addEventListener('abort', stop, { once: true });
|
|
74
|
+
if (signal?.aborted) stop();
|
|
75
|
+
child.once('error', error => { failure = error; });
|
|
76
|
+
child.once('close', (code, termination) => {
|
|
77
|
+
clearTimeout(escalation); signal?.removeEventListener('abort', stop);
|
|
78
|
+
// No npm helper may outlive the owned operation.
|
|
79
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
80
|
+
if (failure) reject(failure);
|
|
81
|
+
else if (code !== 0) reject(new Error(`${program} failed (${termination ?? code}): ${stderr.trim() || stdout.trim()}`));
|
|
82
|
+
else resolveResult(stdout.trim());
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function context({ signal, run = runCommand } = {}) {
|
|
88
|
+
const options = { signal, cwd: homedir() };
|
|
89
|
+
return { signal, npm: args => run('npm', args, options), run: (program, args) => run(program, args, options) };
|
|
90
|
+
}
|
|
91
|
+
async function latestVersion(ctx) {
|
|
92
|
+
const latest = JSON.parse(await ctx.npm(['view', `${packageName}@latest`, 'version', '--json', '--prefer-online', '--global']));
|
|
93
|
+
versionParts(latest);
|
|
94
|
+
return latest;
|
|
95
|
+
}
|
|
96
|
+
export async function installation(packageJson, ctx) {
|
|
97
|
+
const rootPath = await ctx.npm(['root', '--global']);
|
|
98
|
+
const prefix = await ctx.npm(['prefix', '--global']);
|
|
99
|
+
if (!isAbsolute(rootPath) || !isAbsolute(prefix)) return null;
|
|
100
|
+
const root = await realpath(rootPath).catch(() => null);
|
|
101
|
+
if (!root) return null;
|
|
102
|
+
const expected = join(root, packageName, 'package.json');
|
|
103
|
+
const actual = await realpath(packageJson);
|
|
104
|
+
// npm's layout is direct. pnpm stores and linked packages are not ours to mutate.
|
|
105
|
+
if (actual !== expected || await exists(join(root, '.modules.yaml')) || await exists(join(root, '.yarn-integrity'))) return null;
|
|
106
|
+
if (root !== await realpath(join(prefix, 'lib', 'node_modules')).catch(() => null)) return null;
|
|
107
|
+
if ((await readJson(actual)).name !== packageName) return null;
|
|
108
|
+
return { root, prefix, packageJson: actual };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function lockInstallation(root, signal) {
|
|
112
|
+
const path = join(root, '@base_bit', '.vmg-update.lock');
|
|
113
|
+
for (;;) {
|
|
114
|
+
signal?.throwIfAborted();
|
|
115
|
+
try {
|
|
116
|
+
const file = await open(path, 'wx', 0o600);
|
|
117
|
+
try { await file.writeFile(JSON.stringify({ pid: process.pid, createdAt: Date.now() })); }
|
|
118
|
+
catch (error) { await file.close(); await rm(path, { force: true }); throw error; }
|
|
119
|
+
await file.close();
|
|
120
|
+
return () => rm(path, { force: true });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.code !== 'EEXIST') throw error;
|
|
123
|
+
const owner = await readJson(path).catch(() => null);
|
|
124
|
+
if (Number.isSafeInteger(owner?.pid) && owner.pid > 0) {
|
|
125
|
+
try { process.kill(owner.pid, 0); }
|
|
126
|
+
catch (cause) {
|
|
127
|
+
if (cause.code === 'ESRCH') throw new Error(`Stale update lock: ${path}. Remove it after confirming no update is running.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await new Promise(resolveWait => setTimeout(resolveWait, 100));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function update(packageJson, options, { ctx = context(), platformName, resolveBinary } = {}) {
|
|
136
|
+
const original = await readJson(packageJson);
|
|
137
|
+
const latest = await latestVersion(ctx);
|
|
138
|
+
const summary = { ok: true, currentVersion: original.version, latestVersion: latest, updateAvailable: compareVersions(latest, original.version) > 0, updated: false };
|
|
139
|
+
if (options.check) return summary;
|
|
140
|
+
const location = await installation(packageJson, ctx);
|
|
141
|
+
if (!location) return { ...summary, ok: false, message: 'Automatic updates require a confirmed npm global installation. For a project dependency use its package manager; for npx run npx @base_bit/vmg-cli@latest; for pnpm/yarn use their global update command. For npm global installation: ' + repair };
|
|
142
|
+
if (!summary.updateAvailable) return summary;
|
|
143
|
+
const unlock = await lockInstallation(location.root, ctx.signal);
|
|
144
|
+
let installStarted = false;
|
|
145
|
+
try {
|
|
146
|
+
const current = await readJson(packageJson);
|
|
147
|
+
summary.currentVersion = current.version;
|
|
148
|
+
summary.updateAvailable = compareVersions(latest, current.version) > 0;
|
|
149
|
+
if (!summary.updateAvailable) return summary;
|
|
150
|
+
if (!platformName) throw new Error('This platform is not supported by VMG');
|
|
151
|
+
const available = JSON.parse(await ctx.npm(['view', `${platformName}@${latest}`, 'version', '--json', '--prefer-online', '--global']));
|
|
152
|
+
if (available !== latest) throw new Error(`Missing matching platform package ${platformName}@${latest}`);
|
|
153
|
+
ctx.signal?.throwIfAborted();
|
|
154
|
+
installStarted = true;
|
|
155
|
+
await ctx.npm(['install', '--global', '--prefix', location.prefix, `${packageName}@${latest}`, '--include=optional', '--ignore-scripts', '--no-audit', '--no-fund']);
|
|
156
|
+
ctx.signal?.throwIfAborted();
|
|
157
|
+
if ((await readJson(packageJson)).version !== latest) throw new Error('Installed entry package does not match the requested version');
|
|
158
|
+
const binary = resolveBinary(packageJson);
|
|
159
|
+
if (await ctx.run(binary, ['--version']) !== `vmg ${latest}`) throw new Error('Installed native binary version does not match the requested version');
|
|
160
|
+
return { ...summary, updated: true, installedVersion: latest };
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!installStarted) throw error;
|
|
163
|
+
throw new Error(`${error.message}\nInstallation may be incomplete. Repair the same npm prefix with: npm install -g --prefix ${shellQuote(location.prefix)} ${packageName}@${latest} --include=optional --ignore-scripts`, { cause: error });
|
|
164
|
+
} finally { await unlock(); }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function shouldNotify(args, env = process.env, tty = process.stdin.isTTY && process.stderr.isTTY) {
|
|
168
|
+
return Boolean(tty && !env.CI && env.VMG_NO_UPDATE_NOTIFIER !== '1' && !args.some(x => ['--json', '--help', '-h', '--version', '-V'].includes(x)));
|
|
169
|
+
}
|
|
170
|
+
export function notificationCache(packageJson) {
|
|
171
|
+
const base = process.env.XDG_CACHE_HOME && resolve(process.env.XDG_CACHE_HOME) === process.env.XDG_CACHE_HOME
|
|
172
|
+
? process.env.XDG_CACHE_HOME : process.platform === 'darwin' ? join(homedir(), 'Library', 'Caches') : join(homedir(), '.cache');
|
|
173
|
+
return join(base, 'vmg', 'updates', createHash('sha256').update(packageJson).digest('hex') + '.json');
|
|
174
|
+
}
|
|
175
|
+
export async function notify(packageJson, { ctx = context(), cachePath = notificationCache(packageJson), now = Date.now(), emit = text => process.stderr.write(text) } = {}) {
|
|
176
|
+
try {
|
|
177
|
+
const current = (await readJson(packageJson)).version;
|
|
178
|
+
const cached = await readJson(cachePath).catch(() => null);
|
|
179
|
+
let latest = cached?.latest;
|
|
180
|
+
if (!(Number.isFinite(cached?.checkedAt) && now >= cached.checkedAt && now - cached.checkedAt < checkInterval)) {
|
|
181
|
+
await mkdir(dirname(cachePath), { recursive: true, mode: 0o700 });
|
|
182
|
+
// Record attempts too, so offline sessions do not repeatedly spawn npm.
|
|
183
|
+
const save = async value => {
|
|
184
|
+
const temporary = `${cachePath}.${randomUUID()}.tmp`;
|
|
185
|
+
try {
|
|
186
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
187
|
+
try { await file.writeFile(JSON.stringify(value)); } finally { await file.close(); }
|
|
188
|
+
await rename(temporary, cachePath);
|
|
189
|
+
} finally { await rm(temporary, { force: true }); }
|
|
190
|
+
};
|
|
191
|
+
await save({ checkedAt: now });
|
|
192
|
+
latest = await latestVersion(ctx);
|
|
193
|
+
ctx.signal?.throwIfAborted();
|
|
194
|
+
await save({ checkedAt: now, latest });
|
|
195
|
+
}
|
|
196
|
+
ctx.signal?.throwIfAborted();
|
|
197
|
+
if (latest && compareVersions(latest, current) > 0) emit(`VMG update available: ${current} → ${latest}. Run vmg update (npm global installs); otherwise update with your package manager.\n`);
|
|
198
|
+
} catch { /* Best effort only: never change a normal command's outcome. */ }
|
|
199
|
+
}
|
package/bin/vmg.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { readFileSync, realpathSync } from 'node:fs';
|
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
import { parseUpdate, update, context, shouldNotify, notify } from './npm-update.mjs';
|
|
7
8
|
|
|
8
9
|
export const platforms = Object.freeze({ 'darwin-arm64': '@base_bit/vmg-cli-darwin-arm64' });
|
|
9
10
|
|
|
@@ -24,23 +25,64 @@ export function resolveBinary(packageJson, platform = process.platform, arch = p
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function launch(binary, args) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
return new Promise(resolveDone => {
|
|
29
|
+
// Keep Rust attached to the terminal. Rust owns the separately grouped bridge
|
|
30
|
+
// and must finish reaping it before this launcher exits.
|
|
31
|
+
const child = spawn(binary, args, { stdio: 'inherit', shell: false });
|
|
32
|
+
const interrupt = () => child.kill('SIGINT'), terminate = () => child.kill('SIGTERM');
|
|
33
|
+
process.on('SIGINT', interrupt); process.on('SIGTERM', terminate);
|
|
34
|
+
const cleanup = () => { process.off('SIGINT', interrupt); process.off('SIGTERM', terminate); };
|
|
35
|
+
let spawnFailed = false;
|
|
36
|
+
child.once('error', error => { spawnFailed = true; cleanup(); console.error(`vmg: Cannot start platform binary: ${error.message}`); process.exitCode = 1; });
|
|
37
|
+
child.once('close', (code, signal) => {
|
|
38
|
+
cleanup();
|
|
39
|
+
if (spawnFailed) { resolveDone(); return; }
|
|
40
|
+
process.exitCode = code ?? (signal === 'SIGINT' ? 130 : signal === 'SIGTERM' ? 143 : 1);
|
|
41
|
+
resolveDone(signal);
|
|
42
|
+
});
|
|
40
43
|
});
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
export async function main(args, packageJson) {
|
|
47
|
+
const options = parseUpdate(args);
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
if (options) {
|
|
50
|
+
const stop = signal => {
|
|
51
|
+
const error = new Error(`Update interrupted by ${signal}`);
|
|
52
|
+
error.signal = signal; controller.abort(error);
|
|
53
|
+
process.exitCode = signal === 'SIGINT' ? 130 : 143;
|
|
54
|
+
};
|
|
55
|
+
const interrupt = () => stop('SIGINT'), terminate = () => stop('SIGTERM');
|
|
56
|
+
process.on('SIGINT', interrupt); process.on('SIGTERM', terminate);
|
|
57
|
+
const timer = setTimeout(() => controller.abort(new Error('Update timed out')), options.timeout * 1000);
|
|
58
|
+
try {
|
|
59
|
+
const result = await update(packageJson, options, { ctx: context({ signal: controller.signal }), platformName: platforms[`${process.platform}-${process.arch}`], resolveBinary });
|
|
60
|
+
controller.signal.throwIfAborted();
|
|
61
|
+
if (options.json) console.log(JSON.stringify(result));
|
|
62
|
+
else if (result.message) console.log(result.message);
|
|
63
|
+
else if (result.updated) console.log(`VMG updated: ${result.currentVersion} → ${result.installedVersion}`);
|
|
64
|
+
else console.log(result.updateAvailable ? `VMG update available: ${result.currentVersion} → ${result.latestVersion}. Run vmg update.` : `VMG ${result.currentVersion} is up to date (latest: ${result.latestVersion}).`);
|
|
65
|
+
if (!result.ok) process.exitCode = 1;
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timer); process.off('SIGINT', interrupt); process.off('SIGTERM', terminate);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const binary = resolveBinary(packageJson);
|
|
72
|
+
const finished = launch(binary, args);
|
|
73
|
+
const timer = setTimeout(() => controller.abort(new Error('Update check timed out')), 3000);
|
|
74
|
+
const notification = shouldNotify(args) ? notify(packageJson, { ctx: context({ signal: controller.signal }) }) : Promise.resolve();
|
|
75
|
+
const signal = await finished;
|
|
76
|
+
controller.abort(new Error('VMG command finished')); clearTimeout(timer);
|
|
77
|
+
await notification;
|
|
78
|
+
if (signal) process.kill(process.pid, signal);
|
|
79
|
+
}
|
|
80
|
+
|
|
43
81
|
if (process.argv[1] && pathToFileURL(realpathSync(resolve(process.argv[1]))).href === import.meta.url) {
|
|
44
|
-
try {
|
|
45
|
-
catch (error) {
|
|
82
|
+
try { await main(process.argv.slice(2), fileURLToPath(new URL('../package.json', import.meta.url))); }
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (process.argv.slice(2).includes('--json')) console.error(JSON.stringify({ error: error.message }));
|
|
85
|
+
else console.error(`vmg: ${error.message}`);
|
|
86
|
+
process.exitCode ||= 1;
|
|
87
|
+
}
|
|
46
88
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
2
|
+
"version": "0.0.5",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"vmg": "bin/vmg.mjs"
|
|
16
16
|
},
|
|
17
17
|
"optionalDependencies": {
|
|
18
|
-
"@base_bit/vmg-cli-darwin-arm64": "0.0.
|
|
18
|
+
"@base_bit/vmg-cli-darwin-arm64": "0.0.5"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"bin/vmg.mjs",
|
|
22
|
+
"bin/npm-update.mjs",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE",
|
|
24
25
|
"NOTICE",
|