@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.
Files changed (106) hide show
  1. package/dist/api/admin.js +5 -11
  2. package/dist/api/admin.js.map +1 -1
  3. package/dist/api/common.d.ts +1 -1
  4. package/dist/api/common.js +7 -4
  5. package/dist/api/common.js.map +1 -1
  6. package/dist/api/graphql/find_org.js +2 -2
  7. package/dist/api/graphql/find_org.js.map +1 -1
  8. package/dist/api/identity.js +14 -4
  9. package/dist/api/identity.js.map +1 -1
  10. package/dist/api/partners.d.ts +0 -6
  11. package/dist/api/partners.js +0 -32
  12. package/dist/api/partners.js.map +1 -1
  13. package/dist/http/fetch.js +13 -0
  14. package/dist/http/fetch.js.map +1 -1
  15. package/dist/index.d.ts +0 -1
  16. package/dist/index.js +0 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/metadata.d.ts +2 -2
  19. package/dist/output.js +1 -3
  20. package/dist/output.js.map +1 -1
  21. package/dist/path.d.ts +4 -1
  22. package/dist/path.js +9 -1
  23. package/dist/path.js.map +1 -1
  24. package/dist/private/node/ui/components/Command.js.map +1 -1
  25. package/dist/private/node/ui/components/ConcurrentOutput.js +5 -1
  26. package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
  27. package/dist/{testing/fixtures/render-concurrent.d.ts → private/node/ui/components/ConcurrentOutput.test.d.ts} +0 -0
  28. package/dist/private/node/ui/components/ConcurrentOutput.test.js +53 -0
  29. package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -0
  30. package/dist/private/node/ui/components/FullScreen.js +2 -2
  31. package/dist/private/node/ui/components/FullScreen.js.map +1 -1
  32. package/dist/private/node/ui/components/Link.js.map +1 -1
  33. package/dist/private/node/ui/components/Prompt.d.ts +10 -0
  34. package/dist/private/node/ui/components/Prompt.js +23 -0
  35. package/dist/private/node/ui/components/Prompt.js.map +1 -0
  36. package/dist/private/node/ui/components/Prompt.test.d.ts +1 -0
  37. package/dist/private/node/ui/components/Prompt.test.js +71 -0
  38. package/dist/private/node/ui/components/Prompt.test.js.map +1 -0
  39. package/dist/private/node/ui/components/SelectInput.d.ts +12 -0
  40. package/dist/private/node/ui/components/SelectInput.js +93 -0
  41. package/dist/private/node/ui/components/SelectInput.js.map +1 -0
  42. package/dist/private/node/ui/components/SelectInput.test.d.ts +1 -0
  43. package/dist/private/node/ui/components/SelectInput.test.js +200 -0
  44. package/dist/private/node/ui/components/SelectInput.test.js.map +1 -0
  45. package/dist/private/node/ui/components/Table.d.ts +8 -0
  46. package/dist/private/node/ui/components/Table.js +17 -0
  47. package/dist/private/node/ui/components/Table.js.map +1 -0
  48. package/dist/private/node/ui/components/TextAnimation.js +1 -1
  49. package/dist/private/node/ui/components/TextAnimation.js.map +1 -1
  50. package/dist/private/node/ui.d.ts +2 -0
  51. package/dist/private/node/ui.js +14 -0
  52. package/dist/private/node/ui.js.map +1 -1
  53. package/dist/public/common/array.d.ts +1 -1
  54. package/dist/public/common/array.js.map +1 -1
  55. package/dist/public/node/base-command.d.ts +2 -2
  56. package/dist/public/node/base-command.js +2 -4
  57. package/dist/public/node/base-command.js.map +1 -1
  58. package/dist/public/node/checksum.d.ts +1 -1
  59. package/dist/public/node/checksum.js +1 -1
  60. package/dist/public/node/checksum.js.map +1 -1
  61. package/dist/public/node/cli.js.map +1 -1
  62. package/dist/public/node/dot-env.js.map +1 -1
  63. package/dist/public/node/error-handler.d.ts +5 -1
  64. package/dist/public/node/error-handler.js.map +1 -1
  65. package/dist/public/node/framework.js.map +1 -1
  66. package/dist/public/node/hooks/prerun.js.map +1 -1
  67. package/dist/public/node/node-package-manager.d.ts +1 -0
  68. package/dist/public/node/node-package-manager.js.map +1 -1
  69. package/dist/public/node/ruby.js +2 -2
  70. package/dist/public/node/ruby.js.map +1 -1
  71. package/dist/public/node/ui.d.ts +42 -0
  72. package/dist/public/node/ui.js +60 -6
  73. package/dist/public/node/ui.js.map +1 -1
  74. package/dist/session/exchange.js +4 -2
  75. package/dist/session/exchange.js.map +1 -1
  76. package/dist/session/validate.js +3 -13
  77. package/dist/session/validate.js.map +1 -1
  78. package/dist/session.d.ts +1 -1
  79. package/dist/session.js +3 -1
  80. package/dist/session.js.map +1 -1
  81. package/dist/string.d.ts +1 -0
  82. package/dist/string.js +3 -0
  83. package/dist/string.js.map +1 -1
  84. package/dist/system.d.ts +2 -2
  85. package/dist/system.js +17 -2
  86. package/dist/system.js.map +1 -1
  87. package/dist/testing/ui.d.ts +4 -8
  88. package/dist/testing/ui.js +17 -17
  89. package/dist/testing/ui.js.map +1 -1
  90. package/dist/tsconfig.tsbuildinfo +1 -1
  91. package/dist/ui/executor.js +7 -5
  92. package/dist/ui/executor.js.map +1 -1
  93. package/dist/ui/inquirer/autocomplete.js +13 -4
  94. package/dist/ui/inquirer/autocomplete.js.map +1 -1
  95. package/dist/ui.d.ts +6 -0
  96. package/dist/ui.js +0 -11
  97. package/dist/ui.js.map +1 -1
  98. package/package.json +35 -6
  99. package/dist/log.d.ts +0 -30
  100. package/dist/log.js +0 -169
  101. package/dist/log.js.map +0 -1
  102. package/dist/private/node/ui/error.d.ts +0 -2
  103. package/dist/private/node/ui/error.js +0 -8
  104. package/dist/private/node/ui/error.js.map +0 -1
  105. package/dist/testing/fixtures/render-concurrent.js +0 -26
  106. 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":"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","sourcesContent":["import {OutputProcess} from '../../../../output.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 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"]}
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"]}
@@ -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 = (props) => {
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 }, props.children));
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,KAAmC,EAAe,EAAE;IAChF,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,KAAK,CAAC,QAAQ,CACX,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 = (props: {children?: React.ReactNode}): 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 {props.children}\n </Box>\n )\n}\n\nexport default FullScreen\n"]}
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,EAAiC,EAAe,EAAE;IAC1F,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}: React.PropsWithChildren<Props>): JSX.Element => {\n return <Text>{terminalLink(label ?? url, url, {fallback: label ? fallback : false})}</Text>\n}\n\nexport {Link}\n"]}
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
+ ✔ second"
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
+ > (f) first
53
+ (s) second
54
+ (3) third
55
+ (4) fourth
56
+ (5) seventh
57
+ (6) tenth
58
+
59
+ Automations
60
+ (7) fifth
61
+ (8) sixth
62
+
63
+ Merchant Admin
64
+ (9) eighth
65
+ (10) ninth
66
+
67
+ navigate with arrows, enter to select"
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":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,sCAAsC,CAAA;AAC7D,OAAO,KAAK,EAAE,EAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAC,MAAM,OAAO,CAAA;AACrE,OAAO,EAAC,GAAG,EAAO,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAC,MAAM,KAAK,CAAA;AACpD,OAAO,EAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAC,MAAM,WAAW,CAAA;AAcrD,SAAS,UAAU,CAAI,KAAgB;IACrC,IAAI,KAAK,GAAG,CAAC,CAAA;IAEb,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CACvD,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;QAC3B,MAAM,IAAI,GAAG,EAAC,GAAG,SAAS,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAC,CAAA;QAChF,KAAK,IAAI,CAAC,CAAA;QACV,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CACH,CAAA;AACH,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAI,EAAC,KAAK,EAAE,QAAQ,EAAoC;IACzF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACjE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAwB,IAAI,CAAC,CAAA;IAC7E,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,EAAC,IAAI,EAAE,UAAU,EAAC,GAAG,MAAM,EAAE,CAAA;IACnC,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IACtC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAE7C,MAAM,aAAa,GAAG,MAAM,CAAY,KAAK,CAAC,CAAA;IAE9C,gCAAgC;IAChC,SAAS,CAAC,GAAG,EAAE;QACb,IACE,CAAC,OAAO,CACN,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAC/C,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAChC,EACD;YACA,gBAAgB,CAAC,CAAC,CAAC,CAAA;SACpB;QAED,aAAa,CAAC,OAAO,GAAG,KAAK,CAAA;IAC/B,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,KAAa,EAAE,GAAQ,EAAE,EAAE;QAC1B,IAAI,KAAK,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE;YAC7B,iFAAiF;YACjF,wEAAwE;YACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;SAChB;QAED,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAEvC,IAAI,WAAW,KAAK,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;YACxD,gBAAgB,CAAC,WAAW,GAAG,CAAC,CAAC,CAAA;SAClC;aAAM,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YAClC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,CAAA;YAC3D,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;gBAChB,gBAAgB,CAAC,KAAK,CAAC,CAAA;aACxB;SACF;QAED,IAAI,GAAG,CAAC,OAAO,EAAE;YACf,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;YAElC,gBAAgB,CAAC,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA;SACtE;aAAM,IAAI,GAAG,CAAC,SAAS,EAAE;YACxB,gBAAgB,CAAC,aAAa,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA;SAC7E;aAAM,IAAI,GAAG,CAAC,MAAM,EAAE;YACrB,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAE,CAAC,KAAK,CAAC,CAAA;YACrC,yEAAyE;YACzE,+DAA+D;YAC/D,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,UAAU,EAAE,CAAA;SAC5C;IACH,CAAC,EACD,CAAC,aAAa,EAAE,KAAK,EAAE,QAAQ,CAAC,CACjC,CAAA;IAED,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE;YAC5E,MAAM,aAAa,GAAG,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,GAAG,KAAK,CAAA;YAEtE,aAAa,CAAC,aAAa,CAAC,CAAA;YAE5B,IAAI,YAAY,KAAK,IAAI,EAAE;gBACzB,YAAY,CAAC,YAAY,CAAC,CAAA;aAC3B;YAED,eAAe,CACb,UAAU,CAAC,GAAG,EAAE;gBACd,WAAW,CAAC,aAAa,EAAE,GAAG,CAAC,CAAA;gBAC/B,aAAa,CAAC,IAAI,CAAC,CAAA;gBACnB,eAAe,CAAC,IAAI,CAAC,CAAA;YACvB,CAAC,EAAE,GAAG,CAAC,CACR,CAAA;SACF;aAAM;YACL,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;SACxB;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ;QACxB,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACzB,MAAM,QAAQ,GAAG,KAAK,KAAK,WAAW,CAAA;YAEtC,OAAO,CACL,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK,EAAE,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChE,QAAQ,IAAI,CACX,oBAAC,GAAG,IAAC,UAAU,EAAE,CAAC;oBAChB,oBAAC,IAAI,IAAC,IAAI,UAAE,KAAK,CAAQ,CACrB,CACP;gBACA,YAAY,CAAC,KAAK,CAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;oBACjC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,KAAK,aAAa,CAAA;oBAE/C,OAAO,CACL,oBAAC,GAAG,IAAC,GAAG,EAAE,IAAI,CAAC,GAAG;wBAChB,oBAAC,GAAG,IAAC,WAAW,EAAE,CAAC,IAAG,UAAU,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,KAAK,EAAC,MAAM,IAAE,GAAG,CAAQ,CAAC,CAAC,CAAC,oBAAC,IAAI,YAAS,CAAO;wBAE1F,oBAAC,IAAI,IAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,IAAG,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,KAAK,EAAE,CAAQ,CAChF,CACP,CAAA;gBACH,CAAC,CAAC,CACE,CACP,CAAA;QACH,CAAC,CAAC;QAEF,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;YAC9B,oBAAC,IAAI,IAAC,QAAQ,kDAA6C,CACvD,CACF,CACP,CAAA;AACH,CAAC","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,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
+ > (3) Third
32
+
33
+ navigate with arrows, enter to select"
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
+ > (2) Second
57
+ (3) Third
58
+
59
+ navigate with arrows, enter to select"
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
+ > (10) Tenth
107
+
108
+ navigate with arrows, enter to select"
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
+ > (t) Third
134
+
135
+ navigate with arrows, enter to select"
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
+ "> (1) First
160
+ (2) Second
161
+ (3) Third
162
+
163
+ navigate with arrows, enter to select"
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
+ "> (f) first
182
+ (s) second
183
+ (3) third
184
+ (4) fourth
185
+ (5) seventh
186
+ (6) tenth
187
+
188
+ Automations
189
+ (7) fifth
190
+ (8) sixth
191
+
192
+ Merchant Admin
193
+ (9) eighth
194
+ (10) ninth
195
+
196
+ navigate with arrows, enter to select"
197
+ `);
198
+ });
199
+ });
200
+ //# sourceMappingURL=SelectInput.test.js.map