@shopify/cli-kit 4.9.5 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.d.ts +5 -0
- package/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.js +22 -0
- package/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.js.map +1 -1
- package/dist/private/node/api/graphql.d.ts +2 -1
- package/dist/private/node/api/graphql.js +12 -1
- package/dist/private/node/api/graphql.js.map +1 -1
- package/dist/private/node/api.d.ts +2 -0
- package/dist/private/node/api.js +49 -22
- package/dist/private/node/api.js.map +1 -1
- package/dist/private/node/command-json-schema.js +1 -0
- package/dist/private/node/command-json-schema.js.map +1 -1
- package/dist/private/node/constants.d.ts +1 -0
- package/dist/private/node/constants.js +1 -0
- package/dist/private/node/constants.js.map +1 -1
- package/dist/private/node/dev-instance-output.d.ts +47 -0
- package/dist/private/node/dev-instance-output.js +143 -0
- package/dist/private/node/dev-instance-output.js.map +1 -0
- package/dist/private/node/json-error.d.ts +1 -15
- package/dist/private/node/json-error.js +4 -98
- package/dist/private/node/json-error.js.map +1 -1
- package/dist/private/node/output-context.d.ts +8 -0
- package/dist/private/node/output-context.js +10 -0
- package/dist/private/node/output-context.js.map +1 -0
- package/dist/private/node/output.test.js +4 -4
- package/dist/private/node/output.test.js.map +1 -1
- package/dist/private/node/session/automation-token.d.ts +50 -0
- package/dist/private/node/session/automation-token.js +77 -0
- package/dist/private/node/session/automation-token.js.map +1 -0
- package/dist/private/node/session/exchange.d.ts +5 -1
- package/dist/private/node/session/exchange.js +97 -7
- package/dist/private/node/session/exchange.js.map +1 -1
- package/dist/private/node/session/permanent-store-domain.d.ts +21 -0
- package/dist/private/node/session/permanent-store-domain.js +72 -0
- package/dist/private/node/session/permanent-store-domain.js.map +1 -0
- package/dist/private/node/session.d.ts +3 -2
- package/dist/private/node/session.js +12 -3
- package/dist/private/node/session.js.map +1 -1
- package/dist/private/node/themes/trust-challenge.d.ts +31 -0
- package/dist/private/node/themes/trust-challenge.js +143 -0
- package/dist/private/node/themes/trust-challenge.js.map +1 -0
- package/dist/private/node/ui/alert.js +1 -1
- package/dist/private/node/ui/alert.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.d.ts +2 -7
- package/dist/private/node/ui/components/ConcurrentOutput.js +4 -8
- package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
- package/dist/private/node/ui/components/LoadingBar.js +9 -15
- package/dist/private/node/ui/components/LoadingBar.js.map +1 -1
- package/dist/private/node/ui/components/LoadingBar.test.js +45 -107
- package/dist/private/node/ui/components/LoadingBar.test.js.map +1 -1
- package/dist/private/node/ui/components/LoadingIndicator.d.ts +7 -0
- package/dist/private/node/ui/components/LoadingIndicator.js +22 -0
- package/dist/private/node/ui/components/LoadingIndicator.js.map +1 -0
- package/dist/private/node/ui/components/Table/Table.d.ts +8 -0
- package/dist/private/node/ui/components/Table/Table.js +18 -2
- package/dist/private/node/ui/components/Table/Table.js.map +1 -1
- package/dist/public/common/version.d.ts +1 -1
- package/dist/public/common/version.js +1 -1
- package/dist/public/common/version.js.map +1 -1
- package/dist/public/node/api/admin.js +13 -0
- package/dist/public/node/api/admin.js.map +1 -1
- package/dist/public/node/api/graphql.js +38 -7
- package/dist/public/node/api/graphql.js.map +1 -1
- package/dist/public/node/archiver.js +13 -6
- package/dist/public/node/archiver.js.map +1 -1
- package/dist/public/node/base-command.d.ts +6 -0
- package/dist/public/node/base-command.js +97 -16
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/dev-instance.d.ts +138 -0
- package/dist/public/node/dev-instance.js +365 -0
- package/dist/public/node/dev-instance.js.map +1 -0
- package/dist/public/node/environment.d.ts +6 -3
- package/dist/public/node/environment.js +11 -4
- package/dist/public/node/environment.js.map +1 -1
- package/dist/public/node/error/index.js +2 -1
- package/dist/public/node/error/index.js.map +1 -1
- package/dist/public/node/error/schema.d.ts +24 -24
- package/dist/public/node/error/serialization.d.ts +8 -0
- package/dist/public/node/error/serialization.js +103 -0
- package/dist/public/node/error/serialization.js.map +1 -0
- package/dist/public/node/graphiql/server.js +18 -2
- package/dist/public/node/graphiql/server.js.map +1 -1
- package/dist/public/node/is-global.js +2 -1
- package/dist/public/node/is-global.js.map +1 -1
- package/dist/public/node/notifications-system.d.ts +6 -6
- package/dist/public/node/path.d.ts +19 -3
- package/dist/public/node/path.js +21 -3
- package/dist/public/node/path.js.map +1 -1
- package/dist/public/node/session.d.ts +38 -3
- package/dist/public/node/session.js +56 -9
- package/dist/public/node/session.js.map +1 -1
- package/dist/public/node/system.d.ts +4 -0
- package/dist/public/node/system.js +39 -9
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/themes/api.d.ts +9 -0
- package/dist/public/node/themes/api.js +50 -22
- package/dist/public/node/themes/api.js.map +1 -1
- package/dist/public/node/themes/theme-manager.d.ts +8 -1
- package/dist/public/node/themes/theme-manager.js +1 -1
- package/dist/public/node/themes/theme-manager.js.map +1 -1
- package/dist/public/node/tree-kill.js +12 -2
- package/dist/public/node/tree-kill.js.map +1 -1
- package/dist/public/node/ui/components.d.ts +1 -0
- package/dist/public/node/ui/components.js +1 -0
- package/dist/public/node/ui/components.js.map +1 -1
- package/dist/public/node/ui.d.ts +24 -4
- package/dist/public/node/ui.js +54 -21
- package/dist/public/node/ui.js.map +1 -1
- package/dist/public/node/upgrade/output.d.ts +17 -0
- package/dist/public/node/upgrade/output.js +51 -0
- package/dist/public/node/upgrade/output.js.map +1 -0
- package/dist/public/node/upgrade/result.d.ts +8 -0
- package/dist/public/node/upgrade/result.js +21 -0
- package/dist/public/node/upgrade/result.js.map +1 -0
- package/dist/public/node/upgrade/types.d.ts +61 -0
- package/dist/public/node/upgrade/types.js +46 -0
- package/dist/public/node/upgrade/types.js.map +1 -0
- package/dist/public/node/upgrade.d.ts +8 -0
- package/dist/public/node/upgrade.js +57 -22
- package/dist/public/node/upgrade.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/private/node/ui/components/TextAnimation.d.ts +0 -10
- package/dist/private/node/ui/components/TextAnimation.js +0 -56
- package/dist/private/node/ui/components/TextAnimation.js.map +0 -1
- package/dist/private/node/ui/components/TextAnimation.test.d.ts +0 -1
- package/dist/private/node/ui/components/TextAnimation.test.js +0 -73
- package/dist/private/node/ui/components/TextAnimation.test.js.map +0 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { extractGraphQLErrors } from '../api/graphql.js';
|
|
2
|
+
import { commandEventOutputMode } from '../command-event-context.js';
|
|
3
|
+
import { recordEvent } from '../../../public/node/analytics.js';
|
|
4
|
+
import { adminFqdn } from '../../../public/node/context/fqdn.js';
|
|
5
|
+
import { AbortError } from '../../../public/node/error.js';
|
|
6
|
+
import { outputDebug } from '../../../public/node/output.js';
|
|
7
|
+
import { isCI, openURL } from '../../../public/node/system.js';
|
|
8
|
+
import { isTTY, keypress, renderInfo, renderSuccess } from '../../../public/node/ui.js';
|
|
9
|
+
const TRUST_CHALLENGE_ERROR_CODE = 'CHALLENGE_REQUIRED';
|
|
10
|
+
// Enter or Space. `theme dev` binds other single keys on the same stdin to its own shortcuts.
|
|
11
|
+
const CONTINUE_KEYS = ['\r', '\n', ' '];
|
|
12
|
+
/**
|
|
13
|
+
* The abort that ends a Trust Battery challenge: the session cannot answer it, or
|
|
14
|
+
* the request was challenged again after the verification.
|
|
15
|
+
*/
|
|
16
|
+
export class TrustChallengeError extends AbortError {
|
|
17
|
+
}
|
|
18
|
+
let pendingChallenge;
|
|
19
|
+
let completedChallenges = 0;
|
|
20
|
+
/**
|
|
21
|
+
* Runs a theme Admin API request and, when Core answers with a Trust Battery
|
|
22
|
+
* challenge, waits for the user to complete it in the browser before retrying once.
|
|
23
|
+
*
|
|
24
|
+
* A request that starts while another request's challenge is still being completed
|
|
25
|
+
* waits for that challenge first, so it is sent once instead of being challenged
|
|
26
|
+
* itself and then retried. A request that was already in flight when a challenge
|
|
27
|
+
* was completed retries without a second prompt, unless another prompt is open:
|
|
28
|
+
* then it waits for that prompt too.
|
|
29
|
+
*
|
|
30
|
+
* @param request - The request to run and possibly retry.
|
|
31
|
+
* @returns The result of the request.
|
|
32
|
+
*/
|
|
33
|
+
export async function withTrustChallengeRetry(request) {
|
|
34
|
+
if (pendingChallenge)
|
|
35
|
+
await pendingChallenge;
|
|
36
|
+
const completedChallengesAtStart = completedChallenges;
|
|
37
|
+
const attempt = await attemptRequest(request);
|
|
38
|
+
if (!attempt.challenged)
|
|
39
|
+
return attempt.result;
|
|
40
|
+
const verifiedSinceStart = completedChallenges > completedChallengesAtStart;
|
|
41
|
+
if (pendingChallenge || !verifiedSinceStart)
|
|
42
|
+
await completeTrustChallenge(attempt.challengeUrl);
|
|
43
|
+
const retry = await attemptRequest(request);
|
|
44
|
+
if (!retry.challenged)
|
|
45
|
+
return retry.result;
|
|
46
|
+
recordEvent('theme-api:trust-challenge:retry-challenged');
|
|
47
|
+
throw new TrustChallengeError('Shopify still needs to verify your identity before it can continue.', undefined, [
|
|
48
|
+
['Complete the verification at', { link: { url: retry.challengeUrl } }, 'then run the command again.'],
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Runs the request once. A Trust Battery challenge becomes a result with the challenge URL;
|
|
53
|
+
* any other error is thrown.
|
|
54
|
+
*/
|
|
55
|
+
async function attemptRequest(request) {
|
|
56
|
+
try {
|
|
57
|
+
return { challenged: false, result: await request() };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const challengeUrl = await trustChallengeUrl(error);
|
|
61
|
+
if (!challengeUrl)
|
|
62
|
+
throw error;
|
|
63
|
+
return { challenged: true, challengeUrl };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the challenge URL from a Trust Battery `CHALLENGE_REQUIRED` GraphQL error.
|
|
68
|
+
*
|
|
69
|
+
* Only URLs on the Admin host are trusted. Anything else is not treated as a
|
|
70
|
+
* challenge and the original error surfaces through normal error handling.
|
|
71
|
+
*
|
|
72
|
+
* @param error - The error thrown by an Admin API request.
|
|
73
|
+
* @returns The challenge URL, or undefined when the error is not a trust challenge.
|
|
74
|
+
*/
|
|
75
|
+
export async function trustChallengeUrl(error) {
|
|
76
|
+
const challengeError = extractGraphQLErrors(error).find((graphQLError) => graphQLError.extensions?.code === TRUST_CHALLENGE_ERROR_CODE);
|
|
77
|
+
const redirectTo = challengeError?.extensions?.redirect_to;
|
|
78
|
+
if (typeof redirectTo !== 'string')
|
|
79
|
+
return undefined;
|
|
80
|
+
if (!(await isAdminUrl(redirectTo))) {
|
|
81
|
+
outputDebug(`Ignoring trust challenge with an unexpected URL host: ${redirectTo}`);
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return redirectTo;
|
|
85
|
+
}
|
|
86
|
+
async function isAdminUrl(url) {
|
|
87
|
+
try {
|
|
88
|
+
const parsedUrl = new URL(url);
|
|
89
|
+
return parsedUrl.protocol === 'https:' && parsedUrl.hostname === (await adminFqdn());
|
|
90
|
+
// eslint-disable-next-line no-catch-all/no-catch-all
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Concurrent theme requests (for example the parallel batches of `theme push`)
|
|
98
|
+
* all receive the same challenge. Share one prompt so the browser opens once and
|
|
99
|
+
* every waiting request retries after the same verification.
|
|
100
|
+
*/
|
|
101
|
+
async function completeTrustChallenge(challengeUrl) {
|
|
102
|
+
pendingChallenge ??= promptForTrustChallenge(challengeUrl)
|
|
103
|
+
.then(() => {
|
|
104
|
+
completedChallenges += 1;
|
|
105
|
+
})
|
|
106
|
+
.finally(() => {
|
|
107
|
+
pendingChallenge = undefined;
|
|
108
|
+
});
|
|
109
|
+
return pendingChallenge;
|
|
110
|
+
}
|
|
111
|
+
async function promptForTrustChallenge(challengeUrl) {
|
|
112
|
+
recordEvent('theme-api:trust-challenge:required');
|
|
113
|
+
if (isCI() || !isTTY() || commandEventOutputMode() === 'json') {
|
|
114
|
+
recordEvent('theme-api:trust-challenge:non-interactive');
|
|
115
|
+
throw new TrustChallengeError('Shopify needs to verify your identity before it can continue.', undefined, [
|
|
116
|
+
[
|
|
117
|
+
'Open',
|
|
118
|
+
{ link: { url: challengeUrl } },
|
|
119
|
+
"in a browser where you're logged in to Shopify with the same account you use in Shopify CLI, complete the verification, then run the command again.",
|
|
120
|
+
],
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
const opened = await openURL(challengeUrl);
|
|
124
|
+
recordEvent(opened ? 'theme-api:trust-challenge:browser-opened' : 'theme-api:trust-challenge:browser-not-opened');
|
|
125
|
+
// Everything the user needs is inside the banner: long-running commands such as
|
|
126
|
+
// `theme push` redraw an Ink progress bar that erases any plain line written after it.
|
|
127
|
+
renderInfo({
|
|
128
|
+
headline: 'Verify your identity to continue.',
|
|
129
|
+
body: opened
|
|
130
|
+
? 'A verification page opened in your browser.'
|
|
131
|
+
: 'Shopify needs to verify your identity before it can continue.',
|
|
132
|
+
link: opened ? undefined : { label: 'Open the verification page', url: challengeUrl },
|
|
133
|
+
nextSteps: [
|
|
134
|
+
['Complete the verification in your browser, logged in with the same account you use in Shopify CLI.'],
|
|
135
|
+
['Press', { userInput: 'Enter' }, 'here once you have completed it.'],
|
|
136
|
+
],
|
|
137
|
+
orderedNextSteps: true,
|
|
138
|
+
});
|
|
139
|
+
await keypress(process.stdin, undefined, { keys: CONTINUE_KEYS });
|
|
140
|
+
recordEvent('theme-api:trust-challenge:acknowledged');
|
|
141
|
+
renderSuccess({ headline: 'Continuing.' });
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=trust-challenge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-challenge.js","sourceRoot":"","sources":["../../../../src/private/node/themes/trust-challenge.ts"],"names":[],"mappings":"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","sourcesContent":["import {extractGraphQLErrors} from '../api/graphql.js'\nimport {commandEventOutputMode} from '../command-event-context.js'\nimport {recordEvent} from '../../../public/node/analytics.js'\nimport {adminFqdn} from '../../../public/node/context/fqdn.js'\nimport {AbortError} from '../../../public/node/error.js'\nimport {outputDebug} from '../../../public/node/output.js'\nimport {isCI, openURL} from '../../../public/node/system.js'\nimport {isTTY, keypress, renderInfo, renderSuccess} from '../../../public/node/ui.js'\n\nconst TRUST_CHALLENGE_ERROR_CODE = 'CHALLENGE_REQUIRED'\n// Enter or Space. `theme dev` binds other single keys on the same stdin to its own shortcuts.\nconst CONTINUE_KEYS = ['\\r', '\\n', ' ']\n\n/**\n * The abort that ends a Trust Battery challenge: the session cannot answer it, or\n * the request was challenged again after the verification.\n */\nexport class TrustChallengeError extends AbortError {}\n\nlet pendingChallenge: Promise<void> | undefined\nlet completedChallenges = 0\n\n/**\n * Runs a theme Admin API request and, when Core answers with a Trust Battery\n * challenge, waits for the user to complete it in the browser before retrying once.\n *\n * A request that starts while another request's challenge is still being completed\n * waits for that challenge first, so it is sent once instead of being challenged\n * itself and then retried. A request that was already in flight when a challenge\n * was completed retries without a second prompt, unless another prompt is open:\n * then it waits for that prompt too.\n *\n * @param request - The request to run and possibly retry.\n * @returns The result of the request.\n */\nexport async function withTrustChallengeRetry<T>(request: () => Promise<T>): Promise<T> {\n if (pendingChallenge) await pendingChallenge\n const completedChallengesAtStart = completedChallenges\n\n const attempt = await attemptRequest(request)\n if (!attempt.challenged) return attempt.result\n\n const verifiedSinceStart = completedChallenges > completedChallengesAtStart\n if (pendingChallenge || !verifiedSinceStart) await completeTrustChallenge(attempt.challengeUrl)\n\n const retry = await attemptRequest(request)\n if (!retry.challenged) return retry.result\n\n recordEvent('theme-api:trust-challenge:retry-challenged')\n throw new TrustChallengeError('Shopify still needs to verify your identity before it can continue.', undefined, [\n ['Complete the verification at', {link: {url: retry.challengeUrl}}, 'then run the command again.'],\n ])\n}\n\ntype RequestAttempt<T> = {challenged: false; result: T} | {challenged: true; challengeUrl: string}\n\n/**\n * Runs the request once. A Trust Battery challenge becomes a result with the challenge URL;\n * any other error is thrown.\n */\nasync function attemptRequest<T>(request: () => Promise<T>): Promise<RequestAttempt<T>> {\n try {\n return {challenged: false, result: await request()}\n } catch (error) {\n const challengeUrl = await trustChallengeUrl(error)\n if (!challengeUrl) throw error\n return {challenged: true, challengeUrl}\n }\n}\n\n/**\n * Extracts the challenge URL from a Trust Battery `CHALLENGE_REQUIRED` GraphQL error.\n *\n * Only URLs on the Admin host are trusted. Anything else is not treated as a\n * challenge and the original error surfaces through normal error handling.\n *\n * @param error - The error thrown by an Admin API request.\n * @returns The challenge URL, or undefined when the error is not a trust challenge.\n */\nexport async function trustChallengeUrl(error: unknown): Promise<string | undefined> {\n const challengeError = extractGraphQLErrors(error).find(\n (graphQLError) => graphQLError.extensions?.code === TRUST_CHALLENGE_ERROR_CODE,\n )\n const redirectTo = challengeError?.extensions?.redirect_to\n if (typeof redirectTo !== 'string') return undefined\n\n if (!(await isAdminUrl(redirectTo))) {\n outputDebug(`Ignoring trust challenge with an unexpected URL host: ${redirectTo}`)\n return undefined\n }\n\n return redirectTo\n}\n\nasync function isAdminUrl(url: string): Promise<boolean> {\n try {\n const parsedUrl = new URL(url)\n return parsedUrl.protocol === 'https:' && parsedUrl.hostname === (await adminFqdn())\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch {\n return false\n }\n}\n\n/**\n * Concurrent theme requests (for example the parallel batches of `theme push`)\n * all receive the same challenge. Share one prompt so the browser opens once and\n * every waiting request retries after the same verification.\n */\nasync function completeTrustChallenge(challengeUrl: string): Promise<void> {\n pendingChallenge ??= promptForTrustChallenge(challengeUrl)\n .then(() => {\n completedChallenges += 1\n })\n .finally(() => {\n pendingChallenge = undefined\n })\n return pendingChallenge\n}\n\nasync function promptForTrustChallenge(challengeUrl: string): Promise<void> {\n recordEvent('theme-api:trust-challenge:required')\n\n if (isCI() || !isTTY() || commandEventOutputMode() === 'json') {\n recordEvent('theme-api:trust-challenge:non-interactive')\n throw new TrustChallengeError('Shopify needs to verify your identity before it can continue.', undefined, [\n [\n 'Open',\n {link: {url: challengeUrl}},\n \"in a browser where you're logged in to Shopify with the same account you use in Shopify CLI, complete the verification, then run the command again.\",\n ],\n ])\n }\n\n const opened = await openURL(challengeUrl)\n recordEvent(opened ? 'theme-api:trust-challenge:browser-opened' : 'theme-api:trust-challenge:browser-not-opened')\n\n // Everything the user needs is inside the banner: long-running commands such as\n // `theme push` redraw an Ink progress bar that erases any plain line written after it.\n renderInfo({\n headline: 'Verify your identity to continue.',\n body: opened\n ? 'A verification page opened in your browser.'\n : 'Shopify needs to verify your identity before it can continue.',\n link: opened ? undefined : {label: 'Open the verification page', url: challengeUrl},\n nextSteps: [\n ['Complete the verification in your browser, logged in with the same account you use in Shopify CLI.'],\n ['Press', {userInput: 'Enter'}, 'here once you have completed it.'],\n ],\n orderedNextSteps: true,\n })\n\n await keypress(process.stdin, undefined, {keys: CONTINUE_KEYS})\n\n recordEvent('theme-api:trust-challenge:acknowledged')\n renderSuccess({headline: 'Continuing.'})\n}\n"]}
|
|
@@ -12,7 +12,7 @@ const typeToLogLevel = {
|
|
|
12
12
|
};
|
|
13
13
|
export function alert(options) {
|
|
14
14
|
const { type, headline, body, nextSteps, reference, link, customSections, orderedNextSteps = false, renderOptions, } = options;
|
|
15
|
-
if ((type === 'info' || type === 'warning') && commandEventOutputMode() === 'json') {
|
|
15
|
+
if ((type === 'info' || type === 'warning' || type === 'success') && commandEventOutputMode() === 'json') {
|
|
16
16
|
const message = alertMessage(options);
|
|
17
17
|
if (type === 'warning')
|
|
18
18
|
outputWarn(message);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","sourceRoot":"","sources":["../../../../src/private/node/ui/alert.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAa,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAC,qBAAqB,EAAC,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAC,UAAU,EAAC,MAAM,UAAU,CAAA;AACnC,OAAO,EAAC,sBAAsB,EAAC,MAAM,6BAA6B,CAAA;AAClE,OAAO,EAAW,UAAU,EAAE,UAAU,EAAC,MAAM,gCAAgC,CAAA;AAC/E,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,MAAM,cAAc,GAAyC;IAC3D,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,MAAM;IACf,KAAK,EAAE,OAAO;CACf,CAAA;AAMD,MAAM,UAAU,KAAK,CAAC,OAAqB;IACzC,MAAM,EACJ,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,SAAS,EACT,IAAI,EACJ,cAAc,EACd,gBAAgB,GAAG,KAAK,EACxB,aAAa,GACd,GAAG,OAAO,CAAA;IAEX,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,SAAS,CAAC,IAAI,sBAAsB,EAAE,KAAK,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"alert.js","sourceRoot":"","sources":["../../../../src/private/node/ui/alert.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Alert, AlertProps} from './components/Alert.js'\nimport {tokenItemToJsonString} from './components/token-item.js'\nimport {renderOnce} from '../ui.js'\nimport {commandEventOutputMode} from '../command-event-context.js'\nimport {LogLevel, outputInfo, outputWarn} from '../../../public/node/output.js'\nimport React from 'react'\n\nimport {RenderOptions} from 'ink'\n\nconst typeToLogLevel: Record<AlertProps['type'], LogLevel> = {\n info: 'info',\n warning: 'warn',\n success: 'info',\n error: 'error',\n}\n\nexport interface AlertOptions extends AlertProps {\n renderOptions?: RenderOptions\n}\n\nexport function alert(options: AlertOptions) {\n const {\n type,\n headline,\n body,\n nextSteps,\n reference,\n link,\n customSections,\n orderedNextSteps = false,\n renderOptions,\n } = options\n\n if ((type === 'info' || type === 'warning' || type === 'success') && commandEventOutputMode() === 'json') {\n const message = alertMessage(options)\n if (type === 'warning') outputWarn(message)\n else outputInfo(message)\n return message\n }\n\n return renderOnce(\n <Alert\n type={type}\n headline={headline}\n body={body}\n nextSteps={nextSteps}\n reference={reference}\n link={link}\n orderedNextSteps={orderedNextSteps}\n customSections={customSections}\n />,\n {logLevel: typeToLogLevel[type], renderOptions},\n )\n}\n\nfunction alertMessage({headline, body, nextSteps, reference, link, customSections}: AlertProps): string {\n const sections = [\n headline ? tokenItemToJsonString(headline) : undefined,\n body ? tokenItemToJsonString(body) : undefined,\n nextSteps?.length ? `Next steps:\\n${nextSteps.map(tokenItemToJsonString).join('\\n')}` : undefined,\n reference?.length ? `Reference:\\n${reference.map(tokenItemToJsonString).join('\\n')}` : undefined,\n link ? tokenItemToJsonString({link}) : undefined,\n ...(customSections ?? []).map((section) => {\n const content =\n typeof section.body === 'object' && 'tabularData' in section.body\n ? section.body.tabularData.map((row) => row.map(tokenItemToJsonString).join('\\t')).join('\\n')\n : tokenItemToJsonString(section.body)\n return section.title ? `${section.title}:\\n${content}` : content\n }),\n ]\n return sections.filter((section): section is string => section !== undefined).join('\\n\\n')\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConcurrentOutputContext, getConcurrentOutputContext, useConcurrentOutputContext } from '../../output-context.js';
|
|
1
2
|
import { type OutputProcess } from '../../../../public/node/output.js';
|
|
2
3
|
import { AbortSignal } from '../../../../public/node/abort.js';
|
|
3
4
|
import { FunctionComponent } from 'react';
|
|
@@ -13,12 +14,6 @@ export interface ConcurrentOutputProps {
|
|
|
13
14
|
keepRunningAfterProcessesResolve?: boolean;
|
|
14
15
|
useAlternativeColorPalette?: boolean;
|
|
15
16
|
}
|
|
16
|
-
interface ConcurrentOutputContext {
|
|
17
|
-
outputPrefix?: string;
|
|
18
|
-
/** Controls ANSI stripping for terminal output. JSON diagnostics are always unstyled. */
|
|
19
|
-
stripAnsi?: boolean;
|
|
20
|
-
}
|
|
21
|
-
declare function useConcurrentOutputContext<T>(context: ConcurrentOutputContext, callback: () => T): T;
|
|
22
17
|
/** Runs finite processes concurrently and routes their output through the shared diagnostic context. */
|
|
23
18
|
export declare function runConcurrentProcessesForJson({ processes, abortSignal, }: Pick<ConcurrentOutputProps, 'processes' | 'abortSignal'>): Promise<void>;
|
|
24
19
|
/**
|
|
@@ -55,4 +50,4 @@ export declare function runConcurrentProcessesForJson({ processes, abortSignal,
|
|
|
55
50
|
* ```
|
|
56
51
|
*/
|
|
57
52
|
declare const ConcurrentOutput: FunctionComponent<ConcurrentOutputProps>;
|
|
58
|
-
export { ConcurrentOutput, ConcurrentOutputContext, useConcurrentOutputContext };
|
|
53
|
+
export { ConcurrentOutput, ConcurrentOutputContext, getConcurrentOutputContext, useConcurrentOutputContext };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getConcurrentOutputContext, useConcurrentOutputContext } from '../../output-context.js';
|
|
1
2
|
import { outputInfo } from '../../../../public/node/output.js';
|
|
2
3
|
import { useComplete } from '../../ui.js';
|
|
3
4
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
@@ -5,7 +6,6 @@ import { Box, Static, Text } from 'ink';
|
|
|
5
6
|
import figures from 'figures';
|
|
6
7
|
import stripAnsi from 'strip-ansi';
|
|
7
8
|
import { Writable } from 'stream';
|
|
8
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
9
|
import { StringDecoder } from 'node:string_decoder';
|
|
10
10
|
function addLeadingZero(number) {
|
|
11
11
|
if (number < 10) {
|
|
@@ -22,10 +22,6 @@ function currentTime() {
|
|
|
22
22
|
const seconds = addLeadingZero(currentDateTime.getSeconds());
|
|
23
23
|
return `${hours}:${minutes}:${seconds}`;
|
|
24
24
|
}
|
|
25
|
-
const outputContextStore = new AsyncLocalStorage();
|
|
26
|
-
function useConcurrentOutputContext(context, callback) {
|
|
27
|
-
return outputContextStore.run(context, callback);
|
|
28
|
-
}
|
|
29
25
|
/** Runs finite processes concurrently and routes their output through the shared diagnostic context. */
|
|
30
26
|
export async function runConcurrentProcessesForJson({ processes, abortSignal, }) {
|
|
31
27
|
await Promise.all(processes.map(async (process) => {
|
|
@@ -45,7 +41,7 @@ export async function runConcurrentProcessesForJson({ processes, abortSignal, })
|
|
|
45
41
|
next();
|
|
46
42
|
return;
|
|
47
43
|
}
|
|
48
|
-
const currentPrefix =
|
|
44
|
+
const currentPrefix = getConcurrentOutputContext()?.outputPrefix ?? process.prefix;
|
|
49
45
|
if (!hasPendingLine)
|
|
50
46
|
prefix = currentPrefix;
|
|
51
47
|
const lines = (pending + decoder.write(chunk)).split(/\r?\n/);
|
|
@@ -141,7 +137,7 @@ const ConcurrentOutput = ({ processes, prefixColumnSize, abortSignal, showTimest
|
|
|
141
137
|
const writableStream = useCallback((process, prefixes) => {
|
|
142
138
|
return new Writable({
|
|
143
139
|
write(chunk, _encoding, next) {
|
|
144
|
-
const context =
|
|
140
|
+
const context = getConcurrentOutputContext();
|
|
145
141
|
const prefix = context?.outputPrefix ?? process.prefix;
|
|
146
142
|
const shouldStripAnsi = context?.stripAnsi ?? true;
|
|
147
143
|
const log = chunk.toString('utf8').replace(/(\n)$/, '');
|
|
@@ -211,5 +207,5 @@ const ConcurrentOutput = ({ processes, prefixColumnSize, abortSignal, showTimest
|
|
|
211
207
|
line)))))));
|
|
212
208
|
}));
|
|
213
209
|
};
|
|
214
|
-
export { ConcurrentOutput, useConcurrentOutputContext };
|
|
210
|
+
export { ConcurrentOutput, getConcurrentOutputContext, useConcurrentOutputContext };
|
|
215
211
|
//# sourceMappingURL=ConcurrentOutput.js.map
|
|
@@ -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 {outputInfo, type OutputProcess} from '../../../../public/node/output.js'\nimport {AbortSignal} from '../../../../public/node/abort.js'\nimport {useComplete} from '../../ui.js'\nimport React, {FunctionComponent, useCallback, useEffect, useMemo, useState} from 'react'\nimport {Box, Static, Text, TextProps} from 'ink'\nimport figures from 'figures'\nimport stripAnsi from 'strip-ansi'\n\nimport {Writable} from 'stream'\nimport {AsyncLocalStorage} from 'node:async_hooks'\nimport {StringDecoder} from 'node:string_decoder'\n\nexport interface ConcurrentOutputProps {\n processes: OutputProcess[]\n prefixColumnSize?: number\n abortSignal: AbortSignal\n showTimestamps?: boolean\n /**\n * Keeps terminal UI running after all processes finish. Defaults to false.\n * In JSON mode, false uses finite progress/diagnostic events; true retains streaming terminal UI.\n */\n keepRunningAfterProcessesResolve?: boolean\n useAlternativeColorPalette?: boolean\n}\n\ninterface Chunk {\n color: TextProps['color']\n prefix: string\n lines: string[]\n}\n\nfunction addLeadingZero(number: number) {\n if (number < 10) {\n return `0${number}`\n } else {\n return number.toString()\n }\n}\n\nfunction currentTime() {\n const currentDateTime = new Date()\n const hours = addLeadingZero(currentDateTime.getHours())\n const minutes = addLeadingZero(currentDateTime.getMinutes())\n const seconds = addLeadingZero(currentDateTime.getSeconds())\n return `${hours}:${minutes}:${seconds}`\n}\n\ninterface ConcurrentOutputContext {\n outputPrefix?: string\n /** Controls ANSI stripping for terminal output. JSON diagnostics are always unstyled. */\n stripAnsi?: boolean\n}\n\nconst outputContextStore = new AsyncLocalStorage<ConcurrentOutputContext>()\n\nfunction useConcurrentOutputContext<T>(context: ConcurrentOutputContext, callback: () => T): T {\n return outputContextStore.run(context, callback)\n}\n\n/** Runs finite processes concurrently and routes their output through the shared diagnostic context. */\nexport async function runConcurrentProcessesForJson({\n processes,\n abortSignal,\n}: Pick<ConcurrentOutputProps, 'processes' | 'abortSignal'>): Promise<void> {\n await Promise.all(\n processes.map(async (process) => {\n const createStream = () => {\n const decoder = new StringDecoder('utf8')\n let pending = ''\n let hasPendingLine = false\n let prefix = process.prefix\n const emitLine = (line: string) => {\n const message = stripAnsi(line)\n if (message.trim().length > 0) outputInfo(`${prefix}: ${message}`)\n }\n const stream = new Writable({\n write(chunk, _encoding, next) {\n if (chunk.length === 0) {\n next()\n return\n }\n const currentPrefix = outputContextStore.getStore()?.outputPrefix ?? process.prefix\n if (!hasPendingLine) prefix = currentPrefix\n const lines = (pending + decoder.write(chunk)).split(/\\r?\\n/)\n pending = lines.pop() ?? ''\n for (const line of lines) {\n emitLine(line)\n prefix = currentPrefix\n }\n hasPendingLine = chunk[chunk.length - 1] !== 10\n next()\n },\n })\n return {\n stream,\n flush: () => {\n emitLine((pending + decoder.end()).replace(/\\r$/, ''))\n pending = ''\n hasPendingLine = false\n },\n }\n }\n const stdout = createStream()\n const stderr = createStream()\n try {\n await process.action(stdout.stream, stderr.stream, abortSignal)\n } finally {\n stdout.flush()\n stderr.flush()\n }\n }),\n )\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 |\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<ConcurrentOutputProps> = ({\n processes,\n prefixColumnSize,\n abortSignal,\n showTimestamps = true,\n keepRunningAfterProcessesResolve = false,\n useAlternativeColorPalette = false,\n}) => {\n const [processOutput, setProcessOutput] = useState<Chunk[]>([])\n const [completionResult, setCompletionResult] = useState<{error?: Error} | null>(null)\n const complete = useComplete()\n const concurrentColors: TextProps['color'][] = useMemo(\n () =>\n useAlternativeColorPalette\n ? ['#b994c3', '#e69e19', '#d17a73', 'cyan', 'magenta', 'blue']\n : ['yellow', 'cyan', 'magenta', 'green', 'blue'],\n [useAlternativeColorPalette],\n )\n\n const calculatedPrefixColumnSize = useMemo(() => {\n const maxColumnSize = 25\n\n // If the prefixColumnSize is not provided, we calculate it based on the longest process prefix\n const columnSize =\n prefixColumnSize ??\n processes.reduce((maxPrefixLength, process) => Math.max(maxPrefixLength, process.prefix.length), 0)\n\n // Apply overall limit to the prefix column size\n return Math.min(columnSize, maxColumnSize)\n }, [processes, prefixColumnSize])\n\n const addPrefix = (prefix: string, prefixes: string[]) => {\n const index = prefixes.indexOf(prefix)\n if (index !== -1) {\n return index\n }\n prefixes.push(prefix)\n return prefixes.length - 1\n }\n\n const lineColor = useCallback(\n (index: number) => {\n const colorIndex = index % concurrentColors.length\n return concurrentColors[colorIndex]\n },\n [concurrentColors],\n )\n\n const writableStream = useCallback(\n (process: OutputProcess, prefixes: string[]) => {\n return new Writable({\n write(chunk, _encoding, next) {\n const context = outputContextStore.getStore()\n const prefix = context?.outputPrefix ?? process.prefix\n const shouldStripAnsi = context?.stripAnsi ?? true\n const log = chunk.toString('utf8').replace(/(\\n)$/, '')\n\n const index = addPrefix(prefix, prefixes)\n\n const lines = shouldStripAnsi ? stripAnsi(log).split(/\\n/) : log.split(/\\n/)\n setProcessOutput((previousProcessOutput) => [\n ...previousProcessOutput,\n {\n color: lineColor(index),\n prefix,\n lines,\n },\n ])\n next()\n },\n })\n },\n [lineColor],\n )\n\n const formatPrefix = (prefix: string) => {\n // Truncate prefix if needed\n if (prefix.length > calculatedPrefixColumnSize) {\n return prefix.substring(0, calculatedPrefixColumnSize)\n }\n\n return `${' '.repeat(calculatedPrefixColumnSize - prefix.length)}${prefix}`\n }\n\n useEffect(() => {\n const runProcesses = async () => {\n const prefixes: string[] = []\n\n try {\n await Promise.all(\n processes.map(async (process) => {\n const stdout = writableStream(process, prefixes)\n const stderr = writableStream(process, prefixes)\n await process.action(stdout, stderr, abortSignal)\n }),\n )\n if (!keepRunningAfterProcessesResolve) {\n setCompletionResult({})\n }\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error: unknown) {\n if (!keepRunningAfterProcessesResolve) {\n setCompletionResult({error: error as Error})\n }\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n runProcesses()\n }, [abortSignal, processes, writableStream, keepRunningAfterProcessesResolve])\n\n useEffect(() => {\n if (completionResult !== null) {\n complete(completionResult.error)\n }\n }, [completionResult, complete])\n\n const {lineVertical} = figures\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 <Text>\n {showTimestamps ? (\n <Text>\n {currentTime()} {lineVertical}{' '}\n </Text>\n ) : null}\n <Text color={chunk.color}>{formatPrefix(chunk.prefix)}</Text>\n <Text>\n {' '}\n {lineVertical} {line}\n </Text>\n </Text>\n </Box>\n ))}\n </Box>\n )\n }}\n </Static>\n )\n}\nexport {ConcurrentOutput, ConcurrentOutputContext, useConcurrentOutputContext}\n"]}
|
|
1
|
+
{"version":3,"file":"ConcurrentOutput.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/ConcurrentOutput.tsx"],"names":[],"mappings":"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","sourcesContent":["import {ConcurrentOutputContext, getConcurrentOutputContext, useConcurrentOutputContext} from '../../output-context.js'\nimport {outputInfo, type OutputProcess} from '../../../../public/node/output.js'\nimport {AbortSignal} from '../../../../public/node/abort.js'\nimport {useComplete} from '../../ui.js'\nimport React, {FunctionComponent, useCallback, useEffect, useMemo, useState} from 'react'\nimport {Box, Static, Text, TextProps} from 'ink'\nimport figures from 'figures'\nimport stripAnsi from 'strip-ansi'\n\nimport {Writable} from 'stream'\nimport {StringDecoder} from 'node:string_decoder'\n\nexport interface ConcurrentOutputProps {\n processes: OutputProcess[]\n prefixColumnSize?: number\n abortSignal: AbortSignal\n showTimestamps?: boolean\n /**\n * Keeps terminal UI running after all processes finish. Defaults to false.\n * In JSON mode, false uses finite progress/diagnostic events; true retains streaming terminal UI.\n */\n keepRunningAfterProcessesResolve?: boolean\n useAlternativeColorPalette?: boolean\n}\n\ninterface Chunk {\n color: TextProps['color']\n prefix: string\n lines: string[]\n}\n\nfunction addLeadingZero(number: number) {\n if (number < 10) {\n return `0${number}`\n } else {\n return number.toString()\n }\n}\n\nfunction currentTime() {\n const currentDateTime = new Date()\n const hours = addLeadingZero(currentDateTime.getHours())\n const minutes = addLeadingZero(currentDateTime.getMinutes())\n const seconds = addLeadingZero(currentDateTime.getSeconds())\n return `${hours}:${minutes}:${seconds}`\n}\n\n/** Runs finite processes concurrently and routes their output through the shared diagnostic context. */\nexport async function runConcurrentProcessesForJson({\n processes,\n abortSignal,\n}: Pick<ConcurrentOutputProps, 'processes' | 'abortSignal'>): Promise<void> {\n await Promise.all(\n processes.map(async (process) => {\n const createStream = () => {\n const decoder = new StringDecoder('utf8')\n let pending = ''\n let hasPendingLine = false\n let prefix = process.prefix\n const emitLine = (line: string) => {\n const message = stripAnsi(line)\n if (message.trim().length > 0) outputInfo(`${prefix}: ${message}`)\n }\n const stream = new Writable({\n write(chunk, _encoding, next) {\n if (chunk.length === 0) {\n next()\n return\n }\n const currentPrefix = getConcurrentOutputContext()?.outputPrefix ?? process.prefix\n if (!hasPendingLine) prefix = currentPrefix\n const lines = (pending + decoder.write(chunk)).split(/\\r?\\n/)\n pending = lines.pop() ?? ''\n for (const line of lines) {\n emitLine(line)\n prefix = currentPrefix\n }\n hasPendingLine = chunk[chunk.length - 1] !== 10\n next()\n },\n })\n return {\n stream,\n flush: () => {\n emitLine((pending + decoder.end()).replace(/\\r$/, ''))\n pending = ''\n hasPendingLine = false\n },\n }\n }\n const stdout = createStream()\n const stderr = createStream()\n try {\n await process.action(stdout.stream, stderr.stream, abortSignal)\n } finally {\n stdout.flush()\n stderr.flush()\n }\n }),\n )\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 |\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<ConcurrentOutputProps> = ({\n processes,\n prefixColumnSize,\n abortSignal,\n showTimestamps = true,\n keepRunningAfterProcessesResolve = false,\n useAlternativeColorPalette = false,\n}) => {\n const [processOutput, setProcessOutput] = useState<Chunk[]>([])\n const [completionResult, setCompletionResult] = useState<{error?: Error} | null>(null)\n const complete = useComplete()\n const concurrentColors: TextProps['color'][] = useMemo(\n () =>\n useAlternativeColorPalette\n ? ['#b994c3', '#e69e19', '#d17a73', 'cyan', 'magenta', 'blue']\n : ['yellow', 'cyan', 'magenta', 'green', 'blue'],\n [useAlternativeColorPalette],\n )\n\n const calculatedPrefixColumnSize = useMemo(() => {\n const maxColumnSize = 25\n\n // If the prefixColumnSize is not provided, we calculate it based on the longest process prefix\n const columnSize =\n prefixColumnSize ??\n processes.reduce((maxPrefixLength, process) => Math.max(maxPrefixLength, process.prefix.length), 0)\n\n // Apply overall limit to the prefix column size\n return Math.min(columnSize, maxColumnSize)\n }, [processes, prefixColumnSize])\n\n const addPrefix = (prefix: string, prefixes: string[]) => {\n const index = prefixes.indexOf(prefix)\n if (index !== -1) {\n return index\n }\n prefixes.push(prefix)\n return prefixes.length - 1\n }\n\n const lineColor = useCallback(\n (index: number) => {\n const colorIndex = index % concurrentColors.length\n return concurrentColors[colorIndex]\n },\n [concurrentColors],\n )\n\n const writableStream = useCallback(\n (process: OutputProcess, prefixes: string[]) => {\n return new Writable({\n write(chunk, _encoding, next) {\n const context = getConcurrentOutputContext()\n const prefix = context?.outputPrefix ?? process.prefix\n const shouldStripAnsi = context?.stripAnsi ?? true\n const log = chunk.toString('utf8').replace(/(\\n)$/, '')\n\n const index = addPrefix(prefix, prefixes)\n\n const lines = shouldStripAnsi ? stripAnsi(log).split(/\\n/) : log.split(/\\n/)\n setProcessOutput((previousProcessOutput) => [\n ...previousProcessOutput,\n {\n color: lineColor(index),\n prefix,\n lines,\n },\n ])\n next()\n },\n })\n },\n [lineColor],\n )\n\n const formatPrefix = (prefix: string) => {\n // Truncate prefix if needed\n if (prefix.length > calculatedPrefixColumnSize) {\n return prefix.substring(0, calculatedPrefixColumnSize)\n }\n\n return `${' '.repeat(calculatedPrefixColumnSize - prefix.length)}${prefix}`\n }\n\n useEffect(() => {\n const runProcesses = async () => {\n const prefixes: string[] = []\n\n try {\n await Promise.all(\n processes.map(async (process) => {\n const stdout = writableStream(process, prefixes)\n const stderr = writableStream(process, prefixes)\n await process.action(stdout, stderr, abortSignal)\n }),\n )\n if (!keepRunningAfterProcessesResolve) {\n setCompletionResult({})\n }\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error: unknown) {\n if (!keepRunningAfterProcessesResolve) {\n setCompletionResult({error: error as Error})\n }\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n runProcesses()\n }, [abortSignal, processes, writableStream, keepRunningAfterProcessesResolve])\n\n useEffect(() => {\n if (completionResult !== null) {\n complete(completionResult.error)\n }\n }, [completionResult, complete])\n\n const {lineVertical} = figures\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 <Text>\n {showTimestamps ? (\n <Text>\n {currentTime()} {lineVertical}{' '}\n </Text>\n ) : null}\n <Text color={chunk.color}>{formatPrefix(chunk.prefix)}</Text>\n <Text>\n {' '}\n {lineVertical} {line}\n </Text>\n </Text>\n </Box>\n ))}\n </Box>\n )\n }}\n </Static>\n )\n}\nexport {ConcurrentOutput, ConcurrentOutputContext, getConcurrentOutputContext, useConcurrentOutputContext}\n"]}
|
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import useLayout from '../hooks/use-layout.js';
|
|
1
|
+
import { LoadingIndicator } from './LoadingIndicator.js';
|
|
3
2
|
import { shouldDisplayColors } from '../../../../public/node/output.js';
|
|
4
3
|
import React from 'react';
|
|
5
|
-
import {
|
|
6
|
-
const loadingBarChar = '▀';
|
|
7
|
-
const hillString = '▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁';
|
|
4
|
+
import { Text, useStdout } from 'ink';
|
|
8
5
|
const LoadingBar = ({ title, noColor, noProgressBar }) => {
|
|
9
|
-
const { twoThirds } = useLayout();
|
|
10
6
|
const { stdout } = useStdout();
|
|
11
7
|
// On real Node streams, isTTY is only present as an own property when the
|
|
12
8
|
// stream IS a TTY. When Ink's output stream is not a TTY (e.g. AI agents
|
|
13
|
-
// capturing stderr via 2>&1), the animated
|
|
9
|
+
// capturing stderr via 2>&1), the animated loading indicator can't overwrite
|
|
14
10
|
// previous frames and would flood the output. Show only the static title
|
|
15
11
|
// in that case.
|
|
16
12
|
const isTTY = Boolean(stdout.isTTY);
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
return (React.createElement(Box, { flexDirection: "column" },
|
|
22
|
-
isTTY && !noProgressBar && React.createElement(TextAnimation, { text: loadingBar, maxWidth: twoThirds }),
|
|
23
|
-
React.createElement(Text, null,
|
|
13
|
+
const colorsDisabled = noColor ?? !shouldDisplayColors();
|
|
14
|
+
if (!isTTY || noProgressBar) {
|
|
15
|
+
return React.createElement(Text, null,
|
|
24
16
|
title,
|
|
25
|
-
" ...")
|
|
17
|
+
" ...");
|
|
18
|
+
}
|
|
19
|
+
return React.createElement(LoadingIndicator, { title: title, noColor: colorsDisabled });
|
|
26
20
|
};
|
|
27
21
|
export { LoadingBar };
|
|
28
22
|
//# sourceMappingURL=LoadingBar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingBar.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/LoadingBar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"LoadingBar.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/LoadingBar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAC,mBAAmB,EAAC,MAAM,mCAAmC,CAAA;AACrE,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAC,IAAI,EAAE,SAAS,EAAC,MAAM,KAAK,CAAA;AAQnC,MAAM,UAAU,GAAG,CAAC,EAAC,KAAK,EAAE,OAAO,EAAE,aAAa,EAA2C,EAAE,EAAE;IAC/F,MAAM,EAAC,MAAM,EAAC,GAAG,SAAS,EAAE,CAAA;IAE5B,0EAA0E;IAC1E,0EAA0E;IAC1E,6EAA6E;IAC7E,0EAA0E;IAC1E,gBAAgB;IAChB,MAAM,KAAK,GAAG,OAAO,CAAE,MAA6C,CAAC,KAAK,CAAC,CAAA;IAC3E,MAAM,cAAc,GAAG,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAA;IAExD,IAAI,CAAC,KAAK,IAAI,aAAa,EAAE,CAAC;QAC5B,OAAO,oBAAC,IAAI;YAAE,KAAK;mBAAY,CAAA;IACjC,CAAC;IAED,OAAO,oBAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,GAAI,CAAA;AACpE,CAAC,CAAA;AAED,OAAO,EAAC,UAAU,EAAC,CAAA","sourcesContent":["import {LoadingIndicator} from './LoadingIndicator.js'\nimport {shouldDisplayColors} from '../../../../public/node/output.js'\nimport React from 'react'\n\nimport {Text, useStdout} from 'ink'\n\ninterface LoadingBarProps {\n title: string\n noColor?: boolean\n noProgressBar?: boolean\n}\n\nconst LoadingBar = ({title, noColor, noProgressBar}: React.PropsWithChildren<LoadingBarProps>) => {\n const {stdout} = useStdout()\n\n // On real Node streams, isTTY is only present as an own property when the\n // stream IS a TTY. When Ink's output stream is not a TTY (e.g. AI agents\n // capturing stderr via 2>&1), the animated loading indicator can't overwrite\n // previous frames and would flood the output. Show only the static title\n // in that case.\n const isTTY = Boolean((stdout as unknown as Record<string, unknown>).isTTY)\n const colorsDisabled = noColor ?? !shouldDisplayColors()\n\n if (!isTTY || noProgressBar) {\n return <Text>{title} ...</Text>\n }\n\n return <LoadingIndicator title={title} noColor={colorsDisabled} />\n}\n\nexport {LoadingBar}\n"]}
|
|
@@ -2,10 +2,9 @@ import { LoadingBar } from './LoadingBar.js';
|
|
|
2
2
|
import { Stdout } from '../../ui.js';
|
|
3
3
|
import { render } from '../../testing/ui.js';
|
|
4
4
|
import { shouldDisplayColors, unstyled } from '../../../../public/node/output.js';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import React, { act } from 'react';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
7
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
8
|
-
vi.mock('../hooks/use-layout.js');
|
|
9
8
|
vi.mock('../../../../public/node/output.js', async () => {
|
|
10
9
|
const original = await vi.importActual('../../../../public/node/output.js');
|
|
11
10
|
return {
|
|
@@ -14,130 +13,69 @@ vi.mock('../../../../public/node/output.js', async () => {
|
|
|
14
13
|
};
|
|
15
14
|
});
|
|
16
15
|
beforeEach(() => {
|
|
17
|
-
vi.mocked(useLayout).mockReturnValue({
|
|
18
|
-
twoThirds: 53,
|
|
19
|
-
oneThird: 27,
|
|
20
|
-
fullWidth: 80,
|
|
21
|
-
});
|
|
22
16
|
vi.mocked(shouldDisplayColors).mockReturnValue(true);
|
|
23
17
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* On real Node streams, isTTY is only present as an own property when the
|
|
27
|
-
* stream IS a TTY.
|
|
28
|
-
*/
|
|
29
|
-
function createTTYStdout(columns = 100) {
|
|
30
|
-
const stdout = new Stdout({ columns });
|
|
18
|
+
function createTTYStdout() {
|
|
19
|
+
const stdout = new Stdout({ columns: 100 });
|
|
31
20
|
stdout.isTTY = true;
|
|
32
21
|
return stdout;
|
|
33
22
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Renders LoadingBar with a TTY stdout so the animated progress bar renders.
|
|
36
|
-
*/
|
|
37
23
|
function renderWithTTY(element) {
|
|
38
24
|
const stdout = createTTYStdout();
|
|
39
25
|
const instance = render(element, { stdout });
|
|
40
26
|
return { lastFrame: stdout.lastFrame, unmount: instance.unmount };
|
|
41
27
|
}
|
|
42
28
|
describe('LoadingBar', () => {
|
|
43
|
-
test('renders
|
|
44
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Loading content" }));
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Processing files", noColor: true }));
|
|
52
|
-
expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
|
|
53
|
-
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅
|
|
54
|
-
Processing files ..."
|
|
55
|
-
`);
|
|
56
|
-
});
|
|
57
|
-
test('renders loading bar with hill pattern when shouldDisplayColors returns false', async () => {
|
|
58
|
-
vi.mocked(shouldDisplayColors).mockReturnValue(false);
|
|
59
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Downloading packages" }));
|
|
60
|
-
expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
|
|
61
|
-
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅
|
|
62
|
-
Downloading packages ..."
|
|
63
|
-
`);
|
|
64
|
-
});
|
|
65
|
-
test('handles narrow terminal width correctly', async () => {
|
|
66
|
-
vi.mocked(useLayout).mockReturnValue({ twoThirds: 20, oneThird: 10, fullWidth: 30 });
|
|
67
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Building app" }));
|
|
68
|
-
expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
|
|
69
|
-
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
70
|
-
Building app ..."
|
|
71
|
-
`);
|
|
29
|
+
test('renders the Shopify loading indicator', async () => {
|
|
30
|
+
const { lastFrame, unmount } = renderWithTTY(React.createElement(LoadingBar, { title: "Loading content" }));
|
|
31
|
+
const frame = lastFrame();
|
|
32
|
+
expect(unstyled(frame)).toBe('S> Loading content ...');
|
|
33
|
+
expect(frame).toContain('\u001B[1m');
|
|
34
|
+
expect(frame).toContain('\u001B[3m');
|
|
35
|
+
expect(frame).toContain(chalk.hex('#96BF48')('>'));
|
|
36
|
+
unmount();
|
|
72
37
|
});
|
|
73
|
-
test('
|
|
74
|
-
vi.
|
|
75
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
38
|
+
test('blinks the chevron without shifting the title', async () => {
|
|
39
|
+
vi.useFakeTimers();
|
|
40
|
+
const { lastFrame, unmount } = renderWithTTY(React.createElement(LoadingBar, { title: "Uploading theme" }));
|
|
41
|
+
try {
|
|
42
|
+
await act(async () => {
|
|
43
|
+
await vi.advanceTimersByTimeAsync(350);
|
|
44
|
+
});
|
|
45
|
+
expect(unstyled(lastFrame())).toBe('S Uploading theme ...');
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
unmount();
|
|
49
|
+
vi.useRealTimers();
|
|
50
|
+
}
|
|
80
51
|
});
|
|
81
|
-
test('
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
expect(unstyled(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`);
|
|
52
|
+
test('renders the chevron without color when noColor is true', async () => {
|
|
53
|
+
const { lastFrame, unmount } = renderWithTTY(React.createElement(LoadingBar, { title: "Processing files", noColor: true }));
|
|
54
|
+
const frame = lastFrame();
|
|
55
|
+
expect(unstyled(frame)).toBe('S> Processing files ...');
|
|
56
|
+
expect(frame).not.toContain(chalk.hex('#96BF48')('>'));
|
|
57
|
+
unmount();
|
|
88
58
|
});
|
|
89
|
-
test('
|
|
90
|
-
vi.mocked(
|
|
91
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
test('handles wide terminal width correctly in no-color mode with pattern repetition', async () => {
|
|
98
|
-
vi.mocked(useLayout).mockReturnValue({ twoThirds: 90, oneThird: 45, fullWidth: 135 });
|
|
99
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Analyzing dependencies", noColor: true }));
|
|
100
|
-
expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
|
|
101
|
-
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁
|
|
102
|
-
Analyzing dependencies ..."
|
|
103
|
-
`);
|
|
104
|
-
});
|
|
105
|
-
test('renders correctly with empty title', async () => {
|
|
106
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "" }));
|
|
107
|
-
expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
|
|
108
|
-
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
109
|
-
..."
|
|
110
|
-
`);
|
|
111
|
-
});
|
|
112
|
-
test('noColor prop overrides shouldDisplayColors when both would show colors', async () => {
|
|
113
|
-
vi.mocked(shouldDisplayColors).mockReturnValue(true);
|
|
114
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Testing override", noColor: true }));
|
|
115
|
-
expect(unstyled(lastFrame())).toMatchInlineSnapshot(`
|
|
116
|
-
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅
|
|
117
|
-
Testing override ..."
|
|
118
|
-
`);
|
|
59
|
+
test('renders the chevron without color when colors are disabled', async () => {
|
|
60
|
+
vi.mocked(shouldDisplayColors).mockReturnValue(false);
|
|
61
|
+
const { lastFrame, unmount } = renderWithTTY(React.createElement(LoadingBar, { title: "Downloading packages" }));
|
|
62
|
+
const frame = lastFrame();
|
|
63
|
+
expect(unstyled(frame)).toBe('S> Downloading packages ...');
|
|
64
|
+
expect(frame).not.toContain(chalk.hex('#96BF48')('>'));
|
|
65
|
+
unmount();
|
|
119
66
|
});
|
|
120
|
-
test('renders
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
expect(frame1()).toBe(frame2());
|
|
67
|
+
test('renders correctly with an empty title', async () => {
|
|
68
|
+
const { lastFrame, unmount } = renderWithTTY(React.createElement(LoadingBar, { title: "" }));
|
|
69
|
+
expect(unstyled(lastFrame())).toBe('S> ...');
|
|
70
|
+
unmount();
|
|
125
71
|
});
|
|
126
|
-
test('hides
|
|
127
|
-
vi.mocked(shouldDisplayColors).mockReturnValue(true);
|
|
72
|
+
test('hides the loading indicator when noProgressBar is true', async () => {
|
|
128
73
|
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "task 1", noProgressBar: true }));
|
|
129
|
-
expect(unstyled(lastFrame())).
|
|
74
|
+
expect(unstyled(lastFrame())).toBe('task 1 ...');
|
|
130
75
|
});
|
|
131
76
|
test('shows only static title text when output stream is not a TTY', async () => {
|
|
132
|
-
// Default test Stdout has no isTTY property, simulating a non-TTY stream
|
|
133
77
|
const { lastFrame } = render(React.createElement(LoadingBar, { title: "Installing dependencies" }));
|
|
134
|
-
expect(unstyled(lastFrame())).
|
|
135
|
-
});
|
|
136
|
-
test('shows animated progress bar when output stream is a TTY', async () => {
|
|
137
|
-
const { lastFrame } = renderWithTTY(React.createElement(LoadingBar, { title: "Uploading theme" }));
|
|
138
|
-
const frame = unstyled(lastFrame());
|
|
139
|
-
expect(frame).toContain('▀');
|
|
140
|
-
expect(frame).toContain('Uploading theme ...');
|
|
78
|
+
expect(unstyled(lastFrame())).toBe('Installing dependencies ...');
|
|
141
79
|
});
|
|
142
80
|
});
|
|
143
81
|
//# sourceMappingURL=LoadingBar.test.js.map
|