Skip to content

Commit

Permalink
fix(core): limit the amount of choices shown so that the prompt fits … (
Browse files Browse the repository at this point in the history
#26132)

…on screen

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->

When multiselect prompts have too many choices, `enquirer` does weird
unexpected things with the terminal.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Multiselect prompts are limited to enough lines so that they fit in the
terminal view preventing unexpected behavior.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
FrozenPandaz authored May 27, 2024
1 parent a6e23c1 commit e8b0972
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,35 @@ export async function addNxToMonorepo(options: Options) {
});

targetDefaults = (
(await prompt([
await prompt<{ targetDefaults: string[] }>([
{
type: 'multiselect',
name: 'targetDefaults',
message:
'Which scripts need to be run in order? (e.g. before building a project, dependent projects must be built)',
choices: scripts,
},
])) as any
/**
* limit is missing from the interface but it limits the amount of options shown
*/
limit: process.stdout.rows - 4, // 4 leaves room for the header above, the prompt and some whitespace
} as any,
])
).targetDefaults;

cacheableOperations = (
(await prompt([
await prompt<{ cacheableOperations: string[] }>([
{
type: 'multiselect',
name: 'cacheableOperations',
message:
'Which scripts are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not)',
choices: scripts,
},
])) as any
/**
* limit is missing from the interface but it limits the amount of options shown
*/
limit: process.stdout.rows - 4, // 4 leaves room for the header above, the prompt and some whitespace
} as any,
])
).cacheableOperations;

for (const scriptName of cacheableOperations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,19 @@ export async function addNxToNest(options: Options, packageJson: PackageJson) {
'🧑‍🔧 Please answer the following questions about the scripts found in your package.json in order to generate task runner configuration',
});
cacheableOperations = (
(await enquirer.prompt([
await enquirer.prompt<{ cacheableOperations: string[] }>([
{
type: 'multiselect',
name: 'cacheableOperations',
message:
'Which of the following scripts are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not)',
choices: scripts,
},
])) as any
/**
* limit is missing from the interface but it limits the amount of options shown
*/
limit: process.stdout.rows - 4, // 4 leaves room for the header above, the prompt and some whitespace
} as any,
])
).cacheableOperations;

for (const scriptName of cacheableOperations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@ export async function addNxToNpmRepo(options: Options) {
});

cacheableOperations = (
(await enquirer.prompt([
await enquirer.prompt<{ cacheableOperations: string[] }>([
{
type: 'multiselect',
name: 'cacheableOperations',
message:
'Which of the following scripts are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not). You can use spacebar to select one or more scripts.',
choices: scripts,
},
])) as any
/**
* limit is missing from the interface but it limits the amount of options shown
*/
limit: process.stdout.rows - 4, // 4 leaves room for the header above, the prompt and some whitespace
} as any,
])
).cacheableOperations;

for (const scriptName of cacheableOperations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ async function collectCacheableOperations(options: Options): Promise<string[]> {
'Which of the following targets are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not)',
// enquirer mutates the array below, create a new one to avoid it
choices: [...workspaceTargets],
},
/**
* limit is missing from the interface but it limits the amount of options shown
*/
limit: process.stdout.rows - 4, // 4 leaves room for the header above, the prompt and some whitespace
} as any,
])) as any
).cacheableOperations;
} else {
Expand Down
6 changes: 5 additions & 1 deletion packages/nx/src/command-line/init/init-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ async function detectPlugins(): Promise<{
type: 'multiselect',
message: `Which plugins would you like to add? Press <Space> to select and <Enter> to submit.`,
choices: plugins.map((p) => ({ name: p, value: p })),
},
/**
* limit is missing from the interface but it limits the amount of options shown
*/
limit: process.stdout.rows - 4, // 4 leaves room for the header above, the prompt and some whitespace
} as any,
]).then((r) => r.plugins);

if (pluginsToInstall?.length === 0)
Expand Down
26 changes: 10 additions & 16 deletions packages/nx/src/utils/params.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1870,22 +1870,16 @@ describe('params', () => {
}
);

expect(prompts).toMatchInlineSnapshot(`
[
{
"choices": [
"cat",
"dog",
"fish",
],
"limit": 10,
"message": "What kind of pets do you have?",
"name": "pets",
"type": "multiselect",
"validate": [Function],
},
]
`);
expect(prompts).toEqual([
{
message: 'What kind of pets do you have?',
name: 'pets',
type: 'multiselect',
choices: ['cat', 'dog', 'fish'],
limit: expect.any(Number),
validate: expect.any(Function),
},
]);
});

describe('Project prompts', () => {
Expand Down
14 changes: 8 additions & 6 deletions packages/nx/src/utils/params.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { logger } from './logger';
import type { NxJsonConfiguration } from '../config/nx-json';
import type {
TargetConfiguration,
ProjectsConfigurations,
TargetConfiguration,
} from '../config/workspace-json-project-json';
import { output } from './output';
import type { ProjectGraphError } from '../project-graph/error-types';
import { daemonClient } from '../daemon/client/client';

const LIST_CHOICE_DISPLAY_LIMIT = 10;

type PropertyDescription = {
type?: string | string[];
required?: string[];
Expand Down Expand Up @@ -892,10 +890,14 @@ export function getPromptsForSchema(
}
};

// Limit the number of choices displayed so that the prompt fits on the screen
const limitForChoicesDisplayed =
process.stdout.rows - question.message.split('\n').length;

if (v.type === 'string' && v.enum && Array.isArray(v.enum)) {
question.type = 'autocomplete';
question.choices = [...v.enum];
question.limit = LIST_CHOICE_DISPLAY_LIMIT;
question.limit = limitForChoicesDisplayed;
} else if (
v.type === 'string' &&
(v.$default?.$source === 'projectName' ||
Expand All @@ -906,7 +908,7 @@ export function getPromptsForSchema(
) {
question.type = 'autocomplete';
question.choices = Object.keys(projectsConfigurations.projects);
question.limit = LIST_CHOICE_DISPLAY_LIMIT;
question.limit = limitForChoicesDisplayed;
} else if (v.type === 'number' || v['x-prompt'].type == 'number') {
question.type = 'numeral';
} else if (
Expand All @@ -931,7 +933,7 @@ export function getPromptsForSchema(
};
}
});
question.limit = LIST_CHOICE_DISPLAY_LIMIT;
question.limit = limitForChoicesDisplayed;
} else if (v.type === 'boolean') {
question.type = 'confirm';
} else {
Expand Down

0 comments on commit e8b0972

Please sign in to comment.