@shopify/cli-kit 3.28.0 → 3.30.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/admin.js +5 -11
- package/dist/api/admin.js.map +1 -1
- 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/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +2 -2
- package/dist/output.js +1 -3
- 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/common/array.d.ts +1 -1
- package/dist/public/common/array.js.map +1 -1
- package/dist/public/node/base-command.d.ts +2 -2
- package/dist/public/node/base-command.js +2 -4
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/checksum.d.ts +1 -1
- package/dist/public/node/checksum.js +1 -1
- package/dist/public/node/checksum.js.map +1 -1
- package/dist/public/node/cli.js.map +1 -1
- package/dist/public/node/dot-env.js.map +1 -1
- package/dist/public/node/error-handler.d.ts +5 -1
- package/dist/public/node/error-handler.js.map +1 -1
- package/dist/public/node/framework.js.map +1 -1
- package/dist/public/node/hooks/prerun.js.map +1 -1
- package/dist/public/node/node-package-manager.d.ts +1 -0
- package/dist/public/node/node-package-manager.js.map +1 -1
- 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 +0 -11
- package/dist/ui.js.map +1 -1
- package/package.json +35 -6
- package/dist/log.d.ts +0 -30
- package/dist/log.js +0 -169
- package/dist/log.js.map +0 -1
- 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":"ConcurrentOutput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAoB,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AACnE,OAAO,EAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,KAAK,CAAA;AAC7C,OAAO,SAAS,MAAM,YAAY,CAAA;AAElC,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAA;AAmBpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,gBAAgB,GAA6B,CAAC,EAAC,SAAS,EAAE,eAAe,EAAE,cAAc,GAAG,IAAI,EAAC,EAAE,EAAE;IACzG,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAU,EAAE,CAAC,CAAA;IAC/D,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;IACvE,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IACvF,MAAM,EAAC,IAAI,EAAE,UAAU,EAAC,GAAG,MAAM,EAAE,CAAA;IAEnC,SAAS,SAAS,CAAC,KAAa;QAC9B,MAAM,UAAU,GAAG,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAA;QAC5F,OAAO,gBAAgB,CAAC,UAAU,CAAE,CAAA;IACtC,CAAC;IAED,MAAM,cAAc,GAAG,CAAC,OAAsB,EAAE,KAAa,EAAE,EAAE;QAC/D,OAAO,IAAI,QAAQ,CAAC;YAClB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI;gBAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBAEjF,gBAAgB,CAAC,CAAC,qBAAqB,EAAE,EAAE,CAAC;oBAC1C,GAAG,qBAAqB;oBACxB;wBACE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;wBACvB,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,KAAK;qBACN;iBACF,CAAC,CAAA;gBAEF,IAAI,EAAE,CAAA;YACR,CAAC;SACF,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,KAAK,IAAI,EAAE;QAC9B,MAAM,OAAO,CAAC,GAAG,CACf,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACrC,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAE7C,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;QAC9D,CAAC,CAAC,CACH,CAAA;QAED,UAAU,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"ConcurrentOutput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.tsx"],"names":[],"mappings":"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","sourcesContent":["import {OutputProcess} from '../../../../output.js'\nimport {isTruthy} from '../../../../environment/utilities.js'\nimport React, {FunctionComponent, useEffect, useState} from 'react'\nimport {Box, Static, Text, useApp} from 'ink'\nimport stripAnsi from 'strip-ansi'\nimport AbortController from 'abort-controller'\nimport {Writable} from 'node:stream'\n\nexport type WritableStream = (process: OutputProcess, index: number) => Writable\nexport type RunProcesses = (\n writableStream: WritableStream,\n unmountInk: (error?: Error | undefined) => void,\n) => Promise<void>\n\ninterface Props {\n processes: OutputProcess[]\n abortController: AbortController\n showTimestamps?: boolean\n}\ninterface Chunk {\n color: string\n prefix: string\n lines: string[]\n}\n\n/**\n * Renders output from concurrent processes to the terminal.\n * Output will be divided in a three column layout\n * with the left column containing the timestamp,\n * the right column containing the output,\n * and the middle column containing the process prefix.\n * Every process will be rendered with a different color, up to 4 colors.\n *\n * For example running `shopify app dev`:\n *\n * ```shell\n * 2022-10-10 13:11:03 | backend | npm\n * 2022-10-10 13:11:03 | backend | WARN ignoring workspace config at ...\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend | > shopify-app-template-node@0.1.0 dev\n * 2022-10-10 13:11:03 | backend | > cross-env NODE_ENV=development nodemon backend/index.js --watch ./backend\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | frontend | > starter-react-frontend-app@0.1.0 dev\n * 2022-10-10 13:11:03 | frontend | > cross-env NODE_ENV=development node vite-server.js\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | frontend |\n * 2022-10-10 13:11:03 | backend | [nodemon] 2.0.19\n * 2022-10-10 13:11:03 | backend |\n * 2022-10-10 13:11:03 | backend | [nodemon] to restart at any time, enter `rs`\n * 2022-10-10 13:11:03 | backend | [nodemon] watching path(s): backend/\n * 2022-10-10 13:11:03 | backend | [nodemon] watching extensions: js,mjs,json\n * 2022-10-10 13:11:03 | backend | [nodemon] starting `node backend/index.js`\n * 2022-10-10 13:11:03 | backend |\n *\n * ```\n */\nconst ConcurrentOutput: FunctionComponent<Props> = ({processes, abortController, showTimestamps = true}) => {\n const [processOutput, setProcessOutput] = useState<Chunk[]>([])\n const concurrentColors = ['yellow', 'cyan', 'magenta', 'green', 'blue']\n const prefixColumnSize = Math.max(...processes.map((process) => process.prefix.length))\n const {exit: unmountInk} = useApp()\n\n function lineColor(index: number) {\n const colorIndex = index < concurrentColors.length ? index : index % concurrentColors.length\n return concurrentColors[colorIndex]!\n }\n\n const writableStream = (process: OutputProcess, index: number) => {\n return new Writable({\n write(chunk, _encoding, next) {\n const lines = stripAnsi(chunk.toString('ascii').replace(/(\\n)$/, '')).split(/\\n/)\n\n setProcessOutput((previousProcessOutput) => [\n ...previousProcessOutput,\n {\n color: lineColor(index),\n prefix: process.prefix,\n lines,\n },\n ])\n\n next()\n },\n })\n }\n\n const runProcesses = async () => {\n await Promise.all(\n processes.map(async (process, index) => {\n const stdout = writableStream(process, index)\n const stderr = writableStream(process, index)\n\n await process.action(stdout, stderr, abortController.signal)\n }),\n )\n\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 useEffect(() => {\n runProcesses().catch((error) => {\n abortController.abort()\n unmountInk(error)\n })\n }, [])\n\n return (\n <Static items={processOutput}>\n {(chunk, index) => {\n return (\n <Box flexDirection=\"column\" key={index}>\n {chunk.lines.map((line, index) => (\n <Box key={index} flexDirection=\"row\">\n {showTimestamps && (\n <Box>\n <Box marginRight={1}>\n <Text color={chunk.color}>{new Date().toISOString().replace(/T/, ' ').replace(/\\..+/, '')}</Text>\n </Box>\n\n <Text bold color={chunk.color}>\n |\n </Text>\n </Box>\n )}\n\n <Box width={prefixColumnSize} marginX={1}>\n <Text color={chunk.color}>{chunk.prefix}</Text>\n </Box>\n\n <Text bold color={chunk.color}>\n |\n </Text>\n\n <Box flexGrow={1} paddingLeft={1}>\n <Text color={chunk.color}>{line}</Text>\n </Box>\n </Box>\n ))}\n </Box>\n )\n }}\n </Static>\n )\n}\n\nexport default ConcurrentOutput\n"]}
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import ConcurrentOutput from './ConcurrentOutput.js';
|
|
2
|
+
import { unstyled } from '../../../../output.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { describe, expect, test } from 'vitest';
|
|
5
|
+
import { AbortController } from 'abort-controller';
|
|
6
|
+
import { render } from 'ink-testing-library';
|
|
7
|
+
describe('ConcurrentOutput', () => {
|
|
8
|
+
test('renders a stream of concurrent outputs from sub-processes', async () => {
|
|
9
|
+
// Given
|
|
10
|
+
let backendPromiseResolve;
|
|
11
|
+
let frontendPromiseResolve;
|
|
12
|
+
const backendPromise = new Promise(function (resolve, _reject) {
|
|
13
|
+
backendPromiseResolve = resolve;
|
|
14
|
+
});
|
|
15
|
+
const frontendPromise = new Promise(function (resolve, _reject) {
|
|
16
|
+
frontendPromiseResolve = resolve;
|
|
17
|
+
});
|
|
18
|
+
const backendProcess = {
|
|
19
|
+
prefix: 'backend',
|
|
20
|
+
action: async (stdout, _stderr, _signal) => {
|
|
21
|
+
stdout.write('first backend message');
|
|
22
|
+
stdout.write('second backend message');
|
|
23
|
+
stdout.write('third backend message');
|
|
24
|
+
backendPromiseResolve();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const frontendProcess = {
|
|
28
|
+
prefix: 'frontend',
|
|
29
|
+
action: async (stdout, _stderr, _signal) => {
|
|
30
|
+
await backendPromise;
|
|
31
|
+
stdout.write('first frontend message');
|
|
32
|
+
stdout.write('second frontend message');
|
|
33
|
+
stdout.write('third frontend message');
|
|
34
|
+
frontendPromiseResolve();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
// When
|
|
38
|
+
const { lastFrame } = render(React.createElement(ConcurrentOutput, { processes: [backendProcess, frontendProcess], abortController: new AbortController() }));
|
|
39
|
+
// wait for all output to be rendered
|
|
40
|
+
await frontendPromise;
|
|
41
|
+
// Then
|
|
42
|
+
expect(unstyled(lastFrame()).replace(/\d/g, '0')).toMatchInlineSnapshot(`
|
|
43
|
+
"0000-00-00 00:00:00 | backend | first backend message
|
|
44
|
+
0000-00-00 00:00:00 | backend | second backend message
|
|
45
|
+
0000-00-00 00:00:00 | backend | third backend message
|
|
46
|
+
0000-00-00 00:00:00 | frontend | first frontend message
|
|
47
|
+
0000-00-00 00:00:00 | frontend | second frontend message
|
|
48
|
+
0000-00-00 00:00:00 | frontend | third frontend message
|
|
49
|
+
"
|
|
50
|
+
`);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=ConcurrentOutput.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConcurrentOutput.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.test.tsx"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,uBAAuB,CAAA;AAEpD,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAC9C,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAC,MAAM,EAAC,MAAM,qBAAqB,CAAA;AAG1C,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,IAAI,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QAC3E,QAAQ;QACR,IAAI,qBAAiC,CAAA;QACrC,IAAI,sBAAkC,CAAA;QAEtC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAO,UAAU,OAAO,EAAE,OAAO;YACjE,qBAAqB,GAAG,OAAO,CAAA;QACjC,CAAC,CAAC,CAAA;QAEF,MAAM,eAAe,GAAG,IAAI,OAAO,CAAO,UAAU,OAAO,EAAE,OAAO;YAClE,sBAAsB,GAAG,OAAO,CAAA;QAClC,CAAC,CAAC,CAAA;QAEF,MAAM,cAAc,GAAG;YACrB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,KAAK,EAAE,MAAgB,EAAE,OAAiB,EAAE,OAAe,EAAE,EAAE;gBACrE,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;gBACrC,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;gBACtC,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;gBAErC,qBAAqB,EAAE,CAAA;YACzB,CAAC;SACF,CAAA;QAED,MAAM,eAAe,GAAG;YACtB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,KAAK,EAAE,MAAgB,EAAE,OAAiB,EAAE,OAAe,EAAE,EAAE;gBACrE,MAAM,cAAc,CAAA;gBAEpB,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;gBACtC,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAA;gBACvC,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;gBAEtC,sBAAsB,EAAE,CAAA;YAC1B,CAAC;SACF,CAAA;QACD,OAAO;QAEP,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CACxB,oBAAC,gBAAgB,IAAC,SAAS,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC,EAAE,eAAe,EAAE,IAAI,eAAe,EAAE,GAAI,CAC3G,CAAA;QAED,qCAAqC;QACrC,MAAM,eAAe,CAAA;QAErB,OAAO;QACP,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;KAQxE,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import ConcurrentOutput from './ConcurrentOutput.js'\nimport {Signal} from '../../../../abort.js'\nimport {unstyled} from '../../../../output.js'\nimport React from 'react'\nimport {describe, expect, test} from 'vitest'\nimport {AbortController} from 'abort-controller'\nimport {render} from 'ink-testing-library'\nimport {Writable} from 'node:stream'\n\ndescribe('ConcurrentOutput', () => {\n test('renders a stream of concurrent outputs from sub-processes', async () => {\n // Given\n let backendPromiseResolve: () => void\n let frontendPromiseResolve: () => void\n\n const backendPromise = new Promise<void>(function (resolve, _reject) {\n backendPromiseResolve = resolve\n })\n\n const frontendPromise = new Promise<void>(function (resolve, _reject) {\n frontendPromiseResolve = resolve\n })\n\n const backendProcess = {\n prefix: 'backend',\n action: async (stdout: Writable, _stderr: Writable, _signal: Signal) => {\n stdout.write('first backend message')\n stdout.write('second backend message')\n stdout.write('third backend message')\n\n backendPromiseResolve()\n },\n }\n\n const frontendProcess = {\n prefix: 'frontend',\n action: async (stdout: Writable, _stderr: Writable, _signal: Signal) => {\n await backendPromise\n\n stdout.write('first frontend message')\n stdout.write('second frontend message')\n stdout.write('third frontend message')\n\n frontendPromiseResolve()\n },\n }\n // When\n\n const {lastFrame} = render(\n <ConcurrentOutput processes={[backendProcess, frontendProcess]} abortController={new AbortController()} />,\n )\n\n // wait for all output to be rendered\n await frontendPromise\n\n // Then\n expect(unstyled(lastFrame()!).replace(/\\d/g, '0')).toMatchInlineSnapshot(`\n \"0000-00-00 00:00:00 | backend | first backend message\n 0000-00-00 00:00:00 | backend | second backend message\n 0000-00-00 00:00:00 | backend | third backend message\n 0000-00-00 00:00:00 | frontend | first frontend message\n 0000-00-00 00:00:00 | frontend | second frontend message\n 0000-00-00 00:00:00 | frontend | third frontend message\n \"\n `)\n })\n})\n"]}
|
|
@@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
5
5
|
* - 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`.
|
|
6
6
|
* - 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.
|
|
7
7
|
*/
|
|
8
|
-
const FullScreen = (
|
|
8
|
+
const FullScreen = ({ children }) => {
|
|
9
9
|
const [size, setSize] = useState({
|
|
10
10
|
columns: process.stdout.columns,
|
|
11
11
|
rows: process.stdout.rows,
|
|
@@ -26,7 +26,7 @@ const FullScreen = (props) => {
|
|
|
26
26
|
process.stdout.write('\u001B[?1049l');
|
|
27
27
|
};
|
|
28
28
|
}, []);
|
|
29
|
-
return (React.createElement(Box, { width: size.columns, height: size.rows },
|
|
29
|
+
return (React.createElement(Box, { width: size.columns, height: size.rows }, children));
|
|
30
30
|
};
|
|
31
31
|
export default FullScreen;
|
|
32
32
|
//# sourceMappingURL=FullScreen.js.map
|
|
@@ -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
|