@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
|
@@ -13,7 +13,7 @@ import { colorize } from '../../utils/colors';
|
|
|
13
13
|
import { listSuites } from '../../utils/suite';
|
|
14
14
|
import { runPostCloneHooks } from '../../utils/hooks';
|
|
15
15
|
import { GitHubRepo } from '../../types';
|
|
16
|
-
import
|
|
16
|
+
import { select } from '../../utils/prompt';
|
|
17
17
|
import { validateWorkspaceName, checkWorkspaceExists, sanitizeWorkspaceName, resolveWorkspaceNameConflict } from '../../utils/validation';
|
|
18
18
|
|
|
19
19
|
export interface SuiteUseOpts {
|
|
@@ -64,19 +64,14 @@ export async function main(opts?: SuiteUseOpts) {
|
|
|
64
64
|
}
|
|
65
65
|
selectedSuite = found;
|
|
66
66
|
} else {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
name: '
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
short: s.name,
|
|
76
|
-
})),
|
|
77
|
-
pageSize: 15,
|
|
78
|
-
},
|
|
79
|
-
]);
|
|
67
|
+
const suite = await select({
|
|
68
|
+
message: 'Select a suite:',
|
|
69
|
+
choices: suites.map(s => ({
|
|
70
|
+
name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
|
|
71
|
+
value: s,
|
|
72
|
+
})),
|
|
73
|
+
pageSize: 15,
|
|
74
|
+
});
|
|
80
75
|
selectedSuite = suite;
|
|
81
76
|
}
|
|
82
77
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive prompt primitives.
|
|
3
|
+
*
|
|
4
|
+
* We depend on the modular `@inquirer/prompts` (the maintained successor to the
|
|
5
|
+
* classic `inquirer` package and its `inquirer-autocomplete-prompt` plugin).
|
|
6
|
+
* All prompt call sites import from HERE, not from `@inquirer/prompts` directly,
|
|
7
|
+
* so there is a single place to:
|
|
8
|
+
* - centralize the ESM-only package (loaded from our CommonJS build via
|
|
9
|
+
* Node 22's stable `require(esm)` — the CLI requires Node >= 22), and
|
|
10
|
+
* - stub prompts in tests (mock '../utils/prompt').
|
|
11
|
+
*
|
|
12
|
+
* The classic array API (`inquirer.prompt([{ type, name, message }])` returning
|
|
13
|
+
* `{ name: value }`) is replaced by these functions, which take an options
|
|
14
|
+
* object and return the answer VALUE directly.
|
|
15
|
+
*/
|
|
16
|
+
export { confirm, input, select, checkbox, password, search, Separator } from '@inquirer/prompts';
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
3
|
+
const { mockSearch, mockInput } = vi.hoisted(() => ({
|
|
4
|
+
mockSearch: vi.fn(),
|
|
5
|
+
mockInput: vi.fn(),
|
|
5
6
|
}));
|
|
6
7
|
|
|
7
|
-
// Mock
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
vi.
|
|
16
|
-
|
|
8
|
+
// Mock the modular prompt module. promptMultiWorkspaceSelection uses search();
|
|
9
|
+
// the modular API returns the selected VALUE directly (not { name: value }).
|
|
10
|
+
vi.mock('./prompt', () => ({
|
|
11
|
+
search: mockSearch,
|
|
12
|
+
input: mockInput,
|
|
13
|
+
confirm: vi.fn(),
|
|
14
|
+
select: vi.fn(),
|
|
15
|
+
checkbox: vi.fn(),
|
|
16
|
+
password: vi.fn(),
|
|
17
|
+
Separator: class {},
|
|
17
18
|
}));
|
|
18
19
|
|
|
19
20
|
vi.mock('fuzzy', () => ({
|
|
@@ -35,7 +36,8 @@ vi.mock('./validation', () => ({
|
|
|
35
36
|
sanitizeWorkspaceName: vi.fn((input: string) => input),
|
|
36
37
|
}));
|
|
37
38
|
|
|
38
|
-
import { promptMultiWorkspaceSelection } from './prompts';
|
|
39
|
+
import { promptMultiWorkspaceSelection, promptWorkspaceName } from './prompts';
|
|
40
|
+
import { validateWorkspaceName, sanitizeWorkspaceName } from './validation';
|
|
39
41
|
|
|
40
42
|
const makeWorkspaces = (...names: string[]) =>
|
|
41
43
|
names.map(name => ({
|
|
@@ -44,6 +46,33 @@ const makeWorkspaces = (...names: string[]) =>
|
|
|
44
46
|
metadata: { repositories: [], createdAt: new Date().toISOString() },
|
|
45
47
|
}));
|
|
46
48
|
|
|
49
|
+
describe('promptWorkspaceName', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.clearAllMocks();
|
|
52
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('validates the SANITIZED name, not the raw input (classic filter-before-validate parity)', async () => {
|
|
56
|
+
// Classic inquirer ran filter (sanitize) BEFORE validate, so a name like
|
|
57
|
+
// "My Workspace" was validated as "my-workspace". Regression guard.
|
|
58
|
+
(sanitizeWorkspaceName as any).mockImplementation((s: string) =>
|
|
59
|
+
s.trim().toLowerCase().replace(/ +/g, '-'));
|
|
60
|
+
(validateWorkspaceName as any).mockReturnValue(true);
|
|
61
|
+
|
|
62
|
+
mockInput.mockImplementation(async (opts: any) => {
|
|
63
|
+
// Simulate the user submitting a raw value containing a space.
|
|
64
|
+
opts.validate('My Workspace');
|
|
65
|
+
return 'My Workspace';
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const result = await promptWorkspaceName();
|
|
69
|
+
|
|
70
|
+
// validate must have seen the sanitized value, and the return is sanitized.
|
|
71
|
+
expect(validateWorkspaceName).toHaveBeenCalledWith('my-workspace');
|
|
72
|
+
expect(result).toBe('my-workspace');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
47
76
|
describe('promptMultiWorkspaceSelection', () => {
|
|
48
77
|
beforeEach(() => {
|
|
49
78
|
vi.clearAllMocks();
|
|
@@ -51,10 +80,10 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
51
80
|
});
|
|
52
81
|
|
|
53
82
|
it('returns selected workspace names', async () => {
|
|
54
|
-
|
|
55
|
-
.mockResolvedValueOnce(
|
|
56
|
-
.mockResolvedValueOnce(
|
|
57
|
-
.mockResolvedValueOnce(
|
|
83
|
+
mockSearch
|
|
84
|
+
.mockResolvedValueOnce('ws-alpha')
|
|
85
|
+
.mockResolvedValueOnce('ws-beta')
|
|
86
|
+
.mockResolvedValueOnce('done');
|
|
58
87
|
|
|
59
88
|
const result = await promptMultiWorkspaceSelection(
|
|
60
89
|
makeWorkspaces('ws-alpha', 'ws-beta', 'ws-gamma')
|
|
@@ -65,10 +94,10 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
65
94
|
|
|
66
95
|
it('requires at least one selection before accepting done', async () => {
|
|
67
96
|
// First "done" should be rejected, then select one, then done
|
|
68
|
-
|
|
69
|
-
.mockResolvedValueOnce(
|
|
70
|
-
.mockResolvedValueOnce(
|
|
71
|
-
.mockResolvedValueOnce(
|
|
97
|
+
mockSearch
|
|
98
|
+
.mockResolvedValueOnce('done')
|
|
99
|
+
.mockResolvedValueOnce('ws-alpha')
|
|
100
|
+
.mockResolvedValueOnce('done');
|
|
72
101
|
|
|
73
102
|
const result = await promptMultiWorkspaceSelection(
|
|
74
103
|
makeWorkspaces('ws-alpha', 'ws-beta')
|
|
@@ -76,13 +105,13 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
76
105
|
|
|
77
106
|
expect(result).toEqual(['ws-alpha']);
|
|
78
107
|
// Should have prompted 3 times (done rejected, select, done accepted)
|
|
79
|
-
expect(
|
|
108
|
+
expect(mockSearch).toHaveBeenCalledTimes(3);
|
|
80
109
|
});
|
|
81
110
|
|
|
82
111
|
it('auto-completes when all workspaces are selected', async () => {
|
|
83
|
-
|
|
84
|
-
.mockResolvedValueOnce(
|
|
85
|
-
.mockResolvedValueOnce(
|
|
112
|
+
mockSearch
|
|
113
|
+
.mockResolvedValueOnce('ws-alpha')
|
|
114
|
+
.mockResolvedValueOnce('ws-beta');
|
|
86
115
|
|
|
87
116
|
const result = await promptMultiWorkspaceSelection(
|
|
88
117
|
makeWorkspaces('ws-alpha', 'ws-beta')
|
|
@@ -90,13 +119,13 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
90
119
|
|
|
91
120
|
expect(result).toEqual(['ws-alpha', 'ws-beta']);
|
|
92
121
|
// Only 2 prompts needed - loop breaks when no available names left
|
|
93
|
-
expect(
|
|
122
|
+
expect(mockSearch).toHaveBeenCalledTimes(2);
|
|
94
123
|
});
|
|
95
124
|
|
|
96
125
|
it('returns single workspace selection', async () => {
|
|
97
|
-
|
|
98
|
-
.mockResolvedValueOnce(
|
|
99
|
-
.mockResolvedValueOnce(
|
|
126
|
+
mockSearch
|
|
127
|
+
.mockResolvedValueOnce('ws-only')
|
|
128
|
+
.mockResolvedValueOnce('done');
|
|
100
129
|
|
|
101
130
|
const result = await promptMultiWorkspaceSelection(
|
|
102
131
|
makeWorkspaces('ws-only', 'ws-other')
|
|
@@ -110,7 +139,7 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
110
139
|
throw new Error('process.exit');
|
|
111
140
|
});
|
|
112
141
|
|
|
113
|
-
|
|
142
|
+
mockSearch.mockRejectedValueOnce(new Error('User force closed'));
|
|
114
143
|
|
|
115
144
|
await expect(
|
|
116
145
|
promptMultiWorkspaceSelection(makeWorkspaces('ws-alpha'))
|
|
@@ -122,9 +151,9 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
122
151
|
|
|
123
152
|
it('prints summary with correct singular form', async () => {
|
|
124
153
|
const logSpy = vi.spyOn(console, 'log');
|
|
125
|
-
|
|
126
|
-
.mockResolvedValueOnce(
|
|
127
|
-
.mockResolvedValueOnce(
|
|
154
|
+
mockSearch
|
|
155
|
+
.mockResolvedValueOnce('ws-alpha')
|
|
156
|
+
.mockResolvedValueOnce('done');
|
|
128
157
|
|
|
129
158
|
await promptMultiWorkspaceSelection(makeWorkspaces('ws-alpha', 'ws-beta'));
|
|
130
159
|
|
|
@@ -136,10 +165,10 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
136
165
|
|
|
137
166
|
it('prints summary with correct plural form', async () => {
|
|
138
167
|
const logSpy = vi.spyOn(console, 'log');
|
|
139
|
-
|
|
140
|
-
.mockResolvedValueOnce(
|
|
141
|
-
.mockResolvedValueOnce(
|
|
142
|
-
.mockResolvedValueOnce(
|
|
168
|
+
mockSearch
|
|
169
|
+
.mockResolvedValueOnce('ws-alpha')
|
|
170
|
+
.mockResolvedValueOnce('ws-beta')
|
|
171
|
+
.mockResolvedValueOnce('done');
|
|
143
172
|
|
|
144
173
|
await promptMultiWorkspaceSelection(
|
|
145
174
|
makeWorkspaces('ws-alpha', 'ws-beta', 'ws-gamma')
|
|
@@ -153,10 +182,10 @@ describe('promptMultiWorkspaceSelection', () => {
|
|
|
153
182
|
|
|
154
183
|
it('prints green confirmation after each selection', async () => {
|
|
155
184
|
const logSpy = vi.spyOn(console, 'log');
|
|
156
|
-
|
|
157
|
-
.mockResolvedValueOnce(
|
|
158
|
-
.mockResolvedValueOnce(
|
|
159
|
-
.mockResolvedValueOnce(
|
|
185
|
+
mockSearch
|
|
186
|
+
.mockResolvedValueOnce('ws-alpha')
|
|
187
|
+
.mockResolvedValueOnce('ws-beta')
|
|
188
|
+
.mockResolvedValueOnce('done');
|
|
160
189
|
|
|
161
190
|
await promptMultiWorkspaceSelection(
|
|
162
191
|
makeWorkspaces('ws-alpha', 'ws-beta', 'ws-gamma')
|
package/src/utils/prompts.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
1
|
+
import { confirm, input, select, search } from './prompt';
|
|
3
2
|
import * as fuzzy from 'fuzzy';
|
|
4
3
|
import { GitHubRepo } from '../types';
|
|
5
4
|
import { validateWorkspaceName, checkWorkspaceExists, sanitizeWorkspaceName } from './validation';
|
|
6
5
|
import { colorize } from './colors';
|
|
7
6
|
|
|
8
|
-
// Register autocomplete prompt type
|
|
9
|
-
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
10
|
-
|
|
11
7
|
export interface RepoSelection {
|
|
12
8
|
repo: GitHubRepo;
|
|
13
9
|
directoryName: string;
|
|
@@ -17,32 +13,28 @@ export const promptInstanceSuffix = async (
|
|
|
17
13
|
repoName: string,
|
|
18
14
|
existingDirectoryNames: string[]
|
|
19
15
|
): Promise<string> => {
|
|
20
|
-
const
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!input || input.trim().length === 0) {
|
|
27
|
-
return 'Suffix cannot be empty';
|
|
28
|
-
}
|
|
16
|
+
const suffix = await input({
|
|
17
|
+
message: `"${repoName}" already exists. Enter a suffix for this instance:`,
|
|
18
|
+
validate: (input: string) => {
|
|
19
|
+
if (!input || input.trim().length === 0) {
|
|
20
|
+
return 'Suffix cannot be empty';
|
|
21
|
+
}
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
const trimmed = input.trim();
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
// Validate characters (alphanumeric, hyphens, underscores)
|
|
26
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
|
|
27
|
+
return 'Suffix can only contain letters, numbers, hyphens, and underscores';
|
|
28
|
+
}
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
const candidateName = `${repoName}-${trimmed}`;
|
|
31
|
+
if (existingDirectoryNames.includes(candidateName)) {
|
|
32
|
+
return `"${candidateName}" already exists. Choose a different suffix`;
|
|
33
|
+
}
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
},
|
|
35
|
+
return true;
|
|
44
36
|
},
|
|
45
|
-
|
|
37
|
+
});
|
|
46
38
|
|
|
47
39
|
return suffix.trim();
|
|
48
40
|
};
|
|
@@ -63,43 +55,39 @@ export const promptRepositorySelection = async (
|
|
|
63
55
|
|
|
64
56
|
while (true) {
|
|
65
57
|
try {
|
|
66
|
-
const
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
},
|
|
100
|
-
pageSize: 16,
|
|
101
|
-
} as any,
|
|
102
|
-
]);
|
|
58
|
+
const repoName = await search<string>({
|
|
59
|
+
message: `Search and select repository (${colorize(String(selectedEntries.length), 'cyan')} selected):`,
|
|
60
|
+
pageSize: 16,
|
|
61
|
+
source: async (term: string | undefined) => {
|
|
62
|
+
const searchInput = term || '';
|
|
63
|
+
|
|
64
|
+
// Always include done option
|
|
65
|
+
const doneOption = { name: colorize('done - Finish selection', 'green'), value: 'done' };
|
|
66
|
+
|
|
67
|
+
// If no input or "done" typed, show done + top repos
|
|
68
|
+
if (!searchInput || searchInput.toLowerCase().startsWith('done')) {
|
|
69
|
+
return [
|
|
70
|
+
doneOption,
|
|
71
|
+
...repos.slice(0, 15).map(repo => ({
|
|
72
|
+
name: `${repo.name}${repo.description ? ` - ${colorize(repo.description, 'gray')}` : ''}`,
|
|
73
|
+
value: repo.name,
|
|
74
|
+
}))
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Perform fuzzy search
|
|
79
|
+
const results = fuzzy.filter(searchInput, repos, {
|
|
80
|
+
extract: (repo) => `${repo.name} ${repo.description || ''}`,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const suggestions = results.slice(0, 15).map(result => ({
|
|
84
|
+
name: `${result.original.name}${result.original.description ? ` - ${colorize(result.original.description, 'gray')}` : ''}`,
|
|
85
|
+
value: result.original.name,
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
return [doneOption, ...suggestions];
|
|
89
|
+
},
|
|
90
|
+
});
|
|
103
91
|
|
|
104
92
|
if (repoName === 'done') {
|
|
105
93
|
if (selectedEntries.length === 0) {
|
|
@@ -147,23 +135,18 @@ export const promptRepositorySelection = async (
|
|
|
147
135
|
};
|
|
148
136
|
|
|
149
137
|
export const promptWorkspaceName = async (): Promise<string> => {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return validationResult;
|
|
159
|
-
}
|
|
160
|
-
return true;
|
|
161
|
-
},
|
|
162
|
-
filter: (input: string) => sanitizeWorkspaceName(input),
|
|
138
|
+
const raw = await input({
|
|
139
|
+
message: 'Enter workspace name:',
|
|
140
|
+
validate: (value: string) => {
|
|
141
|
+
const validationResult = validateWorkspaceName(sanitizeWorkspaceName(value));
|
|
142
|
+
if (validationResult !== true) {
|
|
143
|
+
return validationResult;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
163
146
|
},
|
|
164
|
-
|
|
147
|
+
});
|
|
165
148
|
|
|
166
|
-
return
|
|
149
|
+
return sanitizeWorkspaceName(raw);
|
|
167
150
|
};
|
|
168
151
|
|
|
169
152
|
export const confirmWorkspaceCreation = async (
|
|
@@ -179,14 +162,10 @@ export const confirmWorkspaceCreation = async (
|
|
|
179
162
|
console.log(`Repositories: ${colorize(String(repoCount), 'yellow')}`);
|
|
180
163
|
console.log('='.repeat(60) + '\n');
|
|
181
164
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
message: 'Create workspace with these settings?',
|
|
187
|
-
default: true,
|
|
188
|
-
},
|
|
189
|
-
]);
|
|
165
|
+
const confirmed = await confirm({
|
|
166
|
+
message: 'Create workspace with these settings?',
|
|
167
|
+
default: true,
|
|
168
|
+
});
|
|
190
169
|
|
|
191
170
|
return confirmed;
|
|
192
171
|
};
|
|
@@ -201,18 +180,13 @@ export const promptWorkspaceSelection = async (workspaces: Array<{ name: string;
|
|
|
201
180
|
? `${ws.name} (${ws.metadata.repositories.length} repos)`
|
|
202
181
|
: ws.name,
|
|
203
182
|
value: ws.name,
|
|
204
|
-
short: ws.name,
|
|
205
183
|
}));
|
|
206
184
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
choices,
|
|
213
|
-
pageSize: 15,
|
|
214
|
-
},
|
|
215
|
-
]);
|
|
185
|
+
const workspaceName = await select<string>({
|
|
186
|
+
message: 'Select workspace to update:',
|
|
187
|
+
choices,
|
|
188
|
+
pageSize: 15,
|
|
189
|
+
});
|
|
216
190
|
|
|
217
191
|
return workspaceName;
|
|
218
192
|
};
|
|
@@ -236,38 +210,34 @@ export const promptMultiWorkspaceSelection = async (
|
|
|
236
210
|
break;
|
|
237
211
|
}
|
|
238
212
|
|
|
239
|
-
const
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
},
|
|
268
|
-
pageSize: 16,
|
|
269
|
-
} as any,
|
|
270
|
-
]);
|
|
213
|
+
const workspaceName = await search<string>({
|
|
214
|
+
message: `Search and select workspace (${colorize(String(selectedNames.length), 'cyan')} selected):`,
|
|
215
|
+
pageSize: 16,
|
|
216
|
+
source: async (term: string | undefined) => {
|
|
217
|
+
const searchInput = term || '';
|
|
218
|
+
|
|
219
|
+
const doneOption = { name: colorize('done - Finish selection', 'green'), value: 'done' };
|
|
220
|
+
|
|
221
|
+
if (!searchInput || searchInput.toLowerCase().startsWith('done')) {
|
|
222
|
+
return [
|
|
223
|
+
doneOption,
|
|
224
|
+
...availableNames.slice(0, 15).map(name => ({
|
|
225
|
+
name,
|
|
226
|
+
value: name,
|
|
227
|
+
}))
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const results = fuzzy.filter(searchInput, availableNames);
|
|
232
|
+
|
|
233
|
+
const suggestions = results.slice(0, 15).map(result => ({
|
|
234
|
+
name: result.original,
|
|
235
|
+
value: result.original,
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
return [doneOption, ...suggestions];
|
|
239
|
+
},
|
|
240
|
+
});
|
|
271
241
|
|
|
272
242
|
if (workspaceName === 'done') {
|
|
273
243
|
if (selectedNames.length === 0) {
|