@amodalai/amodal 0.2.1 → 0.2.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/src/commands/connect-channel.d.ts +11 -0
  3. package/dist/src/commands/connect-channel.d.ts.map +1 -0
  4. package/dist/src/commands/connect-channel.js +169 -0
  5. package/dist/src/commands/connect-channel.js.map +1 -0
  6. package/dist/src/commands/connect.d.ts +3 -1
  7. package/dist/src/commands/connect.d.ts.map +1 -1
  8. package/dist/src/commands/connect.js +27 -16
  9. package/dist/src/commands/connect.js.map +1 -1
  10. package/dist/src/commands/groups/connect.d.ts +8 -0
  11. package/dist/src/commands/groups/connect.d.ts.map +1 -0
  12. package/dist/src/commands/groups/connect.js +17 -0
  13. package/dist/src/commands/groups/connect.js.map +1 -0
  14. package/dist/src/commands/groups/pkg.d.ts.map +1 -1
  15. package/dist/src/commands/groups/pkg.js +0 -12
  16. package/dist/src/commands/groups/pkg.js.map +1 -1
  17. package/dist/src/commands/index.d.ts +1 -1
  18. package/dist/src/commands/index.d.ts.map +1 -1
  19. package/dist/src/commands/index.js +3 -1
  20. package/dist/src/commands/index.js.map +1 -1
  21. package/dist/src/commands/init.d.ts.map +1 -1
  22. package/dist/src/commands/init.js +4 -1
  23. package/dist/src/commands/init.js.map +1 -1
  24. package/dist/src/commands/inspect.d.ts.map +1 -1
  25. package/dist/src/commands/inspect.js +32 -38
  26. package/dist/src/commands/inspect.js.map +1 -1
  27. package/dist/src/commands/install-pkg.d.ts +5 -5
  28. package/dist/src/commands/install-pkg.d.ts.map +1 -1
  29. package/dist/src/commands/install-pkg.js +35 -77
  30. package/dist/src/commands/install-pkg.js.map +1 -1
  31. package/dist/src/commands/uninstall.d.ts +2 -2
  32. package/dist/src/commands/uninstall.d.ts.map +1 -1
  33. package/dist/src/commands/uninstall.js +6 -32
  34. package/dist/src/commands/uninstall.js.map +1 -1
  35. package/dist/src/commands/validate.d.ts.map +1 -1
  36. package/dist/src/commands/validate.js +15 -21
  37. package/dist/src/commands/validate.js.map +1 -1
  38. package/dist/src/shared/tarball.js +1 -1
  39. package/dist/src/shared/tarball.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +5 -5
  42. package/src/commands/command-exports.test.ts +1 -1
  43. package/src/commands/connect-channel.ts +197 -0
  44. package/src/commands/connect.test.ts +28 -40
  45. package/src/commands/connect.ts +30 -24
  46. package/src/commands/groups/connect.ts +20 -0
  47. package/src/commands/groups/pkg.ts +0 -12
  48. package/src/commands/index.ts +5 -1
  49. package/src/commands/init.test.ts +2 -2
  50. package/src/commands/init.ts +5 -1
  51. package/src/commands/inspect.test.ts +1 -18
  52. package/src/commands/inspect.ts +31 -36
  53. package/src/commands/install-pkg.test.ts +39 -73
  54. package/src/commands/install-pkg.ts +38 -87
  55. package/src/commands/uninstall.test.ts +11 -51
  56. package/src/commands/uninstall.ts +7 -36
  57. package/src/commands/validate.test.ts +5 -26
  58. package/src/commands/validate.ts +15 -20
  59. package/src/e2e-commands.test.ts +0 -1
  60. package/src/e2e-plugins.test.ts +0 -1
  61. package/src/e2e.test.ts +0 -89
  62. package/src/shared/tarball.ts +1 -1
  63. package/dist/src/commands/diff.d.ts +0 -17
  64. package/dist/src/commands/diff.d.ts.map +0 -1
  65. package/dist/src/commands/diff.js +0 -118
  66. package/dist/src/commands/diff.js.map +0 -1
  67. package/dist/src/commands/list.d.ts +0 -18
  68. package/dist/src/commands/list.d.ts.map +0 -1
  69. package/dist/src/commands/list.js +0 -82
  70. package/dist/src/commands/list.js.map +0 -1
  71. package/dist/src/commands/publish.d.ts +0 -18
  72. package/dist/src/commands/publish.d.ts.map +0 -1
  73. package/dist/src/commands/publish.js +0 -121
  74. package/dist/src/commands/publish.js.map +0 -1
  75. package/dist/src/commands/search.d.ts +0 -19
  76. package/dist/src/commands/search.d.ts.map +0 -1
  77. package/dist/src/commands/search.js +0 -97
  78. package/dist/src/commands/search.js.map +0 -1
  79. package/dist/src/commands/update.d.ts +0 -19
  80. package/dist/src/commands/update.d.ts.map +0 -1
  81. package/dist/src/commands/update.js +0 -158
  82. package/dist/src/commands/update.js.map +0 -1
  83. package/src/commands/diff.test.ts +0 -165
  84. package/src/commands/diff.ts +0 -141
  85. package/src/commands/list.test.ts +0 -141
  86. package/src/commands/list.ts +0 -99
  87. package/src/commands/publish.test.ts +0 -169
  88. package/src/commands/publish.ts +0 -141
  89. package/src/commands/search.test.ts +0 -171
  90. package/src/commands/search.ts +0 -120
  91. package/src/commands/update.test.ts +0 -256
  92. package/src/commands/update.ts +0 -196
@@ -1,171 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Amodal Labs, Inc.
4
- * SPDX-License-Identifier: MIT
5
- */
6
-
7
- import {describe, it, expect, vi, beforeEach} from 'vitest';
8
-
9
- const mockFindRepoRoot = vi.fn(() => '/test/repo');
10
- const mockEnsureNpmContext = vi.fn();
11
- const mockNpmSearch = vi.fn();
12
- const mockFromNpmName = vi.fn((npm: string) => {
13
- const prefix = '@amodalai/';
14
- if (!npm.startsWith(prefix)) throw new Error('Not amodal');
15
- return npm.slice(prefix.length);
16
- });
17
-
18
- vi.mock('../shared/repo-discovery.js', () => ({
19
- findRepoRoot: mockFindRepoRoot,
20
- }));
21
-
22
- vi.mock('@amodalai/core', () => ({
23
- ensureNpmContext: mockEnsureNpmContext,
24
- npmSearch: mockNpmSearch,
25
- fromNpmName: mockFromNpmName,
26
- }));
27
-
28
- const mockPaths = {
29
- root: '/test/repo/.amodal/packages',
30
- npmDir: '/test/repo/.amodal/packages/.npm',
31
- npmrc: '/test/repo/.amodal/packages/.npm/.npmrc',
32
- packageJson: '/test/repo/.amodal/packages/.npm/package.json',
33
- nodeModules: '/test/repo/.amodal/packages/.npm/node_modules',
34
- };
35
-
36
- describe('runSearch', () => {
37
- let stderrOutput: string;
38
- let stdoutOutput: string;
39
-
40
- beforeEach(() => {
41
- vi.clearAllMocks();
42
- mockFindRepoRoot.mockReturnValue('/test/repo');
43
- mockEnsureNpmContext.mockResolvedValue(mockPaths);
44
- stderrOutput = '';
45
- stdoutOutput = '';
46
- vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
47
- stderrOutput += String(chunk);
48
- return true;
49
- });
50
- vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
51
- stdoutOutput += String(chunk);
52
- return true;
53
- });
54
- });
55
-
56
- it('should search and display results', async () => {
57
- mockNpmSearch.mockResolvedValue([
58
- {name: '@amodalai/connection-salesforce', version: '2.1.0', description: 'Salesforce'},
59
- {name: '@amodalai/connection-stripe', version: '1.0.0', description: 'Stripe'},
60
- ]);
61
-
62
- const {runSearch} = await import('./search.js');
63
- const result = await runSearch({query: 'salesforce'});
64
- expect(result).toBe(0);
65
- expect(stdoutOutput).toContain('salesforce');
66
- expect(stdoutOutput).toContain('stripe');
67
- });
68
-
69
- it('should filter by type', async () => {
70
- mockNpmSearch.mockResolvedValue([
71
- {name: '@amodalai/connection-salesforce', version: '2.1.0', description: 'Salesforce'},
72
- {name: '@amodalai/skill-triage', version: '1.0.0', description: 'Triage'},
73
- ]);
74
-
75
- const {runSearch} = await import('./search.js');
76
- const result = await runSearch({tag: 'connection'});
77
- expect(result).toBe(0);
78
- expect(stdoutOutput).toContain('salesforce');
79
- expect(stdoutOutput).not.toContain('triage');
80
- });
81
-
82
- it('should output JSON when json flag set', async () => {
83
- mockNpmSearch.mockResolvedValue([
84
- {name: '@amodalai/connection-salesforce', version: '2.1.0', description: 'Salesforce'},
85
- ]);
86
-
87
- const {runSearch} = await import('./search.js');
88
- const result = await runSearch({json: true});
89
- expect(result).toBe(0);
90
- const parsed = JSON.parse(stdoutOutput);
91
- expect(parsed).toHaveLength(1);
92
- expect(parsed[0].name).toBe('connection-salesforce');
93
- });
94
-
95
- it('should handle no results', async () => {
96
- mockNpmSearch.mockResolvedValue([]);
97
-
98
- const {runSearch} = await import('./search.js');
99
- const result = await runSearch({query: 'nonexistent'});
100
- expect(result).toBe(0);
101
- expect(stderrOutput).toContain('No packages found');
102
- });
103
-
104
- it('should handle registry unreachable', async () => {
105
- mockNpmSearch.mockRejectedValue(new Error('Registry unreachable'));
106
-
107
- const {runSearch} = await import('./search.js');
108
- const result = await runSearch({query: 'test'});
109
- expect(result).toBe(1);
110
- expect(stderrOutput).toContain('Search failed');
111
- });
112
-
113
- it('should filter out non-amodal packages', async () => {
114
- mockNpmSearch.mockResolvedValue([
115
- {name: '@amodalai/connection-salesforce', version: '1.0.0', description: 'Salesforce'},
116
- {name: 'lodash', version: '4.0.0', description: 'Utility'},
117
- ]);
118
-
119
- const {runSearch} = await import('./search.js');
120
- const result = await runSearch({});
121
- expect(result).toBe(0);
122
- expect(stdoutOutput).toContain('salesforce');
123
- expect(stdoutOutput).not.toContain('lodash');
124
- });
125
-
126
- it('should work without repo root', async () => {
127
- mockFindRepoRoot.mockImplementation(() => {
128
- throw new Error('Not found');
129
- });
130
- mockNpmSearch.mockResolvedValue([
131
- {name: '@amodalai/skill-triage', version: '1.0.0', description: 'Triage'},
132
- ]);
133
-
134
- const {runSearch} = await import('./search.js');
135
- const result = await runSearch({query: 'triage'});
136
- expect(result).toBe(0);
137
- });
138
-
139
- it('should list results with short names', async () => {
140
- mockNpmSearch.mockResolvedValue([
141
- {name: '@amodalai/connection-salesforce', version: '2.0.0', description: 'Salesforce'},
142
- {name: '@amodalai/skill-triage', version: '1.0.0', description: 'Triage'},
143
- ]);
144
-
145
- const {runSearch} = await import('./search.js');
146
- const result = await runSearch({});
147
- expect(result).toBe(0);
148
- expect(stdoutOutput).toContain('connection-salesforce');
149
- expect(stdoutOutput).toContain('skill-triage');
150
- });
151
-
152
- it('should handle npm context failure', async () => {
153
- mockEnsureNpmContext.mockRejectedValue(new Error('Permission denied'));
154
-
155
- const {runSearch} = await import('./search.js');
156
- const result = await runSearch({});
157
- expect(result).toBe(1);
158
- expect(stderrOutput).toContain('Failed to set up npm context');
159
- });
160
-
161
- it('should show package count', async () => {
162
- mockNpmSearch.mockResolvedValue([
163
- {name: '@amodalai/connection-salesforce', version: '1.0.0', description: 'Salesforce'},
164
- {name: '@amodalai/connection-stripe', version: '1.0.0', description: 'Stripe'},
165
- ]);
166
-
167
- const {runSearch} = await import('./search.js');
168
- await runSearch({});
169
- expect(stderrOutput).toContain('2 packages found');
170
- });
171
- });
@@ -1,120 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Amodal Labs, Inc.
4
- * SPDX-License-Identifier: MIT
5
- */
6
-
7
- import type {CommandModule} from 'yargs';
8
- import {
9
- ensureNpmContext,
10
- fromNpmName,
11
- npmSearch,
12
- } from '@amodalai/core';
13
- import {findRepoRoot} from '../shared/repo-discovery.js';
14
-
15
- export interface SearchOptions {
16
- cwd?: string;
17
- query?: string;
18
- tag?: string;
19
- json?: boolean;
20
- }
21
-
22
- /**
23
- * Search the registry for packages.
24
- * Returns 0 on success, 1 on error.
25
- */
26
- export async function runSearch(options: SearchOptions = {}): Promise<number> {
27
- let repoPath: string | undefined;
28
- try {
29
- repoPath = findRepoRoot(options.cwd);
30
- } catch {
31
- // Not in a repo — use temp context
32
- }
33
-
34
- let paths;
35
- try {
36
- paths = await ensureNpmContext(repoPath ?? process.cwd());
37
- } catch (err) {
38
- const msg = err instanceof Error ? err.message : String(err);
39
- process.stderr.write(`[search] Failed to set up npm context: ${msg}\n`);
40
- return 1;
41
- }
42
-
43
- // Build query
44
- let query = '@amodalai/';
45
- if (options.tag) {
46
- query += `${options.tag}-`;
47
- }
48
- if (options.query) {
49
- query += options.query;
50
- }
51
-
52
- process.stderr.write(`[search] Searching for "${query}"...\n`);
53
-
54
- let results;
55
- try {
56
- results = await npmSearch(paths, query);
57
- } catch (err) {
58
- const msg = err instanceof Error ? err.message : String(err);
59
- process.stderr.write(`[search] Search failed: ${msg}\n`);
60
- return 1;
61
- }
62
-
63
- // Filter to @amodalai/ packages only
64
- results = results.filter((r) => r.name.startsWith('@amodalai/'));
65
-
66
- // Filter by tag if specified (match against npm name prefix)
67
- if (options.tag) {
68
- const prefix = `@amodalai/${options.tag}-`;
69
- results = results.filter((r) => r.name.startsWith(prefix));
70
- }
71
-
72
- if (results.length === 0) {
73
- process.stderr.write('[search] No packages found.\n');
74
- return 0;
75
- }
76
-
77
- if (options.json) {
78
- const output = results.map((r) => ({
79
- npm: r.name,
80
- name: fromNpmName(r.name),
81
- version: r.version,
82
- description: r.description,
83
- }));
84
- process.stdout.write(JSON.stringify(output, null, 2) + '\n');
85
- return 0;
86
- }
87
-
88
- // Display results
89
- const nameWidth = Math.max(4, ...results.map((r) => fromNpmName(r.name).length));
90
- const versionWidth = Math.max(7, ...results.map((r) => r.version.length));
91
-
92
- for (const r of results) {
93
- const shortName = fromNpmName(r.name);
94
- process.stdout.write(` ${shortName.padEnd(nameWidth)} ${r.version.padEnd(versionWidth)} ${r.description}\n`);
95
- }
96
-
97
- process.stderr.write(`\n[search] ${results.length} package${results.length === 1 ? '' : 's'} found.\n`);
98
- return 0;
99
- }
100
-
101
- export const searchCommand: CommandModule = {
102
- command: 'search [query]',
103
- describe: 'Search the registry for packages',
104
- builder: (yargs) =>
105
- yargs
106
- .positional('query', {type: 'string', describe: 'Search query'})
107
- .option('tag', {type: 'string', describe: 'Filter by tag prefix (e.g., connection, skill)'})
108
- .option('json', {type: 'boolean', default: false, describe: 'Output as JSON'}),
109
- handler: async (argv) => {
110
- const code = await runSearch({
111
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
112
- query: argv['query'] as string | undefined,
113
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
114
- tag: argv['tag'] as string | undefined,
115
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
116
- json: argv['json'] as boolean,
117
- });
118
- process.exit(code);
119
- },
120
- };
@@ -1,256 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Amodal Labs, Inc.
4
- * SPDX-License-Identifier: MIT
5
- */
6
-
7
- import {describe, it, expect, vi, beforeEach} from 'vitest';
8
-
9
- const mockFindRepoRoot = vi.fn(() => '/test/repo');
10
- const mockEnsureNpmContext = vi.fn();
11
- const mockReadLockFile = vi.fn();
12
- const mockNpmViewVersions = vi.fn();
13
- const mockNpmInstall = vi.fn();
14
- const mockFromNpmName = vi.fn((npm: string) => npm.replace('@amodalai/', ''));
15
- const mockDiscoverInstalledPackages = vi.fn();
16
- const mockBuildLockFile = vi.fn();
17
-
18
- vi.mock('../shared/repo-discovery.js', () => ({
19
- findRepoRoot: mockFindRepoRoot,
20
- }));
21
-
22
- vi.mock('@amodalai/core', () => ({
23
- ensureNpmContext: mockEnsureNpmContext,
24
- readLockFile: mockReadLockFile,
25
- npmViewVersions: mockNpmViewVersions,
26
- npmInstall: mockNpmInstall,
27
- fromNpmName: mockFromNpmName,
28
- discoverInstalledPackages: mockDiscoverInstalledPackages,
29
- buildLockFile: mockBuildLockFile,
30
- }));
31
-
32
- vi.mock('semver', () => ({
33
- maxSatisfying: vi.fn((versions: string[], range: string) => {
34
- // Simple mock: return the last version for '*', first matching for '^'
35
- if (range === '*') return versions[versions.length - 1];
36
- // For ^x.y.z, return last version with same major
37
- const major = range.replace('^', '').split('.')[0];
38
- const matching = versions.filter((v) => v.startsWith(major + '.'));
39
- return matching.length > 0 ? matching[matching.length - 1] : null;
40
- }),
41
- }));
42
-
43
- const mockPaths = {
44
- root: '/test/repo/.amodal/packages',
45
- npmDir: '/test/repo/.amodal/packages/.npm',
46
- npmrc: '/test/repo/.amodal/packages/.npm/.npmrc',
47
- packageJson: '/test/repo/.amodal/packages/.npm/package.json',
48
- nodeModules: '/test/repo/.amodal/packages/.npm/node_modules',
49
- };
50
-
51
- describe('runUpdate', () => {
52
- let stderrOutput: string;
53
-
54
- beforeEach(() => {
55
- vi.clearAllMocks();
56
- mockFindRepoRoot.mockReturnValue('/test/repo');
57
- mockEnsureNpmContext.mockResolvedValue(mockPaths);
58
- mockNpmInstall.mockResolvedValue({version: '2.0.0', integrity: 'sha512-new'});
59
- mockDiscoverInstalledPackages.mockResolvedValue([]);
60
- mockBuildLockFile.mockResolvedValue(undefined);
61
- stderrOutput = '';
62
- vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
63
- stderrOutput += String(chunk);
64
- return true;
65
- });
66
- });
67
-
68
- it('should update all packages', async () => {
69
- mockReadLockFile.mockResolvedValue({
70
- lockVersion: 2,
71
- packages: {
72
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
73
- },
74
- });
75
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0', '1.2.0']);
76
-
77
- const {runUpdate} = await import('./update.js');
78
- const result = await runUpdate();
79
- expect(result).toBe(0);
80
- expect(mockNpmInstall).toHaveBeenCalled();
81
- expect(stderrOutput).toContain('Updated');
82
- });
83
-
84
- it('should update single package by name', async () => {
85
- mockReadLockFile.mockResolvedValue({
86
- lockVersion: 2,
87
- packages: {
88
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
89
- '@amodalai/skill-triage': {version: '1.0.0', integrity: 'sha512-old'},
90
- },
91
- });
92
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0']);
93
-
94
- const {runUpdate} = await import('./update.js');
95
- const result = await runUpdate({name: 'connection-salesforce'});
96
- expect(result).toBe(0);
97
- expect(mockNpmViewVersions).toHaveBeenCalledTimes(1);
98
- });
99
-
100
- it('should report already up to date', async () => {
101
- mockReadLockFile.mockResolvedValue({
102
- lockVersion: 2,
103
- packages: {
104
- '@amodalai/connection-salesforce': {version: '1.2.0', integrity: 'sha512-old'},
105
- },
106
- });
107
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0', '1.2.0']);
108
-
109
- const {runUpdate} = await import('./update.js');
110
- const result = await runUpdate();
111
- expect(result).toBe(0);
112
- expect(stderrOutput).toContain('up to date');
113
- expect(mockNpmInstall).not.toHaveBeenCalled();
114
- });
115
-
116
- it('should show dry run output', async () => {
117
- mockReadLockFile.mockResolvedValue({
118
- lockVersion: 2,
119
- packages: {
120
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
121
- },
122
- });
123
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0']);
124
-
125
- const {runUpdate} = await import('./update.js');
126
- const result = await runUpdate({dryRun: true});
127
- expect(result).toBe(0);
128
- expect(stderrOutput).toContain('Dry run');
129
- expect(stderrOutput).toContain('1.1.0');
130
- expect(mockNpmInstall).not.toHaveBeenCalled();
131
- });
132
-
133
- it('should use latest flag for cross-major updates', async () => {
134
- mockReadLockFile.mockResolvedValue({
135
- lockVersion: 2,
136
- packages: {
137
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
138
- },
139
- });
140
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0', '2.0.0', '3.0.0']);
141
-
142
- const {runUpdate} = await import('./update.js');
143
- const result = await runUpdate({latest: true});
144
- expect(result).toBe(0);
145
- expect(stderrOutput).toContain('3.0.0');
146
- });
147
-
148
- it('should return 1 when no lock file', async () => {
149
- mockReadLockFile.mockResolvedValue(null);
150
-
151
- const {runUpdate} = await import('./update.js');
152
- const result = await runUpdate();
153
- expect(result).toBe(1);
154
- expect(stderrOutput).toContain('No lock file');
155
- });
156
-
157
- it('should return 1 when repo not found', async () => {
158
- mockFindRepoRoot.mockImplementation(() => {
159
- throw new Error('Not found');
160
- });
161
-
162
- const {runUpdate} = await import('./update.js');
163
- const result = await runUpdate();
164
- expect(result).toBe(1);
165
- expect(stderrOutput).toContain('Not found');
166
- });
167
-
168
- it('should handle registry unreachable', async () => {
169
- mockReadLockFile.mockResolvedValue({
170
- lockVersion: 2,
171
- packages: {
172
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
173
- },
174
- });
175
- mockNpmViewVersions.mockRejectedValue(new Error('Registry unreachable'));
176
-
177
- const {runUpdate} = await import('./update.js');
178
- const result = await runUpdate();
179
- expect(result).toBe(1);
180
- expect(stderrOutput).toContain('Failed to check');
181
- });
182
-
183
- it('should continue on partial failure', async () => {
184
- mockReadLockFile.mockResolvedValue({
185
- lockVersion: 2,
186
- packages: {
187
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
188
- '@amodalai/skill-triage': {version: '1.0.0', integrity: 'sha512-old'},
189
- },
190
- });
191
- mockNpmViewVersions
192
- .mockResolvedValueOnce(['1.0.0', '1.1.0'])
193
- .mockResolvedValueOnce(['1.0.0', '1.1.0']);
194
- mockNpmInstall
195
- .mockRejectedValueOnce(new Error('Install failed'))
196
- .mockResolvedValueOnce({version: '1.1.0', integrity: 'sha512-new'});
197
-
198
- const {runUpdate} = await import('./update.js');
199
- const result = await runUpdate();
200
- expect(result).toBe(1);
201
- expect(stderrOutput).toContain('1 of 2');
202
- });
203
-
204
- it('should handle empty lock file', async () => {
205
- mockReadLockFile.mockResolvedValue({lockVersion: 2, packages: {}});
206
-
207
- const {runUpdate} = await import('./update.js');
208
- const result = await runUpdate();
209
- expect(result).toBe(0);
210
- expect(stderrOutput).toContain('No packages installed');
211
- });
212
-
213
- it('should report no matching packages', async () => {
214
- mockReadLockFile.mockResolvedValue({
215
- lockVersion: 2,
216
- packages: {
217
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
218
- },
219
- });
220
-
221
- const {runUpdate} = await import('./update.js');
222
- const result = await runUpdate({name: 'nonexistent'});
223
- expect(result).toBe(0);
224
- expect(stderrOutput).toContain('No matching packages');
225
- });
226
-
227
- it('should rebuild lock file after update', async () => {
228
- mockReadLockFile.mockResolvedValue({
229
- lockVersion: 2,
230
- packages: {
231
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
232
- },
233
- });
234
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0']);
235
-
236
- const {runUpdate} = await import('./update.js');
237
- await runUpdate();
238
- expect(mockDiscoverInstalledPackages).toHaveBeenCalledWith(mockPaths);
239
- expect(mockBuildLockFile).toHaveBeenCalled();
240
- });
241
-
242
- it('should use singular form for 1 package', async () => {
243
- mockReadLockFile.mockResolvedValue({
244
- lockVersion: 2,
245
- packages: {
246
- '@amodalai/connection-salesforce': {version: '1.0.0', integrity: 'sha512-old'},
247
- },
248
- });
249
- mockNpmViewVersions.mockResolvedValue(['1.0.0', '1.1.0']);
250
-
251
- const {runUpdate} = await import('./update.js');
252
- await runUpdate();
253
- expect(stderrOutput).toContain('1 package updated');
254
- expect(stderrOutput).not.toContain('1 packages');
255
- });
256
- });