Skip to content

Commit c598dbc

Browse files
authored
Merge pull request #68 from constructive-io/devin/1772748045-add-skip-prompt
feat(inquirerer): add skipPrompt flag to skip prompting for optional fields
2 parents 5ae067a + a16e95f commit c598dbc

4 files changed

Lines changed: 148 additions & 0 deletions

File tree

packages/inquirerer/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,10 @@ interface BaseQuestion {
146146
default?: any; // Default value
147147
defaultFrom?: string; // Dynamic default from resolver (e.g., 'git.user.name')
148148
setFrom?: string; // Auto-set value from resolver, bypassing prompt entirely
149+
optionsFrom?: string; // Dynamic options from another answer's value
149150
useDefault?: boolean; // Skip prompt and use default
150151
required?: boolean; // Validation requirement
152+
skipPrompt?: boolean; // Skip prompting entirely (field still in man pages / CLI flags)
151153
validate?: (input: any, answers: any) => boolean | Validation;
152154
sanitize?: (input: any, answers: any) => any;
153155
pattern?: string; // Regex pattern for validation
@@ -156,6 +158,40 @@ interface BaseQuestion {
156158
}
157159
```
158160

161+
#### Skipping Prompts
162+
163+
Use `skipPrompt: true` to skip interactive prompting for a question entirely. The field is omitted from the answers object unless the user explicitly passes it via a CLI flag. This is useful for fields with backend-managed defaults where the CLI should not prompt, but should still allow overrides.
164+
165+
```typescript
166+
const questions: Question[] = [
167+
{
168+
type: 'text',
169+
name: 'username',
170+
message: 'Username',
171+
required: true
172+
},
173+
{
174+
type: 'text',
175+
name: 'status',
176+
message: 'Account status',
177+
skipPrompt: true // Won't prompt, but user can pass --status active
178+
}
179+
];
180+
181+
const result = await prompter.prompt({}, questions);
182+
// { username: 'john' } — status is not included
183+
184+
const result2 = await prompter.prompt({ status: 'active' }, questions);
185+
// { username: 'john', status: 'active' } — CLI flag override works
186+
```
187+
188+
Key behaviors:
189+
- The question still appears in generated man pages
190+
- CLI flag overrides (e.g. `--status active`) still work
191+
- The field is simply left out of the answers if not provided
192+
- Different from `when`: `skipPrompt` is unconditional, while `when` depends on other answers
193+
- Different from `useDefault`: `skipPrompt` does not apply a default value
194+
159195
### Non-Interactive Mode
160196

161197
When running in CI/CD or without a TTY, inquirerer automatically falls back to default values:

packages/inquirerer/__tests__/prompt.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,108 @@ describe('Inquirerer', () => {
232232
snap(result);
233233
});
234234

235+
describe('skipPrompt', () => {
236+
it('should skip question with skipPrompt: true and not include it in result', async () => {
237+
enqueueInputResponse({ type: 'read', value: 'my name' });
238+
239+
const prompter = new Inquirerer({
240+
input: mockInput,
241+
output: mockOutput,
242+
noTty: false
243+
});
244+
const questions: Question[] = [
245+
{ name: 'name', type: 'text' },
246+
{ name: 'status', type: 'text', skipPrompt: true },
247+
];
248+
249+
const result = await prompter.prompt({}, questions);
250+
251+
// 'status' should not be in the result since it was skipped
252+
expect(result).toEqual({ name: 'my name' });
253+
expect('status' in result).toBe(false);
254+
});
255+
256+
it('should still allow CLI flag override when skipPrompt is true', async () => {
257+
enqueueInputResponse({ type: 'read', value: 'my name' });
258+
259+
const prompter = new Inquirerer({
260+
input: mockInput,
261+
output: mockOutput,
262+
noTty: false
263+
});
264+
const questions: Question[] = [
265+
{ name: 'name', type: 'text' },
266+
{ name: 'status', type: 'text', skipPrompt: true },
267+
];
268+
269+
// Pass status via argv (simulating --status "active")
270+
const result = await prompter.prompt({ status: 'active' }, questions);
271+
272+
expect(result).toEqual({ name: 'my name', status: 'active' });
273+
});
274+
275+
it('should skip question in noTty mode with skipPrompt: true', async () => {
276+
const prompter = new Inquirerer({
277+
input: mockInput,
278+
output: mockOutput,
279+
noTty: true
280+
});
281+
const questions: Question[] = [
282+
{ name: 'name', type: 'text', required: true },
283+
{ name: 'status', type: 'text', skipPrompt: true },
284+
];
285+
286+
const result = await prompter.prompt({ name: 'test' }, questions);
287+
288+
expect(result).toEqual({ name: 'test' });
289+
expect('status' in result).toBe(false);
290+
});
291+
292+
it('should include skipPrompt question in man page', () => {
293+
const prompter = new Inquirerer({
294+
input: mockInput,
295+
output: mockOutput,
296+
noTty: false
297+
});
298+
const questions: Question[] = [
299+
{ name: 'name', type: 'text', required: true },
300+
{ name: 'status', type: 'text', skipPrompt: true },
301+
];
302+
303+
const manPage = prompter.generateManPage({
304+
commandName: 'test-cmd',
305+
questions,
306+
});
307+
308+
// Both fields should appear in the man page
309+
expect(manPage).toContain('NAME');
310+
expect(manPage).toContain('STATUS');
311+
});
312+
313+
it('should skip multiple skipPrompt questions and only prompt remaining', async () => {
314+
enqueueInputResponse({ type: 'read', value: 'hello' });
315+
316+
const prompter = new Inquirerer({
317+
input: mockInput,
318+
output: mockOutput,
319+
noTty: false
320+
});
321+
const questions: Question[] = [
322+
{ name: 'greeting', type: 'text' },
323+
{ name: 'createdAt', type: 'text', skipPrompt: true },
324+
{ name: 'updatedAt', type: 'text', skipPrompt: true },
325+
{ name: 'internalId', type: 'text', skipPrompt: true },
326+
];
327+
328+
const result = await prompter.prompt({}, questions);
329+
330+
expect(result).toEqual({ greeting: 'hello' });
331+
expect('createdAt' in result).toBe(false);
332+
expect('updatedAt' in result).toBe(false);
333+
expect('internalId' in result).toBe(false);
334+
});
335+
});
336+
235337
it('handles readline inputs', async () => {
236338

237339
const prompter = new Inquirerer({

packages/inquirerer/src/prompt.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,15 @@ export class Inquirerer {
475475
continue;
476476
}
477477

478+
// Skip prompt entirely if skipPrompt is set.
479+
// The question still appears in man pages and CLI flag overrides still work
480+
// (handled by the `question.name in obj` check above), but no interactive
481+
// prompt is shown. The field is simply left out of the answers object.
482+
if (question.skipPrompt) {
483+
ctx.nextQuestion();
484+
continue;
485+
}
486+
478487
// Apply default value if applicable
479488
// this is if useDefault is set, rare! not typical defaults which happen AFTER
480489
// this is mostly to avoid a prompt for "hidden" options

packages/inquirerer/src/question/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface BaseQuestion {
3232
pattern?: string;
3333
dependsOn?: string[];
3434
when?: (answers: any) => boolean;
35+
skipPrompt?: boolean;
3536
}
3637

3738
export interface ConfirmQuestion extends BaseQuestion {

0 commit comments

Comments
 (0)