@nemus-cli/nemus 0.9.0 → 0.10.0
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 +18 -0
- package/README.md +10 -5
- package/bin/workspace.js +12 -0
- package/dist/commands/analyze-deps.js +5 -12
- package/dist/commands/archive.js +13 -18
- package/dist/commands/branch/create.js +5 -12
- package/dist/commands/branch/switch.js +14 -25
- package/dist/commands/cache/manager.js +13 -24
- package/dist/commands/cleanup.js +14 -26
- package/dist/commands/configure-claude.js +4 -8
- package/dist/commands/configure.js +38 -32
- package/dist/commands/dashboard/session-picker.js +19 -26
- package/dist/commands/dashboard/workspace-picker.js +19 -26
- package/dist/commands/delete.js +15 -30
- package/dist/commands/ghq-status.js +7 -12
- package/dist/commands/go.js +18 -27
- package/dist/commands/history.js +6 -13
- package/dist/commands/list.js +20 -29
- package/dist/commands/remove-repo.js +21 -29
- package/dist/commands/save-context.js +5 -10
- package/dist/commands/sessions.js +19 -25
- package/dist/commands/suite/create.js +27 -53
- package/dist/commands/suite/delete.js +13 -25
- package/dist/commands/suite/export.js +20 -36
- package/dist/commands/suite/import.js +9 -20
- package/dist/commands/suite/use.js +9 -17
- package/dist/utils/prompt.js +26 -0
- package/dist/utils/prompts.js +86 -118
- package/package.json +3 -6
- package/src/commands/analyze-deps.ts +5 -9
- package/src/commands/archive.ts +5 -7
- package/src/commands/branch/create.ts +5 -9
- package/src/commands/branch/switch.ts +14 -22
- package/src/commands/cache/manager.ts +13 -21
- package/src/commands/cleanup.ts +14 -23
- package/src/commands/configure-claude.ts +4 -5
- package/src/commands/configure.ts +35 -29
- package/src/commands/dashboard/session-picker.ts +6 -11
- package/src/commands/dashboard/workspace-picker.ts +6 -11
- package/src/commands/delete.test.ts +36 -42
- package/src/commands/delete.ts +15 -27
- package/src/commands/ghq-status.ts +5 -7
- package/src/commands/go.ts +18 -25
- package/src/commands/history.ts +6 -10
- package/src/commands/list.test.ts +19 -26
- package/src/commands/list.ts +24 -31
- package/src/commands/remove-repo.ts +11 -17
- package/src/commands/save-context.ts +3 -5
- package/src/commands/sessions.ts +6 -10
- package/src/commands/suite/create.ts +28 -50
- package/src/commands/suite/delete.ts +14 -23
- package/src/commands/suite/export.ts +20 -33
- package/src/commands/suite/import.ts +9 -17
- package/src/commands/suite/use.ts +9 -14
- package/src/utils/prompt.ts +16 -0
- package/src/utils/prompts.test.ts +70 -41
- package/src/utils/prompts.ts +98 -128
|
@@ -3,12 +3,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
3
3
|
const {
|
|
4
4
|
mockListWorkspaces,
|
|
5
5
|
mockGetWorkspaceSessions,
|
|
6
|
-
|
|
6
|
+
mockSearch,
|
|
7
7
|
mockWriteFile,
|
|
8
8
|
} = vi.hoisted(() => ({
|
|
9
9
|
mockListWorkspaces: vi.fn(),
|
|
10
10
|
mockGetWorkspaceSessions: vi.fn(),
|
|
11
|
-
|
|
11
|
+
mockSearch: vi.fn(),
|
|
12
12
|
mockWriteFile: vi.fn(),
|
|
13
13
|
}));
|
|
14
14
|
|
|
@@ -33,15 +33,8 @@ vi.mock('fs/promises', () => ({
|
|
|
33
33
|
writeFile: mockWriteFile,
|
|
34
34
|
}));
|
|
35
35
|
|
|
36
|
-
vi.mock('
|
|
37
|
-
|
|
38
|
-
prompt: mockPrompt,
|
|
39
|
-
registerPrompt: vi.fn(),
|
|
40
|
-
},
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
vi.mock('inquirer-autocomplete-prompt', () => ({
|
|
44
|
-
default: vi.fn(),
|
|
36
|
+
vi.mock('../utils/prompt', () => ({
|
|
37
|
+
search: mockSearch,
|
|
45
38
|
}));
|
|
46
39
|
|
|
47
40
|
vi.mock('fuzzy', () => ({
|
|
@@ -91,11 +84,11 @@ describe('list-workspaces main', () => {
|
|
|
91
84
|
|
|
92
85
|
it('lists workspaces and prompts for selection', async () => {
|
|
93
86
|
mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b'));
|
|
94
|
-
|
|
87
|
+
mockSearch.mockResolvedValueOnce('ws-a');
|
|
95
88
|
|
|
96
89
|
await main();
|
|
97
90
|
|
|
98
|
-
expect(
|
|
91
|
+
expect(mockSearch).toHaveBeenCalledTimes(1);
|
|
99
92
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
100
93
|
expect.stringContaining('.workspace-last-go'),
|
|
101
94
|
'/test-workspaces/ws-a',
|
|
@@ -109,7 +102,7 @@ describe('list-workspaces main', () => {
|
|
|
109
102
|
await main();
|
|
110
103
|
|
|
111
104
|
expect(logInfo).toHaveBeenCalledWith('No workspaces found');
|
|
112
|
-
expect(
|
|
105
|
+
expect(mockSearch).not.toHaveBeenCalled();
|
|
113
106
|
});
|
|
114
107
|
|
|
115
108
|
it('does not prompt when no archived workspaces exist', async () => {
|
|
@@ -119,12 +112,12 @@ describe('list-workspaces main', () => {
|
|
|
119
112
|
await main();
|
|
120
113
|
|
|
121
114
|
expect(logInfo).toHaveBeenCalledWith('No archived workspaces found');
|
|
122
|
-
expect(
|
|
115
|
+
expect(mockSearch).not.toHaveBeenCalled();
|
|
123
116
|
});
|
|
124
117
|
|
|
125
118
|
it('writes temp file on selection', async () => {
|
|
126
119
|
mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('my-workspace'));
|
|
127
|
-
|
|
120
|
+
mockSearch.mockResolvedValueOnce('my-workspace');
|
|
128
121
|
|
|
129
122
|
await main();
|
|
130
123
|
|
|
@@ -145,7 +138,7 @@ describe('list-workspaces main', () => {
|
|
|
145
138
|
lastActiveLabel: '2m ago',
|
|
146
139
|
agentType: 'pi',
|
|
147
140
|
}]);
|
|
148
|
-
|
|
141
|
+
mockSearch.mockResolvedValueOnce('my-workspace');
|
|
149
142
|
|
|
150
143
|
await main();
|
|
151
144
|
|
|
@@ -159,7 +152,7 @@ describe('list-workspaces main', () => {
|
|
|
159
152
|
it('does not write resume flag when workspace has no session', async () => {
|
|
160
153
|
mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('my-workspace'));
|
|
161
154
|
mockGetWorkspaceSessions.mockResolvedValueOnce([]);
|
|
162
|
-
|
|
155
|
+
mockSearch.mockResolvedValueOnce('my-workspace');
|
|
163
156
|
|
|
164
157
|
await main();
|
|
165
158
|
|
|
@@ -173,11 +166,11 @@ describe('list-workspaces main', () => {
|
|
|
173
166
|
process.argv = ['node', 'list-workspaces.js', '--archived'];
|
|
174
167
|
const archived = makeArchivedWorkspaceList('old-ws');
|
|
175
168
|
mockListWorkspaces.mockResolvedValueOnce(archived);
|
|
176
|
-
|
|
169
|
+
mockSearch.mockResolvedValueOnce('old-ws');
|
|
177
170
|
|
|
178
171
|
await main();
|
|
179
172
|
|
|
180
|
-
expect(
|
|
173
|
+
expect(mockSearch).toHaveBeenCalledTimes(1);
|
|
181
174
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
182
175
|
expect.stringContaining('.workspace-last-go'),
|
|
183
176
|
'/test-workspaces/old-ws',
|
|
@@ -188,7 +181,7 @@ describe('list-workspaces main', () => {
|
|
|
188
181
|
it('displays workspace name in the list output', async () => {
|
|
189
182
|
const logSpy = vi.spyOn(console, 'log');
|
|
190
183
|
mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('ws-a'));
|
|
191
|
-
|
|
184
|
+
mockSearch.mockResolvedValueOnce('ws-a');
|
|
192
185
|
|
|
193
186
|
await main();
|
|
194
187
|
|
|
@@ -203,7 +196,7 @@ describe('list-workspaces main', () => {
|
|
|
203
196
|
|
|
204
197
|
await main();
|
|
205
198
|
|
|
206
|
-
expect(
|
|
199
|
+
expect(mockSearch).not.toHaveBeenCalled();
|
|
207
200
|
expect(writeSpy).toHaveBeenCalledTimes(1);
|
|
208
201
|
const payload = JSON.parse(writeSpy.mock.calls[0][0] as string);
|
|
209
202
|
expect(payload.count).toBe(2);
|
|
@@ -220,7 +213,7 @@ describe('list-workspaces main', () => {
|
|
|
220
213
|
await main();
|
|
221
214
|
|
|
222
215
|
expect(logInfo).not.toHaveBeenCalled();
|
|
223
|
-
expect(
|
|
216
|
+
expect(mockSearch).not.toHaveBeenCalled();
|
|
224
217
|
const payload = JSON.parse(writeSpy.mock.calls[0][0] as string);
|
|
225
218
|
expect(payload).toEqual({ archived: false, count: 0, workspaces: [] });
|
|
226
219
|
writeSpy.mockRestore();
|
|
@@ -235,14 +228,14 @@ describe('list-workspaces main', () => {
|
|
|
235
228
|
lastActiveAt: new Date(),
|
|
236
229
|
lastActiveLabel: '5m ago',
|
|
237
230
|
}]);
|
|
238
|
-
|
|
231
|
+
mockSearch.mockResolvedValueOnce('has-session');
|
|
239
232
|
|
|
240
233
|
await main();
|
|
241
234
|
|
|
242
235
|
// The prompt source function should show has-session first
|
|
243
|
-
const promptCall =
|
|
236
|
+
const promptCall = mockSearch.mock.calls[0][0];
|
|
244
237
|
const source = promptCall.source;
|
|
245
|
-
const items = await source(
|
|
238
|
+
const items = await source('');
|
|
246
239
|
expect(items[0].value).toBe('has-session');
|
|
247
240
|
expect(items[1].value).toBe('no-session');
|
|
248
241
|
});
|
package/src/commands/list.ts
CHANGED
|
@@ -7,12 +7,9 @@ import { getWorkspaceSessions } from '../utils/claude-sessions';
|
|
|
7
7
|
import { logInfo, logError } from '../utils/logger';
|
|
8
8
|
import { outputJson } from '../utils/output';
|
|
9
9
|
import { colorize } from '../utils/colors';
|
|
10
|
-
import
|
|
11
|
-
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
10
|
+
import { search } from '../utils/prompt';
|
|
12
11
|
import * as fuzzy from 'fuzzy';
|
|
13
12
|
|
|
14
|
-
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
15
|
-
|
|
16
13
|
const TEMP_FILE = path.join(os.homedir(), '.workspace-last-go');
|
|
17
14
|
const RESUME_FLAG_FILE = path.join(os.homedir(), '.workspace-resume-session');
|
|
18
15
|
|
|
@@ -136,33 +133,29 @@ async function handleList(opts: { archived?: boolean; json?: boolean }) {
|
|
|
136
133
|
console.log('');
|
|
137
134
|
|
|
138
135
|
if (items.length > 0 && (process.stdout.isTTY || process.env.VITEST === 'true')) {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
},
|
|
163
|
-
pageSize: 15,
|
|
164
|
-
} as any,
|
|
165
|
-
]);
|
|
136
|
+
const selected = await search<string>({
|
|
137
|
+
message: 'Select workspace to open:',
|
|
138
|
+
pageSize: 15,
|
|
139
|
+
source: async (term: string | undefined) => {
|
|
140
|
+
const searchInput = term || '';
|
|
141
|
+
|
|
142
|
+
const source = items.map(item => ({
|
|
143
|
+
name: `${item.name} ${item.lastActiveLabel ? colorize(item.lastActiveLabel, 'dim') : colorize('no session', 'dim')} ${colorize(`${item.repoCount} repos`, 'dim')}`,
|
|
144
|
+
value: item.name,
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
if (!searchInput) return source;
|
|
148
|
+
|
|
149
|
+
const results = fuzzy.filter(searchInput, items, {
|
|
150
|
+
extract: (item) => item.name,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return results.map(result => ({
|
|
154
|
+
name: `${result.original.name} ${result.original.lastActiveLabel ? colorize(result.original.lastActiveLabel, 'dim') : colorize('no session', 'dim')} ${colorize(`${result.original.repoCount} repos`, 'dim')}`,
|
|
155
|
+
value: result.original.name,
|
|
156
|
+
}));
|
|
157
|
+
},
|
|
158
|
+
});
|
|
166
159
|
|
|
167
160
|
const selectedItem = items.find(i => i.name === selected);
|
|
168
161
|
if (selectedItem) {
|
|
@@ -6,13 +6,10 @@ import { loadMetadata, saveMetadata } from '../utils/workspace-meta';
|
|
|
6
6
|
import { generateClaudeContext } from '../utils/claude-integration';
|
|
7
7
|
import { logInfo, logSuccess, logError, logStep } from '../utils/logger';
|
|
8
8
|
import { colorize } from '../utils/colors';
|
|
9
|
-
import
|
|
10
|
-
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
9
|
+
import { confirm, search } from '../utils/prompt';
|
|
11
10
|
import * as fuzzy from 'fuzzy';
|
|
12
11
|
import { getGlobalOpts, resolveWorkspace, parseList } from '../utils/command-helpers';
|
|
13
12
|
|
|
14
|
-
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
15
|
-
|
|
16
13
|
export function registerRemoveRepoCommand(parent: Command) {
|
|
17
14
|
parent
|
|
18
15
|
.command('remove-repo')
|
|
@@ -75,10 +72,9 @@ async function handleRemoveRepo(opts: {
|
|
|
75
72
|
await fs.rm(workspacePath, { recursive: true, force: true });
|
|
76
73
|
logSuccess(`Workspace "${workspaceName}" deleted.`);
|
|
77
74
|
} else {
|
|
78
|
-
const
|
|
79
|
-
type: 'confirm', name: 'deleteWorkspace',
|
|
75
|
+
const deleteWorkspace = await confirm({
|
|
80
76
|
message: `Delete the entire workspace folder "${workspaceName}"?`, default: false,
|
|
81
|
-
}
|
|
77
|
+
});
|
|
82
78
|
if (deleteWorkspace) {
|
|
83
79
|
await fs.rm(workspacePath, { recursive: true, force: true });
|
|
84
80
|
logSuccess(`Workspace "${workspaceName}" deleted.`);
|
|
@@ -115,11 +111,11 @@ async function handleRemoveRepo(opts: {
|
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
try {
|
|
118
|
-
const
|
|
119
|
-
type: 'autocomplete', name: 'repoDir',
|
|
114
|
+
const repoDir = await search<string>({
|
|
120
115
|
message: `Search and select repository (${colorize(String(selectedDirs.length), 'cyan')} selected):`,
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
pageSize: 16,
|
|
117
|
+
source: async (term: string | undefined) => {
|
|
118
|
+
const searchInput = term || '';
|
|
123
119
|
const doneOption = { name: colorize('done - Finish selection', 'green'), value: 'done' };
|
|
124
120
|
if (!searchInput || searchInput.toLowerCase().startsWith('done')) {
|
|
125
121
|
return [doneOption, ...available.map(e => ({ name: e.displayName, value: e.dirName }))];
|
|
@@ -127,8 +123,7 @@ async function handleRemoveRepo(opts: {
|
|
|
127
123
|
const results = fuzzy.filter(searchInput, available, { extract: (e) => e.displayName });
|
|
128
124
|
return [doneOption, ...results.map(result => ({ name: result.original.displayName, value: result.original.dirName }))];
|
|
129
125
|
},
|
|
130
|
-
|
|
131
|
-
} as any]);
|
|
126
|
+
});
|
|
132
127
|
|
|
133
128
|
if (repoDir === 'done') {
|
|
134
129
|
if (selectedDirs.length === 0) { console.log(colorize('\nYou must select at least one repository\n', 'yellow')); continue; }
|
|
@@ -147,11 +142,10 @@ async function handleRemoveRepo(opts: {
|
|
|
147
142
|
console.log('');
|
|
148
143
|
|
|
149
144
|
if (!opts.yes) {
|
|
150
|
-
const
|
|
151
|
-
type: 'confirm', name: 'confirm',
|
|
145
|
+
const confirmed = await confirm({
|
|
152
146
|
message: `Remove ${selectedDirs.length} instance(s)? This will delete the directories.`, default: false,
|
|
153
|
-
}
|
|
154
|
-
if (!
|
|
147
|
+
});
|
|
148
|
+
if (!confirmed) { logInfo('Removal cancelled'); process.exit(0); }
|
|
155
149
|
}
|
|
156
150
|
|
|
157
151
|
logStep(4, 4, 'Removing instances...');
|
|
@@ -6,7 +6,7 @@ import { logInfo, logSuccess, logError } from '../utils/logger';
|
|
|
6
6
|
import { colorize } from '../utils/colors';
|
|
7
7
|
import { loadMetadata, saveMetadata, listWorkspaces } from '../utils/workspace-meta';
|
|
8
8
|
import { formatContextFile, appendToContextFile } from '../utils/context-file';
|
|
9
|
-
import
|
|
9
|
+
import { select } from '../utils/prompt';
|
|
10
10
|
|
|
11
11
|
const CONTEXT_FILENAME = 'CONTEXT.md';
|
|
12
12
|
|
|
@@ -66,12 +66,10 @@ async function resolveWorkspacePath(workspaceName?: string): Promise<{ name: str
|
|
|
66
66
|
logError('No workspaces found');
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
|
-
const
|
|
70
|
-
type: 'list',
|
|
71
|
-
name: 'selected',
|
|
69
|
+
const selected = await select({
|
|
72
70
|
message: 'Select workspace:',
|
|
73
71
|
choices: workspaces.map(w => ({ name: w.name, value: w.name })),
|
|
74
|
-
}
|
|
72
|
+
});
|
|
75
73
|
return { name: selected, path: path.join(WORKSPACES_DIR, selected) };
|
|
76
74
|
}
|
|
77
75
|
|
package/src/commands/sessions.ts
CHANGED
|
@@ -7,12 +7,9 @@ import { listWorkspaces } from '../utils/workspace-meta';
|
|
|
7
7
|
import { logError, logInfo } from '../utils/logger';
|
|
8
8
|
import { outputJson, outputJsonError } from '../utils/output';
|
|
9
9
|
import { colorize } from '../utils/colors';
|
|
10
|
-
import
|
|
11
|
-
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
10
|
+
import { search } from '../utils/prompt';
|
|
12
11
|
import * as fuzzy from 'fuzzy';
|
|
13
12
|
|
|
14
|
-
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
15
|
-
|
|
16
13
|
const TEMP_FILE = path.join(os.homedir(), '.workspace-last-go');
|
|
17
14
|
const RESUME_FLAG_FILE = path.join(os.homedir(), '.workspace-resume-session');
|
|
18
15
|
|
|
@@ -85,11 +82,11 @@ async function handleSessions(opts: { json?: boolean } = {}) {
|
|
|
85
82
|
}
|
|
86
83
|
console.log('');
|
|
87
84
|
|
|
88
|
-
const
|
|
89
|
-
type: 'autocomplete', name: 'selected',
|
|
85
|
+
const selected = await search<WorkspaceSession>({
|
|
90
86
|
message: 'Select workspace to resume:',
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
pageSize: 15,
|
|
88
|
+
source: async (term: string | undefined) => {
|
|
89
|
+
const searchInput = term || '';
|
|
93
90
|
const source = items.map(item => ({
|
|
94
91
|
name: `${item.session.workspaceName} ${colorize(item.session.lastActiveLabel, 'dim')} ${colorize(item.repoLabel, 'dim')}`,
|
|
95
92
|
value: item.session,
|
|
@@ -101,8 +98,7 @@ async function handleSessions(opts: { json?: boolean } = {}) {
|
|
|
101
98
|
value: result.original.session,
|
|
102
99
|
}));
|
|
103
100
|
},
|
|
104
|
-
|
|
105
|
-
} as any]);
|
|
101
|
+
});
|
|
106
102
|
|
|
107
103
|
const session = selected as WorkspaceSession;
|
|
108
104
|
await fs.writeFile(TEMP_FILE, session.workspacePath, 'utf-8');
|
|
@@ -7,37 +7,27 @@ import { logInfo, logSuccess, logError, logStep } from '../../utils/logger';
|
|
|
7
7
|
import { colorize } from '../../utils/colors';
|
|
8
8
|
import { saveSuite, getSuite, validateSuiteName } from '../../utils/suite';
|
|
9
9
|
import { SuiteEntry, WorkspaceSuite, PostCloneHook } from '../../types';
|
|
10
|
-
import
|
|
10
|
+
import { confirm, input, select } from '../../utils/prompt';
|
|
11
11
|
|
|
12
12
|
async function promptSuiteDetails(defaultName?: string): Promise<{ name: string; description: string }> {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
type: 'input',
|
|
24
|
-
name: 'description',
|
|
25
|
-
message: 'Suite description (optional):',
|
|
26
|
-
},
|
|
27
|
-
]);
|
|
13
|
+
const name = (await input({
|
|
14
|
+
message: 'Suite name:',
|
|
15
|
+
default: defaultName,
|
|
16
|
+
validate: (input: string) => validateSuiteName(input.trim()),
|
|
17
|
+
})).trim();
|
|
18
|
+
|
|
19
|
+
const description = await input({
|
|
20
|
+
message: 'Suite description (optional):',
|
|
21
|
+
});
|
|
28
22
|
|
|
29
23
|
return { name, description: description || '' };
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
async function promptPostCloneHooks(): Promise<PostCloneHook[] | undefined> {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
message: 'Add post-clone hooks?',
|
|
38
|
-
default: false,
|
|
39
|
-
},
|
|
40
|
-
]);
|
|
27
|
+
const addHooks = await confirm({
|
|
28
|
+
message: 'Add post-clone hooks?',
|
|
29
|
+
default: false,
|
|
30
|
+
});
|
|
41
31
|
|
|
42
32
|
if (!addHooks) return undefined;
|
|
43
33
|
|
|
@@ -45,13 +35,9 @@ async function promptPostCloneHooks(): Promise<PostCloneHook[] | undefined> {
|
|
|
45
35
|
console.log('Enter commands one at a time. Type "done" when finished.\n');
|
|
46
36
|
|
|
47
37
|
while (true) {
|
|
48
|
-
const
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
name: 'command',
|
|
52
|
-
message: `Command ${commands.length + 1} (or "done"):`,
|
|
53
|
-
},
|
|
54
|
-
]);
|
|
38
|
+
const command = await input({
|
|
39
|
+
message: `Command ${commands.length + 1} (or "done"):`,
|
|
40
|
+
});
|
|
55
41
|
|
|
56
42
|
if (command.trim().toLowerCase() === 'done') break;
|
|
57
43
|
if (command.trim()) {
|
|
@@ -71,14 +57,10 @@ async function checkOverwrite(name: string): Promise<boolean> {
|
|
|
71
57
|
const existing = await getSuite(name);
|
|
72
58
|
if (!existing) return true;
|
|
73
59
|
|
|
74
|
-
const
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
message: `Suite "${name}" already exists (${existing.entries.length} repos). Overwrite?`,
|
|
79
|
-
default: false,
|
|
80
|
-
},
|
|
81
|
-
]);
|
|
60
|
+
const overwrite = await confirm({
|
|
61
|
+
message: `Suite "${name}" already exists (${existing.entries.length} repos). Overwrite?`,
|
|
62
|
+
default: false,
|
|
63
|
+
});
|
|
82
64
|
|
|
83
65
|
return overwrite;
|
|
84
66
|
}
|
|
@@ -211,17 +193,13 @@ export async function main() {
|
|
|
211
193
|
console.log('='.repeat(60) + '\n');
|
|
212
194
|
|
|
213
195
|
try {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
name: '
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
{ name: 'Save from an existing workspace', value: 'workspace' },
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
]);
|
|
196
|
+
const mode = await select({
|
|
197
|
+
message: 'How would you like to create the suite?',
|
|
198
|
+
choices: [
|
|
199
|
+
{ name: 'Pick repositories interactively', value: 'interactive' },
|
|
200
|
+
{ name: 'Save from an existing workspace', value: 'workspace' },
|
|
201
|
+
],
|
|
202
|
+
});
|
|
225
203
|
|
|
226
204
|
if (mode === 'interactive') {
|
|
227
205
|
await createInteractive();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { listSuites, deleteSuite } from '../../utils/suite';
|
|
4
4
|
import { logInfo, logSuccess, logError } from '../../utils/logger';
|
|
5
5
|
import { colorize } from '../../utils/colors';
|
|
6
|
-
import
|
|
6
|
+
import { confirm, select } from '../../utils/prompt';
|
|
7
7
|
|
|
8
8
|
export async function main() {
|
|
9
9
|
console.log('\n' + '='.repeat(60));
|
|
@@ -18,28 +18,19 @@ export async function main() {
|
|
|
18
18
|
process.exit(0);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
name: '
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const { confirmed } = await inquirer.prompt([
|
|
36
|
-
{
|
|
37
|
-
type: 'confirm',
|
|
38
|
-
name: 'confirmed',
|
|
39
|
-
message: `Are you sure you want to delete suite "${suiteName}"?`,
|
|
40
|
-
default: false,
|
|
41
|
-
},
|
|
42
|
-
]);
|
|
21
|
+
const suiteName = await select({
|
|
22
|
+
message: 'Select a suite to delete:',
|
|
23
|
+
choices: suites.map(s => ({
|
|
24
|
+
name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
|
|
25
|
+
value: s.name,
|
|
26
|
+
})),
|
|
27
|
+
pageSize: 15,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const confirmed = await confirm({
|
|
31
|
+
message: `Are you sure you want to delete suite "${suiteName}"?`,
|
|
32
|
+
default: false,
|
|
33
|
+
});
|
|
43
34
|
|
|
44
35
|
if (!confirmed) {
|
|
45
36
|
logInfo('Deletion cancelled');
|
|
@@ -5,7 +5,7 @@ import * as path from 'path';
|
|
|
5
5
|
import { listSuites, exportSuite, exportAllSuites } from '../../utils/suite';
|
|
6
6
|
import { logInfo, logSuccess, logError } from '../../utils/logger';
|
|
7
7
|
import { colorize } from '../../utils/colors';
|
|
8
|
-
import
|
|
8
|
+
import { input, select } from '../../utils/prompt';
|
|
9
9
|
|
|
10
10
|
export async function main(opts?: { file?: string }) {
|
|
11
11
|
console.log('\n' + '='.repeat(60));
|
|
@@ -20,35 +20,26 @@ export async function main(opts?: { file?: string }) {
|
|
|
20
20
|
process.exit(0);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
name: '
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{ name: 'All suites', value: 'all' },
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
]);
|
|
23
|
+
const mode = await select({
|
|
24
|
+
message: 'What would you like to export?',
|
|
25
|
+
choices: [
|
|
26
|
+
{ name: 'A single suite', value: 'single' },
|
|
27
|
+
{ name: 'All suites', value: 'all' },
|
|
28
|
+
],
|
|
29
|
+
});
|
|
34
30
|
|
|
35
31
|
let data;
|
|
36
32
|
let defaultFilename: string;
|
|
37
33
|
|
|
38
34
|
if (mode === 'single') {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
name: '
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
short: s.name,
|
|
48
|
-
})),
|
|
49
|
-
pageSize: 15,
|
|
50
|
-
},
|
|
51
|
-
]);
|
|
35
|
+
const suiteName = await select({
|
|
36
|
+
message: 'Select a suite to export:',
|
|
37
|
+
choices: suites.map(s => ({
|
|
38
|
+
name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
|
|
39
|
+
value: s.name,
|
|
40
|
+
})),
|
|
41
|
+
pageSize: 15,
|
|
42
|
+
});
|
|
52
43
|
|
|
53
44
|
data = await exportSuite(suiteName);
|
|
54
45
|
if (!data) {
|
|
@@ -68,14 +59,10 @@ export async function main(opts?: { file?: string }) {
|
|
|
68
59
|
if (outputArg && !outputArg.startsWith('-')) {
|
|
69
60
|
outputPath = path.resolve(outputArg);
|
|
70
61
|
} else {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
message: 'Output file path:',
|
|
76
|
-
default: `./${defaultFilename}`,
|
|
77
|
-
},
|
|
78
|
-
]);
|
|
62
|
+
const filePath = await input({
|
|
63
|
+
message: 'Output file path:',
|
|
64
|
+
default: `./${defaultFilename}`,
|
|
65
|
+
});
|
|
79
66
|
outputPath = path.resolve(filePath);
|
|
80
67
|
}
|
|
81
68
|
|
|
@@ -6,7 +6,7 @@ import { importSuites } from '../../utils/suite';
|
|
|
6
6
|
import { logInfo, logSuccess, logError, logWarning } from '../../utils/logger';
|
|
7
7
|
import { colorize } from '../../utils/colors';
|
|
8
8
|
import { SuitesStore } from '../../types';
|
|
9
|
-
import
|
|
9
|
+
import { confirm, input } from '../../utils/prompt';
|
|
10
10
|
|
|
11
11
|
export async function main(opts?: { file?: string }) {
|
|
12
12
|
console.log('\n' + '='.repeat(60));
|
|
@@ -20,14 +20,10 @@ export async function main(opts?: { file?: string }) {
|
|
|
20
20
|
if (fileArg && !fileArg.startsWith('-')) {
|
|
21
21
|
filePath = path.resolve(fileArg);
|
|
22
22
|
} else {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
message: 'Path to suite JSON file:',
|
|
28
|
-
validate: (input: string) => input.trim().length > 0 || 'File path is required',
|
|
29
|
-
},
|
|
30
|
-
]);
|
|
23
|
+
const inputPath = await input({
|
|
24
|
+
message: 'Path to suite JSON file:',
|
|
25
|
+
validate: (input: string) => input.trim().length > 0 || 'File path is required',
|
|
26
|
+
});
|
|
31
27
|
filePath = path.resolve(inputPath);
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -61,14 +57,10 @@ export async function main(opts?: { file?: string }) {
|
|
|
61
57
|
}
|
|
62
58
|
console.log('');
|
|
63
59
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
message: 'Overwrite existing suites with the same name?',
|
|
69
|
-
default: false,
|
|
70
|
-
},
|
|
71
|
-
]);
|
|
60
|
+
const overwrite = await confirm({
|
|
61
|
+
message: 'Overwrite existing suites with the same name?',
|
|
62
|
+
default: false,
|
|
63
|
+
});
|
|
72
64
|
|
|
73
65
|
const result = await importSuites(data, overwrite);
|
|
74
66
|
|