@shopify/cli-kit 3.28.0 → 3.29.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/dist/api/common.d.ts +1 -1
- package/dist/api/common.js +7 -4
- package/dist/api/common.js.map +1 -1
- package/dist/api/graphql/find_org.js +2 -2
- package/dist/api/graphql/find_org.js.map +1 -1
- package/dist/api/identity.js +14 -4
- package/dist/api/identity.js.map +1 -1
- package/dist/api/partners.d.ts +0 -6
- package/dist/api/partners.js +0 -32
- package/dist/api/partners.js.map +1 -1
- package/dist/http/fetch.js +13 -0
- package/dist/http/fetch.js.map +1 -1
- package/dist/log.d.ts +1 -0
- package/dist/metadata.d.ts +2 -2
- package/dist/output.js +1 -1
- package/dist/output.js.map +1 -1
- package/dist/path.d.ts +4 -1
- package/dist/path.js +9 -1
- package/dist/path.js.map +1 -1
- package/dist/private/node/ui/components/Command.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.js +5 -1
- package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
- package/dist/{testing/fixtures/render-concurrent.d.ts → private/node/ui/components/ConcurrentOutput.test.d.ts} +0 -0
- package/dist/private/node/ui/components/ConcurrentOutput.test.js +53 -0
- package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -0
- package/dist/private/node/ui/components/FullScreen.js +2 -2
- package/dist/private/node/ui/components/FullScreen.js.map +1 -1
- package/dist/private/node/ui/components/Link.js.map +1 -1
- package/dist/private/node/ui/components/Prompt.d.ts +10 -0
- package/dist/private/node/ui/components/Prompt.js +23 -0
- package/dist/private/node/ui/components/Prompt.js.map +1 -0
- package/dist/private/node/ui/components/Prompt.test.d.ts +1 -0
- package/dist/private/node/ui/components/Prompt.test.js +71 -0
- package/dist/private/node/ui/components/Prompt.test.js.map +1 -0
- package/dist/private/node/ui/components/SelectInput.d.ts +12 -0
- package/dist/private/node/ui/components/SelectInput.js +93 -0
- package/dist/private/node/ui/components/SelectInput.js.map +1 -0
- package/dist/private/node/ui/components/SelectInput.test.d.ts +1 -0
- package/dist/private/node/ui/components/SelectInput.test.js +200 -0
- package/dist/private/node/ui/components/SelectInput.test.js.map +1 -0
- package/dist/private/node/ui/components/Table.d.ts +8 -0
- package/dist/private/node/ui/components/Table.js +17 -0
- package/dist/private/node/ui/components/Table.js.map +1 -0
- package/dist/private/node/ui/components/TextAnimation.js +1 -1
- package/dist/private/node/ui/components/TextAnimation.js.map +1 -1
- package/dist/private/node/ui.d.ts +2 -0
- package/dist/private/node/ui.js +14 -0
- package/dist/private/node/ui.js.map +1 -1
- package/dist/public/node/base-command.d.ts +2 -2
- package/dist/public/node/base-command.js +2 -2
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/node-package-manager.d.ts +1 -0
- package/dist/public/node/ruby.js +2 -2
- package/dist/public/node/ruby.js.map +1 -1
- package/dist/public/node/ui.d.ts +42 -0
- package/dist/public/node/ui.js +60 -6
- package/dist/public/node/ui.js.map +1 -1
- package/dist/session/exchange.js +4 -2
- package/dist/session/exchange.js.map +1 -1
- package/dist/session/validate.js +3 -13
- package/dist/session/validate.js.map +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +3 -1
- package/dist/session.js.map +1 -1
- package/dist/string.d.ts +1 -0
- package/dist/string.js +3 -0
- package/dist/string.js.map +1 -1
- package/dist/system.d.ts +2 -2
- package/dist/system.js +17 -2
- package/dist/system.js.map +1 -1
- package/dist/testing/ui.d.ts +4 -8
- package/dist/testing/ui.js +17 -17
- package/dist/testing/ui.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/executor.js +7 -5
- package/dist/ui/executor.js.map +1 -1
- package/dist/ui/inquirer/autocomplete.js +13 -4
- package/dist/ui/inquirer/autocomplete.js.map +1 -1
- package/dist/ui.d.ts +6 -0
- package/dist/ui.js.map +1 -1
- package/package.json +35 -6
- package/dist/private/node/ui/error.d.ts +0 -2
- package/dist/private/node/ui/error.js +0 -8
- package/dist/private/node/ui/error.js.map +0 -1
- package/dist/testing/fixtures/render-concurrent.js +0 -26
- package/dist/testing/fixtures/render-concurrent.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FullScreen.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/FullScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAC,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAEhD;;;;GAIG;AACH,MAAM,UAAU,GAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"FullScreen.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/FullScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAC,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAEhD;;;;GAIG;AACH,MAAM,UAAU,GAAa,CAAC,EAAC,QAAQ,EAAC,EAAe,EAAE;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO;QAC/B,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;KAC1B,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,QAAQ;YACf,OAAO,CAAC;gBACN,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO;gBAC/B,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;aAC1B,CAAC,CAAA;QACJ,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACrC,gCAAgC;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACrC,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YACtC,iCAAiC;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;QACvC,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,CACL,oBAAC,GAAG,IAAC,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,IACxC,QAAQ,CACL,CACP,CAAA;AACH,CAAC,CAAA;AAED,eAAe,UAAU,CAAA","sourcesContent":["import {Box} from 'ink'\nimport React, {useEffect, useState} from 'react'\n\n/**\n * `FullScreen` renders all output in a new buffer and makes it full screen. This is useful when:\n * - You want to preserve terminal history. `ink` [normally clears the terminal history](https://github.com/vadimdemedes/ink/issues/382) if the rendered output is taller than the terminal window. By rendering in a separate buffer history will be preserved and will be visible after pressing `Ctrl+C`.\n * - You want to respond to the resize event of the terminal. Whenever the user resizes their terminal window the output's height and width will be recalculated and re-rendered properly.\n */\nconst FullScreen: React.FC = ({children}): JSX.Element => {\n const [size, setSize] = useState({\n columns: process.stdout.columns,\n rows: process.stdout.rows,\n })\n\n useEffect(() => {\n function onResize() {\n setSize({\n columns: process.stdout.columns,\n rows: process.stdout.rows,\n })\n }\n\n process.stdout.on('resize', onResize)\n // switch to an alternate buffer\n process.stdout.write('\\u001B[?1049h')\n return () => {\n process.stdout.off('resize', onResize)\n // switch back to the main buffer\n process.stdout.write('\\u001B[?1049l')\n }\n }, [])\n\n return (\n <Box width={size.columns} height={size.rows}>\n {children}\n </Box>\n )\n}\n\nexport default FullScreen\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Link.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAC,IAAI,EAAC,MAAM,KAAK,CAAA;AACxB,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,YAAY,MAAM,eAAe,CAAA;AAOxC,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAA;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,IAAI,GAAoB,CAAC,EAAC,GAAG,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"Link.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAC,IAAI,EAAC,MAAM,KAAK,CAAA;AACxB,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,YAAY,MAAM,eAAe,CAAA;AAOxC,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAA;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,IAAI,GAAoB,CAAC,EAAC,GAAG,EAAE,KAAK,EAAC,EAAe,EAAE;IAC1D,OAAO,oBAAC,IAAI,QAAE,YAAY,CAAC,KAAK,IAAI,GAAG,EAAE,GAAG,EAAE,EAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAC,CAAC,CAAQ,CAAA;AAC7F,CAAC,CAAA;AAED,OAAO,EAAC,IAAI,EAAC,CAAA","sourcesContent":["import chalk from 'chalk'\nimport {Text} from 'ink'\nimport React from 'react'\nimport terminalLink from 'terminal-link'\n\ninterface Props {\n url: string\n label?: string\n}\n\nfunction fallback(text: string, url: string) {\n return `${text} ${chalk.dim(`(${url})`)}`\n}\n\n/**\n * `Link` displays a clickable link when supported by the terminal.\n */\nconst Link: React.FC<Props> = ({url, label}): JSX.Element => {\n return <Text>{terminalLink(label ?? url, url, {fallback: label ? fallback : false})}</Text>\n}\n\nexport {Link}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Props as SelectProps } from './SelectInput.js';
|
|
2
|
+
import { Props as TableProps } from './Table.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
export interface Props<T> {
|
|
5
|
+
message: string;
|
|
6
|
+
choices: SelectProps<T>['items'];
|
|
7
|
+
onChoose: SelectProps<T>['onSelect'];
|
|
8
|
+
infoTable?: TableProps['table'];
|
|
9
|
+
}
|
|
10
|
+
export default function Prompt<T>({ message, choices, infoTable, onChoose, }: React.PropsWithChildren<Props<T>>): JSX.Element | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import SelectInput from './SelectInput.js';
|
|
2
|
+
import Table from './Table.js';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { Box, Text } from 'ink';
|
|
5
|
+
import { figures } from 'listr2';
|
|
6
|
+
export default function Prompt({ message, choices, infoTable, onChoose, }) {
|
|
7
|
+
const [answer, setAnswer] = useState(null);
|
|
8
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
9
|
+
React.createElement(Box, null,
|
|
10
|
+
React.createElement(Box, { marginRight: 2 },
|
|
11
|
+
React.createElement(Text, null, "?")),
|
|
12
|
+
React.createElement(Text, null, message)),
|
|
13
|
+
infoTable && !answer && (React.createElement(Box, { marginLeft: 7 },
|
|
14
|
+
React.createElement(Table, { table: infoTable }))),
|
|
15
|
+
answer ? (React.createElement(Box, null,
|
|
16
|
+
React.createElement(Box, { marginRight: 2 },
|
|
17
|
+
React.createElement(Text, { color: "cyan" }, figures.tick)),
|
|
18
|
+
React.createElement(Text, { color: "cyan" }, answer.label))) : (React.createElement(SelectInput, { items: choices, onSelect: (value) => {
|
|
19
|
+
setAnswer(choices.find((choice) => choice.value === value) ?? null);
|
|
20
|
+
onChoose(value);
|
|
21
|
+
} }))));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=Prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Prompt.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Prompt.tsx"],"names":[],"mappings":"AAAA,OAAO,WAAuD,MAAM,kBAAkB,CAAA;AACtF,OAAO,KAA4B,MAAM,YAAY,CAAA;AACrD,OAAO,KAAK,EAAE,EAAC,QAAQ,EAAC,MAAM,OAAO,CAAA;AACrC,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAA;AAC7B,OAAO,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAA;AAS9B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAI,EAChC,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,GAC0B;IAClC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAuB,IAAI,CAAC,CAAA;IAEhE,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ;QACzB,oBAAC,GAAG;YACF,oBAAC,GAAG,IAAC,WAAW,EAAE,CAAC;gBACjB,oBAAC,IAAI,YAAS,CACV;YACN,oBAAC,IAAI,QAAE,OAAO,CAAQ,CAClB;QACL,SAAS,IAAI,CAAC,MAAM,IAAI,CACvB,oBAAC,GAAG,IAAC,UAAU,EAAE,CAAC;YAChB,oBAAC,KAAK,IAAC,KAAK,EAAE,SAAS,GAAI,CACvB,CACP;QACA,MAAM,CAAC,CAAC,CAAC,CACR,oBAAC,GAAG;YACF,oBAAC,GAAG,IAAC,WAAW,EAAE,CAAC;gBACjB,oBAAC,IAAI,IAAC,KAAK,EAAC,MAAM,IAAE,OAAO,CAAC,IAAI,CAAQ,CACpC;YAEN,oBAAC,IAAI,IAAC,KAAK,EAAC,MAAM,IAAE,MAAM,CAAC,KAAK,CAAQ,CACpC,CACP,CAAC,CAAC,CAAC,CACF,oBAAC,WAAW,IACV,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,CAAC,KAAQ,EAAE,EAAE;gBACrB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,CAAA;gBACnE,QAAQ,CAAC,KAAK,CAAC,CAAA;YACjB,CAAC,GACD,CACH,CACG,CACP,CAAA;AACH,CAAC","sourcesContent":["import SelectInput, {Props as SelectProps, Item as SelectItem} from './SelectInput.js'\nimport Table, {Props as TableProps} from './Table.js'\nimport React, {useState} from 'react'\nimport {Box, Text} from 'ink'\nimport {figures} from 'listr2'\n\nexport interface Props<T> {\n message: string\n choices: SelectProps<T>['items']\n onChoose: SelectProps<T>['onSelect']\n infoTable?: TableProps['table']\n}\n\nexport default function Prompt<T>({\n message,\n choices,\n infoTable,\n onChoose,\n}: React.PropsWithChildren<Props<T>>): JSX.Element | null {\n const [answer, setAnswer] = useState<SelectItem<T> | null>(null)\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Box marginRight={2}>\n <Text>?</Text>\n </Box>\n <Text>{message}</Text>\n </Box>\n {infoTable && !answer && (\n <Box marginLeft={7}>\n <Table table={infoTable} />\n </Box>\n )}\n {answer ? (\n <Box>\n <Box marginRight={2}>\n <Text color=\"cyan\">{figures.tick}</Text>\n </Box>\n\n <Text color=\"cyan\">{answer.label}</Text>\n </Box>\n ) : (\n <SelectInput\n items={choices}\n onSelect={(value: T) => {\n setAnswer(choices.find((choice) => choice.value === value) ?? null)\n onChoose(value)\n }}\n />\n )}\n </Box>\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import Prompt from './Prompt.js';
|
|
2
|
+
import { sendInput, waitForInputsToBeReady } from '../../../../testing/ui.js';
|
|
3
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render } from 'ink-testing-library';
|
|
6
|
+
const ARROW_DOWN = '\u001B[B';
|
|
7
|
+
const ENTER = '\r';
|
|
8
|
+
describe('Prompt', async () => {
|
|
9
|
+
test('choose an answer', async () => {
|
|
10
|
+
const onEnter = vi.fn();
|
|
11
|
+
const items = [
|
|
12
|
+
{ label: 'first', value: 'first' },
|
|
13
|
+
{ label: 'second', value: 'second' },
|
|
14
|
+
{ label: 'third', value: 'third' },
|
|
15
|
+
];
|
|
16
|
+
const infoTable = { Add: ['new-ext'], Remove: ['integrated-demand-ext', 'order-discount'] };
|
|
17
|
+
const renderInstance = render(React.createElement(Prompt, { message: "Associate your project with the org Castile Ventures?", choices: items, infoTable: infoTable, onChoose: onEnter }));
|
|
18
|
+
await waitForInputsToBeReady();
|
|
19
|
+
await sendInput(renderInstance, ARROW_DOWN);
|
|
20
|
+
await sendInput(renderInstance, ENTER);
|
|
21
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
22
|
+
"? Associate your project with the org Castile Ventures?
|
|
23
|
+
[36m✔[39m [36msecond[39m"
|
|
24
|
+
`);
|
|
25
|
+
expect(onEnter).toHaveBeenCalledWith(items[1].value);
|
|
26
|
+
});
|
|
27
|
+
test('supports an info table', async () => {
|
|
28
|
+
const items = [
|
|
29
|
+
{ label: 'first', value: 'first', key: 'f' },
|
|
30
|
+
{ label: 'second', value: 'second', key: 's' },
|
|
31
|
+
{ label: 'third', value: 'third' },
|
|
32
|
+
{ label: 'fourth', value: 'fourth' },
|
|
33
|
+
{ label: 'fifth', value: 'fifth', group: 'Automations' },
|
|
34
|
+
{ label: 'sixth', value: 'sixth', group: 'Automations' },
|
|
35
|
+
{ label: 'seventh', value: 'seventh' },
|
|
36
|
+
{ label: 'eighth', value: 'eighth', group: 'Merchant Admin' },
|
|
37
|
+
{ label: 'ninth', value: 'ninth', group: 'Merchant Admin' },
|
|
38
|
+
{ label: 'tenth', value: 'tenth' },
|
|
39
|
+
];
|
|
40
|
+
const infoTable = {
|
|
41
|
+
Add: ['new-ext'],
|
|
42
|
+
Remove: ['integrated-demand-ext', 'order-discount'],
|
|
43
|
+
};
|
|
44
|
+
const renderInstance = render(React.createElement(Prompt, { message: "Associate your project with the org Castile Ventures?", choices: items, infoTable: infoTable, onChoose: () => { } }));
|
|
45
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
46
|
+
"? Associate your project with the org Castile Ventures?
|
|
47
|
+
|
|
48
|
+
Add: • new-ext
|
|
49
|
+
Remove: • integrated-demand-ext
|
|
50
|
+
• order-discount
|
|
51
|
+
|
|
52
|
+
[36m>[39m [36m(f) first[39m
|
|
53
|
+
(s) second
|
|
54
|
+
(3) third
|
|
55
|
+
(4) fourth
|
|
56
|
+
(5) seventh
|
|
57
|
+
(6) tenth
|
|
58
|
+
|
|
59
|
+
[1mAutomations[22m
|
|
60
|
+
(7) fifth
|
|
61
|
+
(8) sixth
|
|
62
|
+
|
|
63
|
+
[1mMerchant Admin[22m
|
|
64
|
+
(9) eighth
|
|
65
|
+
(10) ninth
|
|
66
|
+
|
|
67
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
68
|
+
`);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=Prompt.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Prompt.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Prompt.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import Prompt from './Prompt.js'\nimport {sendInput, waitForInputsToBeReady} from '../../../../testing/ui.js'\nimport {describe, expect, test, vi} from 'vitest'\nimport React from 'react'\nimport {render} from 'ink-testing-library'\n\nconst ARROW_DOWN = '\\u001B[B'\nconst ENTER = '\\r'\n\ndescribe('Prompt', async () => {\n test('choose an answer', async () => {\n const onEnter = vi.fn()\n\n const items = [\n {label: 'first', value: 'first'},\n {label: 'second', value: 'second'},\n {label: 'third', value: 'third'},\n ]\n\n const infoTable = {Add: ['new-ext'], Remove: ['integrated-demand-ext', 'order-discount']}\n\n const renderInstance = render(\n <Prompt\n message=\"Associate your project with the org Castile Ventures?\"\n choices={items}\n infoTable={infoTable}\n onChoose={onEnter}\n />,\n )\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, ARROW_DOWN)\n await sendInput(renderInstance, ENTER)\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"? Associate your project with the org Castile Ventures?\n \u001b[36m✔\u001b[39m \u001b[36msecond\u001b[39m\"\n `)\n expect(onEnter).toHaveBeenCalledWith(items[1]!.value)\n })\n\n test('supports an info table', async () => {\n const items = [\n {label: 'first', value: 'first', key: 'f'},\n {label: 'second', value: 'second', key: 's'},\n {label: 'third', value: 'third'},\n {label: 'fourth', value: 'fourth'},\n {label: 'fifth', value: 'fifth', group: 'Automations'},\n {label: 'sixth', value: 'sixth', group: 'Automations'},\n {label: 'seventh', value: 'seventh'},\n {label: 'eighth', value: 'eighth', group: 'Merchant Admin'},\n {label: 'ninth', value: 'ninth', group: 'Merchant Admin'},\n {label: 'tenth', value: 'tenth'},\n ]\n\n const infoTable = {\n Add: ['new-ext'],\n Remove: ['integrated-demand-ext', 'order-discount'],\n }\n\n const renderInstance = render(\n <Prompt\n message=\"Associate your project with the org Castile Ventures?\"\n choices={items}\n infoTable={infoTable}\n onChoose={() => {}}\n />,\n )\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"? Associate your project with the org Castile Ventures?\n\n Add: • new-ext\n Remove: • integrated-demand-ext\n • order-discount\n\n \u001b[36m>\u001b[39m \u001b[36m(f) first\u001b[39m\n (s) second\n (3) third\n (4) fourth\n (5) seventh\n (6) tenth\n\n \u001b[1mAutomations\u001b[22m\n (7) fifth\n (8) sixth\n\n \u001b[1mMerchant Admin\u001b[22m\n (9) eighth\n (10) ninth\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n})\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface Props<T> {
|
|
3
|
+
items: Item<T>[];
|
|
4
|
+
onSelect: (value: T) => void;
|
|
5
|
+
}
|
|
6
|
+
export interface Item<T> {
|
|
7
|
+
label: string;
|
|
8
|
+
value: T;
|
|
9
|
+
key?: string;
|
|
10
|
+
group?: string;
|
|
11
|
+
}
|
|
12
|
+
export default function SelectInput<T>({ items, onSelect }: React.PropsWithChildren<Props<T>>): JSX.Element | null;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isTruthy } from '../../../../environment/utilities.js';
|
|
2
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
3
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import { groupBy, isEqual, mapValues } from 'lodash-es';
|
|
5
|
+
function groupItems(items) {
|
|
6
|
+
let index = 0;
|
|
7
|
+
return mapValues(groupBy(items, 'group'), (groupItems) => groupItems.map((groupItem) => {
|
|
8
|
+
const item = { ...groupItem, key: groupItem.key ?? (index + 1).toString(), index };
|
|
9
|
+
index += 1;
|
|
10
|
+
return item;
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
export default function SelectInput({ items, onSelect }) {
|
|
14
|
+
const [inputStack, setInputStack] = useState(null);
|
|
15
|
+
const [inputTimeout, setInputTimeout] = useState(null);
|
|
16
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
17
|
+
const keys = useRef(new Set(items.map((item) => item.key)));
|
|
18
|
+
const { exit: unmountInk } = useApp();
|
|
19
|
+
const groupedItems = groupItems(items);
|
|
20
|
+
const groupTitles = Object.keys(groupedItems);
|
|
21
|
+
const previousItems = useRef(items);
|
|
22
|
+
// reset index when items change
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!isEqual(previousItems.current.map((item) => item.value), items.map((item) => item.value))) {
|
|
25
|
+
setSelectedIndex(0);
|
|
26
|
+
}
|
|
27
|
+
previousItems.current = items;
|
|
28
|
+
}, [items]);
|
|
29
|
+
const handleInput = useCallback((input, key) => {
|
|
30
|
+
if (input === 'c' && key.ctrl) {
|
|
31
|
+
// Exceptions being throw in these hooks aren't being caught by our errorHandler.
|
|
32
|
+
// See also how we handle exceptions in CouncurrentOutput for reference.
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const parsedInput = parseInt(input, 10);
|
|
36
|
+
if (parsedInput !== 0 && parsedInput <= items.length + 1) {
|
|
37
|
+
setSelectedIndex(parsedInput - 1);
|
|
38
|
+
}
|
|
39
|
+
else if (keys.current.has(input)) {
|
|
40
|
+
const index = items.findIndex((item) => item.key === input);
|
|
41
|
+
if (index !== -1) {
|
|
42
|
+
setSelectedIndex(index);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (key.upArrow) {
|
|
46
|
+
const lastIndex = items.length - 1;
|
|
47
|
+
setSelectedIndex(selectedIndex === 0 ? lastIndex : selectedIndex - 1);
|
|
48
|
+
}
|
|
49
|
+
else if (key.downArrow) {
|
|
50
|
+
setSelectedIndex(selectedIndex === items.length - 1 ? 0 : selectedIndex + 1);
|
|
51
|
+
}
|
|
52
|
+
else if (key.return) {
|
|
53
|
+
onSelect(items[selectedIndex].value);
|
|
54
|
+
// This is a workaround needed because Ink behaves differently in CI when
|
|
55
|
+
// unmounting. See https://github.com/vadimdemedes/ink/pull/266
|
|
56
|
+
if (!isTruthy(process.env.CI))
|
|
57
|
+
unmountInk();
|
|
58
|
+
}
|
|
59
|
+
}, [selectedIndex, items, onSelect]);
|
|
60
|
+
useInput((input, key) => {
|
|
61
|
+
if (input.length > 0 && Object.values(key).every((value) => value === false)) {
|
|
62
|
+
const newInputStack = inputStack === null ? input : inputStack + input;
|
|
63
|
+
setInputStack(newInputStack);
|
|
64
|
+
if (inputTimeout !== null) {
|
|
65
|
+
clearTimeout(inputTimeout);
|
|
66
|
+
}
|
|
67
|
+
setInputTimeout(setTimeout(() => {
|
|
68
|
+
handleInput(newInputStack, key);
|
|
69
|
+
setInputStack(null);
|
|
70
|
+
setInputTimeout(null);
|
|
71
|
+
}, 300));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
handleInput(input, key);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
78
|
+
groupTitles.map((title) => {
|
|
79
|
+
const hasTitle = title !== 'undefined';
|
|
80
|
+
return (React.createElement(Box, { key: title, flexDirection: "column", marginTop: hasTitle ? 1 : 0 },
|
|
81
|
+
hasTitle && (React.createElement(Box, { marginLeft: 3 },
|
|
82
|
+
React.createElement(Text, { bold: true }, title))),
|
|
83
|
+
groupedItems[title].map((item) => {
|
|
84
|
+
const isSelected = item.index === selectedIndex;
|
|
85
|
+
return (React.createElement(Box, { key: item.key },
|
|
86
|
+
React.createElement(Box, { marginRight: 2 }, isSelected ? React.createElement(Text, { color: "cyan" }, `>`) : React.createElement(Text, null, " ")),
|
|
87
|
+
React.createElement(Text, { color: isSelected ? 'cyan' : undefined }, `(${item.key}) ${item.label}`)));
|
|
88
|
+
})));
|
|
89
|
+
}),
|
|
90
|
+
React.createElement(Box, { marginTop: 1, marginLeft: 3 },
|
|
91
|
+
React.createElement(Text, { dimColor: true }, "navigate with arrows, enter to select"))));
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=SelectInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectInput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SelectInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import {isTruthy} from '../../../../environment/utilities.js'\nimport React, {useState, useEffect, useRef, useCallback} from 'react'\nimport {Box, Key, Text, useApp, useInput} from 'ink'\nimport {groupBy, isEqual, mapValues} from 'lodash-es'\n\nexport interface Props<T> {\n items: Item<T>[]\n onSelect: (value: T) => void\n}\n\nexport interface Item<T> {\n label: string\n value: T\n key?: string\n group?: string\n}\n\nfunction groupItems<T>(items: Item<T>[]) {\n let index = 0\n\n return mapValues(groupBy(items, 'group'), (groupItems) =>\n groupItems.map((groupItem) => {\n const item = {...groupItem, key: groupItem.key ?? (index + 1).toString(), index}\n index += 1\n return item\n }),\n )\n}\n\nexport default function SelectInput<T>({items, onSelect}: React.PropsWithChildren<Props<T>>): JSX.Element | null {\n const [inputStack, setInputStack] = useState<string | null>(null)\n const [inputTimeout, setInputTimeout] = useState<NodeJS.Timeout | null>(null)\n const [selectedIndex, setSelectedIndex] = useState(0)\n const keys = useRef(new Set(items.map((item) => item.key)))\n const {exit: unmountInk} = useApp()\n const groupedItems = groupItems(items)\n const groupTitles = Object.keys(groupedItems)\n\n const previousItems = useRef<Item<T>[]>(items)\n\n // reset index when items change\n useEffect(() => {\n if (\n !isEqual(\n previousItems.current.map((item) => item.value),\n items.map((item) => item.value),\n )\n ) {\n setSelectedIndex(0)\n }\n\n previousItems.current = items\n }, [items])\n\n const handleInput = useCallback(\n (input: string, key: Key) => {\n if (input === 'c' && key.ctrl) {\n // Exceptions being throw in these hooks aren't being caught by our errorHandler.\n // See also how we handle exceptions in CouncurrentOutput for reference.\n process.exit(1)\n }\n\n const parsedInput = parseInt(input, 10)\n\n if (parsedInput !== 0 && parsedInput <= items.length + 1) {\n setSelectedIndex(parsedInput - 1)\n } else if (keys.current.has(input)) {\n const index = items.findIndex((item) => item.key === input)\n if (index !== -1) {\n setSelectedIndex(index)\n }\n }\n\n if (key.upArrow) {\n const lastIndex = items.length - 1\n\n setSelectedIndex(selectedIndex === 0 ? lastIndex : selectedIndex - 1)\n } else if (key.downArrow) {\n setSelectedIndex(selectedIndex === items.length - 1 ? 0 : selectedIndex + 1)\n } else if (key.return) {\n onSelect(items[selectedIndex]!.value)\n // This is a workaround needed because Ink behaves differently in CI when\n // unmounting. See https://github.com/vadimdemedes/ink/pull/266\n if (!isTruthy(process.env.CI)) unmountInk()\n }\n },\n [selectedIndex, items, onSelect],\n )\n\n useInput((input, key) => {\n if (input.length > 0 && Object.values(key).every((value) => value === false)) {\n const newInputStack = inputStack === null ? input : inputStack + input\n\n setInputStack(newInputStack)\n\n if (inputTimeout !== null) {\n clearTimeout(inputTimeout)\n }\n\n setInputTimeout(\n setTimeout(() => {\n handleInput(newInputStack, key)\n setInputStack(null)\n setInputTimeout(null)\n }, 300),\n )\n } else {\n handleInput(input, key)\n }\n })\n\n return (\n <Box flexDirection=\"column\">\n {groupTitles.map((title) => {\n const hasTitle = title !== 'undefined'\n\n return (\n <Box key={title} flexDirection=\"column\" marginTop={hasTitle ? 1 : 0}>\n {hasTitle && (\n <Box marginLeft={3}>\n <Text bold>{title}</Text>\n </Box>\n )}\n {groupedItems[title]!.map((item) => {\n const isSelected = item.index === selectedIndex\n\n return (\n <Box key={item.key}>\n <Box marginRight={2}>{isSelected ? <Text color=\"cyan\">{`>`}</Text> : <Text> </Text>}</Box>\n\n <Text color={isSelected ? 'cyan' : undefined}>{`(${item.key}) ${item.label}`}</Text>\n </Box>\n )\n })}\n </Box>\n )\n })}\n\n <Box marginTop={1} marginLeft={3}>\n <Text dimColor>navigate with arrows, enter to select</Text>\n </Box>\n </Box>\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import SelectInput from './SelectInput.js';
|
|
2
|
+
import { waitForInputsToBeReady, waitForChange, sendInput } from '../../../../testing/ui.js';
|
|
3
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render } from 'ink-testing-library';
|
|
6
|
+
const ARROW_UP = '\u001B[A';
|
|
7
|
+
const ARROW_DOWN = '\u001B[B';
|
|
8
|
+
const ENTER = '\r';
|
|
9
|
+
describe('SelectInput', async () => {
|
|
10
|
+
test('move up with up arrow key', async () => {
|
|
11
|
+
const items = [
|
|
12
|
+
{
|
|
13
|
+
label: 'First',
|
|
14
|
+
value: 'first',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: 'Second',
|
|
18
|
+
value: 'second',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Third',
|
|
22
|
+
value: 'third',
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: () => { } }));
|
|
26
|
+
await waitForInputsToBeReady();
|
|
27
|
+
await sendInput(renderInstance, ARROW_UP);
|
|
28
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
29
|
+
" (1) First
|
|
30
|
+
(2) Second
|
|
31
|
+
[36m>[39m [36m(3) Third[39m
|
|
32
|
+
|
|
33
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
34
|
+
`);
|
|
35
|
+
});
|
|
36
|
+
test('move down with down arrow key', async () => {
|
|
37
|
+
const items = [
|
|
38
|
+
{
|
|
39
|
+
label: 'First',
|
|
40
|
+
value: 'first',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: 'Second',
|
|
44
|
+
value: 'second',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'Third',
|
|
48
|
+
value: 'third',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: () => { } }));
|
|
52
|
+
await waitForInputsToBeReady();
|
|
53
|
+
await sendInput(renderInstance, ARROW_DOWN);
|
|
54
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
55
|
+
" (1) First
|
|
56
|
+
[36m>[39m [36m(2) Second[39m
|
|
57
|
+
(3) Third
|
|
58
|
+
|
|
59
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
60
|
+
`);
|
|
61
|
+
});
|
|
62
|
+
test('select item with enter key', async () => {
|
|
63
|
+
const onEnter = vi.fn();
|
|
64
|
+
const items = [
|
|
65
|
+
{
|
|
66
|
+
label: 'First',
|
|
67
|
+
value: 'first',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
label: 'Second',
|
|
71
|
+
value: 'second',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Third',
|
|
75
|
+
value: 'third',
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: onEnter }));
|
|
79
|
+
await waitForInputsToBeReady();
|
|
80
|
+
await sendInput(renderInstance, ARROW_DOWN);
|
|
81
|
+
await waitForChange(() => renderInstance.stdin.write(ENTER), () => onEnter.mock.calls.length);
|
|
82
|
+
expect(onEnter).toHaveBeenCalledWith(items[1].value);
|
|
83
|
+
});
|
|
84
|
+
test('handles keys with multiple digits', async () => {
|
|
85
|
+
const items = [
|
|
86
|
+
{
|
|
87
|
+
label: 'First',
|
|
88
|
+
value: 'first',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: 'Second',
|
|
92
|
+
value: 'second',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: 'Tenth',
|
|
96
|
+
value: 'tenth',
|
|
97
|
+
key: '10',
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: () => { } }));
|
|
101
|
+
await waitForInputsToBeReady();
|
|
102
|
+
await sendInput(renderInstance, '1', '0');
|
|
103
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
104
|
+
" (1) First
|
|
105
|
+
(2) Second
|
|
106
|
+
[36m>[39m [36m(10) Tenth[39m
|
|
107
|
+
|
|
108
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
109
|
+
`);
|
|
110
|
+
});
|
|
111
|
+
test('handles custom keys', async () => {
|
|
112
|
+
const items = [
|
|
113
|
+
{
|
|
114
|
+
label: 'First',
|
|
115
|
+
value: 'first',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
label: 'Second',
|
|
119
|
+
value: 'second',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
label: 'Third',
|
|
123
|
+
value: 'third',
|
|
124
|
+
key: 't',
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: () => { } }));
|
|
128
|
+
await waitForInputsToBeReady();
|
|
129
|
+
await sendInput(renderInstance, 't');
|
|
130
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
131
|
+
" (1) First
|
|
132
|
+
(2) Second
|
|
133
|
+
[36m>[39m [36m(t) Third[39m
|
|
134
|
+
|
|
135
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
136
|
+
`);
|
|
137
|
+
});
|
|
138
|
+
test('rotate after reaching the end of the list', async () => {
|
|
139
|
+
const items = [
|
|
140
|
+
{
|
|
141
|
+
label: 'First',
|
|
142
|
+
value: 'first',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: 'Second',
|
|
146
|
+
value: 'second',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: 'Third',
|
|
150
|
+
value: 'third',
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: () => { } }));
|
|
154
|
+
await waitForInputsToBeReady();
|
|
155
|
+
await sendInput(renderInstance, ARROW_DOWN);
|
|
156
|
+
await sendInput(renderInstance, ARROW_DOWN);
|
|
157
|
+
await sendInput(renderInstance, ARROW_DOWN);
|
|
158
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
159
|
+
"[36m>[39m [36m(1) First[39m
|
|
160
|
+
(2) Second
|
|
161
|
+
(3) Third
|
|
162
|
+
|
|
163
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
164
|
+
`);
|
|
165
|
+
});
|
|
166
|
+
test('support groups', async () => {
|
|
167
|
+
const items = [
|
|
168
|
+
{ label: 'first', value: 'first', key: 'f' },
|
|
169
|
+
{ label: 'second', value: 'second', key: 's' },
|
|
170
|
+
{ label: 'third', value: 'third' },
|
|
171
|
+
{ label: 'fourth', value: 'fourth' },
|
|
172
|
+
{ label: 'fifth', value: 'fifth', group: 'Automations' },
|
|
173
|
+
{ label: 'sixth', value: 'sixth', group: 'Automations' },
|
|
174
|
+
{ label: 'seventh', value: 'seventh' },
|
|
175
|
+
{ label: 'eighth', value: 'eighth', group: 'Merchant Admin' },
|
|
176
|
+
{ label: 'ninth', value: 'ninth', group: 'Merchant Admin' },
|
|
177
|
+
{ label: 'tenth', value: 'tenth' },
|
|
178
|
+
];
|
|
179
|
+
const renderInstance = render(React.createElement(SelectInput, { items: items, onSelect: () => { } }));
|
|
180
|
+
expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`
|
|
181
|
+
"[36m>[39m [36m(f) first[39m
|
|
182
|
+
(s) second
|
|
183
|
+
(3) third
|
|
184
|
+
(4) fourth
|
|
185
|
+
(5) seventh
|
|
186
|
+
(6) tenth
|
|
187
|
+
|
|
188
|
+
[1mAutomations[22m
|
|
189
|
+
(7) fifth
|
|
190
|
+
(8) sixth
|
|
191
|
+
|
|
192
|
+
[1mMerchant Admin[22m
|
|
193
|
+
(9) eighth
|
|
194
|
+
(10) ninth
|
|
195
|
+
|
|
196
|
+
[2mnavigate with arrows, enter to select[22m"
|
|
197
|
+
`);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=SelectInput.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectInput.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SelectInput.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import SelectInput from './SelectInput.js'\nimport {waitForInputsToBeReady, waitForChange, sendInput} from '../../../../testing/ui.js'\nimport {describe, expect, test, vi} from 'vitest'\nimport React from 'react'\nimport {render} from 'ink-testing-library'\n\nconst ARROW_UP = '\\u001B[A'\nconst ARROW_DOWN = '\\u001B[B'\nconst ENTER = '\\r'\n\ndescribe('SelectInput', async () => {\n test('move up with up arrow key', async () => {\n const items = [\n {\n label: 'First',\n value: 'first',\n },\n {\n label: 'Second',\n value: 'second',\n },\n {\n label: 'Third',\n value: 'third',\n },\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={() => {}} />)\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, ARROW_UP)\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \" (1) First\n (2) Second\n \u001b[36m>\u001b[39m \u001b[36m(3) Third\u001b[39m\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n\n test('move down with down arrow key', async () => {\n const items = [\n {\n label: 'First',\n value: 'first',\n },\n {\n label: 'Second',\n value: 'second',\n },\n {\n label: 'Third',\n value: 'third',\n },\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={() => {}} />)\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, ARROW_DOWN)\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \" (1) First\n \u001b[36m>\u001b[39m \u001b[36m(2) Second\u001b[39m\n (3) Third\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n\n test('select item with enter key', async () => {\n const onEnter = vi.fn()\n\n const items = [\n {\n label: 'First',\n value: 'first',\n },\n {\n label: 'Second',\n value: 'second',\n },\n {\n label: 'Third',\n value: 'third',\n },\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={onEnter} />)\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, ARROW_DOWN)\n await waitForChange(\n () => renderInstance.stdin.write(ENTER),\n () => onEnter.mock.calls.length,\n )\n\n expect(onEnter).toHaveBeenCalledWith(items[1]!.value)\n })\n\n test('handles keys with multiple digits', async () => {\n const items = [\n {\n label: 'First',\n value: 'first',\n },\n {\n label: 'Second',\n value: 'second',\n },\n {\n label: 'Tenth',\n value: 'tenth',\n key: '10',\n },\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={() => {}} />)\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, '1', '0')\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \" (1) First\n (2) Second\n \u001b[36m>\u001b[39m \u001b[36m(10) Tenth\u001b[39m\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n\n test('handles custom keys', async () => {\n const items = [\n {\n label: 'First',\n value: 'first',\n },\n {\n label: 'Second',\n value: 'second',\n },\n {\n label: 'Third',\n value: 'third',\n key: 't',\n },\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={() => {}} />)\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, 't')\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \" (1) First\n (2) Second\n \u001b[36m>\u001b[39m \u001b[36m(t) Third\u001b[39m\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n\n test('rotate after reaching the end of the list', async () => {\n const items = [\n {\n label: 'First',\n value: 'first',\n },\n {\n label: 'Second',\n value: 'second',\n },\n {\n label: 'Third',\n value: 'third',\n },\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={() => {}} />)\n\n await waitForInputsToBeReady()\n await sendInput(renderInstance, ARROW_DOWN)\n await sendInput(renderInstance, ARROW_DOWN)\n await sendInput(renderInstance, ARROW_DOWN)\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"\u001b[36m>\u001b[39m \u001b[36m(1) First\u001b[39m\n (2) Second\n (3) Third\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n\n test('support groups', async () => {\n const items = [\n {label: 'first', value: 'first', key: 'f'},\n {label: 'second', value: 'second', key: 's'},\n {label: 'third', value: 'third'},\n {label: 'fourth', value: 'fourth'},\n {label: 'fifth', value: 'fifth', group: 'Automations'},\n {label: 'sixth', value: 'sixth', group: 'Automations'},\n {label: 'seventh', value: 'seventh'},\n {label: 'eighth', value: 'eighth', group: 'Merchant Admin'},\n {label: 'ninth', value: 'ninth', group: 'Merchant Admin'},\n {label: 'tenth', value: 'tenth'},\n ]\n\n const renderInstance = render(<SelectInput items={items} onSelect={() => {}} />)\n\n expect(renderInstance.lastFrame()).toMatchInlineSnapshot(`\n \"\u001b[36m>\u001b[39m \u001b[36m(f) first\u001b[39m\n (s) second\n (3) third\n (4) fourth\n (5) seventh\n (6) tenth\n\n \u001b[1mAutomations\u001b[22m\n (7) fifth\n (8) sixth\n\n \u001b[1mMerchant Admin\u001b[22m\n (9) eighth\n (10) ninth\n\n \u001b[2mnavigate with arrows, enter to select\u001b[22m\"\n `)\n })\n})\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { List } from './List.js';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { capitalize } from 'lodash-es';
|
|
5
|
+
const Table = ({ table }) => {
|
|
6
|
+
const headers = Object.keys(table);
|
|
7
|
+
const headerColumnWidth = Math.max(...headers.map((header) => header.length));
|
|
8
|
+
return (React.createElement(Box, { flexDirection: "column", paddingY: 1 }, headers.map((header, index) => (React.createElement(Box, { key: index },
|
|
9
|
+
React.createElement(Box, { width: headerColumnWidth + 1 },
|
|
10
|
+
React.createElement(Text, null,
|
|
11
|
+
capitalize(header),
|
|
12
|
+
":")),
|
|
13
|
+
React.createElement(Box, { flexGrow: 1 },
|
|
14
|
+
React.createElement(List, { items: table[header] })))))));
|
|
15
|
+
};
|
|
16
|
+
export default Table;
|
|
17
|
+
//# sourceMappingURL=Table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Table.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Table.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAA;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAC,UAAU,EAAC,MAAM,WAAW,CAAA;AAQpC,MAAM,KAAK,GAAoB,CAAC,EAAC,KAAK,EAAC,EAAE,EAAE;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IAE7E,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,IACpC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAC9B,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK;QACb,oBAAC,GAAG,IAAC,KAAK,EAAE,iBAAiB,GAAG,CAAC;YAC/B,oBAAC,IAAI;gBAAE,UAAU,CAAC,MAAM,CAAC;oBAAS,CAC9B;QACN,oBAAC,GAAG,IAAC,QAAQ,EAAE,CAAC;YACd,oBAAC,IAAI,IAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAE,GAAI,CAC3B,CACF,CACP,CAAC,CACE,CACP,CAAA;AACH,CAAC,CAAA;AAED,eAAe,KAAK,CAAA","sourcesContent":["import {List} from './List.js'\nimport {Box, Text} from 'ink'\nimport React from 'react'\nimport {capitalize} from 'lodash-es'\n\nexport interface Props {\n table: {\n [header: string]: string[]\n }\n}\n\nconst Table: React.FC<Props> = ({table}) => {\n const headers = Object.keys(table)\n const headerColumnWidth = Math.max(...headers.map((header) => header.length))\n\n return (\n <Box flexDirection=\"column\" paddingY={1}>\n {headers.map((header, index) => (\n <Box key={index}>\n <Box width={headerColumnWidth + 1}>\n <Text>{capitalize(header)}:</Text>\n </Box>\n <Box flexGrow={1}>\n <List items={table[header]!} />\n </Box>\n </Box>\n ))}\n </Box>\n )\n}\n\nexport default Table\n"]}
|
|
@@ -13,7 +13,7 @@ const delays = {
|
|
|
13
13
|
/**
|
|
14
14
|
* `TextAnimation` applies animations from [chalk-animation](https://github.com/bokub/chalk-animation) to `Text` Children
|
|
15
15
|
*/
|
|
16
|
-
const TextAnimation = ({ name = 'rainbow', speed = 1, children
|
|
16
|
+
const TextAnimation = ({ name = 'rainbow', speed = 1, children }) => {
|
|
17
17
|
const [animationTimeout, setAnimationTimeout] = useState(null);
|
|
18
18
|
const animation = chalkAnimation[name]('').stop();
|
|
19
19
|
const [frame, setFrame] = useState('');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextAnimation.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/TextAnimation.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,aAAa,CAAA;AACxC,OAAO,cAAc,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAC,IAAI,EAAC,MAAM,KAAK,CAAA;AACxB,OAAO,KAAK,EAAE,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAShD,MAAM,MAAM,GAAqC;IAC/C,OAAO,EAAE,EAAE;IACX,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,GAAG;IACT,OAAO,EAAE,EAAE;CACZ,CAAA;AAED;;GAEG;AACH,MAAM,aAAa,GAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"TextAnimation.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/TextAnimation.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,aAAa,CAAA;AACxC,OAAO,cAAc,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAC,IAAI,EAAC,MAAM,KAAK,CAAA;AACxB,OAAO,KAAK,EAAE,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAShD,MAAM,MAAM,GAAqC;IAC/C,OAAO,EAAE,EAAE;IACX,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,GAAG;IACT,OAAO,EAAE,EAAE;CACZ,CAAA;AAED;;GAEG;AACH,MAAM,aAAa,GAAoB,CAAC,EAAC,IAAI,GAAG,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE,QAAQ,EAAC,EAAe,EAAE;IAC9F,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAwB,IAAI,CAAC,CAAA;IACrF,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAEtC,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,MAAM,EAAC,MAAM,EAAC,GAAG,YAAY,CAAC,oBAAC,IAAI,QAAE,QAAQ,CAAQ,CAAC,CAAA;QAEtD,yFAAyF;QACzF,uFAAuF;QACvF,0FAA0F;QAC1F,mBAAmB;QAEnB,MAAM,KAAK,GAAG,SAAS;aACpB,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;aACrB,KAAK,EAAE;aACP,OAAO,CAAC,mCAAmC,EAAE,EAAE,CAAC,CAAA,CAAC,uCAAuC;QAE3F,QAAQ,CAAC,KAAK,CAAC,CAAA;QAEf,mBAAmB,CACjB,UAAU,CAAC,GAAG,EAAE;YACd,KAAK,EAAE,CAAA;QACT,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CACzB,CAAA;IACH,CAAC,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,EAAE,CAAA;QAEP,OAAO,GAAG,EAAE;YACV,IAAI,gBAAgB;gBAAE,YAAY,CAAC,gBAAgB,CAAC,CAAA;YAEpD,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAC3B,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,oBAAC,IAAI,QAAE,KAAK,CAAQ,CAAA;AAC7B,CAAC,CAAA;AAED,OAAO,EAAC,aAAa,EAAC,CAAA","sourcesContent":["import {renderString} from '../../ui.js'\nimport chalkAnimation from 'chalk-animation'\nimport {Text} from 'ink'\nimport React, {useEffect, useState} from 'react'\n\ntype AnimationName = 'rainbow' | 'pulse' | 'glitch' | 'radar' | 'neon' | 'karaoke'\n\ninterface Props {\n name?: AnimationName\n speed?: number\n}\n\nconst delays: {[key in AnimationName]: number} = {\n rainbow: 15,\n pulse: 16,\n glitch: 55,\n radar: 50,\n neon: 500,\n karaoke: 50,\n}\n\n/**\n * `TextAnimation` applies animations from [chalk-animation](https://github.com/bokub/chalk-animation) to `Text` Children\n */\nconst TextAnimation: React.FC<Props> = ({name = 'rainbow', speed = 1, children}): JSX.Element => {\n const [animationTimeout, setAnimationTimeout] = useState<NodeJS.Timeout | null>(null)\n const animation = chalkAnimation[name]('').stop()\n const [frame, setFrame] = useState('')\n\n const start = () => {\n const {output} = renderString(<Text>{children}</Text>)\n\n // There's probably some clashing between `chalk-animation` and Ink's rendering mechanism\n // (which uses `log-update`). The solution is to remove the ANSI escape sequence at the\n // start of the frame that we're getting from `chalk-animation` that tells the terminal to\n // clear the lines.\n\n const frame = animation\n .replace(output ?? '')\n .frame()\n .replace(/^\\u001B\\[(\\d)F\\u001B\\[G\\u001B\\[2K/, '') // eslint-disable-line no-control-regex\n\n setFrame(frame)\n\n setAnimationTimeout(\n setTimeout(() => {\n start()\n }, delays[name] / speed),\n )\n }\n\n useEffect(() => {\n start()\n\n return () => {\n if (animationTimeout) clearTimeout(animationTimeout)\n\n setAnimationTimeout(null)\n }\n }, [])\n\n return <Text>{frame}</Text>\n}\n\nexport {TextAnimation}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Logger, LogLevel } from '../../output.js';
|
|
2
|
+
import { Props as PromptProps } from '../../private/node/ui/components/Prompt.js';
|
|
2
3
|
import { ReactElement } from 'react';
|
|
3
4
|
import { RenderOptions } from 'ink';
|
|
4
5
|
export declare function renderOnce(element: JSX.Element, logLevel?: LogLevel, logger?: Logger): void;
|
|
@@ -8,4 +9,5 @@ interface Instance {
|
|
|
8
9
|
unmount: () => void;
|
|
9
10
|
}
|
|
10
11
|
export declare const renderString: (element: ReactElement) => Instance;
|
|
12
|
+
export declare function prompt<T>(options: Omit<PromptProps<T>, 'onChoose'>): Promise<T>;
|
|
11
13
|
export {};
|
package/dist/private/node/ui.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { isUnitTest } from '../../environment/local.js';
|
|
2
2
|
import { collectLog, consoleLog, outputWhereAppropriate } from '../../output.js';
|
|
3
|
+
import Prompt from '../../private/node/ui/components/Prompt.js';
|
|
4
|
+
import React from 'react';
|
|
3
5
|
import { render as inkRender } from 'ink';
|
|
4
6
|
import { EventEmitter } from 'events';
|
|
5
7
|
export function renderOnce(element, logLevel = 'info', logger = consoleLog) {
|
|
@@ -45,4 +47,16 @@ export const renderString = (element) => {
|
|
|
45
47
|
unmount: instance.unmount,
|
|
46
48
|
};
|
|
47
49
|
};
|
|
50
|
+
export async function prompt(options) {
|
|
51
|
+
let onChooseResolve = () => { };
|
|
52
|
+
const onChoosePromise = new Promise((resolve) => {
|
|
53
|
+
onChooseResolve = resolve;
|
|
54
|
+
});
|
|
55
|
+
const props = {
|
|
56
|
+
...options,
|
|
57
|
+
onChoose: onChooseResolve,
|
|
58
|
+
};
|
|
59
|
+
await render(React.createElement(Prompt, { ...props }), { exitOnCtrlC: false });
|
|
60
|
+
return onChoosePromise;
|
|
61
|
+
}
|
|
48
62
|
//# sourceMappingURL=ui.js.map
|