Skip to content

Commit

Permalink
Start re-implementation of inquirer package control flow on top of th…
Browse files Browse the repository at this point in the history
…e new prompts
  • Loading branch information
SBoudrias committed Dec 2, 2022
1 parent 898ad75 commit 0b46582
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 4 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -5,3 +5,4 @@ dist
.nyc_output
packages/*/node_modules
packages/*/__snapshots__
*.test-d.ts
8 changes: 5 additions & 3 deletions .github/workflows/main.yml
Expand Up @@ -24,15 +24,17 @@ jobs:
env:
CI: true
FORCE_COLOR: 1
- name: Compile TS code
run: yarn lerna run tsc

- name: Eslint
run: yarn eslint . --ext .js --ext .ts
- name: Test inquirer package (current version)
run: yarn lerna exec npm test --scope inquirer
- name: Test type definitions
run: yarn tsc -p tsconfig.test.json
- name: Test monorepo packages (new version)
run: |
yarn lerna run tsc
yarn jest
run: yarn jest

- name: Upload code coverage
uses: codecov/codecov-action@v2
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -33,7 +33,7 @@
"publish": "lerna run tsc && lerna publish",
"pretest": "lerna run tsc && eslint . --ext .js --ext .ts",
"jest": "NODE_OPTIONS=--experimental-vm-modules jest",
"test": "yarn jest && lerna exec npm test --scope inquirer",
"test": "yarn tsc -p tsconfig.test.json && yarn jest && lerna exec npm test --scope inquirer",
"ts-node": "lerna run tsc && ts-node"
},
"repository": {
Expand Down
5 changes: 5 additions & 0 deletions packages/canary/.eslintrc.json
@@ -0,0 +1,5 @@
{
"rules": {
"no-await-in-loop": "off"
}
}
35 changes: 35 additions & 0 deletions packages/canary/demo.ts
@@ -0,0 +1,35 @@
import inquirer from './src/index.js';

const answers = await inquirer.prompt([
{
type: 'input',
name: 'first_name',
message: "What's your first name",
},
{
type: 'select',
name: 'ask_last_name',
message: 'Are you willing to share your last name',
choices: [
{ value: '1', name: 'Yes' },
{ value: '', name: 'No' },
],
},
{
type: 'input',
name: 'last_name',
when: (answers) => Boolean(answers.ask_last_name),
message: "What's your last name",
},
{
type: 'input',
name: 'phone',
message: "What's your phone number?",
filter(answer: string): string {
// TODO: What's the multi match flag again?
return answer.replace(/[^\d]+/, '');
},
},
]);

console.log(answers);
74 changes: 74 additions & 0 deletions packages/canary/package.json
@@ -0,0 +1,74 @@
{
"name": "@inquirer/canary",
"type": "module",
"version": "0.0.27-alpha.0",
"description": "Inquirer input text prompt",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"files": [
"dist/"
],
"repository": "SBoudrias/Inquirer.js",
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh"
],
"author": "Simon Boudrias <admin@simonboudrias.com>",
"license": "MIT",
"homepage": "https://github.com/SBoudrias/Inquirer.js",
"dependencies": {
"@inquirer/checkbox": "^0.0.30-alpha.0",
"@inquirer/confirm": "^0.0.28-alpha.0",
"@inquirer/core": "^0.0.30-alpha.0",
"@inquirer/editor": "^0.0.21-alpha.0",
"@inquirer/expand": "^0.0.28-alpha.0",
"@inquirer/input": "^0.0.28-alpha.0",
"@inquirer/password": "^0.0.28-alpha.0",
"@inquirer/rawlist": "^0.0.11-alpha.0",
"@inquirer/select": "^0.0.29-alpha.0",
"@inquirer/type": "^0.0.4-alpha.0",
"lodash": "^4.17.21"
},
"scripts": {
"tsc": "tsc"
},
"publishConfig": {
"access": "public"
}
}
125 changes: 125 additions & 0 deletions packages/canary/src/index.ts
@@ -0,0 +1,125 @@
import checkbox from '@inquirer/checkbox';
import confirm from '@inquirer/confirm';
import editor from '@inquirer/editor';
import expand from '@inquirer/expand';
import input from '@inquirer/input';
import password from '@inquirer/password';
import rawlist from '@inquirer/rawlist';
import select from '@inquirer/select';

import type { Prompt, Context } from '@inquirer/type';

type AsyncValue<Value> = Value | Promise<Value>;

function createPromptModule() {
const promptStore = {
checkbox,
confirm,
editor,
expand,
input,
password,
rawlist,
select,
};

type GetPrompt<U extends keyof typeof promptStore> = typeof promptStore[U];
type GetAnswerType<U extends keyof typeof promptStore> = GetPrompt<U> extends Prompt<
infer Answer,
any
>
? Answer
: never;
type GetPromptFnConfig<U> = U extends any
? U extends Prompt<any, infer Config>
? Config
: never
: never;
type GetPromptConfig<U extends keyof typeof promptStore> = U extends any
? GetPromptFnConfig<GetPrompt<U>>
: never;

type Answers = Record<string, unknown>;
type ControlFlowConfig<U extends keyof typeof promptStore> = U extends any
? {
name: string;
when?: (answers: Answers) => boolean | Promise<boolean>;
filter?: (
answer: GetAnswerType<U>,
answers: Answers
) => AsyncValue<GetAnswerType<U>>;
}
: never;
type PromptNameToFullConfig<U extends keyof typeof promptStore> = U extends any
? ControlFlowConfig<U> & GetPromptConfig<U> & { type: U }
: never;

async function prompt(
config:
| PromptNameToFullConfig<keyof typeof promptStore>
| PromptNameToFullConfig<keyof typeof promptStore>[],
context?: Context
) {
const answers: Answers = {};
const promptSeries = Array.isArray(config) ? config : [config];

for (const promptConfig of promptSeries) {
promptConfig as PromptNameToFullConfig<typeof promptConfig.type>;
const { type, name, when, filter, ...configRest } = promptConfig;

const promptFn = promptStore[type];

if (when != null && !(await when(answers))) {
continue;
}

const answer = await promptFn(configRest as any, context);
answers[name] = filter ? await (filter as any)(answer, answers) : answer;
}

return answers;
}

function registerPrompt(name: string, promptFn: Prompt<any, any>) {
// @ts-ignore: To fix later
promptStore[name] = promptFn;
}

function restoreDefaultPrompts() {
registerPrompt('checkbox', checkbox);
registerPrompt('confirm', confirm);
registerPrompt('editor', editor);
registerPrompt('expand', expand);
registerPrompt('input', input);
registerPrompt('password', password);
registerPrompt('rawlist', rawlist);
registerPrompt('select', select);
}

prompt.registerPrompt = registerPrompt;
prompt.createPromptModule = createPromptModule;
prompt.restoreDefaultPrompts = restoreDefaultPrompts;

return prompt;
}

const prompt = createPromptModule();
const inquirer = {
prompt,
registerPrompt: prompt.registerPrompt,
createPromptModule: prompt.createPromptModule,
restoreDefaultPrompts: prompt.restoreDefaultPrompts,
};
export default inquirer;

export {
createPromptModule,
checkbox,
confirm,
editor,
expand,
input,
password,
rawlist,
select,
};
7 changes: 7 additions & 0 deletions packages/canary/tsconfig.json
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
},
"include": ["./src"]
}
84 changes: 84 additions & 0 deletions packages/canary/types.test-d.ts
@@ -0,0 +1,84 @@
import inquirer from './src/index.js';

describe('Library types work properly', () => {
it('input', () => {
expect(() => {
return inquirer.prompt([
{
type: 'input',
name: 'first_name',
message: "What's your first name",
},
{
type: 'input',
name: 'first_name',
message: "What's your first name",
default: 'John',
},
{
type: 'input',
name: 'first_name',
message: "What's your first name",
/* @ts-expect-error: type shouldn't allow this format */
default: 6,
},
{
type: 'input',
name: 'foo',
message: 'foo',
filter(answer: string) {
return 'other string';
},
transformer(answer: string, { isFinal }: { isFinal: boolean }) {
return 'other string';
},
validate(answer: string) {
return 'oh no';
},
when() {
return true;
},
},
{
type: 'input',
name: 'foo',
message: 'foo',
/* @ts-expect-error: type shouldn't allow this format */
filter(answer: number) {
return 3;
},
/* @ts-expect-error: type shouldn't allow this format */
transformer(answer: string) {
return 45;
},
/* @ts-expect-error: type shouldn't allow this format */
validate(answer: string) {
return 1;
},
},
]);
}).toBeInstanceOf(Function);
});

it('select', () => {
expect(() => {
return inquirer.prompt([
{
type: 'select',
name: 'ask_last_name',
message: 'Are you willing to share your last name',
choices: [
{ value: '1', name: 'Yes' },
{ value: '', name: 'No' },
],
},
/* @ts-expect-error: choices option is required */
{
type: 'select',
name: 'ask_last_name',
message: 'Are you willing to share your last name',
},
]);
}).toBeInstanceOf(Function);
});
});
9 changes: 9 additions & 0 deletions tsconfig.test.json
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": ["**/*.test-d.ts"]
}

0 comments on commit 0b46582

Please sign in to comment.