@ghl-ai/aw 0.1.30-beta.1 → 0.1.30-beta.3
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/commands/init.mjs +12 -48
- package/commands/nuke.mjs +26 -17
- package/mcp.mjs +8 -12
- package/package.json +1 -1
- package/update.mjs +88 -17
package/commands/init.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// commands/init.mjs — Clean init: clone registry, link IDEs, git hooks
|
|
1
|
+
// commands/init.mjs — Clean init: clone registry, link IDEs, global git hooks.
|
|
2
2
|
//
|
|
3
3
|
// No shell profile modifications. No daemons. No background processes.
|
|
4
|
-
// Uses git
|
|
4
|
+
// Uses core.hooksPath (git-lfs pattern) for system-wide hook interception.
|
|
5
5
|
// Uses IDE tasks for auto-pull on workspace open.
|
|
6
6
|
|
|
7
|
-
import { mkdirSync, existsSync, writeFileSync, symlinkSync
|
|
7
|
+
import { mkdirSync, existsSync, writeFileSync, symlinkSync } from 'node:fs';
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
9
|
import { join, dirname } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
@@ -18,6 +18,7 @@ import { linkWorkspace } from '../link.mjs';
|
|
|
18
18
|
import { generateCommands, copyInstructions, initAwDocs } from '../integrate.mjs';
|
|
19
19
|
import { setupMcp } from '../mcp.mjs';
|
|
20
20
|
import { autoUpdate, promptUpdate } from '../update.mjs';
|
|
21
|
+
import { installGlobalHooks } from '../hooks.mjs';
|
|
21
22
|
|
|
22
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
@@ -25,46 +26,6 @@ const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), '
|
|
|
25
26
|
const HOME = homedir();
|
|
26
27
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
27
28
|
const MANIFEST_PATH = join(GLOBAL_AW_DIR, '.aw-manifest.json');
|
|
28
|
-
const GIT_TEMPLATE_DIR = join(HOME, '.git-templates', 'aw');
|
|
29
|
-
|
|
30
|
-
// ── Git template hook for omnipresence ──────────────────────────────────
|
|
31
|
-
// Git's init.templateDir copies hooks into every new clone/init.
|
|
32
|
-
// post-checkout fires on clone and branch switch — perfect for auto-linking.
|
|
33
|
-
|
|
34
|
-
const POST_CHECKOUT_HOOK = `#!/bin/sh
|
|
35
|
-
# aw: auto-link registry on clone/checkout (installed by aw init)
|
|
36
|
-
AW_REGISTRY="$HOME/.aw_registry"
|
|
37
|
-
if [ -d "$AW_REGISTRY" ] && [ ! -e ".aw_registry" ] && [ -d ".git" ]; then
|
|
38
|
-
ln -s "$AW_REGISTRY" ".aw_registry" 2>/dev/null
|
|
39
|
-
fi
|
|
40
|
-
`;
|
|
41
|
-
|
|
42
|
-
function installGitTemplate() {
|
|
43
|
-
const hooksDir = join(GIT_TEMPLATE_DIR, 'hooks');
|
|
44
|
-
mkdirSync(hooksDir, { recursive: true });
|
|
45
|
-
|
|
46
|
-
const hookPath = join(hooksDir, 'post-checkout');
|
|
47
|
-
writeFileSync(hookPath, POST_CHECKOUT_HOOK);
|
|
48
|
-
chmodSync(hookPath, '755');
|
|
49
|
-
|
|
50
|
-
// Set git global template dir (merges with existing hooks)
|
|
51
|
-
try {
|
|
52
|
-
const current = execSync('git config --global init.templateDir', {
|
|
53
|
-
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
54
|
-
}).trim();
|
|
55
|
-
|
|
56
|
-
if (current && current !== GIT_TEMPLATE_DIR) {
|
|
57
|
-
// Another template dir is set — don't override, just inform
|
|
58
|
-
fmt.logWarn(`Git template dir already set to ${current}`);
|
|
59
|
-
fmt.logStep(chalk.dim(' Skipping git template — run: git config --global init.templateDir ~/.git-templates/aw'));
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
} catch { /* not set yet — good */ }
|
|
63
|
-
|
|
64
|
-
execSync(`git config --global init.templateDir "${GIT_TEMPLATE_DIR}"`, { stdio: 'pipe' });
|
|
65
|
-
fmt.logStep('Git template hook installed (auto-links on clone)');
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
29
|
|
|
69
30
|
// ── IDE tasks for auto-pull ─────────────────────────────────────────────
|
|
70
31
|
|
|
@@ -237,12 +198,14 @@ export async function initCommand(args) {
|
|
|
237
198
|
await Promise.all(pullJobs);
|
|
238
199
|
}
|
|
239
200
|
|
|
240
|
-
// Re-link IDE dirs (idempotent)
|
|
201
|
+
// Re-link IDE dirs + hooks (idempotent)
|
|
241
202
|
linkWorkspace(HOME);
|
|
242
203
|
generateCommands(HOME);
|
|
243
204
|
copyInstructions(HOME, null, freshCfg?.namespace || team) || [];
|
|
244
205
|
initAwDocs(HOME);
|
|
245
206
|
setupMcp(HOME, freshCfg?.namespace || team) || [];
|
|
207
|
+
if (cwd !== HOME) setupMcp(cwd, freshCfg?.namespace || team);
|
|
208
|
+
installGlobalHooks();
|
|
246
209
|
|
|
247
210
|
// Link current project if needed
|
|
248
211
|
if (cwd !== HOME && !existsSync(join(cwd, '.aw_registry'))) {
|
|
@@ -321,7 +284,8 @@ export async function initCommand(args) {
|
|
|
321
284
|
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
322
285
|
initAwDocs(HOME);
|
|
323
286
|
const mcpFiles = setupMcp(HOME, team) || [];
|
|
324
|
-
|
|
287
|
+
if (cwd !== HOME) setupMcp(cwd, team);
|
|
288
|
+
const hooksInstalled = installGlobalHooks();
|
|
325
289
|
installIdeTasks();
|
|
326
290
|
|
|
327
291
|
// Step 4: Symlink in current directory if it's a git repo
|
|
@@ -341,7 +305,7 @@ export async function initCommand(args) {
|
|
|
341
305
|
...instructionFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
342
306
|
...mcpFiles.map(p => p.startsWith(HOME) ? p.slice(HOME.length + 1) : p),
|
|
343
307
|
],
|
|
344
|
-
|
|
308
|
+
globalHooksDir: hooksInstalled ? join(HOME, '.aw', 'hooks') : null,
|
|
345
309
|
};
|
|
346
310
|
saveManifest(manifest);
|
|
347
311
|
|
|
@@ -354,13 +318,13 @@ export async function initCommand(args) {
|
|
|
354
318
|
'',
|
|
355
319
|
` ${chalk.green('✓')} Source of truth: ~/.aw_registry/`,
|
|
356
320
|
` ${chalk.green('✓')} IDE integration: ~/.claude/, ~/.cursor/, ~/.codex/`,
|
|
357
|
-
|
|
321
|
+
hooksInstalled ? ` ${chalk.green('✓')} Git hooks: auto-sync on pull/clone (core.hooksPath)` : null,
|
|
358
322
|
` ${chalk.green('✓')} IDE task: auto-sync on workspace open`,
|
|
359
323
|
cwd !== HOME && existsSync(join(cwd, '.aw_registry')) ? ` ${chalk.green('✓')} Linked in current project` : null,
|
|
360
324
|
'',
|
|
361
325
|
` ${chalk.dim('Existing repos:')} ${chalk.bold('cd <project> && aw link')}`,
|
|
362
326
|
` ${chalk.dim('New clones:')} auto-linked via git hook`,
|
|
363
|
-
` ${chalk.dim('Update:')} ${chalk.bold('aw init')} ${chalk.dim('(or auto on IDE open)')}`,
|
|
327
|
+
` ${chalk.dim('Update:')} ${chalk.bold('aw init')} ${chalk.dim('(or auto on pull/IDE open)')}`,
|
|
364
328
|
` ${chalk.dim('Uninstall:')} ${chalk.bold('aw nuke')}`,
|
|
365
329
|
].filter(Boolean).join('\n'));
|
|
366
330
|
}
|
package/commands/nuke.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { homedir } from 'node:os';
|
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
9
|
import * as fmt from '../fmt.mjs';
|
|
10
10
|
import { chalk } from '../fmt.mjs';
|
|
11
|
+
import { removeGlobalHooks } from '../hooks.mjs';
|
|
11
12
|
|
|
12
13
|
const HOME = homedir();
|
|
13
14
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
@@ -169,21 +170,24 @@ function removeProjectSymlinks() {
|
|
|
169
170
|
if (removed > 0) fmt.logStep(`Removed ${removed} project .aw_registry symlink${removed > 1 ? 's' : ''}`);
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
// 4. Remove git template
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
if (!existsSync(gitTemplateDir)) return;
|
|
173
|
+
// 4. Remove git hooks (global core.hooksPath + legacy template)
|
|
174
|
+
function removeGitHooks(manifest) {
|
|
175
|
+
removeGlobalHooks();
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
177
|
+
// Backward compat: also clean up old git template dir from previous versions
|
|
178
|
+
const legacyTemplateDir = manifest?.gitTemplate || join(HOME, '.git-templates', 'aw');
|
|
179
|
+
if (existsSync(legacyTemplateDir)) {
|
|
180
|
+
rmSync(legacyTemplateDir, { recursive: true, force: true });
|
|
181
|
+
try {
|
|
182
|
+
const current = execSync('git config --global init.templateDir', {
|
|
183
|
+
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
184
|
+
}).trim();
|
|
185
|
+
if (current === legacyTemplateDir) {
|
|
186
|
+
execSync('git config --global --unset init.templateDir', { stdio: 'pipe' });
|
|
187
|
+
}
|
|
188
|
+
} catch { /* not set */ }
|
|
189
|
+
fmt.logStep('Removed legacy git template hook');
|
|
190
|
+
}
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
// 5. Remove IDE auto-init tasks
|
|
@@ -242,12 +246,17 @@ export function nukeCommand(args) {
|
|
|
242
246
|
// 3. Remove .aw_registry symlinks from ALL project directories
|
|
243
247
|
removeProjectSymlinks();
|
|
244
248
|
|
|
245
|
-
// 4. Remove git template
|
|
246
|
-
|
|
249
|
+
// 4. Remove git hooks (core.hooksPath + legacy template)
|
|
250
|
+
removeGitHooks(manifest);
|
|
247
251
|
|
|
248
252
|
// 5. Remove IDE auto-init tasks
|
|
249
253
|
removeIdeTasks();
|
|
250
254
|
|
|
255
|
+
// 5b. Remove upgrade lock/log (inside .aw_registry, must happen before dir removal)
|
|
256
|
+
for (const p of [join(GLOBAL_AW_DIR, '.aw-upgrade.lock'), join(GLOBAL_AW_DIR, '.aw-upgrade.log')]) {
|
|
257
|
+
try { if (existsSync(p)) rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
// 6. Remove ~/.aw_docs/
|
|
252
261
|
const awDocs = join(HOME, '.aw_docs');
|
|
253
262
|
if (existsSync(awDocs)) {
|
|
@@ -287,7 +296,7 @@ export function nukeCommand(args) {
|
|
|
287
296
|
` ${chalk.green('✓')} Generated files cleaned`,
|
|
288
297
|
` ${chalk.green('✓')} IDE symlinks cleaned`,
|
|
289
298
|
` ${chalk.green('✓')} Project symlinks cleaned`,
|
|
290
|
-
` ${chalk.green('✓')} Git
|
|
299
|
+
` ${chalk.green('✓')} Git hooks removed`,
|
|
291
300
|
` ${chalk.green('✓')} IDE auto-sync tasks removed`,
|
|
292
301
|
` ${chalk.green('✓')} Source of truth deleted`,
|
|
293
302
|
'',
|
package/mcp.mjs
CHANGED
|
@@ -84,10 +84,13 @@ function resolveGitHubToken() {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// 3. No
|
|
88
|
-
fmt.logWarn('No GitHub token found — MCP
|
|
89
|
-
fmt.logWarn('
|
|
90
|
-
fmt.logWarn('
|
|
87
|
+
// 3. No token found anywhere
|
|
88
|
+
fmt.logWarn('⚠ No GitHub token found — MCP authentication will not work!');
|
|
89
|
+
fmt.logWarn('');
|
|
90
|
+
fmt.logWarn(' Fix (recommended): brew install gh && gh auth login');
|
|
91
|
+
fmt.logWarn(' Fix (manual): export GITHUB_TOKEN=ghp_xxx');
|
|
92
|
+
fmt.logWarn('');
|
|
93
|
+
fmt.logWarn(' Then re-run: aw init');
|
|
91
94
|
return null;
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -135,13 +138,6 @@ export function setupMcp(cwd, namespace) {
|
|
|
135
138
|
headers: { Authorization: `Bearer ${ghToken || '${GITHUB_TOKEN}'}` },
|
|
136
139
|
};
|
|
137
140
|
|
|
138
|
-
// Server config with env var interpolation for committed project files
|
|
139
|
-
const ghlAiServerProject = {
|
|
140
|
-
type: 'http',
|
|
141
|
-
url: mcpUrl,
|
|
142
|
-
headers: { Authorization: 'Bearer ${GITHUB_TOKEN}' },
|
|
143
|
-
};
|
|
144
|
-
|
|
145
141
|
const gitJenkinsServer = paths.gitJenkinsPath
|
|
146
142
|
? { command: 'node', args: [paths.gitJenkinsPath] }
|
|
147
143
|
: null;
|
|
@@ -157,7 +153,7 @@ export function setupMcp(cwd, namespace) {
|
|
|
157
153
|
|
|
158
154
|
// ── Claude Code: .mcp.json (project root — committed, uses env var) ──
|
|
159
155
|
const projectMcpPath = join(cwd, '.mcp.json');
|
|
160
|
-
if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai',
|
|
156
|
+
if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai', ghlAiServerLocal)) {
|
|
161
157
|
updatedFiles.push(projectMcpPath);
|
|
162
158
|
}
|
|
163
159
|
|
package/package.json
CHANGED
package/update.mjs
CHANGED
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
//
|
|
3
3
|
// checkForUpdate() → fires npm view in background, returns { current, latest, hasUpdate }
|
|
4
4
|
// notifyUpdate(r) → prints banner if r says update available
|
|
5
|
-
// autoUpdate(r) →
|
|
5
|
+
// autoUpdate(r) → synchronous install with atomic lock, verification, logging
|
|
6
6
|
// promptUpdate(r) → asks user interactively (normal init)
|
|
7
7
|
|
|
8
|
-
import { readFileSync } from 'node:fs';
|
|
9
|
-
import { exec as execCb, execSync
|
|
8
|
+
import { readFileSync, mkdirSync, writeFileSync, statSync, rmSync, appendFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { exec as execCb, execSync } from 'node:child_process';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
11
13
|
import * as fmt from './fmt.mjs';
|
|
12
14
|
import { chalk } from './fmt.mjs';
|
|
13
15
|
|
|
14
16
|
const exec = promisify(execCb);
|
|
15
17
|
|
|
16
18
|
const PKG_NAME = '@ghl-ai/aw';
|
|
19
|
+
const LOCK_DIR = join(homedir(), '.aw_registry', '.aw-upgrade.lock');
|
|
20
|
+
const LOG_PATH = join(homedir(), '.aw_registry', '.aw-upgrade.log');
|
|
21
|
+
const LOCK_MAX_AGE_MS = 5 * 60 * 1000;
|
|
22
|
+
const LOG_MAX_ENTRIES = 50;
|
|
17
23
|
|
|
18
24
|
function getLocalVersion() {
|
|
19
25
|
try {
|
|
@@ -45,13 +51,66 @@ function compareVersions(a, b) {
|
|
|
45
51
|
return 0;
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
// ── Atomic lock via mkdir ──────────────────────────────────────────────
|
|
55
|
+
// mkdir is atomic on all POSIX filesystems. Stale detection uses
|
|
56
|
+
// directory mtime (OS-maintained) rather than parsing PIDs.
|
|
57
|
+
|
|
58
|
+
function acquireLock() {
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(LOCK_DIR);
|
|
61
|
+
writeFileSync(join(LOCK_DIR, 'pid'), String(process.pid));
|
|
62
|
+
return true;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
if (e.code !== 'EEXIST') return false;
|
|
65
|
+
try {
|
|
66
|
+
const stat = statSync(LOCK_DIR);
|
|
67
|
+
if (Date.now() - stat.mtimeMs > LOCK_MAX_AGE_MS) {
|
|
68
|
+
rmSync(LOCK_DIR, { recursive: true, force: true });
|
|
69
|
+
return acquireLock();
|
|
70
|
+
}
|
|
71
|
+
} catch { /* stat failed — lock dir vanished, retry */ return acquireLock(); }
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function releaseLock() {
|
|
77
|
+
try { rmSync(LOCK_DIR, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Upgrade log ────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
function appendLog(entry) {
|
|
83
|
+
try {
|
|
84
|
+
const line = JSON.stringify(entry) + '\n';
|
|
85
|
+
appendFileSync(LOG_PATH, line);
|
|
86
|
+
|
|
87
|
+
const content = readFileSync(LOG_PATH, 'utf8');
|
|
88
|
+
const lines = content.trim().split('\n');
|
|
89
|
+
if (lines.length > LOG_MAX_ENTRIES) {
|
|
90
|
+
writeFileSync(LOG_PATH, lines.slice(-LOG_MAX_ENTRIES).join('\n') + '\n');
|
|
91
|
+
}
|
|
92
|
+
} catch { /* logging is best-effort */ }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Installed version verification ─────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function getInstalledVersion() {
|
|
98
|
+
try {
|
|
99
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10_000 }).trim();
|
|
100
|
+
const pkgPath = join(prefix, 'lib', 'node_modules', PKG_NAME, 'package.json');
|
|
101
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
|
|
102
|
+
} catch {
|
|
103
|
+
return getLocalVersion();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
48
107
|
/**
|
|
49
|
-
* Fetch the latest version from npm (non-blocking,
|
|
108
|
+
* Fetch the latest version from npm (non-blocking, 15s timeout).
|
|
50
109
|
* Call at the start of any command, await at the end.
|
|
51
110
|
*/
|
|
52
111
|
export async function checkForUpdate() {
|
|
53
112
|
try {
|
|
54
|
-
const { stdout } = await exec(`npm view ${PKG_NAME} version`, { timeout:
|
|
113
|
+
const { stdout } = await exec(`npm view ${PKG_NAME} version`, { timeout: 15_000 });
|
|
55
114
|
const latest = stdout.trim();
|
|
56
115
|
const current = getLocalVersion();
|
|
57
116
|
return { current, latest, hasUpdate: compareVersions(latest, current) > 0 };
|
|
@@ -76,19 +135,29 @@ export function notifyUpdate(result) {
|
|
|
76
135
|
}
|
|
77
136
|
|
|
78
137
|
/**
|
|
79
|
-
*
|
|
138
|
+
* Synchronous auto-update with atomic lock, verification, and logging.
|
|
80
139
|
* Used by `aw init --silent`. Skipped for prerelease versions.
|
|
140
|
+
* @returns {{ status: string, reason?: string, from?: string, to?: string, error?: string }}
|
|
81
141
|
*/
|
|
82
142
|
export function autoUpdate(result) {
|
|
83
|
-
if (!result?.hasUpdate) return;
|
|
84
|
-
if (isPrerelease(result.current)) return;
|
|
143
|
+
if (!result?.hasUpdate) return { status: 'skipped', reason: 'up-to-date' };
|
|
144
|
+
if (isPrerelease(result.current)) return { status: 'skipped', reason: 'prerelease' };
|
|
145
|
+
if (!acquireLock()) return { status: 'skipped', reason: 'locked' };
|
|
146
|
+
|
|
85
147
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
148
|
+
execSync(`npm i -g ${PKG_NAME}@latest`, { stdio: 'pipe', timeout: 60_000 });
|
|
149
|
+
const installed = getInstalledVersion();
|
|
150
|
+
const ok = compareVersions(installed, result.current) > 0;
|
|
151
|
+
const entry = { ts: new Date().toISOString(), from: result.current, to: installed, ok };
|
|
152
|
+
appendLog(entry);
|
|
153
|
+
return { status: ok ? 'upgraded' : 'failed', ...entry };
|
|
154
|
+
} catch (e) {
|
|
155
|
+
const entry = { ts: new Date().toISOString(), from: result.current, error: e.message, ok: false };
|
|
156
|
+
appendLog(entry);
|
|
157
|
+
return { status: 'failed', error: e.message };
|
|
158
|
+
} finally {
|
|
159
|
+
releaseLock();
|
|
160
|
+
}
|
|
92
161
|
}
|
|
93
162
|
|
|
94
163
|
/**
|
|
@@ -115,9 +184,11 @@ export async function promptUpdate(result) {
|
|
|
115
184
|
const s = fmt.spinner();
|
|
116
185
|
s.start(`Updating to ${result.latest}...`);
|
|
117
186
|
try {
|
|
118
|
-
execSync(`npm i -g ${PKG_NAME}@latest`, { stdio: 'pipe', timeout:
|
|
119
|
-
|
|
120
|
-
|
|
187
|
+
execSync(`npm i -g ${PKG_NAME}@latest`, { stdio: 'pipe', timeout: 60_000 });
|
|
188
|
+
const installed = getInstalledVersion();
|
|
189
|
+
const ok = compareVersions(installed, result.current) > 0;
|
|
190
|
+
s.stop(ok ? `Updated to ${chalk.green(installed)}` : chalk.red('Update may have failed — verify with: aw --version'));
|
|
191
|
+
return ok;
|
|
121
192
|
} catch {
|
|
122
193
|
s.stop(chalk.red('Update failed'));
|
|
123
194
|
fmt.logWarn(`Manual update: ${chalk.bold(`npm i -g ${PKG_NAME}@latest`)}`);
|