@shopify/cli-kit 4.8.5 → 4.9.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/private/node/analytics.js +5 -3
- package/dist/private/node/analytics.js.map +1 -1
- package/dist/private/node/api/status-checks.d.ts +7 -0
- package/dist/private/node/api/status-checks.js +11 -0
- package/dist/private/node/api/status-checks.js.map +1 -0
- package/dist/private/node/api.d.ts +15 -0
- package/dist/private/node/api.js +28 -0
- package/dist/private/node/api.js.map +1 -1
- package/dist/private/node/command-event-context.d.ts +29 -0
- package/dist/private/node/command-event-context.js +43 -0
- package/dist/private/node/command-event-context.js.map +1 -0
- package/dist/private/node/command-event-output.d.ts +7 -0
- package/dist/private/node/command-event-output.js +16 -0
- package/dist/private/node/command-event-output.js.map +1 -0
- package/dist/private/node/command-json-schema.d.ts +10 -0
- package/dist/private/node/command-json-schema.js +38 -0
- package/dist/private/node/command-json-schema.js.map +1 -0
- package/dist/private/node/context/agent.d.ts +1 -0
- package/dist/private/node/context/agent.js +44 -0
- package/dist/private/node/context/agent.js.map +1 -0
- package/dist/private/node/json-error.d.ts +23 -0
- package/dist/private/node/json-error.js +123 -0
- package/dist/private/node/json-error.js.map +1 -0
- package/dist/private/node/output.js +3 -1
- package/dist/private/node/output.js.map +1 -1
- package/dist/private/node/output.test.d.ts +1 -0
- package/dist/private/node/output.test.js +74 -0
- package/dist/private/node/output.test.js.map +1 -0
- package/dist/private/node/session/device-authorization.js +49 -15
- package/dist/private/node/session/device-authorization.js.map +1 -1
- package/dist/private/node/session/validate.js +9 -22
- package/dist/private/node/session/validate.js.map +1 -1
- package/dist/private/node/ui/alert.d.ts +1 -1
- package/dist/private/node/ui/alert.js +29 -3
- package/dist/private/node/ui/alert.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.d.ts +8 -1
- package/dist/private/node/ui/components/ConcurrentOutput.js +54 -0
- package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
- package/dist/private/node/ui/components/DangerousConfirmationPrompt.js +3 -3
- package/dist/private/node/ui/components/DangerousConfirmationPrompt.js.map +1 -1
- package/dist/private/node/ui/components/DangerousConfirmationPrompt.test.js +16 -1
- package/dist/private/node/ui/components/DangerousConfirmationPrompt.test.js.map +1 -1
- package/dist/private/node/ui/components/Prompts/PromptLayout.js +3 -2
- package/dist/private/node/ui/components/Prompts/PromptLayout.js.map +1 -1
- package/dist/private/node/ui/components/Prompts/PromptLayout.test.js +20 -1
- package/dist/private/node/ui/components/Prompts/PromptLayout.test.js.map +1 -1
- package/dist/private/node/ui/components/Tasks.d.ts +2 -9
- package/dist/private/node/ui/components/Tasks.js +5 -44
- package/dist/private/node/ui/components/Tasks.js.map +1 -1
- package/dist/private/node/ui/components/TextPrompt.js +3 -3
- package/dist/private/node/ui/components/TextPrompt.js.map +1 -1
- package/dist/private/node/ui/components/TextPrompt.test.js +15 -1
- package/dist/private/node/ui/components/TextPrompt.test.js.map +1 -1
- package/dist/private/node/ui/components/token-item.d.ts +2 -0
- package/dist/private/node/ui/components/token-item.js +26 -0
- package/dist/private/node/ui/components/token-item.js.map +1 -1
- package/dist/private/node/ui/hooks/use-async-and-unmount.d.ts +3 -3
- package/dist/private/node/ui/hooks/use-async-and-unmount.js +2 -2
- package/dist/private/node/ui/hooks/use-async-and-unmount.js.map +1 -1
- package/dist/private/node/ui/tasks.d.ts +10 -0
- package/dist/private/node/ui/tasks.js +68 -0
- package/dist/private/node/ui/tasks.js.map +1 -0
- package/dist/public/common/command-events.d.ts +125 -0
- package/dist/public/common/command-events.js +54 -0
- package/dist/public/common/command-events.js.map +1 -0
- package/dist/public/common/json-output-schema.d.ts +10 -0
- package/dist/public/common/json-output-schema.js +17 -0
- package/dist/public/common/json-output-schema.js.map +1 -0
- 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 +18 -1
- package/dist/public/node/api/admin.js.map +1 -1
- package/dist/public/node/api/app-management.d.ts +1 -0
- package/dist/public/node/api/app-management.js +4 -0
- package/dist/public/node/api/app-management.js.map +1 -1
- package/dist/public/node/api/bulk-operations/download-bulk-operation-results.d.ts +3 -3
- package/dist/public/node/api/bulk-operations/download-bulk-operation-results.js +5 -3
- package/dist/public/node/api/bulk-operations/download-bulk-operation-results.js.map +1 -1
- package/dist/public/node/base-command.d.ts +6 -0
- package/dist/public/node/base-command.js +34 -5
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/cli-launcher.js +20 -2
- package/dist/public/node/cli-launcher.js.map +1 -1
- package/dist/public/node/cli.d.ts +1 -0
- package/dist/public/node/cli.js +2 -0
- package/dist/public/node/cli.js.map +1 -1
- package/dist/public/node/command-events.d.ts +65 -0
- package/dist/public/node/command-events.js +71 -0
- package/dist/public/node/command-events.js.map +1 -0
- package/dist/public/node/context/local.d.ts +6 -0
- package/dist/public/node/context/local.js +10 -1
- package/dist/public/node/context/local.js.map +1 -1
- package/dist/public/node/custom-oclif-loader.d.ts +6 -0
- package/dist/public/node/custom-oclif-loader.js +45 -19
- package/dist/public/node/custom-oclif-loader.js.map +1 -1
- package/dist/public/node/environment.d.ts +2 -1
- package/dist/public/node/environment.js +3 -2
- package/dist/public/node/environment.js.map +1 -1
- package/dist/public/node/error/index.d.ts +91 -0
- package/dist/public/node/error/index.js +266 -0
- package/dist/public/node/error/index.js.map +1 -0
- package/dist/public/node/error/schema.d.ts +472 -0
- package/dist/public/node/error/schema.js +54 -0
- package/dist/public/node/error/schema.js.map +1 -0
- package/dist/public/node/error/types.d.ts +9 -0
- package/dist/public/node/error/types.js +2 -0
- package/dist/public/node/error/types.js.map +1 -0
- package/dist/public/node/error.d.ts +1 -87
- package/dist/public/node/error.js +1 -252
- package/dist/public/node/error.js.map +1 -1
- package/dist/public/node/fs.d.ts +17 -3
- package/dist/public/node/fs.js +7 -5
- package/dist/public/node/fs.js.map +1 -1
- package/dist/public/node/json-output-schema.d.ts +26 -0
- package/dist/public/node/json-output-schema.js +30 -0
- package/dist/public/node/json-output-schema.js.map +1 -0
- package/dist/public/node/local-storage.d.ts +1 -0
- package/dist/public/node/local-storage.js +54 -21
- package/dist/public/node/local-storage.js.map +1 -1
- package/dist/public/node/monorail.d.ts +3 -1
- package/dist/public/node/monorail.js +1 -1
- package/dist/public/node/monorail.js.map +1 -1
- package/dist/public/node/multiple-installation-warning.d.ts +1 -1
- package/dist/public/node/multiple-installation-warning.js +2 -3
- package/dist/public/node/multiple-installation-warning.js.map +1 -1
- package/dist/public/node/no-input.d.ts +11 -0
- package/dist/public/node/no-input.js +19 -0
- package/dist/public/node/no-input.js.map +1 -0
- package/dist/public/node/output.d.ts +6 -0
- package/dist/public/node/output.js +40 -0
- package/dist/public/node/output.js.map +1 -1
- package/dist/public/node/path.js +3 -1
- package/dist/public/node/path.js.map +1 -1
- package/dist/public/node/system.d.ts +2 -2
- package/dist/public/node/system.js +8 -4
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/testing/output.d.ts +30 -0
- package/dist/public/node/testing/output.js +83 -0
- package/dist/public/node/testing/output.js.map +1 -1
- package/dist/public/node/ui.d.ts +5 -1
- package/dist/public/node/ui.js +89 -5
- package/dist/public/node/ui.js.map +1 -1
- package/dist/public/node/upgrade.js +17 -2
- package/dist/public/node/upgrade.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { tokenItemToJsonString } from './ui/components/token-item.js';
|
|
2
|
+
import { FatalErrorType } from '../../public/node/error.js';
|
|
3
|
+
import { jsonErrorOutputSchema } from '../../public/node/error/schema.js';
|
|
4
|
+
import { outputResult, unstyled } from '../../public/node/output.js';
|
|
5
|
+
function externalCommand(error) {
|
|
6
|
+
if (typeof error.command !== 'string' || !Array.isArray(error.args))
|
|
7
|
+
return;
|
|
8
|
+
if (!error.args.every((arg) => typeof arg === 'string'))
|
|
9
|
+
return;
|
|
10
|
+
return { command: error.command, args: error.args };
|
|
11
|
+
}
|
|
12
|
+
function jsonErrorType(error, external) {
|
|
13
|
+
if (error.type === FatalErrorType.Abort) {
|
|
14
|
+
return external ? 'external' : 'abort';
|
|
15
|
+
}
|
|
16
|
+
return 'bug';
|
|
17
|
+
}
|
|
18
|
+
function jsonTokenItem(token) {
|
|
19
|
+
if (token === null || token === undefined)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
const message = tokenItemToJsonString(token);
|
|
23
|
+
return typeof message === 'string' ? unstyled(message) : undefined;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof TypeError)
|
|
27
|
+
return undefined;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function jsonTokenItems(items) {
|
|
32
|
+
if (!Array.isArray(items))
|
|
33
|
+
return;
|
|
34
|
+
const renderedItems = items.map(jsonTokenItem).filter((item) => item !== undefined);
|
|
35
|
+
return renderedItems.length > 0 ? renderedItems : undefined;
|
|
36
|
+
}
|
|
37
|
+
function jsonTable(data) {
|
|
38
|
+
if (!Array.isArray(data))
|
|
39
|
+
return;
|
|
40
|
+
return data
|
|
41
|
+
.filter((row) => Array.isArray(row))
|
|
42
|
+
.map((row) => row.map((cell) => jsonTokenItem(cell) ?? ''));
|
|
43
|
+
}
|
|
44
|
+
function jsonCustomSection(section) {
|
|
45
|
+
if (typeof section !== 'object' || section === null || !('body' in section))
|
|
46
|
+
return;
|
|
47
|
+
const title = 'title' in section && typeof section.title === 'string' ? unstyled(section.title) : undefined;
|
|
48
|
+
const sectionBody = section.body;
|
|
49
|
+
const body = typeof sectionBody === 'object' && sectionBody !== null && 'tabularData' in sectionBody
|
|
50
|
+
? jsonTable(sectionBody.tabularData)
|
|
51
|
+
: jsonTokenItem(sectionBody);
|
|
52
|
+
if (body === undefined)
|
|
53
|
+
return;
|
|
54
|
+
return { ...(title ? { title } : {}), body };
|
|
55
|
+
}
|
|
56
|
+
function jsonCustomSections(sections) {
|
|
57
|
+
if (!Array.isArray(sections))
|
|
58
|
+
return;
|
|
59
|
+
const renderedSections = sections
|
|
60
|
+
.map(jsonCustomSection)
|
|
61
|
+
.filter((section) => section !== undefined);
|
|
62
|
+
return renderedSections.length > 0 ? renderedSections : undefined;
|
|
63
|
+
}
|
|
64
|
+
function jsonErrorDocument(error) {
|
|
65
|
+
if (error.type === FatalErrorType.AbortSilent)
|
|
66
|
+
return;
|
|
67
|
+
const external = externalCommand(error);
|
|
68
|
+
const type = jsonErrorType(error, external);
|
|
69
|
+
const formattedMessage = jsonTokenItem(error.formattedMessage);
|
|
70
|
+
const message = formattedMessage ?? (typeof error.message === 'string' ? unstyled(error.message) : 'Unknown error');
|
|
71
|
+
const tryMessage = jsonTokenItem(error.tryMessage);
|
|
72
|
+
const nextSteps = jsonTokenItems(error.nextSteps);
|
|
73
|
+
const customSections = jsonCustomSections(error.customSections);
|
|
74
|
+
const details = error.details;
|
|
75
|
+
const commonFields = {
|
|
76
|
+
message,
|
|
77
|
+
...(typeof error.code === 'string' && error.code.length > 0 ? { code: error.code } : {}),
|
|
78
|
+
...(tryMessage === undefined ? {} : { tryMessage }),
|
|
79
|
+
...(nextSteps === undefined ? {} : { nextSteps }),
|
|
80
|
+
...(customSections === undefined ? {} : { customSections }),
|
|
81
|
+
...(details === undefined ? {} : { details }),
|
|
82
|
+
};
|
|
83
|
+
let jsonError;
|
|
84
|
+
if (type === 'bug') {
|
|
85
|
+
jsonError = {
|
|
86
|
+
type,
|
|
87
|
+
...commonFields,
|
|
88
|
+
...(typeof error.stack === 'string' ? { stack: unstyled(error.stack) } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
else if (type === 'external' && external) {
|
|
92
|
+
jsonError = { type, ...commonFields, ...external };
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
jsonError = { type: 'abort', ...commonFields };
|
|
96
|
+
}
|
|
97
|
+
return { error: jsonError };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Writes the public JSON representation of a fatal error to stdout.
|
|
101
|
+
*
|
|
102
|
+
* The allow-list mirrors the meaningful content of the regular fatal-error renderer.
|
|
103
|
+
* Arbitrary error properties remain private and are never copied to stdout.
|
|
104
|
+
*
|
|
105
|
+
* @param error - Fatal error to serialize.
|
|
106
|
+
*/
|
|
107
|
+
export function renderFatalErrorAsJson(error) {
|
|
108
|
+
let serializedDocument;
|
|
109
|
+
try {
|
|
110
|
+
const document = jsonErrorDocument(error);
|
|
111
|
+
if (!document)
|
|
112
|
+
return;
|
|
113
|
+
serializedDocument = JSON.stringify(jsonErrorOutputSchema.validate(document));
|
|
114
|
+
// Serialization must not replace the JSON contract with a text banner.
|
|
115
|
+
// eslint-disable-next-line no-catch-all/no-catch-all
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
serializedDocument = JSON.stringify({ error: { type: 'bug', message: 'Failed to serialize the error as JSON.' } });
|
|
119
|
+
}
|
|
120
|
+
// Keep write failures separate: retrying after a partial write could corrupt stdout.
|
|
121
|
+
outputResult(serializedDocument);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=json-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-error.js","sourceRoot":"","sources":["../../../src/private/node/json-error.ts"],"names":[],"mappings":"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","sourcesContent":["import {tokenItemToJsonString, type TokenItem} from './ui/components/token-item.js'\nimport {FatalErrorType} from '../../public/node/error.js'\nimport {jsonErrorOutputSchema} from '../../public/node/error/schema.js'\nimport {outputResult, unstyled} from '../../public/node/output.js'\nimport type {\n JsonError,\n JsonErrorCustomSection,\n JsonErrorDocument,\n JsonErrorType,\n} from '../../public/node/error/types.js'\n\ninterface FatalErrorLike {\n type?: number\n message?: unknown\n formattedMessage?: unknown\n tryMessage?: unknown\n nextSteps?: unknown\n customSections?: unknown\n stack?: unknown\n command?: unknown\n args?: unknown\n details?: unknown\n code?: unknown\n}\n\ninterface ExternalCommand {\n command: string\n args: string[]\n}\n\nfunction externalCommand(error: FatalErrorLike): ExternalCommand | undefined {\n if (typeof error.command !== 'string' || !Array.isArray(error.args)) return\n if (!error.args.every((arg) => typeof arg === 'string')) return\n\n return {command: error.command, args: error.args}\n}\n\nfunction jsonErrorType(error: FatalErrorLike, external: ExternalCommand | undefined): JsonErrorType {\n if (error.type === FatalErrorType.Abort) {\n return external ? 'external' : 'abort'\n }\n return 'bug'\n}\n\nfunction jsonTokenItem(token: unknown): string | undefined {\n if (token === null || token === undefined) return\n\n try {\n const message = tokenItemToJsonString(token as TokenItem)\n return typeof message === 'string' ? unstyled(message) : undefined\n } catch (error) {\n if (error instanceof TypeError) return undefined\n throw error\n }\n}\n\nfunction jsonTokenItems(items: unknown): string[] | undefined {\n if (!Array.isArray(items)) return\n\n const renderedItems = items.map(jsonTokenItem).filter((item): item is string => item !== undefined)\n return renderedItems.length > 0 ? renderedItems : undefined\n}\n\nfunction jsonTable(data: unknown): string[][] | undefined {\n if (!Array.isArray(data)) return\n\n return data\n .filter((row): row is unknown[] => Array.isArray(row))\n .map((row) => row.map((cell) => jsonTokenItem(cell) ?? ''))\n}\n\nfunction jsonCustomSection(section: unknown): JsonErrorCustomSection | undefined {\n if (typeof section !== 'object' || section === null || !('body' in section)) return\n\n const title = 'title' in section && typeof section.title === 'string' ? unstyled(section.title) : undefined\n const sectionBody = section.body\n const body =\n typeof sectionBody === 'object' && sectionBody !== null && 'tabularData' in sectionBody\n ? jsonTable(sectionBody.tabularData)\n : jsonTokenItem(sectionBody)\n\n if (body === undefined) return\n return {...(title ? {title} : {}), body}\n}\n\nfunction jsonCustomSections(sections: unknown): JsonErrorCustomSection[] | undefined {\n if (!Array.isArray(sections)) return\n\n const renderedSections = sections\n .map(jsonCustomSection)\n .filter((section): section is JsonErrorCustomSection => section !== undefined)\n return renderedSections.length > 0 ? renderedSections : undefined\n}\n\nfunction jsonErrorDocument(error: FatalErrorLike): JsonErrorDocument | undefined {\n if (error.type === FatalErrorType.AbortSilent) return\n\n const external = externalCommand(error)\n const type = jsonErrorType(error, external)\n const formattedMessage = jsonTokenItem(error.formattedMessage)\n const message = formattedMessage ?? (typeof error.message === 'string' ? unstyled(error.message) : 'Unknown error')\n const tryMessage = jsonTokenItem(error.tryMessage)\n const nextSteps = jsonTokenItems(error.nextSteps)\n const customSections = jsonCustomSections(error.customSections)\n const details = error.details\n\n const commonFields = {\n message,\n ...(typeof error.code === 'string' && error.code.length > 0 ? {code: error.code} : {}),\n ...(tryMessage === undefined ? {} : {tryMessage}),\n ...(nextSteps === undefined ? {} : {nextSteps}),\n ...(customSections === undefined ? {} : {customSections}),\n ...(details === undefined ? {} : {details}),\n }\n\n let jsonError: JsonError\n if (type === 'bug') {\n jsonError = {\n type,\n ...commonFields,\n ...(typeof error.stack === 'string' ? {stack: unstyled(error.stack)} : {}),\n }\n } else if (type === 'external' && external) {\n jsonError = {type, ...commonFields, ...external}\n } else {\n jsonError = {type: 'abort', ...commonFields}\n }\n\n return {error: jsonError}\n}\n\n/**\n * Writes the public JSON representation of a fatal error to stdout.\n *\n * The allow-list mirrors the meaningful content of the regular fatal-error renderer.\n * Arbitrary error properties remain private and are never copied to stdout.\n *\n * @param error - Fatal error to serialize.\n */\nexport function renderFatalErrorAsJson(error: FatalErrorLike): void {\n let serializedDocument: string\n try {\n const document = jsonErrorDocument(error)\n if (!document) return\n serializedDocument = JSON.stringify(jsonErrorOutputSchema.validate(document))\n // Serialization must not replace the JSON contract with a text banner.\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch {\n serializedDocument = JSON.stringify({error: {type: 'bug', message: 'Failed to serialize the error as JSON.'}})\n }\n\n // Keep write failures separate: retrying after a partial write could corrupt stdout.\n outputResult(serializedDocument)\n}\n"]}
|
|
@@ -28,7 +28,9 @@ export function consoleLog(message) {
|
|
|
28
28
|
* @param message - The message to print.
|
|
29
29
|
*/
|
|
30
30
|
export function consoleWarn(message) {
|
|
31
|
-
|
|
31
|
+
// Ink intercepts console calls to preserve its active UI around log messages.
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.warn(withOrWithoutStyle(message));
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Logs an unformatted message at the given log level.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../../src/private/node/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,oCAAoC,CAAA;AAC7D,OAAO,EAGL,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EAEtB,mBAAmB,EACnB,QAAQ,GACT,MAAM,6BAA6B,CAAA;AAEpC;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,mBAAmB,EAAE,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAA;IAChB,CAAC;SAAM,CAAC;QACN,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../../src/private/node/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,oCAAoC,CAAA;AAC7D,OAAO,EAGL,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EAEtB,mBAAmB,EACnB,QAAQ,GACT,MAAM,6BAA6B,CAAA;AAEpC;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,mBAAmB,EAAE,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAA;IAChB,CAAC;SAAM,CAAC;QACN,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,8EAA8E;IAC9E,sCAAsC;IACtC,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAA;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAC,OAAsB,EAAE,WAAqB,MAAM,EAAE,SAAiB,WAAW;IACtG,IAAI,UAAU,EAAE;QAAE,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC/C,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACzC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;AACnD,CAAC","sourcesContent":["import {isUnitTest} from '../../public/node/context/local.js'\nimport {\n OutputMessage,\n LogLevel,\n collectLog,\n stringifyMessage,\n outputWhereAppropriate,\n Logger,\n shouldDisplayColors,\n unstyled,\n} from '../../public/node/output.js'\n\n/**\n * Returns a colored or uncolored version of a message, depending on the environment.\n *\n * @param message - The message to color or not.\n * @returns The message with or without colors.\n */\nfunction withOrWithoutStyle(message: string): string {\n if (shouldDisplayColors()) {\n return message\n } else {\n return unstyled(message)\n }\n}\n\n/**\n * Prints a log message in the console to stdout.\n *\n * @param message - The message to print.\n */\nexport function consoleLog(message: string): void {\n process.stdout.write(`${withOrWithoutStyle(message)}\\n`)\n}\n\n/**\n * Prints a warning message in the console to stderr.\n *\n * @param message - The message to print.\n */\nexport function consoleWarn(message: string): void {\n // Ink intercepts console calls to preserve its active UI around log messages.\n // eslint-disable-next-line no-console\n console.warn(withOrWithoutStyle(message))\n}\n\n/**\n * Logs an unformatted message at the given log level.\n * Note: By default, messages are sent through the standard error.\n *\n * @param content - The content to be output to the user.\n * @param logLevel - The log level associated with the message.\n * @param logger - The logging function to use to output to the user.\n */\nexport function output(content: OutputMessage, logLevel: LogLevel = 'info', logger: Logger = consoleWarn): void {\n if (isUnitTest()) collectLog(logLevel, content)\n const message = stringifyMessage(content)\n outputWhereAppropriate(logLevel, logger, message)\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { InkLifecycleRoot } from './ui.js';
|
|
2
|
+
import { SingleTask } from './ui/components/SingleTask.js';
|
|
3
|
+
import { outputInfo, outputWarn, outputResult, TokenizedString, unstyled } from '../../public/node/output.js';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render } from 'ink';
|
|
6
|
+
import ansiEscapes from 'ansi-escapes';
|
|
7
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
8
|
+
import { PassThrough } from 'stream';
|
|
9
|
+
// Vitest's console omits the constructor used by Ink's console patch.
|
|
10
|
+
// eslint-disable-next-line n/prefer-global/console
|
|
11
|
+
import { Console } from 'console';
|
|
12
|
+
// Ink detects CI when imported; exercise terminal rendering on CI runners too.
|
|
13
|
+
vi.hoisted(() => {
|
|
14
|
+
vi.stubEnv('CI', 'false');
|
|
15
|
+
vi.stubEnv('CONTINUOUS_INTEGRATION', 'false');
|
|
16
|
+
});
|
|
17
|
+
vi.mock('../../public/node/context/local.js', async (importOriginal) => ({
|
|
18
|
+
...(await importOriginal()),
|
|
19
|
+
isUnitTest: () => false,
|
|
20
|
+
}));
|
|
21
|
+
describe('logging during an Ink task', () => {
|
|
22
|
+
test.each([outputInfo, outputWarn])('clears and redraws the task around %o', async (log) => {
|
|
23
|
+
vi.stubGlobal('console', { ...console, Console });
|
|
24
|
+
const writes = [];
|
|
25
|
+
const terminal = Object.assign(new PassThrough(), { isTTY: true, columns: 80, rows: 24 });
|
|
26
|
+
terminal.on('data', (data) => writes.push(data.toString()));
|
|
27
|
+
let finishTask;
|
|
28
|
+
const taskResult = new Promise((resolve) => {
|
|
29
|
+
finishTask = resolve;
|
|
30
|
+
});
|
|
31
|
+
const stdoutWrite = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
|
|
32
|
+
const stderrWrite = vi.spyOn(process.stderr, 'write').mockReturnValue(true);
|
|
33
|
+
const instance = render(React.createElement(InkLifecycleRoot, null,
|
|
34
|
+
React.createElement(SingleTask, { title: new TokenizedString('Preparing the result'), task: () => taskResult })), {
|
|
35
|
+
stdout: terminal,
|
|
36
|
+
stderr: terminal,
|
|
37
|
+
patchConsole: true,
|
|
38
|
+
exitOnCtrlC: false,
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
await vi.waitFor(() => expect(writes.join('')).toContain('Preparing the result'));
|
|
42
|
+
expect(writes.join('')).toContain('▀');
|
|
43
|
+
const beforeLog = writes.length;
|
|
44
|
+
log('Prepared an item');
|
|
45
|
+
// Clear both UI lines and their trailing newline before writing the log.
|
|
46
|
+
const logWrites = writes.slice(beforeLog);
|
|
47
|
+
const messageIndex = logWrites.findIndex((write) => write.includes('Prepared an item'));
|
|
48
|
+
expect(messageIndex).toBeGreaterThan(0);
|
|
49
|
+
expect(logWrites[messageIndex - 1]).toBe(ansiEscapes.eraseLines(3));
|
|
50
|
+
expect(unstyled(logWrites[messageIndex])).toBe('Prepared an item\n');
|
|
51
|
+
expect(logWrites[messageIndex + 1]).toContain('Preparing the result');
|
|
52
|
+
outputResult('{"items":1}');
|
|
53
|
+
expect(stdoutWrite).toHaveBeenCalledWith('{"items":1}\n');
|
|
54
|
+
expect(stderrWrite).not.toHaveBeenCalled();
|
|
55
|
+
const afterLog = writes.length;
|
|
56
|
+
finishTask();
|
|
57
|
+
await instance.waitUntilExit();
|
|
58
|
+
const cleanup = writes.slice(afterLog).join('');
|
|
59
|
+
expect(cleanup).toContain(ansiEscapes.eraseLines(3));
|
|
60
|
+
expect(unstyled(cleanup).trim()).toBe('');
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
finishTask();
|
|
64
|
+
instance.unmount();
|
|
65
|
+
instance.cleanup();
|
|
66
|
+
stdoutWrite.mockRestore();
|
|
67
|
+
stderrWrite.mockRestore();
|
|
68
|
+
vi.unstubAllGlobals();
|
|
69
|
+
vi.unstubAllEnvs();
|
|
70
|
+
terminal.destroy();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=output.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.test.js","sourceRoot":"","sources":["../../../src/private/node/output.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {InkLifecycleRoot} from './ui.js'\nimport {SingleTask} from './ui/components/SingleTask.js'\nimport {outputInfo, outputWarn, outputResult, TokenizedString, unstyled} from '../../public/node/output.js'\nimport React from 'react'\nimport {render} from 'ink'\nimport ansiEscapes from 'ansi-escapes'\nimport {describe, expect, test, vi} from 'vitest'\nimport {PassThrough} from 'stream'\n// Vitest's console omits the constructor used by Ink's console patch.\n// eslint-disable-next-line n/prefer-global/console\nimport {Console} from 'console'\n\n// Ink detects CI when imported; exercise terminal rendering on CI runners too.\nvi.hoisted(() => {\n vi.stubEnv('CI', 'false')\n vi.stubEnv('CONTINUOUS_INTEGRATION', 'false')\n})\nvi.mock('../../public/node/context/local.js', async (importOriginal) => ({\n ...(await importOriginal<typeof import('../../public/node/context/local.js')>()),\n isUnitTest: () => false,\n}))\n\ndescribe('logging during an Ink task', () => {\n test.each([outputInfo, outputWarn])('clears and redraws the task around %o', async (log) => {\n vi.stubGlobal('console', {...console, Console})\n const writes: string[] = []\n const terminal = Object.assign(new PassThrough(), {isTTY: true, columns: 80, rows: 24})\n terminal.on('data', (data: Buffer) => writes.push(data.toString()))\n let finishTask!: () => void\n const taskResult = new Promise<void>((resolve) => {\n finishTask = resolve\n })\n const stdoutWrite = vi.spyOn(process.stdout, 'write').mockReturnValue(true)\n const stderrWrite = vi.spyOn(process.stderr, 'write').mockReturnValue(true)\n const instance = render(\n <InkLifecycleRoot>\n <SingleTask title={new TokenizedString('Preparing the result')} task={() => taskResult} />\n </InkLifecycleRoot>,\n {\n stdout: terminal as unknown as NodeJS.WriteStream,\n stderr: terminal as unknown as NodeJS.WriteStream,\n patchConsole: true,\n exitOnCtrlC: false,\n },\n )\n\n try {\n await vi.waitFor(() => expect(writes.join('')).toContain('Preparing the result'))\n expect(writes.join('')).toContain('▀')\n const beforeLog = writes.length\n\n log('Prepared an item')\n\n // Clear both UI lines and their trailing newline before writing the log.\n const logWrites = writes.slice(beforeLog)\n const messageIndex = logWrites.findIndex((write) => write.includes('Prepared an item'))\n expect(messageIndex).toBeGreaterThan(0)\n expect(logWrites[messageIndex - 1]).toBe(ansiEscapes.eraseLines(3))\n expect(unstyled(logWrites[messageIndex]!)).toBe('Prepared an item\\n')\n expect(logWrites[messageIndex + 1]).toContain('Preparing the result')\n\n outputResult('{\"items\":1}')\n expect(stdoutWrite).toHaveBeenCalledWith('{\"items\":1}\\n')\n expect(stderrWrite).not.toHaveBeenCalled()\n\n const afterLog = writes.length\n finishTask()\n await instance.waitUntilExit()\n\n const cleanup = writes.slice(afterLog).join('')\n expect(cleanup).toContain(ansiEscapes.eraseLines(3))\n expect(unstyled(cleanup).trim()).toBe('')\n } finally {\n finishTask()\n instance.unmount()\n instance.cleanup()\n stdoutWrite.mockRestore()\n stderrWrite.mockRestore()\n vi.unstubAllGlobals()\n vi.unstubAllEnvs()\n terminal.destroy()\n }\n })\n})\n"]}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { clientId } from './identity.js';
|
|
2
2
|
import { exchangeDeviceCodeForAccessToken } from './exchange.js';
|
|
3
|
+
import { isGatewayErrorStatus } from '../api/status-checks.js';
|
|
3
4
|
import { identityFqdn } from '../../../public/node/context/fqdn.js';
|
|
5
|
+
import { recordRetry } from '../../../public/node/analytics.js';
|
|
4
6
|
import { shopifyFetch } from '../../../public/node/http.js';
|
|
7
|
+
import { isInputDisabled } from '../../../public/node/no-input.js';
|
|
5
8
|
import { outputContent, outputDebug, outputInfo, outputToken } from '../../../public/node/output.js';
|
|
6
9
|
import { AbortError, BugError } from '../../../public/node/error.js';
|
|
7
|
-
import { isCI, openURL } from '../../../public/node/system.js';
|
|
10
|
+
import { isCI, openURL, sleep } from '../../../public/node/system.js';
|
|
11
|
+
const GATEWAY_ERROR_RETRY_LIMIT = 2;
|
|
12
|
+
const GATEWAY_ERROR_INITIAL_RETRY_DELAY_SECONDS = 0.2;
|
|
8
13
|
/**
|
|
9
14
|
* Initiate a device authorization flow.
|
|
10
15
|
* This will return a DeviceAuthorizationResponse containing the URL where user
|
|
@@ -20,19 +25,7 @@ export async function requestDeviceAuthorization(scopes) {
|
|
|
20
25
|
const identityClientId = clientId();
|
|
21
26
|
const queryParams = { client_id: identityClientId, scope: scopes.join(' ') };
|
|
22
27
|
const url = `https://${fqdn}/oauth/device_authorization`;
|
|
23
|
-
const response = await
|
|
24
|
-
method: 'POST',
|
|
25
|
-
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
|
|
26
|
-
body: convertRequestToParams(queryParams),
|
|
27
|
-
});
|
|
28
|
-
// First read the response body as text so we have it for debugging
|
|
29
|
-
let responseText;
|
|
30
|
-
try {
|
|
31
|
-
responseText = await response.text();
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
throw new BugError(`Failed to read response from authorization service (HTTP ${response.status}). Network or streaming error occurred.`, 'Check your network connection and try again.');
|
|
35
|
-
}
|
|
28
|
+
const { response, responseText } = await requestDeviceAuthorizationResponse(url, convertRequestToParams(queryParams));
|
|
36
29
|
// Now try to parse the text as JSON
|
|
37
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
31
|
let jsonResult;
|
|
@@ -42,6 +35,9 @@ export async function requestDeviceAuthorization(scopes) {
|
|
|
42
35
|
catch {
|
|
43
36
|
// JSON.parse failed, handle the parsing error
|
|
44
37
|
const errorMessage = buildAuthorizationParseErrorMessage(response, responseText);
|
|
38
|
+
if (isGatewayErrorStatus(response.status)) {
|
|
39
|
+
throw new AbortError(errorMessage);
|
|
40
|
+
}
|
|
45
41
|
throw new BugError(errorMessage);
|
|
46
42
|
}
|
|
47
43
|
outputDebug(outputContent `Received device authorization response (HTTP ${String(response.status)}): interval=${String(jsonResult.interval ?? 'not provided')}, expires_in=${String(jsonResult.expires_in ?? 'not provided')}`);
|
|
@@ -49,7 +45,7 @@ export async function requestDeviceAuthorization(scopes) {
|
|
|
49
45
|
throw new BugError('Failed to start authorization process');
|
|
50
46
|
}
|
|
51
47
|
outputInfo('\nTo run this command, log in to Shopify.');
|
|
52
|
-
if (isCI()) {
|
|
48
|
+
if (isInputDisabled() || isCI()) {
|
|
53
49
|
throw new AbortError('Authorization is required to continue, but the current environment does not support interactive prompts.', 'To resolve this, specify credentials in your environment, or run the command in an interactive environment such as your local terminal.');
|
|
54
50
|
}
|
|
55
51
|
outputInfo(outputContent `User verification code: ${jsonResult.user_code}`);
|
|
@@ -119,6 +115,44 @@ export async function pollForDeviceAuthorization(code, interval = 5) {
|
|
|
119
115
|
startPolling();
|
|
120
116
|
});
|
|
121
117
|
}
|
|
118
|
+
async function requestDeviceAuthorizationResponse(url, body, gatewayRetriesUsed = 0) {
|
|
119
|
+
const response = await shopifyFetch(url, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
|
|
122
|
+
body,
|
|
123
|
+
});
|
|
124
|
+
let responseText;
|
|
125
|
+
try {
|
|
126
|
+
responseText = await response.text();
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
throw new BugError(`Failed to read response from authorization service (HTTP ${response.status}). Network or streaming error occurred.`, 'Check your network connection and try again.');
|
|
130
|
+
}
|
|
131
|
+
if (!isGatewayErrorStatus(response.status) || gatewayRetriesUsed >= GATEWAY_ERROR_RETRY_LIMIT) {
|
|
132
|
+
return { response, responseText };
|
|
133
|
+
}
|
|
134
|
+
const retryNumber = gatewayRetriesUsed + 1;
|
|
135
|
+
const retryDelaySeconds = retryAfterDelaySeconds(response.headers.get('retry-after')) ??
|
|
136
|
+
GATEWAY_ERROR_INITIAL_RETRY_DELAY_SECONDS * 2 ** gatewayRetriesUsed;
|
|
137
|
+
recordRetry(url, 'device-authorization-gateway-error');
|
|
138
|
+
outputDebug(`Scheduling device authorization retry #${retryNumber} after HTTP ${response.status} in ${retryDelaySeconds} seconds`);
|
|
139
|
+
await sleep(retryDelaySeconds);
|
|
140
|
+
return requestDeviceAuthorizationResponse(url, body, retryNumber);
|
|
141
|
+
}
|
|
142
|
+
function retryAfterDelaySeconds(retryAfter) {
|
|
143
|
+
const value = retryAfter?.trim();
|
|
144
|
+
if (!value)
|
|
145
|
+
return undefined;
|
|
146
|
+
// Retry-After can specify either a delay in seconds or an HTTP date.
|
|
147
|
+
const delaySeconds = Number(value);
|
|
148
|
+
if (!Number.isNaN(delaySeconds)) {
|
|
149
|
+
return Number.isFinite(delaySeconds) && delaySeconds >= 0 ? delaySeconds : undefined;
|
|
150
|
+
}
|
|
151
|
+
const retryAt = Date.parse(value);
|
|
152
|
+
if (Number.isNaN(retryAt))
|
|
153
|
+
return undefined;
|
|
154
|
+
return Math.max(0, (retryAt - Date.now()) / 1000);
|
|
155
|
+
}
|
|
122
156
|
function convertRequestToParams(queryParams) {
|
|
123
157
|
return new URLSearchParams(Object.entries(queryParams).filter(([, value]) => Boolean(value))).toString();
|
|
124
158
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-authorization.js","sourceRoot":"","sources":["../../../../src/private/node/session/device-authorization.ts"],"names":[],"mappings":"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","sourcesContent":["import {clientId} from './identity.js'\nimport {exchangeDeviceCodeForAccessToken} from './exchange.js'\nimport {IdentityToken} from './schema.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport {shopifyFetch} from '../../../public/node/http.js'\nimport {outputContent, outputDebug, outputInfo, outputToken} from '../../../public/node/output.js'\nimport {AbortError, BugError} from '../../../public/node/error.js'\nimport {isCI, openURL} from '../../../public/node/system.js'\n\nimport {Response} from 'node-fetch'\n\nexport interface DeviceAuthorizationResponse {\n deviceCode: string\n userCode: string\n verificationUri: string\n expiresIn: number\n verificationUriComplete?: string\n interval?: number\n}\n\n/**\n * Initiate a device authorization flow.\n * This will return a DeviceAuthorizationResponse containing the URL where user\n * should go to authorize the device without the need of a callback to the CLI.\n *\n * Also returns a `deviceCode` used for polling the token endpoint in the next step.\n *\n * @param scopes - The scopes to request\n * @returns An object with the device authorization response.\n */\nexport async function requestDeviceAuthorization(scopes: string[]): Promise<DeviceAuthorizationResponse> {\n const fqdn = await identityFqdn()\n const identityClientId = clientId()\n const queryParams = {client_id: identityClientId, scope: scopes.join(' ')}\n const url = `https://${fqdn}/oauth/device_authorization`\n\n const response = await shopifyFetch(url, {\n method: 'POST',\n headers: {'Content-type': 'application/x-www-form-urlencoded'},\n body: convertRequestToParams(queryParams),\n })\n\n // First read the response body as text so we have it for debugging\n let responseText: string\n try {\n responseText = await response.text()\n } catch (error) {\n throw new BugError(\n `Failed to read response from authorization service (HTTP ${response.status}). Network or streaming error occurred.`,\n 'Check your network connection and try again.',\n )\n }\n\n // Now try to parse the text as JSON\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let jsonResult: any\n try {\n jsonResult = JSON.parse(responseText)\n } catch {\n // JSON.parse failed, handle the parsing error\n const errorMessage = buildAuthorizationParseErrorMessage(response, responseText)\n throw new BugError(errorMessage)\n }\n\n outputDebug(\n outputContent`Received device authorization response (HTTP ${String(response.status)}): interval=${String(jsonResult.interval ?? 'not provided')}, expires_in=${String(jsonResult.expires_in ?? 'not provided')}`,\n )\n if (!jsonResult.device_code || !jsonResult.verification_uri_complete) {\n throw new BugError('Failed to start authorization process')\n }\n\n outputInfo('\\nTo run this command, log in to Shopify.')\n\n if (isCI()) {\n throw new AbortError(\n 'Authorization is required to continue, but the current environment does not support interactive prompts.',\n 'To resolve this, specify credentials in your environment, or run the command in an interactive environment such as your local terminal.',\n )\n }\n\n outputInfo(outputContent`User verification code: ${jsonResult.user_code}`)\n const linkToken = outputToken.link(jsonResult.verification_uri_complete)\n\n const opened = await openURL(jsonResult.verification_uri_complete)\n if (opened) {\n outputInfo(outputContent`Opened link to start the auth process: ${linkToken}`)\n } else {\n outputInfo(outputContent`👉 Open this link to start the auth process: ${linkToken}`)\n }\n\n return {\n deviceCode: jsonResult.device_code,\n userCode: jsonResult.user_code,\n verificationUri: jsonResult.verification_uri,\n expiresIn: jsonResult.expires_in,\n verificationUriComplete: jsonResult.verification_uri_complete,\n interval: jsonResult.interval,\n }\n}\n\n/**\n * Poll the Oauth token endpoint with the device code obtained from a DeviceAuthorizationResponse.\n * The endpoint will return `authorization_pending` until the user completes the auth flow in the browser.\n * Once the user completes the auth flow, the endpoint will return the identity token.\n *\n * Timeout for the polling is defined by the server and is around 600 seconds.\n *\n * @param code - The device code obtained after starting a device identity flow\n * @param interval - The interval to poll the token endpoint\n * @returns The identity token\n */\nexport async function pollForDeviceAuthorization(code: string, interval = 5): Promise<IdentityToken> {\n let currentIntervalInSeconds = interval\n\n return new Promise<IdentityToken>((resolve, reject) => {\n const onPoll = async () => {\n const result = await exchangeDeviceCodeForAccessToken(code)\n if (!result.isErr()) {\n resolve(result.value)\n return\n }\n\n const error = result.error ?? 'unknown_failure'\n\n outputDebug(outputContent`Polling for device authorization... status: ${error}`)\n switch (error) {\n case 'authorization_pending': {\n startPolling()\n return\n }\n case 'slow_down':\n currentIntervalInSeconds += 5\n startPolling()\n return\n case 'access_denied':\n reject(new AbortError(`Device authorization failed: Access denied.`))\n return\n case 'expired_token':\n reject(new AbortError(`Device authorization failed: Token expired. Please try again.`))\n return\n case 'unknown_failure':\n default:\n reject(new Error(`Device authorization failed: ${error}`))\n }\n }\n\n const startPolling = () => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n setTimeout(onPoll, currentIntervalInSeconds * 1000)\n }\n\n startPolling()\n })\n}\n\nfunction convertRequestToParams(queryParams: {client_id: string; scope: string}): string {\n return new URLSearchParams(Object.entries(queryParams).filter(([, value]) => Boolean(value))).toString()\n}\n\n/**\n * Build a detailed error message for JSON parsing failures from the authorization service.\n * Provides context-specific error messages based on response status and content.\n *\n * @param response - The HTTP response object\n * @param responseText - The raw response body text\n * @returns Detailed error message about the failure\n */\nfunction buildAuthorizationParseErrorMessage(response: Response, responseText: string): string {\n // Build helpful error message based on response status and content\n let errorMessage = `Received invalid response from authorization service (HTTP ${response.status}).`\n\n // Add status-based context\n if (response.status >= 500) {\n errorMessage += ' The service may be experiencing issues.'\n } else if (response.status >= 400) {\n errorMessage += ' The request may be malformed or unauthorized.'\n }\n\n // Add content-based context (check these regardless of status)\n if (responseText.trim().startsWith('<!DOCTYPE') || responseText.trim().startsWith('<html')) {\n errorMessage += ' Received HTML instead of JSON - the service endpoint may have changed.'\n } else if (responseText.trim() === '') {\n errorMessage += ' Received empty response body.'\n } else {\n errorMessage += ' Response could not be parsed as valid JSON.'\n }\n\n return `${errorMessage} If this issue persists, please contact support at https://help.shopify.com`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"device-authorization.js","sourceRoot":"","sources":["../../../../src/private/node/session/device-authorization.ts"],"names":[],"mappings":"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","sourcesContent":["import {clientId} from './identity.js'\nimport {exchangeDeviceCodeForAccessToken} from './exchange.js'\nimport {IdentityToken} from './schema.js'\nimport {isGatewayErrorStatus} from '../api/status-checks.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport {recordRetry} from '../../../public/node/analytics.js'\nimport {shopifyFetch} from '../../../public/node/http.js'\nimport {isInputDisabled} from '../../../public/node/no-input.js'\nimport {outputContent, outputDebug, outputInfo, outputToken} from '../../../public/node/output.js'\nimport {AbortError, BugError} from '../../../public/node/error.js'\nimport {isCI, openURL, sleep} from '../../../public/node/system.js'\n\nimport {Response} from 'node-fetch'\n\nexport interface DeviceAuthorizationResponse {\n deviceCode: string\n userCode: string\n verificationUri: string\n expiresIn: number\n verificationUriComplete?: string\n interval?: number\n}\n\nconst GATEWAY_ERROR_RETRY_LIMIT = 2\nconst GATEWAY_ERROR_INITIAL_RETRY_DELAY_SECONDS = 0.2\n\n/**\n * Initiate a device authorization flow.\n * This will return a DeviceAuthorizationResponse containing the URL where user\n * should go to authorize the device without the need of a callback to the CLI.\n *\n * Also returns a `deviceCode` used for polling the token endpoint in the next step.\n *\n * @param scopes - The scopes to request\n * @returns An object with the device authorization response.\n */\nexport async function requestDeviceAuthorization(scopes: string[]): Promise<DeviceAuthorizationResponse> {\n const fqdn = await identityFqdn()\n const identityClientId = clientId()\n const queryParams = {client_id: identityClientId, scope: scopes.join(' ')}\n const url = `https://${fqdn}/oauth/device_authorization`\n\n const {response, responseText} = await requestDeviceAuthorizationResponse(url, convertRequestToParams(queryParams))\n\n // Now try to parse the text as JSON\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let jsonResult: any\n try {\n jsonResult = JSON.parse(responseText)\n } catch {\n // JSON.parse failed, handle the parsing error\n const errorMessage = buildAuthorizationParseErrorMessage(response, responseText)\n if (isGatewayErrorStatus(response.status)) {\n throw new AbortError(errorMessage)\n }\n throw new BugError(errorMessage)\n }\n\n outputDebug(\n outputContent`Received device authorization response (HTTP ${String(response.status)}): interval=${String(jsonResult.interval ?? 'not provided')}, expires_in=${String(jsonResult.expires_in ?? 'not provided')}`,\n )\n if (!jsonResult.device_code || !jsonResult.verification_uri_complete) {\n throw new BugError('Failed to start authorization process')\n }\n\n outputInfo('\\nTo run this command, log in to Shopify.')\n\n if (isInputDisabled() || isCI()) {\n throw new AbortError(\n 'Authorization is required to continue, but the current environment does not support interactive prompts.',\n 'To resolve this, specify credentials in your environment, or run the command in an interactive environment such as your local terminal.',\n )\n }\n\n outputInfo(outputContent`User verification code: ${jsonResult.user_code}`)\n const linkToken = outputToken.link(jsonResult.verification_uri_complete)\n\n const opened = await openURL(jsonResult.verification_uri_complete)\n if (opened) {\n outputInfo(outputContent`Opened link to start the auth process: ${linkToken}`)\n } else {\n outputInfo(outputContent`👉 Open this link to start the auth process: ${linkToken}`)\n }\n\n return {\n deviceCode: jsonResult.device_code,\n userCode: jsonResult.user_code,\n verificationUri: jsonResult.verification_uri,\n expiresIn: jsonResult.expires_in,\n verificationUriComplete: jsonResult.verification_uri_complete,\n interval: jsonResult.interval,\n }\n}\n\n/**\n * Poll the Oauth token endpoint with the device code obtained from a DeviceAuthorizationResponse.\n * The endpoint will return `authorization_pending` until the user completes the auth flow in the browser.\n * Once the user completes the auth flow, the endpoint will return the identity token.\n *\n * Timeout for the polling is defined by the server and is around 600 seconds.\n *\n * @param code - The device code obtained after starting a device identity flow\n * @param interval - The interval to poll the token endpoint\n * @returns The identity token\n */\nexport async function pollForDeviceAuthorization(code: string, interval = 5): Promise<IdentityToken> {\n let currentIntervalInSeconds = interval\n\n return new Promise<IdentityToken>((resolve, reject) => {\n const onPoll = async () => {\n const result = await exchangeDeviceCodeForAccessToken(code)\n if (!result.isErr()) {\n resolve(result.value)\n return\n }\n\n const error = result.error ?? 'unknown_failure'\n\n outputDebug(outputContent`Polling for device authorization... status: ${error}`)\n switch (error) {\n case 'authorization_pending': {\n startPolling()\n return\n }\n case 'slow_down':\n currentIntervalInSeconds += 5\n startPolling()\n return\n case 'access_denied':\n reject(new AbortError(`Device authorization failed: Access denied.`))\n return\n case 'expired_token':\n reject(new AbortError(`Device authorization failed: Token expired. Please try again.`))\n return\n case 'unknown_failure':\n default:\n reject(new Error(`Device authorization failed: ${error}`))\n }\n }\n\n const startPolling = () => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n setTimeout(onPoll, currentIntervalInSeconds * 1000)\n }\n\n startPolling()\n })\n}\n\nasync function requestDeviceAuthorizationResponse(\n url: string,\n body: string,\n gatewayRetriesUsed = 0,\n): Promise<{response: Response; responseText: string}> {\n const response = await shopifyFetch(url, {\n method: 'POST',\n headers: {'Content-type': 'application/x-www-form-urlencoded'},\n body,\n })\n\n let responseText: string\n try {\n responseText = await response.text()\n } catch {\n throw new BugError(\n `Failed to read response from authorization service (HTTP ${response.status}). Network or streaming error occurred.`,\n 'Check your network connection and try again.',\n )\n }\n\n if (!isGatewayErrorStatus(response.status) || gatewayRetriesUsed >= GATEWAY_ERROR_RETRY_LIMIT) {\n return {response, responseText}\n }\n\n const retryNumber = gatewayRetriesUsed + 1\n const retryDelaySeconds =\n retryAfterDelaySeconds(response.headers.get('retry-after')) ??\n GATEWAY_ERROR_INITIAL_RETRY_DELAY_SECONDS * 2 ** gatewayRetriesUsed\n recordRetry(url, 'device-authorization-gateway-error')\n outputDebug(\n `Scheduling device authorization retry #${retryNumber} after HTTP ${response.status} in ${retryDelaySeconds} seconds`,\n )\n await sleep(retryDelaySeconds)\n\n return requestDeviceAuthorizationResponse(url, body, retryNumber)\n}\n\nfunction retryAfterDelaySeconds(retryAfter: string | null): number | undefined {\n const value = retryAfter?.trim()\n if (!value) return undefined\n\n // Retry-After can specify either a delay in seconds or an HTTP date.\n const delaySeconds = Number(value)\n if (!Number.isNaN(delaySeconds)) {\n return Number.isFinite(delaySeconds) && delaySeconds >= 0 ? delaySeconds : undefined\n }\n\n const retryAt = Date.parse(value)\n if (Number.isNaN(retryAt)) return undefined\n\n return Math.max(0, (retryAt - Date.now()) / 1000)\n}\n\nfunction convertRequestToParams(queryParams: {client_id: string; scope: string}): string {\n return new URLSearchParams(Object.entries(queryParams).filter(([, value]) => Boolean(value))).toString()\n}\n\n/**\n * Build a detailed error message for JSON parsing failures from the authorization service.\n * Provides context-specific error messages based on response status and content.\n *\n * @param response - The HTTP response object\n * @param responseText - The raw response body text\n * @returns Detailed error message about the failure\n */\nfunction buildAuthorizationParseErrorMessage(response: Response, responseText: string): string {\n // Build helpful error message based on response status and content\n let errorMessage = `Received invalid response from authorization service (HTTP ${response.status}).`\n\n // Add status-based context\n if (response.status >= 500) {\n errorMessage += ' The service may be experiencing issues.'\n } else if (response.status >= 400) {\n errorMessage += ' The request may be malformed or unauthorized.'\n }\n\n // Add content-based context (check these regardless of status)\n if (responseText.trim().startsWith('<!DOCTYPE') || responseText.trim().startsWith('<html')) {\n errorMessage += ' Received HTML instead of JSON - the service endpoint may have changed.'\n } else if (responseText.trim() === '') {\n errorMessage += ' Received empty response body.'\n } else {\n errorMessage += ' Response could not be parsed as valid JSON.'\n }\n\n return `${errorMessage} If this issue persists, please contact support at https://help.shopify.com`\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import { validateCachedIdentityTokenStructure } from './schema.js';
|
|
|
2
2
|
import { applicationId } from './identity.js';
|
|
3
3
|
import { sessionConstants } from '../constants.js';
|
|
4
4
|
import { outputDebug } from '../../../public/node/output.js';
|
|
5
|
+
import { getArrayRejectingUndefined } from '../../../public/common/array.js';
|
|
5
6
|
/**
|
|
6
7
|
* Validate if an identity token is valid for the requested scopes
|
|
7
8
|
*/
|
|
@@ -24,28 +25,14 @@ export async function validateSession(scopes, applications, session) {
|
|
|
24
25
|
const scopesAreValid = validateScopes(scopes, session.identity);
|
|
25
26
|
if (!scopesAreValid)
|
|
26
27
|
return 'needs_full_auth';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const token = session.applications[appId];
|
|
36
|
-
tokensAreExpired = tokensAreExpired || isTokenExpired(token);
|
|
37
|
-
}
|
|
38
|
-
if (applications.storefrontRendererApi) {
|
|
39
|
-
const appId = applicationId('storefront-renderer');
|
|
40
|
-
const token = session.applications[appId];
|
|
41
|
-
tokensAreExpired = tokensAreExpired || isTokenExpired(token);
|
|
42
|
-
}
|
|
43
|
-
if (applications.adminApi) {
|
|
44
|
-
const appId = applicationId('admin');
|
|
45
|
-
const realAppId = `${applications.adminApi.storeFqdn}-${appId}`;
|
|
46
|
-
const token = session.applications[realAppId];
|
|
47
|
-
tokensAreExpired = tokensAreExpired || isTokenExpired(token);
|
|
48
|
-
}
|
|
28
|
+
const requestedTokenIds = getArrayRejectingUndefined([
|
|
29
|
+
applications.partnersApi ? applicationId('partners') : undefined,
|
|
30
|
+
applications.appManagementApi ? applicationId('app-management') : undefined,
|
|
31
|
+
applications.storefrontRendererApi ? applicationId('storefront-renderer') : undefined,
|
|
32
|
+
applications.adminApi ? `${applications.adminApi.storeFqdn}-${applicationId('admin')}` : undefined,
|
|
33
|
+
]);
|
|
34
|
+
const tokensAreExpired = isTokenExpired(session.identity) ||
|
|
35
|
+
requestedTokenIds.some((tokenId) => isTokenExpired(session.applications[tokenId]));
|
|
49
36
|
outputDebug(`- Token validation -> It's expired: ${tokensAreExpired}`);
|
|
50
37
|
if (!validateCachedIdentityTokenStructure(session.identity)) {
|
|
51
38
|
return 'needs_full_auth';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../src/private/node/session/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,oCAAoC,EAAC,MAAM,aAAa,CAAA;AAE1G,OAAO,EAAC,aAAa,EAAC,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../src/private/node/session/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,oCAAoC,EAAC,MAAM,aAAa,CAAA;AAE1G,OAAO,EAAC,aAAa,EAAC,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAA;AAC1D,OAAO,EAAC,0BAA0B,EAAC,MAAM,iCAAiC,CAAA;AAI1E;;GAEG;AACH,SAAS,cAAc,CAAC,eAAyB,EAAE,QAAuB;IACxE,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAA;IACrC,IAAI,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAA;IACpD,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAgB,EAChB,YAA+B,EAC/B,OAA4B;IAE5B,IAAI,CAAC,OAAO;QAAE,OAAO,iBAAiB,CAAA;IACtC,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/D,IAAI,CAAC,cAAc;QAAE,OAAO,iBAAiB,CAAA;IAE7C,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;QACnD,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,YAAY,CAAC,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;QAC3E,YAAY,CAAC,qBAAqB,CAAC,CAAC,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,SAAS;QACrF,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;KACnG,CAAC,CAAA;IAEF,MAAM,gBAAgB,GACpB,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC;QAChC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAE,CAAC,CAAC,CAAA;IAErF,WAAW,CAAC,uCAAuC,gBAAgB,EAAE,CAAC,CAAA;IAEtE,IAAI,CAAC,oCAAoC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5D,OAAO,iBAAiB,CAAA;IAC1B,CAAC;IAED,IAAI,gBAAgB;QAAE,OAAO,eAAe,CAAA;IAE5C,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,cAAc,CAAC,KAAuB;IAC7C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,OAAO,KAAK,CAAC,SAAS,GAAG,eAAe,EAAE,CAAA;AAC5C,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,CAAC,6BAA6B,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;AAC1F,CAAC","sourcesContent":["import {ApplicationToken, IdentityToken, Session, validateCachedIdentityTokenStructure} from './schema.js'\n\nimport {applicationId} from './identity.js'\nimport {sessionConstants} from '../constants.js'\nimport {OAuthApplications} from '../session.js'\nimport {outputDebug} from '../../../public/node/output.js'\nimport {getArrayRejectingUndefined} from '../../../public/common/array.js'\n\ntype ValidationResult = 'needs_refresh' | 'needs_full_auth' | 'ok'\n\n/**\n * Validate if an identity token is valid for the requested scopes\n */\nfunction validateScopes(requestedScopes: string[], identity: IdentityToken) {\n const currentScopes = identity.scopes\n if (currentScopes.includes('employee')) return false\n return requestedScopes.every((scope) => currentScopes.includes(scope))\n}\n\n/**\n * Validate if the current session is valid or we need to refresh/re-authenticate\n * @param scopes - requested scopes to validate\n * @param applications - requested applications\n * @param session - current session with identity and application tokens\n * @returns 'ok' if the session is valid, 'needs_full_auth' if we need to re-authenticate, 'needs_refresh' if we need to refresh the session\n */\nexport async function validateSession(\n scopes: string[],\n applications: OAuthApplications,\n session: Session | undefined,\n): Promise<ValidationResult> {\n if (!session) return 'needs_full_auth'\n const scopesAreValid = validateScopes(scopes, session.identity)\n if (!scopesAreValid) return 'needs_full_auth'\n\n const requestedTokenIds = getArrayRejectingUndefined([\n applications.partnersApi ? applicationId('partners') : undefined,\n applications.appManagementApi ? applicationId('app-management') : undefined,\n applications.storefrontRendererApi ? applicationId('storefront-renderer') : undefined,\n applications.adminApi ? `${applications.adminApi.storeFqdn}-${applicationId('admin')}` : undefined,\n ])\n\n const tokensAreExpired =\n isTokenExpired(session.identity) ||\n requestedTokenIds.some((tokenId) => isTokenExpired(session.applications[tokenId]!))\n\n outputDebug(`- Token validation -> It's expired: ${tokensAreExpired}`)\n\n if (!validateCachedIdentityTokenStructure(session.identity)) {\n return 'needs_full_auth'\n }\n\n if (tokensAreExpired) return 'needs_refresh'\n\n return 'ok'\n}\n\nfunction isTokenExpired(token: ApplicationToken): boolean {\n if (!token) return true\n return token.expiresAt < expireThreshold()\n}\n\nfunction expireThreshold(): Date {\n return new Date(Date.now() + sessionConstants.expirationTimeMarginInMinutes * 60 * 1000)\n}\n"]}
|
|
@@ -3,4 +3,4 @@ import { RenderOptions } from 'ink';
|
|
|
3
3
|
export interface AlertOptions extends AlertProps {
|
|
4
4
|
renderOptions?: RenderOptions;
|
|
5
5
|
}
|
|
6
|
-
export declare function alert(
|
|
6
|
+
export declare function alert(options: AlertOptions): string | undefined;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Alert } from './components/Alert.js';
|
|
2
|
+
import { tokenItemToJsonString } from './components/token-item.js';
|
|
2
3
|
import { renderOnce } from '../ui.js';
|
|
4
|
+
import { commandEventOutputMode } from '../command-event-context.js';
|
|
5
|
+
import { outputInfo, outputWarn } from '../../../public/node/output.js';
|
|
3
6
|
import React from 'react';
|
|
4
7
|
const typeToLogLevel = {
|
|
5
8
|
info: 'info',
|
|
@@ -7,9 +10,32 @@ const typeToLogLevel = {
|
|
|
7
10
|
success: 'info',
|
|
8
11
|
error: 'error',
|
|
9
12
|
};
|
|
10
|
-
export function alert(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
export function alert(options) {
|
|
14
|
+
const { type, headline, body, nextSteps, reference, link, customSections, orderedNextSteps = false, renderOptions, } = options;
|
|
15
|
+
if ((type === 'info' || type === 'warning') && commandEventOutputMode() === 'json') {
|
|
16
|
+
const message = alertMessage(options);
|
|
17
|
+
if (type === 'warning')
|
|
18
|
+
outputWarn(message);
|
|
19
|
+
else
|
|
20
|
+
outputInfo(message);
|
|
21
|
+
return message;
|
|
22
|
+
}
|
|
13
23
|
return renderOnce(React.createElement(Alert, { type: type, headline: headline, body: body, nextSteps: nextSteps, reference: reference, link: link, orderedNextSteps: orderedNextSteps, customSections: customSections }), { logLevel: typeToLogLevel[type], renderOptions });
|
|
14
24
|
}
|
|
25
|
+
function alertMessage({ headline, body, nextSteps, reference, link, customSections }) {
|
|
26
|
+
const sections = [
|
|
27
|
+
headline ? tokenItemToJsonString(headline) : undefined,
|
|
28
|
+
body ? tokenItemToJsonString(body) : undefined,
|
|
29
|
+
nextSteps?.length ? `Next steps:\n${nextSteps.map(tokenItemToJsonString).join('\n')}` : undefined,
|
|
30
|
+
reference?.length ? `Reference:\n${reference.map(tokenItemToJsonString).join('\n')}` : undefined,
|
|
31
|
+
link ? tokenItemToJsonString({ link }) : undefined,
|
|
32
|
+
...(customSections ?? []).map((section) => {
|
|
33
|
+
const content = typeof section.body === 'object' && 'tabularData' in section.body
|
|
34
|
+
? section.body.tabularData.map((row) => row.map(tokenItemToJsonString).join('\t')).join('\n')
|
|
35
|
+
: tokenItemToJsonString(section.body);
|
|
36
|
+
return section.title ? `${section.title}:\n${content}` : content;
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
39
|
+
return sections.filter((section) => section !== undefined).join('\n\n');
|
|
40
|
+
}
|
|
15
41
|
//# sourceMappingURL=alert.js.map
|
|
@@ -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,UAAU,EAAC,MAAM,UAAU,CAAA;
|
|
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') && 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,4 +1,4 @@
|
|
|
1
|
-
import { OutputProcess } from '../../../../public/node/output.js';
|
|
1
|
+
import { type OutputProcess } from '../../../../public/node/output.js';
|
|
2
2
|
import { AbortSignal } from '../../../../public/node/abort.js';
|
|
3
3
|
import { FunctionComponent } from 'react';
|
|
4
4
|
export interface ConcurrentOutputProps {
|
|
@@ -6,14 +6,21 @@ export interface ConcurrentOutputProps {
|
|
|
6
6
|
prefixColumnSize?: number;
|
|
7
7
|
abortSignal: AbortSignal;
|
|
8
8
|
showTimestamps?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Keeps terminal UI running after all processes finish. Defaults to false.
|
|
11
|
+
* In JSON mode, false uses finite progress/diagnostic events; true retains streaming terminal UI.
|
|
12
|
+
*/
|
|
9
13
|
keepRunningAfterProcessesResolve?: boolean;
|
|
10
14
|
useAlternativeColorPalette?: boolean;
|
|
11
15
|
}
|
|
12
16
|
interface ConcurrentOutputContext {
|
|
13
17
|
outputPrefix?: string;
|
|
18
|
+
/** Controls ANSI stripping for terminal output. JSON diagnostics are always unstyled. */
|
|
14
19
|
stripAnsi?: boolean;
|
|
15
20
|
}
|
|
16
21
|
declare function useConcurrentOutputContext<T>(context: ConcurrentOutputContext, callback: () => T): T;
|
|
22
|
+
/** Runs finite processes concurrently and routes their output through the shared diagnostic context. */
|
|
23
|
+
export declare function runConcurrentProcessesForJson({ processes, abortSignal, }: Pick<ConcurrentOutputProps, 'processes' | 'abortSignal'>): Promise<void>;
|
|
17
24
|
/**
|
|
18
25
|
* Renders output from concurrent processes to the terminal.
|
|
19
26
|
* Output will be divided in a three column layout
|