@amodalai/amodal 0.2.1 → 0.2.2
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/CHANGELOG.md +29 -0
- package/dist/src/commands/connect-channel.d.ts +11 -0
- package/dist/src/commands/connect-channel.d.ts.map +1 -0
- package/dist/src/commands/connect-channel.js +169 -0
- package/dist/src/commands/connect-channel.js.map +1 -0
- package/dist/src/commands/connect.d.ts +3 -1
- package/dist/src/commands/connect.d.ts.map +1 -1
- package/dist/src/commands/connect.js +27 -16
- package/dist/src/commands/connect.js.map +1 -1
- package/dist/src/commands/groups/connect.d.ts +8 -0
- package/dist/src/commands/groups/connect.d.ts.map +1 -0
- package/dist/src/commands/groups/connect.js +17 -0
- package/dist/src/commands/groups/connect.js.map +1 -0
- package/dist/src/commands/groups/pkg.d.ts.map +1 -1
- package/dist/src/commands/groups/pkg.js +0 -12
- package/dist/src/commands/groups/pkg.js.map +1 -1
- package/dist/src/commands/index.d.ts +1 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +3 -1
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +4 -1
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/inspect.d.ts.map +1 -1
- package/dist/src/commands/inspect.js +32 -38
- package/dist/src/commands/inspect.js.map +1 -1
- package/dist/src/commands/install-pkg.d.ts +5 -5
- package/dist/src/commands/install-pkg.d.ts.map +1 -1
- package/dist/src/commands/install-pkg.js +35 -77
- package/dist/src/commands/install-pkg.js.map +1 -1
- package/dist/src/commands/uninstall.d.ts +2 -2
- package/dist/src/commands/uninstall.d.ts.map +1 -1
- package/dist/src/commands/uninstall.js +6 -32
- package/dist/src/commands/uninstall.js.map +1 -1
- package/dist/src/commands/validate.d.ts.map +1 -1
- package/dist/src/commands/validate.js +15 -21
- package/dist/src/commands/validate.js.map +1 -1
- package/dist/src/shared/tarball.js +1 -1
- package/dist/src/shared/tarball.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/commands/command-exports.test.ts +1 -1
- package/src/commands/connect-channel.ts +197 -0
- package/src/commands/connect.test.ts +28 -40
- package/src/commands/connect.ts +30 -24
- package/src/commands/groups/connect.ts +20 -0
- package/src/commands/groups/pkg.ts +0 -12
- package/src/commands/index.ts +5 -1
- package/src/commands/init.test.ts +2 -2
- package/src/commands/init.ts +5 -1
- package/src/commands/inspect.test.ts +1 -18
- package/src/commands/inspect.ts +31 -36
- package/src/commands/install-pkg.test.ts +39 -73
- package/src/commands/install-pkg.ts +38 -87
- package/src/commands/uninstall.test.ts +11 -51
- package/src/commands/uninstall.ts +7 -36
- package/src/commands/validate.test.ts +5 -26
- package/src/commands/validate.ts +15 -20
- package/src/e2e-commands.test.ts +0 -1
- package/src/e2e-plugins.test.ts +0 -1
- package/src/e2e.test.ts +0 -89
- package/src/shared/tarball.ts +1 -1
- package/dist/src/commands/diff.d.ts +0 -17
- package/dist/src/commands/diff.d.ts.map +0 -1
- package/dist/src/commands/diff.js +0 -118
- package/dist/src/commands/diff.js.map +0 -1
- package/dist/src/commands/list.d.ts +0 -18
- package/dist/src/commands/list.d.ts.map +0 -1
- package/dist/src/commands/list.js +0 -82
- package/dist/src/commands/list.js.map +0 -1
- package/dist/src/commands/publish.d.ts +0 -18
- package/dist/src/commands/publish.d.ts.map +0 -1
- package/dist/src/commands/publish.js +0 -121
- package/dist/src/commands/publish.js.map +0 -1
- package/dist/src/commands/search.d.ts +0 -19
- package/dist/src/commands/search.d.ts.map +0 -1
- package/dist/src/commands/search.js +0 -97
- package/dist/src/commands/search.js.map +0 -1
- package/dist/src/commands/update.d.ts +0 -19
- package/dist/src/commands/update.d.ts.map +0 -1
- package/dist/src/commands/update.js +0 -158
- package/dist/src/commands/update.js.map +0 -1
- package/src/commands/diff.test.ts +0 -165
- package/src/commands/diff.ts +0 -141
- package/src/commands/list.test.ts +0 -141
- package/src/commands/list.ts +0 -99
- package/src/commands/publish.test.ts +0 -169
- package/src/commands/publish.ts +0 -141
- package/src/commands/search.test.ts +0 -171
- package/src/commands/search.ts +0 -120
- package/src/commands/update.test.ts +0 -256
- package/src/commands/update.ts +0 -196
package/src/commands/inspect.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {CommandModule} from 'yargs';
|
|
8
|
-
import {loadRepo, setupSession,
|
|
8
|
+
import {loadRepo, setupSession, resolveAllPackages} from '@amodalai/core';
|
|
9
9
|
import {findRepoRoot} from '../shared/repo-discovery.js';
|
|
10
10
|
|
|
11
11
|
export interface InspectOptions {
|
|
@@ -79,47 +79,42 @@ export async function runInspect(options: InspectOptions = {}): Promise<void> {
|
|
|
79
79
|
if (options.resolved) {
|
|
80
80
|
process.stdout.write('\n=== Resolved Packages ===\n');
|
|
81
81
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (scopeName && name !== scopeName) continue;
|
|
96
|
-
process.stdout.write(`\n Connection: ${name}\n`);
|
|
97
|
-
process.stdout.write(` Endpoints: ${conn.surface.length}\n`);
|
|
98
|
-
process.stdout.write(` Auth: ${conn.spec.auth?.type ?? 'none'}\n`);
|
|
99
|
-
}
|
|
82
|
+
const resolved = await resolveAllPackages({repoPath});
|
|
83
|
+
|
|
84
|
+
// Filter by scope if given
|
|
85
|
+
const scopeParts = options.scope?.split('/');
|
|
86
|
+
const scopeType = scopeParts?.[0];
|
|
87
|
+
const scopeName = scopeParts?.[1];
|
|
88
|
+
|
|
89
|
+
if (!scopeType || scopeType === 'connections') {
|
|
90
|
+
for (const [name, conn] of resolved.connections) {
|
|
91
|
+
if (scopeName && name !== scopeName) continue;
|
|
92
|
+
process.stdout.write(`\n Connection: ${name}\n`);
|
|
93
|
+
process.stdout.write(` Endpoints: ${conn.surface.length}\n`);
|
|
94
|
+
process.stdout.write(` Auth: ${conn.spec.auth?.type ?? 'none'}\n`);
|
|
100
95
|
}
|
|
96
|
+
}
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
98
|
+
if (!scopeType || scopeType === 'skills') {
|
|
99
|
+
for (const skill of resolved.skills) {
|
|
100
|
+
if (scopeName && skill.name !== scopeName) continue;
|
|
101
|
+
process.stdout.write(`\n Skill: ${skill.name}\n`);
|
|
102
|
+
process.stdout.write(` Body: ${skill.body.length} chars\n`);
|
|
108
103
|
}
|
|
104
|
+
}
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
106
|
+
if (!scopeType || scopeType === 'automations') {
|
|
107
|
+
for (const auto of resolved.automations) {
|
|
108
|
+
if (scopeName && auto.name !== scopeName) continue;
|
|
109
|
+
process.stdout.write(`\n Automation: ${auto.name}\n`);
|
|
115
110
|
}
|
|
111
|
+
}
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
113
|
+
// Print warnings
|
|
114
|
+
if (resolved.warnings.length > 0) {
|
|
115
|
+
process.stdout.write('\n Warnings:\n');
|
|
116
|
+
for (const warning of resolved.warnings) {
|
|
117
|
+
process.stdout.write(` - ${warning}\n`);
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
120
|
}
|
|
@@ -1,43 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
8
8
|
|
|
9
9
|
const mockFindRepoRoot = vi.fn(() => '/test/repo');
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
10
|
+
const mockEnsurePackageJson = vi.fn();
|
|
11
|
+
const mockPmAdd = vi.fn();
|
|
12
|
+
const mockPmInstall = vi.fn();
|
|
13
|
+
const mockAddAmodalPackage = vi.fn();
|
|
14
14
|
const mockToNpmName = vi.fn((name: string) => `@amodalai/${name}`);
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const mockBuildLockFile = vi.fn();
|
|
15
|
+
|
|
16
|
+
const mockReadFileSync = vi.fn();
|
|
18
17
|
|
|
19
18
|
vi.mock('../shared/repo-discovery.js', () => ({
|
|
20
19
|
findRepoRoot: mockFindRepoRoot,
|
|
21
20
|
}));
|
|
22
21
|
|
|
23
22
|
vi.mock('@amodalai/core', () => ({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
ensurePackageJson: mockEnsurePackageJson,
|
|
24
|
+
pmAdd: mockPmAdd,
|
|
25
|
+
pmInstall: mockPmInstall,
|
|
26
|
+
addAmodalPackage: mockAddAmodalPackage,
|
|
28
27
|
toNpmName: mockToNpmName,
|
|
29
|
-
addConfigDep: mockAddConfigDep,
|
|
30
|
-
discoverInstalledPackages: mockDiscoverInstalledPackages,
|
|
31
|
-
buildLockFile: mockBuildLockFile,
|
|
32
28
|
}));
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
npmrc: '/test/repo/.amodal/packages/.npm/.npmrc',
|
|
38
|
-
packageJson: '/test/repo/.amodal/packages/.npm/package.json',
|
|
39
|
-
nodeModules: '/test/repo/.amodal/packages/.npm/node_modules',
|
|
40
|
-
};
|
|
30
|
+
vi.mock('node:fs', () => ({
|
|
31
|
+
readFileSync: mockReadFileSync,
|
|
32
|
+
}));
|
|
41
33
|
|
|
42
34
|
describe('runInstallPkg', () => {
|
|
43
35
|
let stderrOutput: string;
|
|
@@ -45,11 +37,9 @@ describe('runInstallPkg', () => {
|
|
|
45
37
|
beforeEach(() => {
|
|
46
38
|
vi.clearAllMocks();
|
|
47
39
|
mockFindRepoRoot.mockReturnValue('/test/repo');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
mockBuildLockFile.mockResolvedValue(undefined);
|
|
52
|
-
mockAddConfigDep.mockResolvedValue(undefined);
|
|
40
|
+
mockPmAdd.mockResolvedValue(undefined);
|
|
41
|
+
mockPmInstall.mockResolvedValue(undefined);
|
|
42
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({name: 'test-project'}));
|
|
53
43
|
stderrOutput = '';
|
|
54
44
|
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
55
45
|
stderrOutput += String(chunk);
|
|
@@ -57,66 +47,48 @@ describe('runInstallPkg', () => {
|
|
|
57
47
|
});
|
|
58
48
|
});
|
|
59
49
|
|
|
60
|
-
it('should
|
|
61
|
-
mockReadLockFile.mockResolvedValue({
|
|
62
|
-
lockVersion: 2,
|
|
63
|
-
packages: {'@amodalai/connection-salesforce': {version: '2.0.0', integrity: 'sha512-x'}},
|
|
64
|
-
});
|
|
65
|
-
|
|
50
|
+
it('should run pmInstall on bare install', async () => {
|
|
66
51
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
67
52
|
const result = await runInstallPkg();
|
|
68
53
|
expect(result).toBe(0);
|
|
69
|
-
expect(
|
|
70
|
-
expect(
|
|
54
|
+
expect(mockEnsurePackageJson).toHaveBeenCalledWith('/test/repo', 'test-project');
|
|
55
|
+
expect(mockPmInstall).toHaveBeenCalledWith('/test/repo');
|
|
56
|
+
expect(stderrOutput).toContain('Done');
|
|
71
57
|
});
|
|
72
58
|
|
|
73
|
-
it('should
|
|
74
|
-
|
|
75
|
-
mockReadConfigDeps.mockResolvedValue({});
|
|
59
|
+
it('should handle pmInstall failure on bare install', async () => {
|
|
60
|
+
mockPmInstall.mockRejectedValue(new Error('Install failed'));
|
|
76
61
|
|
|
77
62
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
78
63
|
const result = await runInstallPkg();
|
|
79
|
-
expect(result).toBe(
|
|
80
|
-
expect(stderrOutput).toContain('
|
|
64
|
+
expect(result).toBe(1);
|
|
65
|
+
expect(stderrOutput).toContain('Install failed');
|
|
81
66
|
});
|
|
82
67
|
|
|
83
68
|
it('should install a single package', async () => {
|
|
84
|
-
mockDiscoverInstalledPackages.mockResolvedValue([
|
|
85
|
-
{npmName: '@amodalai/alert-enrichment', version: '1.0.0', integrity: 'sha512-abc'},
|
|
86
|
-
]);
|
|
87
|
-
|
|
88
69
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
89
70
|
const result = await runInstallPkg({
|
|
90
71
|
packages: ['alert-enrichment'],
|
|
91
72
|
});
|
|
92
73
|
expect(result).toBe(0);
|
|
93
74
|
expect(mockToNpmName).toHaveBeenCalledWith('alert-enrichment');
|
|
94
|
-
expect(
|
|
95
|
-
expect(
|
|
96
|
-
expect(mockBuildLockFile).toHaveBeenCalledWith('/test/repo', expect.any(Array));
|
|
75
|
+
expect(mockPmAdd).toHaveBeenCalledWith('/test/repo', '@amodalai/alert-enrichment');
|
|
76
|
+
expect(mockAddAmodalPackage).toHaveBeenCalledWith('/test/repo', '@amodalai/alert-enrichment');
|
|
97
77
|
});
|
|
98
78
|
|
|
99
79
|
it('should install multiple packages', async () => {
|
|
100
|
-
mockDiscoverInstalledPackages.mockResolvedValue([
|
|
101
|
-
{npmName: '@amodalai/connection-stripe', version: '1.0.0', integrity: 'sha512-abc'},
|
|
102
|
-
{npmName: '@amodalai/soc-agent', version: '1.0.0', integrity: 'sha512-def'},
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
80
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
106
81
|
const result = await runInstallPkg({
|
|
107
82
|
packages: ['connection-stripe', 'soc-agent'],
|
|
108
83
|
});
|
|
109
84
|
expect(result).toBe(0);
|
|
110
|
-
expect(
|
|
85
|
+
expect(mockPmAdd).toHaveBeenCalledTimes(2);
|
|
111
86
|
});
|
|
112
87
|
|
|
113
88
|
it('should continue on error and report failure count', async () => {
|
|
114
|
-
|
|
89
|
+
mockPmAdd
|
|
115
90
|
.mockRejectedValueOnce(new Error('Network error'))
|
|
116
|
-
.mockResolvedValueOnce(
|
|
117
|
-
mockDiscoverInstalledPackages.mockResolvedValue([
|
|
118
|
-
{npmName: '@amodalai/good', version: '1.0.0', integrity: 'sha512-ok'},
|
|
119
|
-
]);
|
|
91
|
+
.mockResolvedValueOnce(undefined);
|
|
120
92
|
|
|
121
93
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
122
94
|
const result = await runInstallPkg({
|
|
@@ -128,10 +100,9 @@ describe('runInstallPkg', () => {
|
|
|
128
100
|
});
|
|
129
101
|
|
|
130
102
|
it('should report all failures', async () => {
|
|
131
|
-
|
|
103
|
+
mockPmAdd
|
|
132
104
|
.mockRejectedValueOnce(new Error('Fail 1'))
|
|
133
105
|
.mockRejectedValueOnce(new Error('Fail 2'));
|
|
134
|
-
mockDiscoverInstalledPackages.mockResolvedValue([]);
|
|
135
106
|
|
|
136
107
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
137
108
|
const result = await runInstallPkg({
|
|
@@ -152,27 +123,25 @@ describe('runInstallPkg', () => {
|
|
|
152
123
|
expect(stderrOutput).toContain('Not found');
|
|
153
124
|
});
|
|
154
125
|
|
|
155
|
-
it('should
|
|
156
|
-
|
|
126
|
+
it('should use default project name when amodal.json is missing', async () => {
|
|
127
|
+
mockReadFileSync.mockImplementation(() => {
|
|
128
|
+
throw new Error('ENOENT');
|
|
129
|
+
});
|
|
157
130
|
|
|
158
131
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
159
132
|
const result = await runInstallPkg();
|
|
160
133
|
expect(result).toBe(0);
|
|
161
|
-
expect(
|
|
134
|
+
expect(mockEnsurePackageJson).toHaveBeenCalledWith('/test/repo', 'amodal-project');
|
|
162
135
|
});
|
|
163
136
|
|
|
164
137
|
it('should pass cwd to findRepoRoot', async () => {
|
|
165
|
-
mockReadLockFile.mockResolvedValue(null);
|
|
166
|
-
mockReadConfigDeps.mockResolvedValue({});
|
|
167
|
-
|
|
168
138
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
169
139
|
await runInstallPkg({cwd: '/custom/dir'});
|
|
170
140
|
expect(mockFindRepoRoot).toHaveBeenCalledWith('/custom/dir');
|
|
171
141
|
});
|
|
172
142
|
|
|
173
|
-
it('should handle npm
|
|
174
|
-
|
|
175
|
-
mockDiscoverInstalledPackages.mockResolvedValue([]);
|
|
143
|
+
it('should handle npm add failure for single package', async () => {
|
|
144
|
+
mockPmAdd.mockRejectedValue(new Error('Registry unreachable'));
|
|
176
145
|
|
|
177
146
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
178
147
|
const result = await runInstallPkg({
|
|
@@ -183,12 +152,9 @@ describe('runInstallPkg', () => {
|
|
|
183
152
|
});
|
|
184
153
|
|
|
185
154
|
it('should handle empty packages array like bare install', async () => {
|
|
186
|
-
mockReadLockFile.mockResolvedValue(null);
|
|
187
|
-
mockReadConfigDeps.mockResolvedValue({});
|
|
188
|
-
|
|
189
155
|
const {runInstallPkg} = await import('./install-pkg.js');
|
|
190
156
|
const result = await runInstallPkg({packages: []});
|
|
191
157
|
expect(result).toBe(0);
|
|
192
|
-
expect(
|
|
158
|
+
expect(mockPmInstall).toHaveBeenCalledWith('/test/repo');
|
|
193
159
|
});
|
|
194
160
|
});
|
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import {readFileSync} from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
7
9
|
import type {CommandModule} from 'yargs';
|
|
8
10
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
npmInstall,
|
|
14
|
-
readConfigDeps,
|
|
15
|
-
readLockFile,
|
|
11
|
+
ensurePackageJson,
|
|
12
|
+
addAmodalPackage,
|
|
13
|
+
pmAdd,
|
|
14
|
+
pmInstall,
|
|
16
15
|
toNpmName,
|
|
17
16
|
} from '@amodalai/core';
|
|
18
17
|
import {findRepoRoot} from '../shared/repo-discovery.js';
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
|
-
* Install one or more packages, or
|
|
20
|
+
* Install one or more packages, or run a bare install.
|
|
22
21
|
* Returns the number of failures (0 = success).
|
|
23
22
|
*
|
|
24
23
|
* Usage:
|
|
25
|
-
* amodal install —
|
|
26
|
-
* amodal install alert-enrichment —
|
|
27
|
-
* amodal install @amodalai/soc-agent —
|
|
24
|
+
* amodal install — run package manager install
|
|
25
|
+
* amodal install alert-enrichment — add @amodalai/alert-enrichment
|
|
26
|
+
* amodal install @amodalai/soc-agent — add with full npm name
|
|
28
27
|
*/
|
|
29
28
|
export async function runInstallPkg(options: {cwd?: string; packages?: string[]} = {}): Promise<number> {
|
|
30
29
|
let repoPath: string;
|
|
@@ -36,74 +35,43 @@ export async function runInstallPkg(options: {cwd?: string; packages?: string[]}
|
|
|
36
35
|
return 1;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const entries = Object.entries(lockFile.packages);
|
|
47
|
-
process.stderr.write(`[install] Restoring ${entries.length} package(s) from lock file...\n`);
|
|
48
|
-
let failures = 0;
|
|
49
|
-
for (const [npmName, entry] of entries) {
|
|
50
|
-
try {
|
|
51
|
-
await npmInstall(paths, npmName, entry.version);
|
|
52
|
-
} catch (err) {
|
|
53
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
54
|
-
process.stderr.write(`[install] Failed: ${npmName}@${entry.version}: ${msg}\n`);
|
|
55
|
-
failures++;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (failures > 0) {
|
|
59
|
-
process.stderr.write(`[install] ${failures} package(s) failed.\n`);
|
|
60
|
-
return failures;
|
|
61
|
-
}
|
|
62
|
-
process.stderr.write(`[install] Restored ${entries.length} package(s).\n`);
|
|
63
|
-
return 0;
|
|
38
|
+
// Read project name from amodal.json for ensurePackageJson
|
|
39
|
+
let projectName = 'amodal-project';
|
|
40
|
+
try {
|
|
41
|
+
const configRaw = readFileSync(path.join(repoPath, 'amodal.json'), 'utf-8');
|
|
42
|
+
const config: unknown = JSON.parse(configRaw);
|
|
43
|
+
if (config && typeof config === 'object' && 'name' in config) {
|
|
44
|
+
projectName = String((config as Record<string, unknown>)['name']);
|
|
64
45
|
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Use default project name
|
|
48
|
+
}
|
|
65
49
|
|
|
66
|
-
|
|
67
|
-
let deps: Record<string, string>;
|
|
68
|
-
try {
|
|
69
|
-
deps = await readConfigDeps(repoPath);
|
|
70
|
-
} catch {
|
|
71
|
-
deps = {};
|
|
72
|
-
}
|
|
50
|
+
ensurePackageJson(repoPath, projectName);
|
|
73
51
|
|
|
74
|
-
|
|
75
|
-
|
|
52
|
+
// Bare install: run package manager install
|
|
53
|
+
if (!options.packages || options.packages.length === 0) {
|
|
54
|
+
process.stderr.write('[install] Running package manager install...\n');
|
|
55
|
+
try {
|
|
56
|
+
await pmInstall(repoPath);
|
|
57
|
+
process.stderr.write('[install] Done.\n');
|
|
76
58
|
return 0;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
process.stderr.write(`[install] Install failed: ${msg}\n`);
|
|
62
|
+
return 1;
|
|
77
63
|
}
|
|
78
|
-
|
|
79
|
-
process.stderr.write(`[install] Installing ${Object.keys(deps).length} package(s) from amodal.json...\n`);
|
|
80
|
-
let failures = 0;
|
|
81
|
-
for (const [name, versionRange] of Object.entries(deps)) {
|
|
82
|
-
const npmName = toNpmName(name);
|
|
83
|
-
try {
|
|
84
|
-
await npmInstall(paths, npmName, versionRange);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
-
process.stderr.write(`[install] Failed: ${npmName}@${versionRange}: ${msg}\n`);
|
|
88
|
-
failures++;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Rebuild lock file from what's actually installed
|
|
93
|
-
const discovered = await discoverInstalledPackages(paths);
|
|
94
|
-
await buildLockFile(repoPath, discovered);
|
|
95
|
-
process.stderr.write(`[install] ${discovered.length} package(s) installed.\n`);
|
|
96
|
-
return failures;
|
|
97
64
|
}
|
|
98
65
|
|
|
99
66
|
// Install specific packages
|
|
100
67
|
let failures = 0;
|
|
101
68
|
for (const name of options.packages) {
|
|
102
69
|
const npmName = toNpmName(name);
|
|
103
|
-
process.stderr.write(`[install]
|
|
70
|
+
process.stderr.write(`[install] Adding ${npmName}...\n`);
|
|
104
71
|
try {
|
|
105
|
-
await
|
|
106
|
-
|
|
72
|
+
await pmAdd(repoPath, npmName);
|
|
73
|
+
addAmodalPackage(repoPath, npmName);
|
|
74
|
+
process.stderr.write(`[install] Added ${npmName}\n`);
|
|
107
75
|
} catch (err) {
|
|
108
76
|
const msg = err instanceof Error ? err.message : String(err);
|
|
109
77
|
process.stderr.write(`[install] Failed: ${npmName}: ${msg}\n`);
|
|
@@ -111,27 +79,10 @@ export async function runInstallPkg(options: {cwd?: string; packages?: string[]}
|
|
|
111
79
|
}
|
|
112
80
|
}
|
|
113
81
|
|
|
114
|
-
// Discover everything installed (including transitive deps) and rebuild lock file
|
|
115
|
-
const discovered = await discoverInstalledPackages(paths);
|
|
116
|
-
await buildLockFile(repoPath, discovered);
|
|
117
|
-
|
|
118
|
-
// Add direct installs to amodal.json dependencies
|
|
119
|
-
for (const name of options.packages) {
|
|
120
|
-
const npmName = toNpmName(name);
|
|
121
|
-
const installed = discovered.find((d) => d.npmName === npmName);
|
|
122
|
-
if (installed) {
|
|
123
|
-
try {
|
|
124
|
-
await addConfigDep(repoPath, npmName, installed.version);
|
|
125
|
-
} catch {
|
|
126
|
-
// amodal.json might not exist yet
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
82
|
if (failures > 0) {
|
|
132
83
|
process.stderr.write(`[install] ${failures} package(s) failed.\n`);
|
|
133
84
|
} else {
|
|
134
|
-
process.stderr.write(`[install] ${
|
|
85
|
+
process.stderr.write(`[install] ${options.packages.length} package(s) added.\n`);
|
|
135
86
|
}
|
|
136
87
|
|
|
137
88
|
return failures;
|
|
@@ -139,7 +90,7 @@ export async function runInstallPkg(options: {cwd?: string; packages?: string[]}
|
|
|
139
90
|
|
|
140
91
|
export const installPkgCommand: CommandModule = {
|
|
141
92
|
command: 'install [packages..]',
|
|
142
|
-
describe: 'Install packages (or
|
|
93
|
+
describe: 'Install packages (or run bare install)',
|
|
143
94
|
builder: (yargs) =>
|
|
144
95
|
yargs.positional('packages', {type: 'string', array: true, describe: 'Package names to install'}),
|
|
145
96
|
handler: async (argv) => {
|
|
@@ -1,60 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
8
8
|
|
|
9
9
|
const mockFindRepoRoot = vi.fn(() => '/test/repo');
|
|
10
|
-
const mockGetNpmContextPaths = vi.fn();
|
|
11
10
|
const mockToNpmName = vi.fn((name: string) => `@amodalai/${name}`);
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const mockDiscoverInstalledPackages = vi.fn();
|
|
15
|
-
const mockBuildLockFile = vi.fn();
|
|
16
|
-
|
|
17
|
-
const mockReadFile = vi.fn();
|
|
18
|
-
const mockWriteFile = vi.fn();
|
|
11
|
+
const mockPmRemove = vi.fn();
|
|
12
|
+
const mockRemoveAmodalPackage = vi.fn();
|
|
19
13
|
|
|
20
14
|
vi.mock('../shared/repo-discovery.js', () => ({
|
|
21
15
|
findRepoRoot: mockFindRepoRoot,
|
|
22
16
|
}));
|
|
23
17
|
|
|
24
18
|
vi.mock('@amodalai/core', () => ({
|
|
25
|
-
getNpmContextPaths: mockGetNpmContextPaths,
|
|
26
19
|
toNpmName: mockToNpmName,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
discoverInstalledPackages: mockDiscoverInstalledPackages,
|
|
30
|
-
buildLockFile: mockBuildLockFile,
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
vi.mock('node:fs/promises', () => ({
|
|
34
|
-
readFile: mockReadFile,
|
|
35
|
-
writeFile: mockWriteFile,
|
|
20
|
+
pmRemove: mockPmRemove,
|
|
21
|
+
removeAmodalPackage: mockRemoveAmodalPackage,
|
|
36
22
|
}));
|
|
37
23
|
|
|
38
|
-
const mockPaths = {
|
|
39
|
-
root: '/test/repo/.amodal/packages',
|
|
40
|
-
npmDir: '/test/repo/.amodal/packages/.npm',
|
|
41
|
-
npmrc: '/test/repo/.amodal/packages/.npm/.npmrc',
|
|
42
|
-
packageJson: '/test/repo/.amodal/packages/.npm/package.json',
|
|
43
|
-
nodeModules: '/test/repo/.amodal/packages/.npm/node_modules',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
24
|
describe('runUninstall', () => {
|
|
47
25
|
let stderrOutput: string;
|
|
48
26
|
|
|
49
27
|
beforeEach(() => {
|
|
50
28
|
vi.clearAllMocks();
|
|
51
29
|
mockFindRepoRoot.mockReturnValue('/test/repo');
|
|
52
|
-
|
|
53
|
-
mockNpmUninstall.mockResolvedValue(undefined);
|
|
54
|
-
mockEnsureNpmContext.mockResolvedValue(mockPaths);
|
|
55
|
-
mockDiscoverInstalledPackages.mockResolvedValue([]);
|
|
56
|
-
mockBuildLockFile.mockResolvedValue(undefined);
|
|
57
|
-
mockReadFile.mockRejectedValue(new Error('ENOENT')); // amodal.json not found by default
|
|
30
|
+
mockPmRemove.mockResolvedValue(undefined);
|
|
58
31
|
stderrOutput = '';
|
|
59
32
|
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
60
33
|
stderrOutput += String(chunk);
|
|
@@ -66,19 +39,18 @@ describe('runUninstall', () => {
|
|
|
66
39
|
const {runUninstall} = await import('./uninstall.js');
|
|
67
40
|
const result = await runUninstall({name: 'connection-stripe'});
|
|
68
41
|
expect(result).toBe(0);
|
|
69
|
-
expect(
|
|
70
|
-
expect(
|
|
71
|
-
expect(mockBuildLockFile).toHaveBeenCalledWith('/test/repo', []);
|
|
42
|
+
expect(mockPmRemove).toHaveBeenCalledWith('/test/repo', '@amodalai/connection-stripe');
|
|
43
|
+
expect(mockRemoveAmodalPackage).toHaveBeenCalledWith('/test/repo', '@amodalai/connection-stripe');
|
|
72
44
|
expect(stderrOutput).toContain('Removed @amodalai/connection-stripe');
|
|
73
45
|
});
|
|
74
46
|
|
|
75
|
-
it('should return 1 when
|
|
76
|
-
|
|
47
|
+
it('should return 1 when pm remove fails', async () => {
|
|
48
|
+
mockPmRemove.mockRejectedValue(new Error('npm error'));
|
|
77
49
|
|
|
78
50
|
const {runUninstall} = await import('./uninstall.js');
|
|
79
51
|
const result = await runUninstall({name: 'skill-triage'});
|
|
80
52
|
expect(result).toBe(1);
|
|
81
|
-
expect(stderrOutput).toContain('
|
|
53
|
+
expect(stderrOutput).toContain('Failed to remove');
|
|
82
54
|
});
|
|
83
55
|
|
|
84
56
|
it('should return 1 when repo not found', async () => {
|
|
@@ -97,16 +69,4 @@ describe('runUninstall', () => {
|
|
|
97
69
|
await runUninstall({cwd: '/custom/dir', name: 'connection-test'});
|
|
98
70
|
expect(mockFindRepoRoot).toHaveBeenCalledWith('/custom/dir');
|
|
99
71
|
});
|
|
100
|
-
|
|
101
|
-
it('should remove dependency from amodal.json when it exists', async () => {
|
|
102
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
103
|
-
name: 'test',
|
|
104
|
-
dependencies: {'@amodalai/connection-stripe': '1.0.0', '@amodalai/soc-agent': '2.0.0'},
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
const {runUninstall} = await import('./uninstall.js');
|
|
108
|
-
const result = await runUninstall({name: 'connection-stripe'});
|
|
109
|
-
expect(result).toBe(0);
|
|
110
|
-
expect(mockWriteFile).toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
72
|
});
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {readFile, writeFile} from 'node:fs/promises';
|
|
8
|
-
import * as path from 'node:path';
|
|
9
7
|
import type {CommandModule} from 'yargs';
|
|
10
8
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
ensureNpmContext,
|
|
14
|
-
getNpmContextPaths,
|
|
15
|
-
npmUninstall,
|
|
9
|
+
pmRemove,
|
|
10
|
+
removeAmodalPackage,
|
|
16
11
|
toNpmName,
|
|
17
12
|
} from '@amodalai/core';
|
|
18
13
|
import {findRepoRoot} from '../shared/repo-discovery.js';
|
|
19
14
|
|
|
20
15
|
/**
|
|
21
|
-
* Uninstall a package
|
|
16
|
+
* Uninstall a package via the detected package manager.
|
|
22
17
|
* Returns 0 on success, 1 on error.
|
|
23
18
|
*/
|
|
24
19
|
export async function runUninstall(options: {cwd?: string; name: string}): Promise<number> {
|
|
@@ -32,40 +27,16 @@ export async function runUninstall(options: {cwd?: string; name: string}): Promi
|
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
const npmName = toNpmName(options.name);
|
|
35
|
-
const paths = getNpmContextPaths(repoPath);
|
|
36
30
|
|
|
37
31
|
try {
|
|
38
|
-
await
|
|
32
|
+
await pmRemove(repoPath, npmName);
|
|
33
|
+
removeAmodalPackage(repoPath, npmName);
|
|
39
34
|
} catch (err) {
|
|
40
35
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
-
process.stderr.write(`[uninstall]
|
|
36
|
+
process.stderr.write(`[uninstall] Failed to remove ${npmName}: ${msg}\n`);
|
|
42
37
|
return 1;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
// Rebuild lock file from remaining packages
|
|
46
|
-
await ensureNpmContext(repoPath);
|
|
47
|
-
const discovered = await discoverInstalledPackages(paths);
|
|
48
|
-
await buildLockFile(repoPath, discovered);
|
|
49
|
-
|
|
50
|
-
// Remove from amodal.json dependencies
|
|
51
|
-
try {
|
|
52
|
-
const configPath = path.join(repoPath, 'amodal.json');
|
|
53
|
-
const configRaw = await readFile(configPath, 'utf-8');
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- config JSON
|
|
55
|
-
const config = JSON.parse(configRaw) as Record<string, unknown>;
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
57
|
-
const deps = (config['dependencies'] as Record<string, string>) ?? {};
|
|
58
|
-
delete deps[npmName];
|
|
59
|
-
if (Object.keys(deps).length > 0) {
|
|
60
|
-
config['dependencies'] = deps;
|
|
61
|
-
} else {
|
|
62
|
-
delete config['dependencies'];
|
|
63
|
-
}
|
|
64
|
-
await writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
65
|
-
} catch {
|
|
66
|
-
// Non-fatal
|
|
67
|
-
}
|
|
68
|
-
|
|
69
40
|
process.stderr.write(`[uninstall] Removed ${npmName}\n`);
|
|
70
41
|
return 0;
|
|
71
42
|
}
|