@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/diff.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {join} from 'node:path';
|
|
8
|
-
|
|
9
|
-
import type {CommandModule} from 'yargs';
|
|
10
|
-
import {
|
|
11
|
-
ensureNpmContext,
|
|
12
|
-
fromNpmName,
|
|
13
|
-
getLockEntry,
|
|
14
|
-
getNpmContextPaths,
|
|
15
|
-
npmView,
|
|
16
|
-
readPackageFile,
|
|
17
|
-
listPackageFiles,
|
|
18
|
-
toNpmName,
|
|
19
|
-
} from '@amodalai/core';
|
|
20
|
-
import {findRepoRoot} from '../shared/repo-discovery.js';
|
|
21
|
-
|
|
22
|
-
export interface DiffOptions {
|
|
23
|
-
cwd?: string;
|
|
24
|
-
name: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Show diff between installed version and latest available.
|
|
29
|
-
* Returns 0 on success, 1 on error.
|
|
30
|
-
*/
|
|
31
|
-
export async function runDiff(options: DiffOptions): Promise<number> {
|
|
32
|
-
let repoPath: string;
|
|
33
|
-
try {
|
|
34
|
-
repoPath = findRepoRoot(options.cwd);
|
|
35
|
-
} catch (err) {
|
|
36
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
37
|
-
process.stderr.write(`[diff] ${msg}\n`);
|
|
38
|
-
return 1;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const paths = await ensureNpmContext(repoPath);
|
|
42
|
-
const npmName = toNpmName(options.name);
|
|
43
|
-
const shortName = fromNpmName(npmName);
|
|
44
|
-
|
|
45
|
-
// Check if installed
|
|
46
|
-
const lockEntry = await getLockEntry(repoPath, npmName);
|
|
47
|
-
if (!lockEntry) {
|
|
48
|
-
process.stderr.write(`[diff] Package ${npmName} is not installed.\n`);
|
|
49
|
-
return 1;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Get latest version info
|
|
53
|
-
let latestVersion: string;
|
|
54
|
-
try {
|
|
55
|
-
const viewResult = await npmView(paths, npmName);
|
|
56
|
-
latestVersion = viewResult.version;
|
|
57
|
-
} catch (err) {
|
|
58
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
59
|
-
process.stderr.write(`[diff] Failed to query registry: ${msg}\n`);
|
|
60
|
-
return 1;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (latestVersion === lockEntry.version) {
|
|
64
|
-
process.stderr.write(`[diff] ${npmName}@${lockEntry.version} is already the latest version.\n`);
|
|
65
|
-
return 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
process.stderr.write(`[diff] Comparing ${npmName}: ${lockEntry.version} (installed) → ${latestVersion} (latest)\n`);
|
|
69
|
-
|
|
70
|
-
// Get installed package directory
|
|
71
|
-
const contextPaths = getNpmContextPaths(repoPath);
|
|
72
|
-
const packageDir = join(contextPaths.nodeModules, npmName);
|
|
73
|
-
|
|
74
|
-
// List installed files
|
|
75
|
-
let installedFiles: string[];
|
|
76
|
-
try {
|
|
77
|
-
installedFiles = await listPackageFiles(packageDir);
|
|
78
|
-
} catch {
|
|
79
|
-
installedFiles = [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Print diff report
|
|
83
|
-
process.stdout.write(`\n ${npmName}\n`);
|
|
84
|
-
process.stdout.write(` Installed: ${lockEntry.version}\n`);
|
|
85
|
-
process.stdout.write(` Latest: ${latestVersion}\n\n`);
|
|
86
|
-
|
|
87
|
-
// Show file-level summary
|
|
88
|
-
process.stdout.write(' Files in installed version:\n');
|
|
89
|
-
for (const file of installedFiles) {
|
|
90
|
-
const content = await readPackageFile(packageDir, file);
|
|
91
|
-
if (content !== null) {
|
|
92
|
-
const lineCount = content.split('\n').length;
|
|
93
|
-
let detail = `${lineCount} lines`;
|
|
94
|
-
|
|
95
|
-
// Add type-specific details
|
|
96
|
-
if (file === 'surface.md') {
|
|
97
|
-
const endpointCount = (content.match(/^##\s/gm) ?? []).length;
|
|
98
|
-
detail += `, ${endpointCount} endpoint${endpointCount === 1 ? '' : 's'}`;
|
|
99
|
-
} else if (file === 'spec.json') {
|
|
100
|
-
try {
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
102
|
-
const spec = JSON.parse(content) as Record<string, unknown>;
|
|
103
|
-
const keys = Object.keys(spec);
|
|
104
|
-
detail += `, keys: ${keys.join(', ')}`;
|
|
105
|
-
} catch {
|
|
106
|
-
// Not valid JSON
|
|
107
|
-
}
|
|
108
|
-
} else if (file === 'access.json') {
|
|
109
|
-
try {
|
|
110
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
111
|
-
const access = JSON.parse(content) as Record<string, unknown>;
|
|
112
|
-
const ruleCount = Object.keys(access).length;
|
|
113
|
-
detail += `, ${ruleCount} rule${ruleCount === 1 ? '' : 's'}`;
|
|
114
|
-
} catch {
|
|
115
|
-
// Not valid JSON
|
|
116
|
-
}
|
|
117
|
-
} else if (file === 'entities.md') {
|
|
118
|
-
const sectionCount = (content.match(/^##\s/gm) ?? []).length;
|
|
119
|
-
detail += `, ${sectionCount} entit${sectionCount === 1 ? 'y' : 'ies'}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
process.stdout.write(` ${file} (${detail})\n`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
process.stdout.write(`\n Run \`amodal update ${shortName}\` to upgrade.\n\n`);
|
|
127
|
-
return 0;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export const diffCommand: CommandModule = {
|
|
131
|
-
command: 'diff <name>',
|
|
132
|
-
describe: 'Show diff between installed and latest version',
|
|
133
|
-
builder: (yargs) =>
|
|
134
|
-
yargs
|
|
135
|
-
.positional('name', {type: 'string', demandOption: true, describe: 'Package name'}),
|
|
136
|
-
handler: async (argv) => {
|
|
137
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
138
|
-
const code = await runDiff({name: argv['name'] as string});
|
|
139
|
-
process.exit(code);
|
|
140
|
-
},
|
|
141
|
-
};
|
|
@@ -1,141 +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 mockListLockEntries = vi.fn();
|
|
11
|
-
const mockFromNpmName = vi.fn((npm: string) => npm.replace('@amodalai/', ''));
|
|
12
|
-
|
|
13
|
-
vi.mock('../shared/repo-discovery.js', () => ({
|
|
14
|
-
findRepoRoot: mockFindRepoRoot,
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
vi.mock('@amodalai/core', () => ({
|
|
18
|
-
listLockEntries: mockListLockEntries,
|
|
19
|
-
fromNpmName: mockFromNpmName,
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
describe('runList', () => {
|
|
23
|
-
let stdoutOutput: string;
|
|
24
|
-
let stderrOutput: string;
|
|
25
|
-
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
mockFindRepoRoot.mockReturnValue('/test/repo');
|
|
29
|
-
stdoutOutput = '';
|
|
30
|
-
stderrOutput = '';
|
|
31
|
-
vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
|
|
32
|
-
stdoutOutput += String(chunk);
|
|
33
|
-
return true;
|
|
34
|
-
});
|
|
35
|
-
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
36
|
-
stderrOutput += String(chunk);
|
|
37
|
-
return true;
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should print "No packages installed" when no entries', async () => {
|
|
42
|
-
mockListLockEntries.mockResolvedValue([]);
|
|
43
|
-
|
|
44
|
-
const {runList} = await import('./list.js');
|
|
45
|
-
const result = await runList();
|
|
46
|
-
expect(result).toBe(0);
|
|
47
|
-
expect(stderrOutput).toContain('No packages installed');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should print entries as formatted table', async () => {
|
|
51
|
-
mockListLockEntries.mockResolvedValue([
|
|
52
|
-
{npmName: '@amodalai/connection-salesforce', entry: {version: '2.1.0', integrity: 'sha512-abc'}},
|
|
53
|
-
{npmName: '@amodalai/skill-triage', entry: {version: '1.0.3', integrity: 'sha512-def'}},
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
const {runList} = await import('./list.js');
|
|
57
|
-
const result = await runList();
|
|
58
|
-
expect(result).toBe(0);
|
|
59
|
-
expect(stdoutOutput).toContain('NAME');
|
|
60
|
-
expect(stdoutOutput).toContain('connection-salesforce');
|
|
61
|
-
expect(stdoutOutput).toContain('skill-triage');
|
|
62
|
-
expect(stderrOutput).toContain('2 packages installed');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should filter by name substring', async () => {
|
|
66
|
-
mockListLockEntries.mockResolvedValue([]);
|
|
67
|
-
|
|
68
|
-
const {runList} = await import('./list.js');
|
|
69
|
-
const result = await runList({filter: 'connection'});
|
|
70
|
-
expect(result).toBe(0);
|
|
71
|
-
expect(mockListLockEntries).toHaveBeenCalledWith('/test/repo');
|
|
72
|
-
expect(stderrOutput).toContain('No packages matching "connection" installed');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should output JSON when json option set', async () => {
|
|
76
|
-
mockListLockEntries.mockResolvedValue([
|
|
77
|
-
{npmName: '@amodalai/connection-stripe', entry: {version: '1.0.0', integrity: 'sha512-xyz'}},
|
|
78
|
-
]);
|
|
79
|
-
|
|
80
|
-
const {runList} = await import('./list.js');
|
|
81
|
-
const result = await runList({json: true});
|
|
82
|
-
expect(result).toBe(0);
|
|
83
|
-
|
|
84
|
-
const parsed = JSON.parse(stdoutOutput);
|
|
85
|
-
expect(parsed).toHaveLength(1);
|
|
86
|
-
expect(parsed[0]).toMatchObject({name: 'connection-stripe', version: '1.0.0'});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should return 1 when repo not found', async () => {
|
|
90
|
-
mockFindRepoRoot.mockImplementation(() => {
|
|
91
|
-
throw new Error('Not found');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const {runList} = await import('./list.js');
|
|
95
|
-
const result = await runList();
|
|
96
|
-
expect(result).toBe(1);
|
|
97
|
-
expect(stderrOutput).toContain('Not found');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should handle single package with correct count', async () => {
|
|
101
|
-
mockListLockEntries.mockResolvedValue([
|
|
102
|
-
{npmName: '@amodalai/connection-datadog', entry: {version: '3.0.0', integrity: 'sha512-ghi'}},
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
const {runList} = await import('./list.js');
|
|
106
|
-
const result = await runList();
|
|
107
|
-
expect(result).toBe(0);
|
|
108
|
-
expect(stderrOutput).toContain('1 package installed');
|
|
109
|
-
expect(stderrOutput).not.toContain('1 packages');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should pass cwd to findRepoRoot', async () => {
|
|
113
|
-
mockListLockEntries.mockResolvedValue([]);
|
|
114
|
-
|
|
115
|
-
const {runList} = await import('./list.js');
|
|
116
|
-
await runList({cwd: '/custom/dir'});
|
|
117
|
-
expect(mockFindRepoRoot).toHaveBeenCalledWith('/custom/dir');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should include integrity in JSON output', async () => {
|
|
121
|
-
mockListLockEntries.mockResolvedValue([
|
|
122
|
-
{npmName: '@amodalai/skill-analyze', entry: {version: '1.0.0', integrity: 'sha512-integrity-hash'}},
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
const {runList} = await import('./list.js');
|
|
126
|
-
await runList({json: true});
|
|
127
|
-
|
|
128
|
-
const parsed = JSON.parse(stdoutOutput);
|
|
129
|
-
expect(parsed[0]['integrity']).toBe('sha512-integrity-hash');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should handle json output with empty list', async () => {
|
|
133
|
-
mockListLockEntries.mockResolvedValue([]);
|
|
134
|
-
|
|
135
|
-
const {runList} = await import('./list.js');
|
|
136
|
-
const result = await runList({json: true});
|
|
137
|
-
expect(result).toBe(0);
|
|
138
|
-
// No JSON output when empty — prints "no packages" to stderr instead
|
|
139
|
-
expect(stderrOutput).toContain('No packages installed');
|
|
140
|
-
});
|
|
141
|
-
});
|
package/src/commands/list.ts
DELETED
|
@@ -1,99 +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 {fromNpmName, listLockEntries} from '@amodalai/core';
|
|
9
|
-
import {findRepoRoot} from '../shared/repo-discovery.js';
|
|
10
|
-
|
|
11
|
-
export interface ListOptions {
|
|
12
|
-
cwd?: string;
|
|
13
|
-
filter?: string;
|
|
14
|
-
json?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* List installed packages from the lock file.
|
|
19
|
-
* Returns 0 on success, 1 on error.
|
|
20
|
-
*/
|
|
21
|
-
export async function runList(options: ListOptions = {}): Promise<number> {
|
|
22
|
-
let repoPath: string;
|
|
23
|
-
try {
|
|
24
|
-
repoPath = findRepoRoot(options.cwd);
|
|
25
|
-
} catch (err) {
|
|
26
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
27
|
-
process.stderr.write(`[list] ${msg}\n`);
|
|
28
|
-
return 1;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let entries = await listLockEntries(repoPath);
|
|
32
|
-
|
|
33
|
-
// Optional string filter on npm name
|
|
34
|
-
if (options.filter) {
|
|
35
|
-
const f = options.filter.toLowerCase();
|
|
36
|
-
entries = entries.filter((e) => e.npmName.toLowerCase().includes(f));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (entries.length === 0) {
|
|
40
|
-
if (options.filter) {
|
|
41
|
-
process.stderr.write(`[list] No packages matching "${options.filter}" installed.\n`);
|
|
42
|
-
} else {
|
|
43
|
-
process.stderr.write('[list] No packages installed.\n');
|
|
44
|
-
}
|
|
45
|
-
return 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (options.json) {
|
|
49
|
-
const output = entries.map((e) => ({
|
|
50
|
-
name: fromNpmName(e.npmName),
|
|
51
|
-
npmName: e.npmName,
|
|
52
|
-
version: e.entry.version,
|
|
53
|
-
integrity: e.entry.integrity,
|
|
54
|
-
}));
|
|
55
|
-
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
56
|
-
return 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Formatted table
|
|
60
|
-
const names = entries.map((e) => fromNpmName(e.npmName));
|
|
61
|
-
const nameWidth = Math.max(4, ...names.map((n) => n.length));
|
|
62
|
-
const npmWidth = Math.max(3, ...entries.map((e) => e.npmName.length));
|
|
63
|
-
const versionWidth = Math.max(7, ...entries.map((e) => e.entry.version.length));
|
|
64
|
-
|
|
65
|
-
const header = [
|
|
66
|
-
'NAME'.padEnd(nameWidth),
|
|
67
|
-
'VERSION'.padEnd(versionWidth),
|
|
68
|
-
'NPM'.padEnd(npmWidth),
|
|
69
|
-
].join(' ');
|
|
70
|
-
|
|
71
|
-
process.stdout.write(header + '\n');
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < entries.length; i++) {
|
|
74
|
-
const e = entries[i];
|
|
75
|
-
const row = [
|
|
76
|
-
names[i].padEnd(nameWidth),
|
|
77
|
-
e.entry.version.padEnd(versionWidth),
|
|
78
|
-
e.npmName.padEnd(npmWidth),
|
|
79
|
-
].join(' ');
|
|
80
|
-
process.stdout.write(row + '\n');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
process.stderr.write(`[list] ${entries.length} package${entries.length === 1 ? '' : 's'} installed.\n`);
|
|
84
|
-
return 0;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export const listCommand: CommandModule = {
|
|
88
|
-
command: 'list',
|
|
89
|
-
describe: 'List installed packages',
|
|
90
|
-
builder: (yargs) =>
|
|
91
|
-
yargs
|
|
92
|
-
.option('filter', {type: 'string', describe: 'Filter by name substring'})
|
|
93
|
-
.option('json', {type: 'boolean', default: false, describe: 'Output as JSON'}),
|
|
94
|
-
handler: async (argv) => {
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
96
|
-
const code = await runList({filter: argv['filter'] as string | undefined, json: argv['json'] as boolean});
|
|
97
|
-
process.exit(code);
|
|
98
|
-
},
|
|
99
|
-
};
|
|
@@ -1,169 +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 mockStat = vi.fn();
|
|
10
|
-
const mockReadFile = vi.fn();
|
|
11
|
-
const mockReaddir = vi.fn();
|
|
12
|
-
const mockExecFile = vi.fn();
|
|
13
|
-
|
|
14
|
-
vi.mock('node:fs/promises', () => ({
|
|
15
|
-
stat: mockStat,
|
|
16
|
-
readFile: mockReadFile,
|
|
17
|
-
readdir: mockReaddir,
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
vi.mock('node:child_process', () => ({
|
|
21
|
-
execFile: mockExecFile,
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
const validPkgJson = JSON.stringify({
|
|
25
|
-
name: '@amodalai/connection-salesforce',
|
|
26
|
-
version: '1.0.0',
|
|
27
|
-
amodal: {name: 'salesforce', tags: ['connection']},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('runPublish', () => {
|
|
31
|
-
let stderrOutput: string;
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
vi.clearAllMocks();
|
|
35
|
-
mockStat.mockResolvedValue({isFile: () => true, isDirectory: () => true});
|
|
36
|
-
mockReadFile.mockResolvedValue(validPkgJson);
|
|
37
|
-
mockReaddir.mockResolvedValue([]);
|
|
38
|
-
stderrOutput = '';
|
|
39
|
-
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
40
|
-
stderrOutput += String(chunk);
|
|
41
|
-
return true;
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should publish successfully', async () => {
|
|
46
|
-
mockExecFile.mockImplementation(
|
|
47
|
-
(_cmd: string, _args: string[], _opts: unknown, cb: (err: Error | null, result: {stdout: string; stderr: string}) => void) => {
|
|
48
|
-
cb(null, {stdout: '', stderr: ''});
|
|
49
|
-
return {};
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const {runPublish} = await import('./publish.js');
|
|
54
|
-
const result = await runPublish();
|
|
55
|
-
expect(result).toBe(0);
|
|
56
|
-
expect(stderrOutput).toContain('Published');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should show dry run output', async () => {
|
|
60
|
-
const {runPublish} = await import('./publish.js');
|
|
61
|
-
const result = await runPublish({dryRun: true});
|
|
62
|
-
expect(result).toBe(0);
|
|
63
|
-
expect(stderrOutput).toContain('Dry run');
|
|
64
|
-
expect(stderrOutput).toContain('salesforce');
|
|
65
|
-
expect(mockExecFile).not.toHaveBeenCalled();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should return 1 when package.json is unreadable', async () => {
|
|
69
|
-
mockReadFile.mockRejectedValue(new Error('ENOENT'));
|
|
70
|
-
|
|
71
|
-
const {runPublish} = await import('./publish.js');
|
|
72
|
-
const result = await runPublish();
|
|
73
|
-
expect(result).toBe(1);
|
|
74
|
-
expect(stderrOutput).toContain('Failed to read package.json');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should return 1 when amodal block missing', async () => {
|
|
78
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
79
|
-
name: '@amodalai/test',
|
|
80
|
-
version: '1.0.0',
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
const {runPublish} = await import('./publish.js');
|
|
84
|
-
const result = await runPublish();
|
|
85
|
-
expect(result).toBe(1);
|
|
86
|
-
expect(stderrOutput).toContain('amodal');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should return 1 when amodal name missing', async () => {
|
|
90
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
91
|
-
name: '@amodalai/test',
|
|
92
|
-
version: '1.0.0',
|
|
93
|
-
amodal: {tags: ['skill']},
|
|
94
|
-
}));
|
|
95
|
-
|
|
96
|
-
const {runPublish} = await import('./publish.js');
|
|
97
|
-
const result = await runPublish();
|
|
98
|
-
expect(result).toBe(1);
|
|
99
|
-
expect(stderrOutput).toContain('name');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should handle npm publish failure', async () => {
|
|
103
|
-
mockExecFile.mockImplementation(
|
|
104
|
-
(_cmd: string, _args: string[], _opts: unknown, cb: (err: Error | null) => void) => {
|
|
105
|
-
cb(new Error('Network error'));
|
|
106
|
-
return {};
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const {runPublish} = await import('./publish.js');
|
|
111
|
-
const result = await runPublish();
|
|
112
|
-
expect(result).toBe(1);
|
|
113
|
-
expect(stderrOutput).toContain('Publish failed');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should handle already published (409)', async () => {
|
|
117
|
-
mockExecFile.mockImplementation(
|
|
118
|
-
(_cmd: string, _args: string[], _opts: unknown, cb: (err: Error | null) => void) => {
|
|
119
|
-
cb(new Error('EPUBLISHCONFLICT'));
|
|
120
|
-
return {};
|
|
121
|
-
},
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const {runPublish} = await import('./publish.js');
|
|
125
|
-
const result = await runPublish();
|
|
126
|
-
expect(result).toBe(1);
|
|
127
|
-
expect(stderrOutput).toContain('already exists');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should suggest npm login on auth error', async () => {
|
|
131
|
-
mockExecFile.mockImplementation(
|
|
132
|
-
(_cmd: string, _args: string[], _opts: unknown, cb: (err: Error | null) => void) => {
|
|
133
|
-
cb(new Error('ENEEDAUTH'));
|
|
134
|
-
return {};
|
|
135
|
-
},
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const {runPublish} = await import('./publish.js');
|
|
139
|
-
const result = await runPublish();
|
|
140
|
-
expect(result).toBe(1);
|
|
141
|
-
expect(stderrOutput).toContain('npm login');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should use custom registry', async () => {
|
|
145
|
-
mockExecFile.mockImplementation(
|
|
146
|
-
(_cmd: string, args: string[], _opts: unknown, cb: (err: Error | null, result: {stdout: string; stderr: string}) => void) => {
|
|
147
|
-
expect(args).toContain('https://custom.registry.com');
|
|
148
|
-
cb(null, {stdout: '', stderr: ''});
|
|
149
|
-
return {};
|
|
150
|
-
},
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const {runPublish} = await import('./publish.js');
|
|
154
|
-
const result = await runPublish({registry: 'https://custom.registry.com'});
|
|
155
|
-
expect(result).toBe(0);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should show package name and version in dry run', async () => {
|
|
159
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
160
|
-
name: '@amodalai/connection-stripe',
|
|
161
|
-
version: '3.2.1',
|
|
162
|
-
amodal: {name: 'stripe', tags: ['connection']},
|
|
163
|
-
}));
|
|
164
|
-
|
|
165
|
-
const {runPublish} = await import('./publish.js');
|
|
166
|
-
await runPublish({dryRun: true});
|
|
167
|
-
expect(stderrOutput).toContain('@amodalai/connection-stripe@3.2.1');
|
|
168
|
-
});
|
|
169
|
-
});
|
package/src/commands/publish.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {readFile, readdir, stat, writeFile} from 'node:fs/promises';
|
|
8
|
-
import {execFile} from 'node:child_process';
|
|
9
|
-
import * as path from 'node:path';
|
|
10
|
-
import {promisify} from 'node:util';
|
|
11
|
-
|
|
12
|
-
import type {CommandModule} from 'yargs';
|
|
13
|
-
import {KNOWN_CONTENT_DIRS} from '@amodalai/core';
|
|
14
|
-
|
|
15
|
-
const execFileAsync = promisify(execFile);
|
|
16
|
-
|
|
17
|
-
const DEFAULT_REGISTRY = 'https://registry.amodalai.com';
|
|
18
|
-
|
|
19
|
-
export interface PublishOptions {
|
|
20
|
-
cwd?: string;
|
|
21
|
-
dryRun?: boolean;
|
|
22
|
-
registry?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Publish a package to the registry.
|
|
27
|
-
* Returns 0 on success, 1 on error.
|
|
28
|
-
*/
|
|
29
|
-
export async function runPublish(options: PublishOptions = {}): Promise<number> {
|
|
30
|
-
const cwd = options.cwd ?? process.cwd();
|
|
31
|
-
const registry = options.registry ?? DEFAULT_REGISTRY;
|
|
32
|
-
|
|
33
|
-
// Validate package.json exists
|
|
34
|
-
const pkgJsonPath = path.join(cwd, 'package.json');
|
|
35
|
-
let pkgName: string;
|
|
36
|
-
let pkgVersion: string;
|
|
37
|
-
let manifestName: string;
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const content = await readFile(pkgJsonPath, 'utf-8');
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- config JSON
|
|
42
|
-
const pkg = JSON.parse(content) as Record<string, unknown>;
|
|
43
|
-
pkgName = String(pkg['name'] ?? '');
|
|
44
|
-
pkgVersion = String(pkg['version'] ?? '');
|
|
45
|
-
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
47
|
-
const amodal = pkg['amodal'] as Record<string, unknown> | undefined;
|
|
48
|
-
if (!amodal || typeof amodal !== 'object' || !amodal['name']) {
|
|
49
|
-
process.stderr.write('[publish] package.json must have an "amodal" block with a "name" field.\n');
|
|
50
|
-
return 1;
|
|
51
|
-
}
|
|
52
|
-
manifestName = String(amodal['name']);
|
|
53
|
-
} catch (err) {
|
|
54
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
-
process.stderr.write(`[publish] Failed to read package.json: ${msg}\n`);
|
|
56
|
-
return 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Scan for known content directories and auto-populate `contains`
|
|
60
|
-
const contains: string[] = [];
|
|
61
|
-
for (const dir of KNOWN_CONTENT_DIRS) {
|
|
62
|
-
try {
|
|
63
|
-
const s = await stat(path.join(cwd, dir));
|
|
64
|
-
if (s.isDirectory()) {
|
|
65
|
-
const entries = await readdir(path.join(cwd, dir));
|
|
66
|
-
if (entries.length > 0) contains.push(dir);
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// Directory doesn't exist
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (contains.length === 0) {
|
|
74
|
-
process.stderr.write('[publish] Warning: no known amodal directories found (connections, skills, etc.).\n');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Write `contains` into package.json before publishing
|
|
78
|
-
try {
|
|
79
|
-
const rawContent = await readFile(pkgJsonPath, 'utf-8');
|
|
80
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- config JSON
|
|
81
|
-
const pkgObj = JSON.parse(rawContent) as Record<string, unknown>;
|
|
82
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
83
|
-
const amodal = pkgObj['amodal'] as Record<string, unknown>;
|
|
84
|
-
amodal['contains'] = contains;
|
|
85
|
-
await writeFile(pkgJsonPath, JSON.stringify(pkgObj, null, 2) + '\n', 'utf-8');
|
|
86
|
-
} catch {
|
|
87
|
-
// Non-fatal — publish will proceed without contains
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (options.dryRun) {
|
|
91
|
-
process.stderr.write(`[publish] Dry run: would publish ${pkgName}@${pkgVersion}\n`);
|
|
92
|
-
process.stderr.write(`[publish] Name: ${manifestName}\n`);
|
|
93
|
-
process.stderr.write(`[publish] Contains: ${contains.length > 0 ? contains.join(', ') : '(none)'}\n`);
|
|
94
|
-
process.stderr.write(`[publish] Registry: ${registry}\n`);
|
|
95
|
-
return 0;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
process.stderr.write(`[publish] Publishing ${pkgName}@${pkgVersion} to ${registry}...\n`);
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
await execFileAsync(
|
|
102
|
-
'npm',
|
|
103
|
-
['publish', '--registry', registry],
|
|
104
|
-
{
|
|
105
|
-
cwd,
|
|
106
|
-
timeout: 120_000,
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
process.stderr.write(`[publish] Published ${pkgName}@${pkgVersion}\n`);
|
|
111
|
-
return 0;
|
|
112
|
-
} catch (err) {
|
|
113
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
114
|
-
if (msg.includes('403') || msg.includes('409') || msg.includes('EPUBLISHCONFLICT')) {
|
|
115
|
-
process.stderr.write(`[publish] Version ${pkgVersion} already exists. Bump the version and try again.\n`);
|
|
116
|
-
} else if (msg.includes('ENEEDAUTH') || msg.includes('E401')) {
|
|
117
|
-
process.stderr.write(`[publish] Not authenticated. Run \`npm login --registry ${registry}\` first.\n`);
|
|
118
|
-
} else {
|
|
119
|
-
process.stderr.write(`[publish] Publish failed: ${msg}\n`);
|
|
120
|
-
}
|
|
121
|
-
return 1;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const publishCommand: CommandModule = {
|
|
126
|
-
command: 'publish',
|
|
127
|
-
describe: 'Publish a package to the registry',
|
|
128
|
-
builder: (yargs) =>
|
|
129
|
-
yargs
|
|
130
|
-
.option('dry-run', {type: 'boolean', default: false, describe: 'Show what would be published'})
|
|
131
|
-
.option('registry', {type: 'string', describe: 'Registry URL'}),
|
|
132
|
-
handler: async (argv) => {
|
|
133
|
-
const code = await runPublish({
|
|
134
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
135
|
-
dryRun: argv['dryRun'] as boolean,
|
|
136
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
137
|
-
registry: argv['registry'] as string | undefined,
|
|
138
|
-
});
|
|
139
|
-
process.exit(code);
|
|
140
|
-
},
|
|
141
|
-
};
|