@shopify/cli-kit 3.94.3 → 4.1.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 (90) hide show
  1. package/README.md +2 -2
  2. package/dist/private/node/api/headers.js +2 -2
  3. package/dist/private/node/api/headers.js.map +1 -1
  4. package/dist/private/node/conf-store.d.ts +3 -2
  5. package/dist/private/node/conf-store.js +3 -2
  6. package/dist/private/node/conf-store.js.map +1 -1
  7. package/dist/private/node/constants.d.ts +1 -1
  8. package/dist/private/node/constants.js +1 -1
  9. package/dist/private/node/constants.js.map +1 -1
  10. package/dist/private/node/ui/components/Alert.test.js +29 -0
  11. package/dist/private/node/ui/components/Alert.test.js.map +1 -1
  12. package/dist/private/node/ui/components/AutocompletePrompt.d.ts +8 -1
  13. package/dist/private/node/ui/components/AutocompletePrompt.js +3 -2
  14. package/dist/private/node/ui/components/AutocompletePrompt.js.map +1 -1
  15. package/dist/private/node/ui/components/AutocompletePrompt.test.js +16 -0
  16. package/dist/private/node/ui/components/AutocompletePrompt.test.js.map +1 -1
  17. package/dist/private/node/ui/components/FatalError.js +6 -1
  18. package/dist/private/node/ui/components/FatalError.js.map +1 -1
  19. package/dist/private/node/ui/components/FatalError.test.js +28 -0
  20. package/dist/private/node/ui/components/FatalError.test.js.map +1 -1
  21. package/dist/private/node/ui/components/Link.js +8 -4
  22. package/dist/private/node/ui/components/Link.js.map +1 -1
  23. package/dist/private/node/ui/components/Link.test.js +45 -0
  24. package/dist/private/node/ui/components/Link.test.js.map +1 -1
  25. package/dist/private/node/ui/components/TokenizedText.js +61 -2
  26. package/dist/private/node/ui/components/TokenizedText.js.map +1 -1
  27. package/dist/private/node/ui/components/TokenizedText.test.js +109 -2
  28. package/dist/private/node/ui/components/TokenizedText.test.js.map +1 -1
  29. package/dist/public/common/string.js +22 -25
  30. package/dist/public/common/string.js.map +1 -1
  31. package/dist/public/common/version.d.ts +1 -1
  32. package/dist/public/common/version.js +1 -1
  33. package/dist/public/common/version.js.map +1 -1
  34. package/dist/public/node/base-command.js +23 -14
  35. package/dist/public/node/base-command.js.map +1 -1
  36. package/dist/public/node/cli-launcher.d.ts +2 -0
  37. package/dist/public/node/cli-launcher.js +7 -3
  38. package/dist/public/node/cli-launcher.js.map +1 -1
  39. package/dist/public/node/cli.d.ts +5 -1
  40. package/dist/public/node/cli.js +3 -3
  41. package/dist/public/node/cli.js.map +1 -1
  42. package/dist/public/node/context/local.d.ts +1 -8
  43. package/dist/public/node/context/local.js +34 -15
  44. package/dist/public/node/context/local.js.map +1 -1
  45. package/dist/public/node/custom-oclif-loader.d.ts +31 -0
  46. package/dist/public/node/custom-oclif-loader.js +45 -0
  47. package/dist/public/node/custom-oclif-loader.js.map +1 -0
  48. package/dist/public/node/error.d.ts +1 -1
  49. package/dist/public/node/error.js +1 -1
  50. package/dist/public/node/error.js.map +1 -1
  51. package/dist/public/node/fs.js +12 -16
  52. package/dist/public/node/fs.js.map +1 -1
  53. package/dist/public/node/git.js +9 -3
  54. package/dist/public/node/git.js.map +1 -1
  55. package/dist/public/node/hooks/postrun.d.ts +15 -0
  56. package/dist/public/node/hooks/postrun.js +75 -14
  57. package/dist/public/node/hooks/postrun.js.map +1 -1
  58. package/dist/public/node/hooks/prerun.d.ts +1 -1
  59. package/dist/public/node/hooks/prerun.js +17 -10
  60. package/dist/public/node/hooks/prerun.js.map +1 -1
  61. package/dist/public/node/import-extractor.js +4 -3
  62. package/dist/public/node/import-extractor.js.map +1 -1
  63. package/dist/public/node/is-global.d.ts +1 -1
  64. package/dist/public/node/is-global.js +27 -11
  65. package/dist/public/node/is-global.js.map +1 -1
  66. package/dist/public/node/monorail.d.ts +2 -1
  67. package/dist/public/node/monorail.js +1 -1
  68. package/dist/public/node/monorail.js.map +1 -1
  69. package/dist/public/node/node-package-manager.d.ts +2 -2
  70. package/dist/public/node/node-package-manager.js +10 -10
  71. package/dist/public/node/node-package-manager.js.map +1 -1
  72. package/dist/public/node/notifications-system.d.ts +6 -6
  73. package/dist/public/node/output.js +2 -0
  74. package/dist/public/node/output.js.map +1 -1
  75. package/dist/public/node/path.js +0 -2
  76. package/dist/public/node/path.js.map +1 -1
  77. package/dist/public/node/session.d.ts +6 -0
  78. package/dist/public/node/session.js +8 -0
  79. package/dist/public/node/session.js.map +1 -1
  80. package/dist/public/node/system.js +19 -42
  81. package/dist/public/node/system.js.map +1 -1
  82. package/dist/public/node/tree-kill.js +17 -5
  83. package/dist/public/node/tree-kill.js.map +1 -1
  84. package/dist/public/node/ui.js +6 -0
  85. package/dist/public/node/ui.js.map +1 -1
  86. package/dist/public/node/upgrade.d.ts +27 -8
  87. package/dist/public/node/upgrade.js +50 -21
  88. package/dist/public/node/upgrade.js.map +1 -1
  89. package/dist/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"Link.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Link.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Link} from './Link.js'\nimport {render} from '../../testing/ui.js'\nimport {describe, expect, test, vi} from 'vitest'\nimport React from 'react'\nimport supportsHyperlinks from 'supports-hyperlinks'\n\nvi.mock('supports-hyperlinks')\n\ndescribe('Link', async () => {\n test(\"renders correctly with a fallback for terminals that don't support hyperlinks\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n label: 'Example',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"Example \u001b[2m( https://example.com )\u001b[22m\"')\n })\n\n test('renders correctly with a fallback for terminals support hyperlinks', async () => {\n // Given\n supportHyperLinks(true)\n\n const link = {\n url: 'https://example.com',\n label: 'Example',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot(`\"${asLink('https://example.com', 'Example')}\"`)\n })\n\n test(\"it doesn't render a fallback if only url is passed and the terminal doesn't support hyperlinks\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"https://example.com\"')\n })\n\n test(\"it doesn't render a fallback if only url is passed and the terminal supports hyperlinks\", async () => {\n // Given\n supportHyperLinks(true)\n\n const link = {\n url: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot(`\"${asLink('https://example.com')}\"`)\n })\n\n test(\"it renders the link as plain text when the terminal doesn't support hyperlinks and the link URL is the same as the label\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n label: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"https://example.com\"')\n })\n\n test(\"it renders the link as plain text when the terminal doesn't support hyperlinks and no label is passed\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"https://example.com\"')\n })\n\n function supportHyperLinks(isSupported: boolean) {\n vi.mocked(supportsHyperlinks).stdout = isSupported\n }\n\n function asLink(url: string, label?: string) {\n return `\\u001b]8;;${url}\\u0007${label ?? url}\\u001b]8;;\\u0007`\n }\n})\n"]}
1
+ {"version":3,"file":"Link.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Link.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Link} from './Link.js'\nimport {LinksContext} from '../contexts/LinksContext.js'\nimport {render} from '../../testing/ui.js'\nimport {describe, expect, test, vi} from 'vitest'\nimport React from 'react'\nimport supportsHyperlinks from 'supports-hyperlinks'\n\nvi.mock('supports-hyperlinks')\n\ndescribe('Link', async () => {\n test(\"renders correctly with a fallback for terminals that don't support hyperlinks\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n label: 'Example',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"Example \u001b[2m( https://example.com )\u001b[22m\"')\n })\n\n test('renders correctly with a fallback for terminals support hyperlinks', async () => {\n // Given\n supportHyperLinks(true)\n\n const link = {\n url: 'https://example.com',\n label: 'Example',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot(`\"${asLink('https://example.com', 'Example')}\"`)\n })\n\n test(\"it doesn't render a fallback if only url is passed and the terminal doesn't support hyperlinks\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"https://example.com\"')\n })\n\n test(\"it doesn't render a fallback if only url is passed and the terminal supports hyperlinks\", async () => {\n // Given\n supportHyperLinks(true)\n\n const link = {\n url: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot(`\"${asLink('https://example.com')}\"`)\n })\n\n test(\"it renders the link as plain text when the terminal doesn't support hyperlinks and the link URL is the same as the label\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n label: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"https://example.com\"')\n })\n\n test(\"it renders the link as plain text when the terminal doesn't support hyperlinks and no label is passed\", async () => {\n // Given\n supportHyperLinks(false)\n\n const link = {\n url: 'https://example.com',\n }\n\n // When\n const {lastFrame} = render(<Link {...link} />)\n\n // Then\n expect(lastFrame()).toMatchInlineSnapshot('\"https://example.com\"')\n })\n\n test('renders a label-bearing link inside a LinksContext as `label [N]` and registers the URL in the footnote table', async () => {\n // Inside a Banner's LinksContext, the visible label stays compact\n // (`label [1]`) and the URL is captured for rendering outside the\n // bordered box, where it can wrap freely without `│` interleaving.\n supportHyperLinks(false)\n\n const links: Record<string, {label: string | undefined; url: string}> = {}\n const link = {\n url: 'https://shopify.dev/docs/apps/build/sales-channels/channel-config-extension#specification-properties',\n label: 'docs',\n }\n\n const {lastFrame} = render(\n <LinksContext.Provider\n value={{\n links: {current: links},\n addLink: (label, url) => {\n const id = (Object.keys(links).length + 1).toString()\n links[id] = {label, url}\n return id\n },\n }}\n >\n <Link {...link} />\n </LinksContext.Provider>,\n )\n\n expect(lastFrame()).toBe('docs [1]')\n expect(links['1']).toEqual({label: 'docs', url: link.url})\n })\n\n test('renders a label-less link inside a LinksContext as a bare `[N]` anchor (no inline URL)', async () => {\n // Regression: previously this path rendered `${url} [N]`, putting the\n // long URL inside the bordered box and defeating the footnote\n // mechanism. The footnote alone is now the source of truth for the URL.\n supportHyperLinks(false)\n\n const links: Record<string, {label: string | undefined; url: string}> = {}\n const link = {\n url: 'https://shopify.dev/docs/apps/build/sales-channels/channel-config-extension#specification-properties',\n }\n\n const {lastFrame} = render(\n <LinksContext.Provider\n value={{\n links: {current: links},\n addLink: (label, url) => {\n const id = (Object.keys(links).length + 1).toString()\n links[id] = {label, url}\n return id\n },\n }}\n >\n <Link {...link} />\n </LinksContext.Provider>,\n )\n\n expect(lastFrame()).toBe('[1]')\n expect(lastFrame()).not.toContain(link.url)\n expect(links['1']).toEqual({label: undefined, url: link.url})\n })\n\n function supportHyperLinks(isSupported: boolean) {\n vi.mocked(supportsHyperlinks).stdout = isSupported\n }\n\n function asLink(url: string, label?: string) {\n return `\\u001b]8;;${url}\\u0007${label ?? url}\\u001b]8;;\\u0007`\n }\n})\n"]}
@@ -4,7 +4,8 @@ import { List } from './List.js';
4
4
  import { UserInput } from './UserInput.js';
5
5
  import { FilePath } from './FilePath.js';
6
6
  import { Subdued } from './Subdued.js';
7
- import React from 'react';
7
+ import { LinksContext } from '../contexts/LinksContext.js';
8
+ import React, { useContext } from 'react';
8
9
  import { Box, Text } from 'ink';
9
10
  function tokenToBlock(token) {
10
11
  return {
@@ -86,13 +87,71 @@ const InlineBlocks = ({ blocks }) => {
86
87
  blockIndex !== 0 && !(typeof block.value !== 'string' && 'char' in block.value) && React.createElement(Text, null, " "),
87
88
  React.createElement(TokenizedText, { item: block.value }))))));
88
89
  };
90
+ // Matches CommonMark inline links of the form `[label](url)` and autolinks of
91
+ // the form `<url>`. We deliberately require an explicit `http://` or
92
+ // `https://` scheme on the URL so callers can't accidentally trigger
93
+ // linkification by typing square brackets or angle brackets in plain prose.
94
+ //
95
+ // The URL portion forbids whitespace, the closing delimiter (`)` or `>`), and
96
+ // `<` to keep parsing predictable. URLs that legitimately contain those
97
+ // characters (e.g. balanced parens) must be percent-encoded by the caller —
98
+ // per CommonMark §6.3 / §6.4.
99
+ const MARKDOWN_LINK_REGEX = /\[([^[\]]+)\]\((https?:\/\/[^()<>\s]+)\)|<(https?:\/\/[^<>\s]+)>/g;
100
+ /**
101
+ * Parses an opt-in CommonMark link or autolink from a plain string token and
102
+ * routes the result through the existing `<Link>` component.
103
+ *
104
+ * Why opt-in rather than auto-detection: server-returned error strings can
105
+ * legitimately contain URL-shaped substrings that are not meant to be
106
+ * clickable (e.g. a tunnel-URL validation error echoing back the user's bad
107
+ * input). Requiring the explicit `[label](url)` / `<url>` shape lets the
108
+ * source of the message (server or client) declare intent, and keeps prose
109
+ * with bare URLs untouched.
110
+ *
111
+ * Only invoked when a `LinksContext` is present (i.e. inside a Banner /
112
+ * Alert / FatalError); outside of that, the underlying `<Link>` would render
113
+ * the URL inline and we'd defeat the wrap-resistance the footnote mechanism
114
+ * provides.
115
+ */
116
+ function renderStringWithMarkdownLinks(str) {
117
+ const matches = Array.from(str.matchAll(MARKDOWN_LINK_REGEX));
118
+ if (matches.length === 0) {
119
+ return React.createElement(Text, null, str);
120
+ }
121
+ const parts = [];
122
+ let cursor = 0;
123
+ matches.forEach((match, index) => {
124
+ const start = match.index;
125
+ if (start === undefined) {
126
+ return;
127
+ }
128
+ const end = start + match[0].length;
129
+ if (start > cursor) {
130
+ parts.push(React.createElement(Text, { key: `t${index}` }, str.slice(cursor, start)));
131
+ }
132
+ // `[label](url)` captures label in group 1 and url in group 2;
133
+ // `<url>` autolinks capture only the url in group 3.
134
+ const label = match[1];
135
+ const url = match[2] ?? match[3];
136
+ if (url === undefined) {
137
+ return;
138
+ }
139
+ parts.push(React.createElement(Link, { key: `l${index}`, label: label, url: url }));
140
+ cursor = end;
141
+ });
142
+ if (cursor < str.length) {
143
+ parts.push(React.createElement(Text, { key: "tail" }, str.slice(cursor)));
144
+ }
145
+ return React.createElement(Text, null, parts);
146
+ }
89
147
  /**
90
148
  * `TokenizedText` renders a text string with tokens that can be either strings,
91
149
  * links, and commands.
92
150
  */
93
151
  const TokenizedText = ({ item }) => {
152
+ const linksContext = useContext(LinksContext);
94
153
  if (typeof item === 'string') {
95
- return React.createElement(Text, null, item);
154
+ return linksContext === null ? React.createElement(Text, null, item) : renderStringWithMarkdownLinks(item);
96
155
  }
97
156
  else if ('command' in item) {
98
157
  return React.createElement(Command, { command: item.command });
@@ -1 +1 @@
1
- {"version":3,"file":"TokenizedText.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/TokenizedText.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Command} from './Command.js'\nimport {Link} from './Link.js'\nimport {List} from './List.js'\nimport {UserInput} from './UserInput.js'\nimport {FilePath} from './FilePath.js'\nimport {Subdued} from './Subdued.js'\nimport React, {FunctionComponent} from 'react'\nimport {Box, Text} from 'ink'\n\nexport interface LinkToken {\n link: {\n label?: string\n url: string\n }\n}\n\nexport interface UserInputToken {\n userInput: string\n}\n\nexport interface ListToken {\n list: {\n title?: TokenItem<InlineToken>\n items: TokenItem<InlineToken>[]\n ordered?: boolean\n }\n}\n\nexport interface BoldToken {\n bold: string\n}\n\nexport type Token =\n | string\n | {\n command: string\n }\n | LinkToken\n | {\n char: string\n }\n | UserInputToken\n | {\n subdued: string\n }\n | {\n filePath: string\n }\n | ListToken\n | BoldToken\n | {\n info: string\n }\n | {\n warn: string\n }\n | {\n error: string\n }\n\nexport type InlineToken = Exclude<Token, ListToken>\nexport type TokenItem<T extends Token = Token> = T | T[]\n\ntype DisplayType = 'block' | 'inline'\ninterface Block {\n display: DisplayType\n value: Token\n}\n\nfunction tokenToBlock(token: Token): Block {\n return {\n display: typeof token !== 'string' && 'list' in token ? 'block' : 'inline',\n value: token,\n }\n}\n\nexport function tokenItemToString(token: TokenItem): string {\n if (typeof token === 'string') {\n return token\n } else if ('command' in token) {\n return token.command\n } else if ('link' in token) {\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty label should fall through to url\n return token.link.label || token.link.url\n } else if ('char' in token) {\n return token.char\n } else if ('userInput' in token) {\n return token.userInput\n } else if ('subdued' in token) {\n return token.subdued\n } else if ('filePath' in token) {\n return token.filePath\n } else if ('list' in token) {\n return token.list.items.map(tokenItemToString).join(' ')\n } else if ('bold' in token) {\n return token.bold\n } else if ('info' in token) {\n return token.info\n } else if ('warn' in token) {\n return token.warn\n } else if ('error' in token) {\n return token.error\n } else {\n return token\n .map((item, index) => {\n if (index !== 0 && !(typeof item !== 'string' && 'char' in item)) {\n return ` ${tokenItemToString(item)}`\n } else {\n return tokenItemToString(item)\n }\n })\n .join('')\n }\n}\n\nexport function appendToTokenItem(token: TokenItem, suffix: string): TokenItem {\n return Array.isArray(token) ? [...token, {char: suffix}] : [token, {char: suffix}]\n}\n\nfunction splitByDisplayType(acc: Block[][], item: Block) {\n if (item.display === 'block') {\n acc.push([item])\n } else {\n const last = acc[acc.length - 1]\n if (last && last[0]!.display === 'inline') {\n last.push(item)\n } else {\n acc.push([item])\n }\n }\n return acc\n}\n\nconst InlineBlocks: React.FC<{blocks: Block[]}> = ({blocks}) => {\n return (\n <Text>\n {blocks.map((block, blockIndex) => (\n <Text key={blockIndex}>\n {blockIndex !== 0 && !(typeof block.value !== 'string' && 'char' in block.value) && <Text> </Text>}\n <TokenizedText item={block.value} />\n </Text>\n ))}\n </Text>\n )\n}\n\ninterface TokenizedTextProps {\n item: TokenItem\n}\n\n/**\n * `TokenizedText` renders a text string with tokens that can be either strings,\n * links, and commands.\n */\nconst TokenizedText: FunctionComponent<TokenizedTextProps> = ({item}) => {\n if (typeof item === 'string') {\n return <Text>{item}</Text>\n } else if ('command' in item) {\n return <Command command={item.command} />\n } else if ('link' in item) {\n return <Link {...item.link} />\n } else if ('char' in item) {\n return <Text>{item.char[0]}</Text>\n } else if ('userInput' in item) {\n return <UserInput userInput={item.userInput} />\n } else if ('subdued' in item) {\n return <Subdued subdued={item.subdued} />\n } else if ('filePath' in item) {\n return <FilePath filePath={item.filePath} />\n } else if ('list' in item) {\n return <List {...item.list} />\n } else if ('bold' in item) {\n return <Text bold>{item.bold}</Text>\n } else if ('info' in item) {\n return <Text color=\"blue\">{item.info}</Text>\n } else if ('warn' in item) {\n return <Text color=\"yellow\">{item.warn}</Text>\n } else if ('error' in item) {\n return <Text color=\"red\">{item.error}</Text>\n } else {\n const groupedItems = item.map(tokenToBlock).reduce(splitByDisplayType, [])\n\n return groupedItems.length === 1 && groupedItems[0]!.every((item) => item.display === 'inline') ? (\n <InlineBlocks blocks={groupedItems[0]!} />\n ) : (\n <Box flexDirection=\"column\">\n {groupedItems.map((items, groupIndex) => {\n if (items[0]!.display === 'inline') {\n return <InlineBlocks blocks={items} key={groupIndex} />\n } else {\n return <List key={groupIndex} {...(items[0]!.value as ListToken).list} />\n }\n })}\n </Box>\n )\n }\n}\n\nexport {TokenizedText}\n"]}
1
+ {"version":3,"file":"TokenizedText.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/TokenizedText.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Command} from './Command.js'\nimport {Link} from './Link.js'\nimport {List} from './List.js'\nimport {UserInput} from './UserInput.js'\nimport {FilePath} from './FilePath.js'\nimport {Subdued} from './Subdued.js'\nimport {LinksContext} from '../contexts/LinksContext.js'\nimport React, {FunctionComponent, useContext} from 'react'\nimport {Box, Text} from 'ink'\n\nexport interface LinkToken {\n link: {\n label?: string\n url: string\n }\n}\n\nexport interface UserInputToken {\n userInput: string\n}\n\nexport interface ListToken {\n list: {\n title?: TokenItem<InlineToken>\n items: TokenItem<InlineToken>[]\n ordered?: boolean\n }\n}\n\nexport interface BoldToken {\n bold: string\n}\n\nexport type Token =\n | string\n | {\n command: string\n }\n | LinkToken\n | {\n char: string\n }\n | UserInputToken\n | {\n subdued: string\n }\n | {\n filePath: string\n }\n | ListToken\n | BoldToken\n | {\n info: string\n }\n | {\n warn: string\n }\n | {\n error: string\n }\n\nexport type InlineToken = Exclude<Token, ListToken>\nexport type TokenItem<T extends Token = Token> = T | T[]\n\ntype DisplayType = 'block' | 'inline'\ninterface Block {\n display: DisplayType\n value: Token\n}\n\nfunction tokenToBlock(token: Token): Block {\n return {\n display: typeof token !== 'string' && 'list' in token ? 'block' : 'inline',\n value: token,\n }\n}\n\nexport function tokenItemToString(token: TokenItem): string {\n if (typeof token === 'string') {\n return token\n } else if ('command' in token) {\n return token.command\n } else if ('link' in token) {\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty label should fall through to url\n return token.link.label || token.link.url\n } else if ('char' in token) {\n return token.char\n } else if ('userInput' in token) {\n return token.userInput\n } else if ('subdued' in token) {\n return token.subdued\n } else if ('filePath' in token) {\n return token.filePath\n } else if ('list' in token) {\n return token.list.items.map(tokenItemToString).join(' ')\n } else if ('bold' in token) {\n return token.bold\n } else if ('info' in token) {\n return token.info\n } else if ('warn' in token) {\n return token.warn\n } else if ('error' in token) {\n return token.error\n } else {\n return token\n .map((item, index) => {\n if (index !== 0 && !(typeof item !== 'string' && 'char' in item)) {\n return ` ${tokenItemToString(item)}`\n } else {\n return tokenItemToString(item)\n }\n })\n .join('')\n }\n}\n\nexport function appendToTokenItem(token: TokenItem, suffix: string): TokenItem {\n return Array.isArray(token) ? [...token, {char: suffix}] : [token, {char: suffix}]\n}\n\nfunction splitByDisplayType(acc: Block[][], item: Block) {\n if (item.display === 'block') {\n acc.push([item])\n } else {\n const last = acc[acc.length - 1]\n if (last && last[0]!.display === 'inline') {\n last.push(item)\n } else {\n acc.push([item])\n }\n }\n return acc\n}\n\nconst InlineBlocks: React.FC<{blocks: Block[]}> = ({blocks}) => {\n return (\n <Text>\n {blocks.map((block, blockIndex) => (\n <Text key={blockIndex}>\n {blockIndex !== 0 && !(typeof block.value !== 'string' && 'char' in block.value) && <Text> </Text>}\n <TokenizedText item={block.value} />\n </Text>\n ))}\n </Text>\n )\n}\n\ninterface TokenizedTextProps {\n item: TokenItem\n}\n\n// Matches CommonMark inline links of the form `[label](url)` and autolinks of\n// the form `<url>`. We deliberately require an explicit `http://` or\n// `https://` scheme on the URL so callers can't accidentally trigger\n// linkification by typing square brackets or angle brackets in plain prose.\n//\n// The URL portion forbids whitespace, the closing delimiter (`)` or `>`), and\n// `<` to keep parsing predictable. URLs that legitimately contain those\n// characters (e.g. balanced parens) must be percent-encoded by the caller —\n// per CommonMark §6.3 / §6.4.\nconst MARKDOWN_LINK_REGEX = /\\[([^[\\]]+)\\]\\((https?:\\/\\/[^()<>\\s]+)\\)|<(https?:\\/\\/[^<>\\s]+)>/g\n\n/**\n * Parses an opt-in CommonMark link or autolink from a plain string token and\n * routes the result through the existing `<Link>` component.\n *\n * Why opt-in rather than auto-detection: server-returned error strings can\n * legitimately contain URL-shaped substrings that are not meant to be\n * clickable (e.g. a tunnel-URL validation error echoing back the user's bad\n * input). Requiring the explicit `[label](url)` / `<url>` shape lets the\n * source of the message (server or client) declare intent, and keeps prose\n * with bare URLs untouched.\n *\n * Only invoked when a `LinksContext` is present (i.e. inside a Banner /\n * Alert / FatalError); outside of that, the underlying `<Link>` would render\n * the URL inline and we'd defeat the wrap-resistance the footnote mechanism\n * provides.\n */\nfunction renderStringWithMarkdownLinks(str: string): JSX.Element {\n const matches = Array.from(str.matchAll(MARKDOWN_LINK_REGEX))\n if (matches.length === 0) {\n return <Text>{str}</Text>\n }\n\n const parts: JSX.Element[] = []\n let cursor = 0\n matches.forEach((match, index) => {\n const start = match.index\n if (start === undefined) {\n return\n }\n const end = start + match[0].length\n if (start > cursor) {\n parts.push(<Text key={`t${index}`}>{str.slice(cursor, start)}</Text>)\n }\n // `[label](url)` captures label in group 1 and url in group 2;\n // `<url>` autolinks capture only the url in group 3.\n const label = match[1]\n const url = match[2] ?? match[3]\n if (url === undefined) {\n return\n }\n parts.push(<Link key={`l${index}`} label={label} url={url} />)\n cursor = end\n })\n if (cursor < str.length) {\n parts.push(<Text key=\"tail\">{str.slice(cursor)}</Text>)\n }\n return <Text>{parts}</Text>\n}\n\n/**\n * `TokenizedText` renders a text string with tokens that can be either strings,\n * links, and commands.\n */\nconst TokenizedText: FunctionComponent<TokenizedTextProps> = ({item}) => {\n const linksContext = useContext(LinksContext)\n if (typeof item === 'string') {\n return linksContext === null ? <Text>{item}</Text> : renderStringWithMarkdownLinks(item)\n } else if ('command' in item) {\n return <Command command={item.command} />\n } else if ('link' in item) {\n return <Link {...item.link} />\n } else if ('char' in item) {\n return <Text>{item.char[0]}</Text>\n } else if ('userInput' in item) {\n return <UserInput userInput={item.userInput} />\n } else if ('subdued' in item) {\n return <Subdued subdued={item.subdued} />\n } else if ('filePath' in item) {\n return <FilePath filePath={item.filePath} />\n } else if ('list' in item) {\n return <List {...item.list} />\n } else if ('bold' in item) {\n return <Text bold>{item.bold}</Text>\n } else if ('info' in item) {\n return <Text color=\"blue\">{item.info}</Text>\n } else if ('warn' in item) {\n return <Text color=\"yellow\">{item.warn}</Text>\n } else if ('error' in item) {\n return <Text color=\"red\">{item.error}</Text>\n } else {\n const groupedItems = item.map(tokenToBlock).reduce(splitByDisplayType, [])\n\n return groupedItems.length === 1 && groupedItems[0]!.every((item) => item.display === 'inline') ? (\n <InlineBlocks blocks={groupedItems[0]!} />\n ) : (\n <Box flexDirection=\"column\">\n {groupedItems.map((items, groupIndex) => {\n if (items[0]!.display === 'inline') {\n return <InlineBlocks blocks={items} key={groupIndex} />\n } else {\n return <List key={groupIndex} {...(items[0]!.value as ListToken).list} />\n }\n })}\n </Box>\n )\n }\n}\n\nexport {TokenizedText}\n"]}
@@ -1,8 +1,30 @@
1
1
  import { tokenItemToString, TokenizedText } from './TokenizedText.js';
2
+ import { LinksContext } from '../contexts/LinksContext.js';
2
3
  import { unstyled } from '../../../../public/node/output.js';
3
4
  import { render } from '../../testing/ui.js';
4
- import { describe, expect, test } from 'vitest';
5
- import React from 'react';
5
+ import { describe, expect, test, vi } from 'vitest';
6
+ import supportsHyperlinks from 'supports-hyperlinks';
7
+ import React, { useRef } from 'react';
8
+ vi.mock('supports-hyperlinks');
9
+ // Matches the on-the-wire OSC 8 sequence emitted by `ansiEscapes.link`,
10
+ // which is what `<Link>` ultimately renders when the terminal supports
11
+ // hyperlinks. Format: `ESC ] 8 ; ; URL BEL TEXT ESC ] 8 ; ; BEL`.
12
+ function asOsc8Link(url, label) {
13
+ return `\u001b]8;;${url}\u0007${label ?? url}\u001b]8;;\u0007`;
14
+ }
15
+ // Mirrors the LinksContext that <Banner> sets up at runtime, without pulling
16
+ // the whole Banner border into these tests.
17
+ const WithLinksContext = ({ children }) => {
18
+ const links = useRef({});
19
+ return (React.createElement(LinksContext.Provider, { value: {
20
+ links,
21
+ addLink: (label, url) => {
22
+ const newId = (Object.keys(links.current).length + 1).toString();
23
+ links.current = { ...links.current, [newId]: { label, url } };
24
+ return newId;
25
+ },
26
+ } }, children));
27
+ };
6
28
  describe('TokenizedText', async () => {
7
29
  test('renders arrays of items separated by spaces', async () => {
8
30
  const item = [
@@ -52,6 +74,91 @@ describe('TokenizedText', async () => {
52
74
  src/this/is/a/test.js some info some warn some error"
53
75
  `);
54
76
  });
77
+ describe('markdown-link parsing in plain strings', async () => {
78
+ test('renders strings without a markdown link unchanged', async () => {
79
+ vi.mocked(supportsHyperlinks).stdout = false;
80
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
81
+ React.createElement(TokenizedText, { item: "no link here, just text" })));
82
+ expect(lastFrame()).toBe('no link here, just text');
83
+ });
84
+ test('does not linkify a bare URL — callers must opt in via `[label](url)` or `<url>`', async () => {
85
+ vi.mocked(supportsHyperlinks).stdout = true;
86
+ const url = 'https://example.com/docs';
87
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
88
+ React.createElement(TokenizedText, { item: `visit ${url} now` })));
89
+ expect(lastFrame()).toBe(`visit ${url} now`);
90
+ expect(lastFrame()).not.toContain(']8;;');
91
+ });
92
+ test('replaces an opt-in `[label](url)` with the label and a `[N]` footnote anchor when the terminal does not support hyperlinks', async () => {
93
+ vi.mocked(supportsHyperlinks).stdout = false;
94
+ const url = 'https://shopify.dev/docs/apps/build/sales-channels/channel-config-extension#specification-properties';
95
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
96
+ React.createElement(TokenizedText, { item: `Reference: [See specification requirements](${url})` })));
97
+ expect(lastFrame()).toBe('Reference: See specification requirements [1]');
98
+ expect(lastFrame()).not.toContain(url);
99
+ });
100
+ test('wraps the label of a `[label](url)` in OSC 8 escapes when the terminal supports hyperlinks', async () => {
101
+ vi.mocked(supportsHyperlinks).stdout = true;
102
+ const url = 'https://example.com/docs';
103
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
104
+ React.createElement(TokenizedText, { item: `Reference: [docs page](${url})` })));
105
+ expect(lastFrame()).toContain(asOsc8Link(url, 'docs page'));
106
+ });
107
+ test('renders a label-less `<url>` autolink as a `[N]` anchor and registers the URL in the footnote table', async () => {
108
+ vi.mocked(supportsHyperlinks).stdout = false;
109
+ const url = 'https://shopify.dev/docs';
110
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
111
+ React.createElement(TokenizedText, { item: `See specification requirements: <${url}>` })));
112
+ expect(lastFrame()).toBe('See specification requirements: [1]');
113
+ expect(lastFrame()).not.toContain(url);
114
+ });
115
+ test('parses multiple opt-in links in the same string', async () => {
116
+ vi.mocked(supportsHyperlinks).stdout = false;
117
+ const first = 'https://example.com/a';
118
+ const second = 'https://example.com/b';
119
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
120
+ React.createElement(TokenizedText, { item: `see [a](${first}) and <${second}>` })));
121
+ expect(lastFrame()).toBe('see a [1] and [2]');
122
+ });
123
+ test('parses back-to-back opt-in links separated only by whitespace', async () => {
124
+ vi.mocked(supportsHyperlinks).stdout = false;
125
+ const first = 'https://example.com/a';
126
+ const second = 'https://example.com/b';
127
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
128
+ React.createElement(TokenizedText, { item: `<${first}> <${second}>` })));
129
+ expect(lastFrame()).toBe('[1] [2]');
130
+ });
131
+ test('does not parse markdown links that omit the http(s) scheme', async () => {
132
+ vi.mocked(supportsHyperlinks).stdout = true;
133
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
134
+ React.createElement(TokenizedText, { item: "see [the section](#anchor) for more" })));
135
+ expect(lastFrame()).toBe('see [the section](#anchor) for more');
136
+ expect(lastFrame()).not.toContain(']8;;');
137
+ });
138
+ test('does not parse opt-in markdown when no LinksContext is present (e.g. outside a Banner)', async () => {
139
+ vi.mocked(supportsHyperlinks).stdout = true;
140
+ const url = 'https://example.com/docs';
141
+ const { lastFrame } = render(React.createElement(TokenizedText, { item: `see [docs](${url}) now` }));
142
+ expect(lastFrame()).toBe(`see [docs](${url}) now`);
143
+ expect(lastFrame()).not.toContain(']8;;');
144
+ });
145
+ test('echoing back a user-supplied URL inside an error message is left as plain text', async () => {
146
+ // Regression: an earlier auto-detection approach would turn the
147
+ // user's bad `--tunnel-url` value into a clickable OSC-8 link,
148
+ // which is misleading. With opt-in markdown the bare URL stays
149
+ // as-is and only the doc reference — which the server marks up —
150
+ // becomes clickable.
151
+ vi.mocked(supportsHyperlinks).stdout = true;
152
+ const tunnelUrl = 'https://wrong';
153
+ const docUrl = 'https://shopify.dev/docs/tunnels';
154
+ const { lastFrame } = render(React.createElement(WithLinksContext, null,
155
+ React.createElement(TokenizedText, { item: `Invalid tunnel URL: ${tunnelUrl}. See [tunnel docs](${docUrl}).` })));
156
+ expect(lastFrame()).toContain(`Invalid tunnel URL: ${tunnelUrl}.`);
157
+ expect(lastFrame()).toContain(asOsc8Link(docUrl, 'tunnel docs'));
158
+ // The user-supplied URL must not be wrapped in an OSC 8 escape.
159
+ expect(lastFrame()).not.toContain(`\u001b]8;;${tunnelUrl}`);
160
+ });
161
+ });
55
162
  describe('tokenItemToString', async () => {
56
163
  test("doesn't add a space before char", async () => {
57
164
  expect(tokenItemToString(['Run', { char: '!' }])).toBe('Run!');
@@ -1 +1 @@
1
- {"version":3,"file":"TokenizedText.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/TokenizedText.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAA;AACnE,OAAO,EAAC,QAAQ,EAAC,MAAM,mCAAmC,CAAA;AAC1D,OAAO,EAAC,MAAM,EAAC,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAE7C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,QAAQ,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;IACnC,IAAI,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,IAAI,GAAG;YACX,KAAK;YACL;gBACE,OAAO,EAAE,qBAAqB;aAC/B;YACD;gBACE,IAAI,EAAE;oBACJ,GAAG,EAAE,qBAAqB;oBAC1B,KAAK,EAAE,SAAS;iBACjB;aACF;YACD;gBACE,IAAI,EAAE,GAAG;aACV;YACD;gBACE,SAAS,EAAE,QAAQ;aACpB;YACD;gBACE,OAAO,EAAE,WAAW;aACrB;YACD;gBACE,IAAI,EAAE;oBACJ,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;iBACtC;aACF;YACD;gBACE,QAAQ,EAAE,uBAAuB;aAClC;YACD;gBACE,IAAI,EAAE,WAAW;aAClB;YACD;gBACE,IAAI,EAAE,WAAW;aAClB;YACD;gBACE,KAAK,EAAE,YAAY;aACpB;SACF,CAAA;QAED,MAAM,EAAC,SAAS,EAAC,GAAG,MAAM,CAAC,oBAAC,aAAa,IAAC,IAAI,EAAE,IAAI,GAAI,CAAC,CAAA;QAEzD,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;KAMpD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;QACvC,IAAI,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,CAAC,iBAAiB,CAAC,CAAC,KAAK,EAAE,EAAC,IAAI,EAAE,GAAG,EAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC9D,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,CACJ,iBAAiB,CAAC;gBAChB,KAAK;gBACL;oBACE,IAAI,EAAE;wBACJ,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;qBACtC;iBACF;aACF,CAAC,CACH,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QACpC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import {tokenItemToString, TokenizedText} from './TokenizedText.js'\nimport {unstyled} from '../../../../public/node/output.js'\nimport {render} from '../../testing/ui.js'\nimport {describe, expect, test} from 'vitest'\n\nimport React from 'react'\n\ndescribe('TokenizedText', async () => {\n test('renders arrays of items separated by spaces', async () => {\n const item = [\n 'Run',\n {\n command: 'cd verification-app',\n },\n {\n link: {\n url: 'https://example.com',\n label: 'Example',\n },\n },\n {\n char: '!',\n },\n {\n userInput: 'my-app',\n },\n {\n subdued: '(my-text)',\n },\n {\n list: {\n items: ['Item 1', 'Item 2', 'Item 3'],\n },\n },\n {\n filePath: 'src/this/is/a/test.js',\n },\n {\n info: 'some info',\n },\n {\n warn: 'some warn',\n },\n {\n error: 'some error',\n },\n ]\n\n const {lastFrame} = render(<TokenizedText item={item} />)\n\n expect(unstyled(lastFrame()!)).toMatchInlineSnapshot(`\n \"Run \\`cd verification-app\\` Example ( https://example.com )! my-app (my-text)\n • Item 1\n • Item 2\n • Item 3\n src/this/is/a/test.js some info some warn some error\"\n `)\n })\n\n describe('tokenItemToString', async () => {\n test(\"doesn't add a space before char\", async () => {\n expect(tokenItemToString(['Run', {char: '!'}])).toBe('Run!')\n })\n\n test('it concatenates list items inline', async () => {\n expect(\n tokenItemToString([\n 'Run',\n {\n list: {\n items: ['Item 1', 'Item 2', 'Item 3'],\n },\n },\n ]),\n ).toBe('Run Item 1 Item 2 Item 3')\n })\n })\n})\n"]}
1
+ {"version":3,"file":"TokenizedText.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/TokenizedText.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {tokenItemToString, TokenizedText} from './TokenizedText.js'\nimport {LinksContext, Link} from '../contexts/LinksContext.js'\nimport {unstyled} from '../../../../public/node/output.js'\nimport {render} from '../../testing/ui.js'\nimport {describe, expect, test, vi} from 'vitest'\nimport supportsHyperlinks from 'supports-hyperlinks'\n\nimport React, {FunctionComponent, useRef} from 'react'\n\nvi.mock('supports-hyperlinks')\n\n// Matches the on-the-wire OSC 8 sequence emitted by `ansiEscapes.link`,\n// which is what `<Link>` ultimately renders when the terminal supports\n// hyperlinks. Format: `ESC ] 8 ; ; URL BEL TEXT ESC ] 8 ; ; BEL`.\nfunction asOsc8Link(url: string, label?: string) {\n return `\\u001b]8;;${url}\\u0007${label ?? url}\\u001b]8;;\\u0007`\n}\n\n// Mirrors the LinksContext that <Banner> sets up at runtime, without pulling\n// the whole Banner border into these tests.\nconst WithLinksContext: FunctionComponent<{children: React.ReactNode}> = ({children}) => {\n const links = useRef<Record<string, Link>>({})\n return (\n <LinksContext.Provider\n value={{\n links,\n addLink: (label, url) => {\n const newId = (Object.keys(links.current).length + 1).toString()\n links.current = {...links.current, [newId]: {label, url}}\n return newId\n },\n }}\n >\n {children}\n </LinksContext.Provider>\n )\n}\n\ndescribe('TokenizedText', async () => {\n test('renders arrays of items separated by spaces', async () => {\n const item = [\n 'Run',\n {\n command: 'cd verification-app',\n },\n {\n link: {\n url: 'https://example.com',\n label: 'Example',\n },\n },\n {\n char: '!',\n },\n {\n userInput: 'my-app',\n },\n {\n subdued: '(my-text)',\n },\n {\n list: {\n items: ['Item 1', 'Item 2', 'Item 3'],\n },\n },\n {\n filePath: 'src/this/is/a/test.js',\n },\n {\n info: 'some info',\n },\n {\n warn: 'some warn',\n },\n {\n error: 'some error',\n },\n ]\n\n const {lastFrame} = render(<TokenizedText item={item} />)\n\n expect(unstyled(lastFrame()!)).toMatchInlineSnapshot(`\n \"Run \\`cd verification-app\\` Example ( https://example.com )! my-app (my-text)\n • Item 1\n • Item 2\n • Item 3\n src/this/is/a/test.js some info some warn some error\"\n `)\n })\n\n describe('markdown-link parsing in plain strings', async () => {\n test('renders strings without a markdown link unchanged', async () => {\n vi.mocked(supportsHyperlinks).stdout = false\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item=\"no link here, just text\" />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe('no link here, just text')\n })\n\n test('does not linkify a bare URL — callers must opt in via `[label](url)` or `<url>`', async () => {\n vi.mocked(supportsHyperlinks).stdout = true\n const url = 'https://example.com/docs'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`visit ${url} now`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe(`visit ${url} now`)\n expect(lastFrame()).not.toContain(']8;;')\n })\n\n test('replaces an opt-in `[label](url)` with the label and a `[N]` footnote anchor when the terminal does not support hyperlinks', async () => {\n vi.mocked(supportsHyperlinks).stdout = false\n const url = 'https://shopify.dev/docs/apps/build/sales-channels/channel-config-extension#specification-properties'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`Reference: [See specification requirements](${url})`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe('Reference: See specification requirements [1]')\n expect(lastFrame()).not.toContain(url)\n })\n\n test('wraps the label of a `[label](url)` in OSC 8 escapes when the terminal supports hyperlinks', async () => {\n vi.mocked(supportsHyperlinks).stdout = true\n const url = 'https://example.com/docs'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`Reference: [docs page](${url})`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toContain(asOsc8Link(url, 'docs page'))\n })\n\n test('renders a label-less `<url>` autolink as a `[N]` anchor and registers the URL in the footnote table', async () => {\n vi.mocked(supportsHyperlinks).stdout = false\n const url = 'https://shopify.dev/docs'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`See specification requirements: <${url}>`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe('See specification requirements: [1]')\n expect(lastFrame()).not.toContain(url)\n })\n\n test('parses multiple opt-in links in the same string', async () => {\n vi.mocked(supportsHyperlinks).stdout = false\n const first = 'https://example.com/a'\n const second = 'https://example.com/b'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`see [a](${first}) and <${second}>`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe('see a [1] and [2]')\n })\n\n test('parses back-to-back opt-in links separated only by whitespace', async () => {\n vi.mocked(supportsHyperlinks).stdout = false\n const first = 'https://example.com/a'\n const second = 'https://example.com/b'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`<${first}> <${second}>`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe('[1] [2]')\n })\n\n test('does not parse markdown links that omit the http(s) scheme', async () => {\n vi.mocked(supportsHyperlinks).stdout = true\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item=\"see [the section](#anchor) for more\" />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toBe('see [the section](#anchor) for more')\n expect(lastFrame()).not.toContain(']8;;')\n })\n\n test('does not parse opt-in markdown when no LinksContext is present (e.g. outside a Banner)', async () => {\n vi.mocked(supportsHyperlinks).stdout = true\n const url = 'https://example.com/docs'\n\n const {lastFrame} = render(<TokenizedText item={`see [docs](${url}) now`} />)\n\n expect(lastFrame()).toBe(`see [docs](${url}) now`)\n expect(lastFrame()).not.toContain(']8;;')\n })\n\n test('echoing back a user-supplied URL inside an error message is left as plain text', async () => {\n // Regression: an earlier auto-detection approach would turn the\n // user's bad `--tunnel-url` value into a clickable OSC-8 link,\n // which is misleading. With opt-in markdown the bare URL stays\n // as-is and only the doc reference — which the server marks up —\n // becomes clickable.\n vi.mocked(supportsHyperlinks).stdout = true\n const tunnelUrl = 'https://wrong'\n const docUrl = 'https://shopify.dev/docs/tunnels'\n\n const {lastFrame} = render(\n <WithLinksContext>\n <TokenizedText item={`Invalid tunnel URL: ${tunnelUrl}. See [tunnel docs](${docUrl}).`} />\n </WithLinksContext>,\n )\n\n expect(lastFrame()).toContain(`Invalid tunnel URL: ${tunnelUrl}.`)\n expect(lastFrame()).toContain(asOsc8Link(docUrl, 'tunnel docs'))\n // The user-supplied URL must not be wrapped in an OSC 8 escape.\n expect(lastFrame()).not.toContain(`\\u001b]8;;${tunnelUrl}`)\n })\n })\n\n describe('tokenItemToString', async () => {\n test(\"doesn't add a space before char\", async () => {\n expect(tokenItemToString(['Run', {char: '!'}])).toBe('Run!')\n })\n\n test('it concatenates list items inline', async () => {\n expect(\n tokenItemToString([\n 'Run',\n {\n list: {\n items: ['Item 1', 'Item 2', 'Item 3'],\n },\n },\n ]),\n ).toBe('Run Item 1 Item 2 Item 3')\n })\n })\n})\n"]}
@@ -1,4 +1,4 @@
1
- import { takeRandomFromArray } from './array.js';
1
+ import { takeRandomFromArray, uniq } from './array.js';
2
2
  import { unstyled } from '../node/output.js';
3
3
  import { camelCase, capitalCase, constantCase, paramCase, snakeCase, pascalCase } from 'change-case';
4
4
  const SAFE_RANDOM_BUSINESS_ADJECTIVES = [
@@ -152,6 +152,16 @@ const SAFE_RANDOM_CREATIVE_NOUNS = [
152
152
  'shapes',
153
153
  'patterns',
154
154
  ];
155
+ const NAME_FAMILIES = {
156
+ business: {
157
+ adjectives: SAFE_RANDOM_BUSINESS_ADJECTIVES,
158
+ nouns: SAFE_RANDOM_BUSINESS_NOUNS,
159
+ },
160
+ creative: {
161
+ adjectives: SAFE_RANDOM_CREATIVE_ADJECTIVES,
162
+ nouns: SAFE_RANDOM_CREATIVE_NOUNS,
163
+ },
164
+ };
155
165
  /**
156
166
  * Generates a random name by combining an adjective and noun.
157
167
  *
@@ -159,17 +169,7 @@ const SAFE_RANDOM_CREATIVE_NOUNS = [
159
169
  * @returns A random name generated by combining an adjective and noun.
160
170
  */
161
171
  export function getRandomName(family = 'business') {
162
- const mapping = {
163
- business: {
164
- adjectives: SAFE_RANDOM_BUSINESS_ADJECTIVES,
165
- nouns: SAFE_RANDOM_BUSINESS_NOUNS,
166
- },
167
- creative: {
168
- adjectives: SAFE_RANDOM_CREATIVE_ADJECTIVES,
169
- nouns: SAFE_RANDOM_CREATIVE_NOUNS,
170
- },
171
- };
172
- return `${takeRandomFromArray(mapping[family].adjectives)}-${takeRandomFromArray(mapping[family].nouns)}`;
172
+ return `${takeRandomFromArray(NAME_FAMILIES[family].adjectives)}-${takeRandomFromArray(NAME_FAMILIES[family].nouns)}`;
173
173
  }
174
174
  /**
175
175
  * Given a string, it returns it with the first letter capitalized.
@@ -208,14 +208,10 @@ export function pluralize(items, plural, singular, none) {
208
208
  * @returns The int if it was able to convert, otherwise undefined.
209
209
  */
210
210
  export function tryParseInt(maybeInt) {
211
- let asInt;
212
- if (maybeInt !== undefined) {
213
- asInt = parseInt(maybeInt, 10);
214
- if (isNaN(asInt)) {
215
- asInt = undefined;
216
- }
217
- }
218
- return asInt;
211
+ if (maybeInt === undefined)
212
+ return undefined;
213
+ const asInt = Number.parseInt(maybeInt, 10);
214
+ return Number.isNaN(asInt) ? undefined : asInt;
219
215
  }
220
216
  /**
221
217
  * Transforms a matrix of strings into a single string with the columns aligned.
@@ -372,11 +368,12 @@ export function pascalize(str) {
372
368
  export function normalizeDelimitedString(delimitedString, delimiter = ',') {
373
369
  if (!delimitedString)
374
370
  return;
375
- const items = delimitedString.split(delimiter).map((value) => value.trim());
376
- const nonEmptyItems = items.filter((value) => value !== '');
377
- const sortedItems = nonEmptyItems.sort();
378
- const uniqueSortedItems = [...new Set(sortedItems)];
379
- return uniqueSortedItems.join(delimiter);
371
+ return uniq(delimitedString
372
+ .split(delimiter)
373
+ .map((item) => item.trim())
374
+ .filter((item) => item !== ''))
375
+ .sort()
376
+ .join(delimiter);
380
377
  }
381
378
  /**
382
379
  * Given two dates, it returns a human-readable string representing the time elapsed between them.
@@ -1 +1 @@
1
- {"version":3,"file":"string.js","sourceRoot":"","sources":["../../../src/public/common/string.ts"],"names":[],"mappings":"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","sourcesContent":["import {takeRandomFromArray} from './array.js'\nimport {unstyled} from '../node/output.js'\nimport {Token, TokenItem} from '../../private/node/ui/components/TokenizedText.js'\n\nimport {camelCase, capitalCase, constantCase, paramCase, snakeCase, pascalCase} from 'change-case'\n\nconst SAFE_RANDOM_BUSINESS_ADJECTIVES = [\n 'commercial',\n 'profitable',\n 'amortizable',\n 'branded',\n 'integrated',\n 'synergistic',\n 'consolidated',\n 'diversified',\n 'lean',\n 'niche',\n 'premium',\n 'luxury',\n 'scalable',\n 'optimized',\n 'empowered',\n 'international',\n 'beneficial',\n 'fruitful',\n 'extensive',\n 'lucrative',\n 'modern',\n 'stable',\n 'strategic',\n 'adaptive',\n 'efficient',\n 'growing',\n 'sustainable',\n 'innovative',\n 'regional',\n 'specialized',\n 'focused',\n 'pragmatic',\n 'ethical',\n 'flexible',\n 'competitive',\n]\n\nconst SAFE_RANDOM_CREATIVE_ADJECTIVES = [\n 'bright',\n 'impactful',\n 'stylish',\n 'colorful',\n 'modern',\n 'minimal',\n 'trendy',\n 'creative',\n 'artistic',\n 'spectacular',\n 'glamorous',\n 'luxury',\n 'retro',\n 'nostalgic',\n 'comfy',\n 'polished',\n 'fabulous',\n 'balanced',\n 'monochrome',\n 'glitched',\n 'contrasted',\n 'elegant',\n 'textured',\n 'vibrant',\n 'harmonious',\n 'versatile',\n 'eclectic',\n 'futuristic',\n 'idealistic',\n 'intricate',\n 'bohemian',\n 'abstract',\n 'meticulous',\n 'refined',\n 'flamboyant',\n]\n\nconst SAFE_RANDOM_BUSINESS_NOUNS = [\n 'account',\n 'consumer',\n 'customer',\n 'enterprise',\n 'business',\n 'venture',\n 'marketplace',\n 'revenue',\n 'vertical',\n 'portfolio',\n 'negotiation',\n 'shipping',\n 'demand',\n 'supply',\n 'growth',\n 'merchant',\n 'investment',\n 'shareholder',\n 'conversion',\n 'capital',\n 'projection',\n 'upside',\n 'trade',\n 'deal',\n 'merchandise',\n 'transaction',\n 'sale',\n 'franchise',\n 'subsidiary',\n 'logistics',\n 'sponsorship',\n 'partnership',\n 'tax',\n 'policy',\n 'outsource',\n 'equity',\n 'strategy',\n 'valuation',\n 'benchmark',\n 'metrics',\n 'duplication',\n]\n\nconst SAFE_RANDOM_CREATIVE_NOUNS = [\n 'vibe',\n 'style',\n 'moment',\n 'mood',\n 'flavor',\n 'look',\n 'appearance',\n 'perspective',\n 'aspect',\n 'ambience',\n 'quality',\n 'backdrop',\n 'focus',\n 'tone',\n 'inspiration',\n 'imagery',\n 'aesthetics',\n 'palette',\n 'ornamentation',\n 'contrast',\n 'colorway',\n 'visuals',\n 'typography',\n 'composition',\n 'scale',\n 'symmetry',\n 'gradients',\n 'proportions',\n 'textures',\n 'harmony',\n 'shapes',\n 'patterns',\n]\n\nexport type RandomNameFamily = 'business' | 'creative'\n\n/**\n * Generates a random name by combining an adjective and noun.\n *\n * @param family - Theme to use for the random name (business or creative).\n * @returns A random name generated by combining an adjective and noun.\n */\nexport function getRandomName(family: RandomNameFamily = 'business'): string {\n const mapping = {\n business: {\n adjectives: SAFE_RANDOM_BUSINESS_ADJECTIVES,\n nouns: SAFE_RANDOM_BUSINESS_NOUNS,\n },\n creative: {\n adjectives: SAFE_RANDOM_CREATIVE_ADJECTIVES,\n nouns: SAFE_RANDOM_CREATIVE_NOUNS,\n },\n }\n return `${takeRandomFromArray(mapping[family].adjectives)}-${takeRandomFromArray(mapping[family].nouns)}`\n}\n\n/**\n * Given a string, it returns it with the first letter capitalized.\n *\n * @param str - String to capitalize.\n * @returns String with the first letter capitalized.\n */\nexport function capitalize(str: string): string {\n return str.substring(0, 1).toUpperCase() + str.substring(1)\n}\n\n/**\n * Given a list of items, it returns a pluralized string based on the amount of items.\n *\n * @param items - List of items.\n * @param plural - Supplier used when the list of items has more than one item.\n * @param singular - Supplier used when the list of items has a single item.\n * @param none - Supplier used when the list has no items.\n * @returns The {@link TokenItem} supplied by the {@link plural}, {@link singular}, or {@link none} functions.\n */\nexport function pluralize<\n T,\n TToken extends Token = Token,\n TPluralToken extends TToken = TToken,\n TSingularToken extends TToken = TToken,\n TNoneToken extends TToken = TToken,\n>(\n items: T[],\n plural: (items: T[]) => TokenItem<TPluralToken>,\n singular: (item: T) => TokenItem<TSingularToken>,\n none?: () => TokenItem<TNoneToken>,\n): TokenItem<TPluralToken | TSingularToken | TNoneToken> | string {\n if (items.length === 1) {\n return singular(items[0]!)\n }\n\n if (items.length > 1) {\n return plural(items)\n }\n\n if (none) {\n return none()\n }\n\n return ''\n}\n\n/**\n * Try to convert a string to an int, falling back to undefined if unable to.\n *\n * @param maybeInt - String to convert to an int.\n * @returns The int if it was able to convert, otherwise undefined.\n */\nexport function tryParseInt(maybeInt: string | undefined): number | undefined {\n let asInt: number | undefined\n if (maybeInt !== undefined) {\n asInt = parseInt(maybeInt, 10)\n if (isNaN(asInt)) {\n asInt = undefined\n }\n }\n return asInt\n}\n\n/**\n * Transforms a matrix of strings into a single string with the columns aligned.\n *\n * @param lines - Array of rows, where each row is an array of strings (representing columns).\n * @returns A string with the columns aligned.\n */\nexport function linesToColumns(lines: string[][]): string {\n const widths: number[] = []\n for (let i = 0; lines[0] && i < lines[0].length; i++) {\n const columnRows = lines.map((line) => line[i]!)\n widths.push(Math.max(...columnRows.map((row) => unstyled(row).length)))\n }\n const paddedLines = lines\n .map((line) => {\n return line\n .map((col, index) => {\n return `${col}${' '.repeat(widths[index]! - unstyled(col).length)}`\n })\n .join(' ')\n .trimEnd()\n })\n .join('\\n')\n return paddedLines\n}\n\n/**\n * Given a string, it transforms it to a slug (lowercase, hyphenated, no special chars, trimmed...).\n *\n * @param str - String to slugify.\n * @returns The slugified string.\n */\nexport function slugify(str: string): string {\n return str\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Given a string, it returns it with the special regex characters escaped.\n * More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping.\n *\n * @param str - String to escape.\n * @returns The escaped string.\n */\nexport function escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Transform a string to camelCase.\n *\n * @param input - String to escape.\n * @returns The escaped string.\n */\nexport function camelize(input: string): string {\n return camelCase(input)\n}\n\n/**\n * Transform a string to capitalCase.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function capitalizeWords(input: string): string {\n return capitalCase(input)\n}\n\n/**\n * Transform a string to param-case.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function hyphenate(input: string): string {\n return paramCase(input)\n}\n\n/**\n * Transform a string to snake_case.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function underscore(input: string): string {\n return snakeCase(input)\n}\n\n/**\n * Transform a string to CONSTANT_CASE.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function constantize(input: string): string {\n return constantCase(input)\n}\n\n/**\n * Given a date, return a formatted string like \"2021-01-01 12:00:00\".\n *\n * @param date - Date to format.\n * @returns The transformed string.\n */\nexport function formatDate(date: Date): string {\n const components = date.toISOString().split('T')\n const dateString = components[0] ?? date.toDateString()\n const timeString = components[1]?.split('.')[0] ?? date.toTimeString()\n return `${dateString} ${timeString}`\n}\n\n/**\n * Given a date in UTC ISO String format, return a formatted string in local time like \"2021-01-01 12:00:00\".\n *\n * @param dateString - UTC ISO Date String.\n * @returns The transformed string in local system time.\n */\nexport function formatLocalDate(dateString: string): string {\n const dateObj = new Date(dateString)\n const localDate = new Date(\n Date.UTC(\n dateObj.getFullYear(),\n dateObj.getMonth(),\n dateObj.getDate(),\n dateObj.getHours(),\n dateObj.getMinutes(),\n dateObj.getSeconds(),\n ),\n )\n return formatDate(localDate)\n}\n\n/**\n * Given a list of items, it returns a string with the items joined by commas and the last item joined by \"and\".\n * All items are wrapped in double quotes.\n * For example: [\"a\", \"b\", \"c\"] returns \"a\", \"b\" and \"c\".\n *\n * @param items - List of items.\n * @returns The joined string.\n */\nexport function joinWithAnd(items: string[]): string {\n if (items.length === 0) return ''\n if (items.length === 1) return `\"${items[0]}\"`\n\n return `${items\n .slice(0, -1)\n .map((item) => `\"${item}\"`)\n .join(', ')} and \"${items[items.length - 1]}\"`\n}\n\n/**\n * Given a string, it returns the PascalCase form of it.\n * Eg: \"pascal_case\" returns \"PascalCase\".\n *\n * @param str - String to PascalCase.\n * @returns String with all the first letter capitalized with no spaces.\n */\nexport function pascalize(str: string): string {\n return pascalCase(str)\n}\n\n/**\n * Given a string that represents a list of delimited tokens, it returns the normalized string representing the same\n * list, without empty elements, sorted, and with no duplicates.\n *\n * @param delimitedString - String to normalize.\n * @param delimiter - Delimiter used to split the string into tokens.\n * @returns String with the normalized list of tokens.\n */\nexport function normalizeDelimitedString(delimitedString?: string, delimiter = ','): string | undefined {\n if (!delimitedString) return\n\n const items = delimitedString.split(delimiter).map((value) => value.trim())\n const nonEmptyItems = items.filter((value) => value !== '')\n const sortedItems = nonEmptyItems.sort()\n const uniqueSortedItems = [...new Set(sortedItems)]\n\n return uniqueSortedItems.join(delimiter)\n}\n\n/**\n * Given two dates, it returns a human-readable string representing the time elapsed between them.\n *\n * @param from - Start date.\n * @param to - End date.\n * @returns A string like \"5 minutes ago\" or \"2 days ago\".\n */\nexport function timeAgo(from: Date, to: Date): string {\n const seconds = Math.floor((to.getTime() - from.getTime()) / 1000)\n if (seconds < 60) return `${formatTimeUnit(seconds, 'second')} ago`\n\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${formatTimeUnit(minutes, 'minute')} ago`\n\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${formatTimeUnit(hours, 'hour')} ago`\n\n const days = Math.floor(hours / 24)\n return `${formatTimeUnit(days, 'day')} ago`\n}\n\nfunction formatTimeUnit(count: number, unit: string): string {\n return `${count} ${unit}${count === 1 ? '' : 's'}`\n}\n"]}
1
+ {"version":3,"file":"string.js","sourceRoot":"","sources":["../../../src/public/common/string.ts"],"names":[],"mappings":"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","sourcesContent":["import {takeRandomFromArray, uniq} from './array.js'\nimport {unstyled} from '../node/output.js'\nimport {Token, TokenItem} from '../../private/node/ui/components/TokenizedText.js'\n\nimport {camelCase, capitalCase, constantCase, paramCase, snakeCase, pascalCase} from 'change-case'\n\nconst SAFE_RANDOM_BUSINESS_ADJECTIVES = [\n 'commercial',\n 'profitable',\n 'amortizable',\n 'branded',\n 'integrated',\n 'synergistic',\n 'consolidated',\n 'diversified',\n 'lean',\n 'niche',\n 'premium',\n 'luxury',\n 'scalable',\n 'optimized',\n 'empowered',\n 'international',\n 'beneficial',\n 'fruitful',\n 'extensive',\n 'lucrative',\n 'modern',\n 'stable',\n 'strategic',\n 'adaptive',\n 'efficient',\n 'growing',\n 'sustainable',\n 'innovative',\n 'regional',\n 'specialized',\n 'focused',\n 'pragmatic',\n 'ethical',\n 'flexible',\n 'competitive',\n]\n\nconst SAFE_RANDOM_CREATIVE_ADJECTIVES = [\n 'bright',\n 'impactful',\n 'stylish',\n 'colorful',\n 'modern',\n 'minimal',\n 'trendy',\n 'creative',\n 'artistic',\n 'spectacular',\n 'glamorous',\n 'luxury',\n 'retro',\n 'nostalgic',\n 'comfy',\n 'polished',\n 'fabulous',\n 'balanced',\n 'monochrome',\n 'glitched',\n 'contrasted',\n 'elegant',\n 'textured',\n 'vibrant',\n 'harmonious',\n 'versatile',\n 'eclectic',\n 'futuristic',\n 'idealistic',\n 'intricate',\n 'bohemian',\n 'abstract',\n 'meticulous',\n 'refined',\n 'flamboyant',\n]\n\nconst SAFE_RANDOM_BUSINESS_NOUNS = [\n 'account',\n 'consumer',\n 'customer',\n 'enterprise',\n 'business',\n 'venture',\n 'marketplace',\n 'revenue',\n 'vertical',\n 'portfolio',\n 'negotiation',\n 'shipping',\n 'demand',\n 'supply',\n 'growth',\n 'merchant',\n 'investment',\n 'shareholder',\n 'conversion',\n 'capital',\n 'projection',\n 'upside',\n 'trade',\n 'deal',\n 'merchandise',\n 'transaction',\n 'sale',\n 'franchise',\n 'subsidiary',\n 'logistics',\n 'sponsorship',\n 'partnership',\n 'tax',\n 'policy',\n 'outsource',\n 'equity',\n 'strategy',\n 'valuation',\n 'benchmark',\n 'metrics',\n 'duplication',\n]\n\nconst SAFE_RANDOM_CREATIVE_NOUNS = [\n 'vibe',\n 'style',\n 'moment',\n 'mood',\n 'flavor',\n 'look',\n 'appearance',\n 'perspective',\n 'aspect',\n 'ambience',\n 'quality',\n 'backdrop',\n 'focus',\n 'tone',\n 'inspiration',\n 'imagery',\n 'aesthetics',\n 'palette',\n 'ornamentation',\n 'contrast',\n 'colorway',\n 'visuals',\n 'typography',\n 'composition',\n 'scale',\n 'symmetry',\n 'gradients',\n 'proportions',\n 'textures',\n 'harmony',\n 'shapes',\n 'patterns',\n]\n\nexport type RandomNameFamily = 'business' | 'creative'\n\nconst NAME_FAMILIES: Record<RandomNameFamily, {adjectives: string[]; nouns: string[]}> = {\n business: {\n adjectives: SAFE_RANDOM_BUSINESS_ADJECTIVES,\n nouns: SAFE_RANDOM_BUSINESS_NOUNS,\n },\n creative: {\n adjectives: SAFE_RANDOM_CREATIVE_ADJECTIVES,\n nouns: SAFE_RANDOM_CREATIVE_NOUNS,\n },\n}\n\n/**\n * Generates a random name by combining an adjective and noun.\n *\n * @param family - Theme to use for the random name (business or creative).\n * @returns A random name generated by combining an adjective and noun.\n */\nexport function getRandomName(family: RandomNameFamily = 'business'): string {\n return `${takeRandomFromArray(NAME_FAMILIES[family].adjectives)}-${takeRandomFromArray(NAME_FAMILIES[family].nouns)}`\n}\n\n/**\n * Given a string, it returns it with the first letter capitalized.\n *\n * @param str - String to capitalize.\n * @returns String with the first letter capitalized.\n */\nexport function capitalize(str: string): string {\n return str.substring(0, 1).toUpperCase() + str.substring(1)\n}\n\n/**\n * Given a list of items, it returns a pluralized string based on the amount of items.\n *\n * @param items - List of items.\n * @param plural - Supplier used when the list of items has more than one item.\n * @param singular - Supplier used when the list of items has a single item.\n * @param none - Supplier used when the list has no items.\n * @returns The {@link TokenItem} supplied by the {@link plural}, {@link singular}, or {@link none} functions.\n */\nexport function pluralize<\n T,\n TToken extends Token = Token,\n TPluralToken extends TToken = TToken,\n TSingularToken extends TToken = TToken,\n TNoneToken extends TToken = TToken,\n>(\n items: T[],\n plural: (items: T[]) => TokenItem<TPluralToken>,\n singular: (item: T) => TokenItem<TSingularToken>,\n none?: () => TokenItem<TNoneToken>,\n): TokenItem<TPluralToken | TSingularToken | TNoneToken> | string {\n if (items.length === 1) {\n return singular(items[0]!)\n }\n\n if (items.length > 1) {\n return plural(items)\n }\n\n if (none) {\n return none()\n }\n\n return ''\n}\n\n/**\n * Try to convert a string to an int, falling back to undefined if unable to.\n *\n * @param maybeInt - String to convert to an int.\n * @returns The int if it was able to convert, otherwise undefined.\n */\nexport function tryParseInt(maybeInt: string | undefined): number | undefined {\n if (maybeInt === undefined) return undefined\n const asInt = Number.parseInt(maybeInt, 10)\n return Number.isNaN(asInt) ? undefined : asInt\n}\n\n/**\n * Transforms a matrix of strings into a single string with the columns aligned.\n *\n * @param lines - Array of rows, where each row is an array of strings (representing columns).\n * @returns A string with the columns aligned.\n */\nexport function linesToColumns(lines: string[][]): string {\n const widths: number[] = []\n for (let i = 0; lines[0] && i < lines[0].length; i++) {\n const columnRows = lines.map((line) => line[i]!)\n widths.push(Math.max(...columnRows.map((row) => unstyled(row).length)))\n }\n const paddedLines = lines\n .map((line) => {\n return line\n .map((col, index) => {\n return `${col}${' '.repeat(widths[index]! - unstyled(col).length)}`\n })\n .join(' ')\n .trimEnd()\n })\n .join('\\n')\n return paddedLines\n}\n\n/**\n * Given a string, it transforms it to a slug (lowercase, hyphenated, no special chars, trimmed...).\n *\n * @param str - String to slugify.\n * @returns The slugified string.\n */\nexport function slugify(str: string): string {\n return str\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Given a string, it returns it with the special regex characters escaped.\n * More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping.\n *\n * @param str - String to escape.\n * @returns The escaped string.\n */\nexport function escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Transform a string to camelCase.\n *\n * @param input - String to escape.\n * @returns The escaped string.\n */\nexport function camelize(input: string): string {\n return camelCase(input)\n}\n\n/**\n * Transform a string to capitalCase.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function capitalizeWords(input: string): string {\n return capitalCase(input)\n}\n\n/**\n * Transform a string to param-case.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function hyphenate(input: string): string {\n return paramCase(input)\n}\n\n/**\n * Transform a string to snake_case.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function underscore(input: string): string {\n return snakeCase(input)\n}\n\n/**\n * Transform a string to CONSTANT_CASE.\n *\n * @param input - String to transform.\n * @returns The transformed string.\n */\nexport function constantize(input: string): string {\n return constantCase(input)\n}\n\n/**\n * Given a date, return a formatted string like \"2021-01-01 12:00:00\".\n *\n * @param date - Date to format.\n * @returns The transformed string.\n */\nexport function formatDate(date: Date): string {\n const components = date.toISOString().split('T')\n const dateString = components[0] ?? date.toDateString()\n const timeString = components[1]?.split('.')[0] ?? date.toTimeString()\n return `${dateString} ${timeString}`\n}\n\n/**\n * Given a date in UTC ISO String format, return a formatted string in local time like \"2021-01-01 12:00:00\".\n *\n * @param dateString - UTC ISO Date String.\n * @returns The transformed string in local system time.\n */\nexport function formatLocalDate(dateString: string): string {\n const dateObj = new Date(dateString)\n const localDate = new Date(\n Date.UTC(\n dateObj.getFullYear(),\n dateObj.getMonth(),\n dateObj.getDate(),\n dateObj.getHours(),\n dateObj.getMinutes(),\n dateObj.getSeconds(),\n ),\n )\n return formatDate(localDate)\n}\n\n/**\n * Given a list of items, it returns a string with the items joined by commas and the last item joined by \"and\".\n * All items are wrapped in double quotes.\n * For example: [\"a\", \"b\", \"c\"] returns \"a\", \"b\" and \"c\".\n *\n * @param items - List of items.\n * @returns The joined string.\n */\nexport function joinWithAnd(items: string[]): string {\n if (items.length === 0) return ''\n if (items.length === 1) return `\"${items[0]}\"`\n\n return `${items\n .slice(0, -1)\n .map((item) => `\"${item}\"`)\n .join(', ')} and \"${items[items.length - 1]}\"`\n}\n\n/**\n * Given a string, it returns the PascalCase form of it.\n * Eg: \"pascal_case\" returns \"PascalCase\".\n *\n * @param str - String to PascalCase.\n * @returns String with all the first letter capitalized with no spaces.\n */\nexport function pascalize(str: string): string {\n return pascalCase(str)\n}\n\n/**\n * Given a string that represents a list of delimited tokens, it returns the normalized string representing the same\n * list, without empty elements, sorted, and with no duplicates.\n *\n * @param delimitedString - String to normalize.\n * @param delimiter - Delimiter used to split the string into tokens.\n * @returns String with the normalized list of tokens.\n */\nexport function normalizeDelimitedString(delimitedString?: string, delimiter = ','): string | undefined {\n if (!delimitedString) return\n\n return uniq(\n delimitedString\n .split(delimiter)\n .map((item) => item.trim())\n .filter((item) => item !== ''),\n )\n .sort()\n .join(delimiter)\n}\n\n/**\n * Given two dates, it returns a human-readable string representing the time elapsed between them.\n *\n * @param from - Start date.\n * @param to - End date.\n * @returns A string like \"5 minutes ago\" or \"2 days ago\".\n */\nexport function timeAgo(from: Date, to: Date): string {\n const seconds = Math.floor((to.getTime() - from.getTime()) / 1000)\n if (seconds < 60) return `${formatTimeUnit(seconds, 'second')} ago`\n\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${formatTimeUnit(minutes, 'minute')} ago`\n\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${formatTimeUnit(hours, 'hour')} ago`\n\n const days = Math.floor(hours / 24)\n return `${formatTimeUnit(days, 'day')} ago`\n}\n\nfunction formatTimeUnit(count: number, unit: string): string {\n return `${count} ${unit}${count === 1 ? '' : 's'}`\n}\n"]}
@@ -1 +1 @@
1
- export declare const CLI_KIT_VERSION = "3.94.3";
1
+ export declare const CLI_KIT_VERSION = "4.1.0";
@@ -1,2 +1,2 @@
1
- export const CLI_KIT_VERSION = '3.94.3';
1
+ export const CLI_KIT_VERSION = '4.1.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/public/common/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAA","sourcesContent":["export const CLI_KIT_VERSION = '3.94.3'\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/public/common/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA","sourcesContent":["export const CLI_KIT_VERSION = '4.1.0'\n"]}