@soleri/cli 9.13.1 → 9.14.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/dist/commands/create.js +3 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/install.d.ts +28 -0
- package/dist/commands/install.js +174 -7
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/pack.js +6 -2
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +71 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +6 -3
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/install-verify.test.ts +109 -0
- package/src/__tests__/install.test.ts +75 -1
- package/src/__tests__/update.test.ts +102 -0
- package/src/commands/create.ts +4 -0
- package/src/commands/install.ts +209 -8
- package/src/commands/pack.ts +13 -2
- package/src/commands/update.ts +82 -0
- package/src/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +6 -3
- package/src/main.ts +2 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
function getCurrentVersion() {
|
|
6
|
+
try {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
8
|
+
return require('../package.json').version;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return 'unknown';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function getLatestVersion() {
|
|
15
|
+
try {
|
|
16
|
+
return execSync('npm view @soleri/cli version', { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
17
|
+
.toString()
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error('Could not reach npm registry. Check your network connection and try again.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function installLatest() {
|
|
25
|
+
execSync('npm install -g soleri@latest', { stdio: 'inherit' });
|
|
26
|
+
}
|
|
27
|
+
export function registerUpdate(program) {
|
|
28
|
+
program
|
|
29
|
+
.command('update')
|
|
30
|
+
.description('Update Soleri CLI to the latest version')
|
|
31
|
+
.action(async () => {
|
|
32
|
+
p.intro('Soleri Update');
|
|
33
|
+
const current = getCurrentVersion();
|
|
34
|
+
p.log.info(`Current version: ${current}`);
|
|
35
|
+
const spinner = p.spinner();
|
|
36
|
+
spinner.start('Checking latest version…');
|
|
37
|
+
let latest;
|
|
38
|
+
try {
|
|
39
|
+
latest = getLatestVersion();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
spinner.stop('Failed');
|
|
43
|
+
p.log.error(err instanceof Error ? err.message : String(err));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
spinner.stop(`Latest version: ${latest}`);
|
|
48
|
+
if (current === latest) {
|
|
49
|
+
p.log.success(`Already on latest (${current})`);
|
|
50
|
+
p.outro('Nothing to do.');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
p.log.info(`Updating ${current} → ${latest}…`);
|
|
54
|
+
try {
|
|
55
|
+
installLatest();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
p.log.error('Update failed. Try manually: npm install -g soleri@latest');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const installed = getLatestVersion();
|
|
62
|
+
if (installed !== latest) {
|
|
63
|
+
p.log.warn(`Installed version (${installed}) does not match expected (${latest}). Verify manually.`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
p.log.success(`Updated ${current} → ${installed}`);
|
|
67
|
+
}
|
|
68
|
+
p.outro('Restart your session to use the new version.');
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
# Soleri Hook Pack: clean-commits
|
|
3
3
|
# Version: 1.0.0
|
|
4
4
|
# Rule: no-ai-attribution
|
|
5
|
+
# NOTE: This hook is intentionally disabled. The host agent (e.g. Claude Code)
|
|
6
|
+
# controls commit attribution via its own system prompt. Blocking attribution
|
|
7
|
+
# patterns here causes a deadlock when the host mandates them.
|
|
5
8
|
name: no-ai-attribution
|
|
6
|
-
enabled:
|
|
9
|
+
enabled: false
|
|
7
10
|
event: bash
|
|
8
11
|
action: block
|
|
9
12
|
conditions:
|
|
@@ -12,7 +15,7 @@ conditions:
|
|
|
12
15
|
pattern: git\s+commit.*(-m|--message)
|
|
13
16
|
- field: command
|
|
14
17
|
operator: regex_match
|
|
15
|
-
pattern: (
|
|
18
|
+
pattern: (placeholder-disabled-pattern)
|
|
16
19
|
---
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
This hook is intentionally disabled. Commit style is enforced by the engine rules, not by blocking patterns.
|
package/dist/main.js
CHANGED
|
@@ -28,6 +28,7 @@ import { registerStaging } from './commands/staging.js';
|
|
|
28
28
|
import { registerVault } from './commands/vault.js';
|
|
29
29
|
import { registerYolo } from './commands/yolo.js';
|
|
30
30
|
import { registerDream } from './commands/dream.js';
|
|
31
|
+
import { registerUpdate } from './commands/update.js';
|
|
31
32
|
const require = createRequire(import.meta.url);
|
|
32
33
|
const { version } = require('../package.json');
|
|
33
34
|
const RESET = '\x1b[0m';
|
|
@@ -86,5 +87,6 @@ registerStaging(program);
|
|
|
86
87
|
registerVault(program);
|
|
87
88
|
registerYolo(program);
|
|
88
89
|
registerDream(program);
|
|
90
|
+
registerUpdate(program);
|
|
89
91
|
program.parse();
|
|
90
92
|
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC7D,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;IACf,OAAO,CAAC,KAAK,CACX,yDAAyD,OAAO,CAAC,QAAQ,CAAC,IAAI,sCAAsC,CACrH,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
// Mock @clack/prompts to suppress console output during tests
|
|
7
|
+
vi.mock('@clack/prompts', () => ({
|
|
8
|
+
log: {
|
|
9
|
+
success: vi.fn(),
|
|
10
|
+
error: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
info: vi.fn(),
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('verifyInstall', () => {
|
|
17
|
+
let tempDir: string;
|
|
18
|
+
let agentDir: string;
|
|
19
|
+
let originalHome: string;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tempDir = join(tmpdir(), `cli-verify-test-${Date.now()}`);
|
|
23
|
+
agentDir = join(tempDir, 'my-agent');
|
|
24
|
+
mkdirSync(agentDir, { recursive: true });
|
|
25
|
+
originalHome = process.env.HOME ?? '';
|
|
26
|
+
process.env.HOME = tempDir;
|
|
27
|
+
process.env.USERPROFILE = tempDir;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
process.env.HOME = originalHome;
|
|
32
|
+
if (originalHome) process.env.USERPROFILE = originalHome;
|
|
33
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return failing checks when nothing is installed', async () => {
|
|
37
|
+
const { verifyInstall } = await import('../commands/install.js');
|
|
38
|
+
const checks = verifyInstall('test-agent', agentDir, 'claude');
|
|
39
|
+
|
|
40
|
+
// Agent entry in claude config should fail (no config file)
|
|
41
|
+
const configCheck = checks.find((c) => c.label.includes('claude config'));
|
|
42
|
+
expect(configCheck).toBeDefined();
|
|
43
|
+
expect(configCheck!.passed).toBe(false);
|
|
44
|
+
|
|
45
|
+
// agent.yaml should fail (not created)
|
|
46
|
+
const yamlCheck = checks.find((c) => c.label.includes('agent.yaml'));
|
|
47
|
+
expect(yamlCheck).toBeDefined();
|
|
48
|
+
expect(yamlCheck!.passed).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should detect agent.yaml when it exists', async () => {
|
|
52
|
+
writeFileSync(join(agentDir, 'agent.yaml'), 'id: test-agent\n');
|
|
53
|
+
|
|
54
|
+
const { verifyInstall } = await import('../commands/install.js');
|
|
55
|
+
const checks = verifyInstall('test-agent', agentDir, 'claude');
|
|
56
|
+
|
|
57
|
+
const yamlCheck = checks.find((c) => c.label.includes('agent.yaml'));
|
|
58
|
+
expect(yamlCheck).toBeDefined();
|
|
59
|
+
expect(yamlCheck!.passed).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should detect claude config entry after installClaude', async () => {
|
|
63
|
+
writeFileSync(join(agentDir, 'agent.yaml'), 'id: test-agent\n');
|
|
64
|
+
|
|
65
|
+
const { installClaude, verifyInstall } = await import('../commands/install.js');
|
|
66
|
+
installClaude('test-agent', agentDir, true);
|
|
67
|
+
|
|
68
|
+
const checks = verifyInstall('test-agent', agentDir, 'claude');
|
|
69
|
+
const configCheck = checks.find((c) => c.label.includes('claude config'));
|
|
70
|
+
expect(configCheck).toBeDefined();
|
|
71
|
+
expect(configCheck!.passed).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should include engine binary check', async () => {
|
|
75
|
+
const { verifyInstall } = await import('../commands/install.js');
|
|
76
|
+
const checks = verifyInstall('test-agent', agentDir, 'claude');
|
|
77
|
+
|
|
78
|
+
const engineCheck = checks.find((c) => c.label.includes('Engine'));
|
|
79
|
+
expect(engineCheck).toBeDefined();
|
|
80
|
+
// Engine always resolves (local or npx fallback)
|
|
81
|
+
expect(engineCheck!.passed).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should check all targets when target is "all"', async () => {
|
|
85
|
+
const { verifyInstall } = await import('../commands/install.js');
|
|
86
|
+
const checks = verifyInstall('test-agent', agentDir, 'all');
|
|
87
|
+
|
|
88
|
+
const configChecks = checks.filter((c) => c.label.includes('config'));
|
|
89
|
+
// Should have entries for claude, codex, and opencode
|
|
90
|
+
expect(configChecks.length).toBe(3);
|
|
91
|
+
expect(configChecks.some((c) => c.label.includes('claude'))).toBe(true);
|
|
92
|
+
expect(configChecks.some((c) => c.label.includes('codex'))).toBe(true);
|
|
93
|
+
expect(configChecks.some((c) => c.label.includes('opencode'))).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return VerifyCheck[] with correct shape', async () => {
|
|
97
|
+
const { verifyInstall } = await import('../commands/install.js');
|
|
98
|
+
const checks = verifyInstall('test-agent', agentDir, 'claude');
|
|
99
|
+
|
|
100
|
+
expect(Array.isArray(checks)).toBe(true);
|
|
101
|
+
expect(checks.length).toBeGreaterThan(0);
|
|
102
|
+
for (const check of checks) {
|
|
103
|
+
expect(check).toHaveProperty('label');
|
|
104
|
+
expect(check).toHaveProperty('passed');
|
|
105
|
+
expect(typeof check.label).toBe('string');
|
|
106
|
+
expect(typeof check.passed).toBe('boolean');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import { mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { toPosix } from '../commands/install.js';
|
|
5
|
+
import { toPosix, getNextStepMessage, resolveEngineBin } from '../commands/install.js';
|
|
6
6
|
|
|
7
7
|
// Mock @clack/prompts to suppress console output during tests
|
|
8
8
|
vi.mock('@clack/prompts', () => ({
|
|
@@ -32,6 +32,80 @@ describe('toPosix', () => {
|
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
describe('getNextStepMessage', () => {
|
|
36
|
+
it('returns Claude-specific instruction', () => {
|
|
37
|
+
const msg = getNextStepMessage('claude');
|
|
38
|
+
expect(msg).toContain('Claude Code');
|
|
39
|
+
expect(msg).toContain('/mcp');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns Codex-specific instruction', () => {
|
|
43
|
+
const msg = getNextStepMessage('codex');
|
|
44
|
+
expect(msg).toContain('Codex');
|
|
45
|
+
expect(msg).toContain('new Codex conversation');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns OpenCode-specific instruction', () => {
|
|
49
|
+
const msg = getNextStepMessage('opencode');
|
|
50
|
+
expect(msg).toContain('OpenCode');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns instructions for all targets when target is "all"', () => {
|
|
54
|
+
const msg = getNextStepMessage('all');
|
|
55
|
+
expect(msg).toContain('Claude Code');
|
|
56
|
+
expect(msg).toContain('Codex');
|
|
57
|
+
expect(msg).toContain('OpenCode');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns instructions for all targets when target is "both"', () => {
|
|
61
|
+
const msg = getNextStepMessage('both');
|
|
62
|
+
expect(msg).toContain('Claude Code');
|
|
63
|
+
expect(msg).toContain('Codex');
|
|
64
|
+
expect(msg).toContain('OpenCode');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('falls back to Claude instructions for unknown target', () => {
|
|
68
|
+
const msg = getNextStepMessage('unknown-target');
|
|
69
|
+
expect(msg).toContain('Claude Code');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('resolveEngineBin', () => {
|
|
74
|
+
it('returns an object with command and bin fields', () => {
|
|
75
|
+
const result = resolveEngineBin();
|
|
76
|
+
expect(result).toHaveProperty('command');
|
|
77
|
+
expect(result).toHaveProperty('bin');
|
|
78
|
+
expect(['node', 'npx']).toContain(result.command);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('npx fallback warning', () => {
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
vi.resetModules();
|
|
85
|
+
vi.doUnmock('node:module');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns npx fallback when core resolution fails', async () => {
|
|
89
|
+
vi.doMock('node:module', async () => {
|
|
90
|
+
const actual = await vi.importActual<typeof import('node:module')>('node:module');
|
|
91
|
+
return {
|
|
92
|
+
...actual,
|
|
93
|
+
createRequire: () =>
|
|
94
|
+
({
|
|
95
|
+
resolve: () => {
|
|
96
|
+
throw new Error('MODULE_NOT_FOUND');
|
|
97
|
+
},
|
|
98
|
+
}) as ReturnType<typeof import('node:module').createRequire>,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const { resolveEngineBin: resolveEngineBinFresh } = await import('../commands/install.js');
|
|
103
|
+
const result = resolveEngineBinFresh();
|
|
104
|
+
expect(result.command).toBe('npx');
|
|
105
|
+
expect(typeof result.bin).toBe('string');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
35
109
|
describe('installClaude path normalization', () => {
|
|
36
110
|
let tempDir: string;
|
|
37
111
|
let originalHome: string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Mock @clack/prompts
|
|
4
|
+
vi.mock('@clack/prompts', () => ({
|
|
5
|
+
intro: vi.fn(),
|
|
6
|
+
outro: vi.fn(),
|
|
7
|
+
spinner: vi.fn(() => ({ start: vi.fn(), stop: vi.fn() })),
|
|
8
|
+
log: {
|
|
9
|
+
success: vi.fn(),
|
|
10
|
+
error: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
info: vi.fn(),
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Mock child_process — hoisted, so update.ts always gets the mock
|
|
17
|
+
vi.mock('node:child_process', () => ({
|
|
18
|
+
execSync: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Import after mocks are hoisted
|
|
22
|
+
import { execSync } from 'node:child_process';
|
|
23
|
+
import * as p from '@clack/prompts';
|
|
24
|
+
import { registerUpdate } from '../commands/update.js';
|
|
25
|
+
|
|
26
|
+
/** Helper: capture the action handler from registerUpdate */
|
|
27
|
+
function captureAction(): () => Promise<void> {
|
|
28
|
+
let action: (() => Promise<void>) | undefined;
|
|
29
|
+
const mockProgram = {
|
|
30
|
+
command: vi.fn(() => ({
|
|
31
|
+
description: vi.fn().mockReturnThis(),
|
|
32
|
+
action: vi.fn((fn: () => Promise<void>) => {
|
|
33
|
+
action = fn;
|
|
34
|
+
return { description: vi.fn(), action: vi.fn() };
|
|
35
|
+
}),
|
|
36
|
+
})),
|
|
37
|
+
};
|
|
38
|
+
registerUpdate(mockProgram as never);
|
|
39
|
+
if (!action) throw new Error('action not registered');
|
|
40
|
+
return action;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('update command', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
vi.clearAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('exports registerUpdate function', () => {
|
|
49
|
+
expect(typeof registerUpdate).toBe('function');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('registers a command named "update" on the program', () => {
|
|
53
|
+
const mockCommand = {
|
|
54
|
+
command: vi.fn().mockReturnThis(),
|
|
55
|
+
description: vi.fn().mockReturnThis(),
|
|
56
|
+
action: vi.fn().mockReturnThis(),
|
|
57
|
+
};
|
|
58
|
+
const mockProgram = { command: vi.fn(() => mockCommand) };
|
|
59
|
+
|
|
60
|
+
registerUpdate(mockProgram as never);
|
|
61
|
+
|
|
62
|
+
expect(mockProgram.command).toHaveBeenCalledWith('update');
|
|
63
|
+
expect(mockCommand.description).toHaveBeenCalledWith(expect.stringContaining('Update'));
|
|
64
|
+
expect(mockCommand.action).toHaveBeenCalledWith(expect.any(Function));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('prints already-on-latest when current version matches latest', async () => {
|
|
68
|
+
// getCurrentVersion() falls back to 'unknown' in test env (no package.json in require path).
|
|
69
|
+
// Return 'unknown' from npm view to match, triggering the "already on latest" branch.
|
|
70
|
+
vi.mocked(execSync).mockReturnValue(Buffer.from('unknown\n'));
|
|
71
|
+
|
|
72
|
+
const action = captureAction();
|
|
73
|
+
await action();
|
|
74
|
+
|
|
75
|
+
expect(p.log.success).toHaveBeenCalledWith(expect.stringContaining('Already on latest'));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('prints "Updated" when an update is available and install succeeds', async () => {
|
|
79
|
+
vi.mocked(execSync)
|
|
80
|
+
.mockReturnValueOnce(Buffer.from('9.99.0\n')) // npm view latest
|
|
81
|
+
.mockReturnValueOnce(Buffer.from('')) // npm install -g (stdio: inherit path skipped)
|
|
82
|
+
.mockReturnValueOnce(Buffer.from('9.99.0\n')); // npm view verify
|
|
83
|
+
|
|
84
|
+
const action = captureAction();
|
|
85
|
+
await action();
|
|
86
|
+
|
|
87
|
+
expect(p.log.success).toHaveBeenCalledWith(expect.stringContaining('Updated'));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('exits with code 1 when npm registry is unreachable', async () => {
|
|
91
|
+
vi.mocked(execSync).mockImplementation(() => {
|
|
92
|
+
throw new Error('network error');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
|
96
|
+
const action = captureAction();
|
|
97
|
+
await action();
|
|
98
|
+
|
|
99
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
100
|
+
mockExit.mockRestore();
|
|
101
|
+
});
|
|
102
|
+
});
|
package/src/commands/create.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
gitPush,
|
|
22
22
|
ghCreateRepo,
|
|
23
23
|
} from '../utils/git.js';
|
|
24
|
+
import { installClaudePermissions } from './install.js';
|
|
24
25
|
|
|
25
26
|
function parseSetupTarget(value?: string): SetupTarget | undefined {
|
|
26
27
|
if (!value) return undefined;
|
|
@@ -314,6 +315,9 @@ export function registerCreate(program: Command): void {
|
|
|
314
315
|
};
|
|
315
316
|
writeF(configPath, JSON.stringify(claudeConfig, null, 2) + '\n', 'utf-8');
|
|
316
317
|
p.log.success(`Registered ${config.id} in ~/.claude.json`);
|
|
318
|
+
|
|
319
|
+
// Pre-approve facade permissions so users don't hit approval prompts
|
|
320
|
+
installClaudePermissions(config.id);
|
|
317
321
|
} catch {
|
|
318
322
|
p.log.warn(
|
|
319
323
|
'Could not auto-register — run `npx @soleri/cli install --target claude` manually.',
|