@shopify/cli-kit 4.6.1 → 4.7.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/bounded-collections.js +1 -4
- package/dist/private/node/analytics/bounded-collections.js.map +1 -1
- package/dist/private/node/analytics/graphql-error-codes.d.ts +2 -2
- package/dist/private/node/analytics/graphql-error-codes.js +2 -2
- package/dist/private/node/analytics/graphql-error-codes.js.map +1 -1
- package/dist/private/node/analytics.js +12 -7
- package/dist/private/node/analytics.js.map +1 -1
- package/dist/private/node/api/headers.js +3 -0
- package/dist/private/node/api/headers.js.map +1 -1
- package/dist/private/node/api.js +8 -8
- package/dist/private/node/api.js.map +1 -1
- package/dist/private/node/conf-store.js +1 -1
- package/dist/private/node/conf-store.js.map +1 -1
- package/dist/private/node/content-tokens.js +27 -2
- package/dist/private/node/content-tokens.js.map +1 -1
- package/dist/private/node/otel-metrics.d.ts +1 -1
- package/dist/private/node/otel-metrics.js +5 -2
- package/dist/private/node/otel-metrics.js.map +1 -1
- package/dist/private/node/plugin-hints.d.ts +16 -0
- package/dist/private/node/plugin-hints.js +29 -0
- package/dist/private/node/plugin-hints.js.map +1 -0
- package/dist/private/node/request-ids.js +2 -3
- package/dist/private/node/request-ids.js.map +1 -1
- package/dist/private/node/session/device-authorization.js +4 -7
- package/dist/private/node/session/device-authorization.js.map +1 -1
- package/dist/private/node/session/exchange.d.ts +4 -4
- package/dist/private/node/session/exchange.js +21 -5
- package/dist/private/node/session/exchange.js.map +1 -1
- package/dist/private/node/testing/ui.js +18 -19
- package/dist/private/node/testing/ui.js.map +1 -1
- package/dist/private/node/ui/components/Alert.d.ts +1 -1
- package/dist/private/node/ui/components/Alert.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.test.js +2 -0
- package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -1
- package/dist/private/node/ui/components/DangerousConfirmationPrompt.d.ts +1 -1
- package/dist/private/node/ui/components/DangerousConfirmationPrompt.js.map +1 -1
- package/dist/private/node/ui/components/List.d.ts +1 -1
- package/dist/private/node/ui/components/List.js.map +1 -1
- package/dist/private/node/ui/components/Prompts/InfoMessage.d.ts +1 -1
- package/dist/private/node/ui/components/Prompts/InfoMessage.js.map +1 -1
- package/dist/private/node/ui/components/Prompts/InfoTable.d.ts +1 -1
- package/dist/private/node/ui/components/Prompts/InfoTable.js.map +1 -1
- package/dist/private/node/ui/components/Prompts/PromptLayout.d.ts +1 -1
- package/dist/private/node/ui/components/Prompts/PromptLayout.js.map +1 -1
- package/dist/private/node/ui/components/SingleTask.test.js +1 -0
- package/dist/private/node/ui/components/SingleTask.test.js.map +1 -1
- package/dist/private/node/ui/components/TabularData.d.ts +1 -1
- package/dist/private/node/ui/components/TabularData.js +2 -1
- package/dist/private/node/ui/components/TabularData.js.map +1 -1
- package/dist/private/node/ui/components/TextPrompt.d.ts +1 -1
- package/dist/private/node/ui/components/TextPrompt.js.map +1 -1
- package/dist/private/node/ui/components/TokenizedText.d.ts +1 -38
- package/dist/private/node/ui/components/TokenizedText.js +0 -54
- package/dist/private/node/ui/components/TokenizedText.js.map +1 -1
- package/dist/private/node/ui/components/TokenizedText.test.js +2 -1
- package/dist/private/node/ui/components/TokenizedText.test.js.map +1 -1
- package/dist/private/node/ui/components/token-item.d.ts +38 -0
- package/dist/private/node/ui/components/token-item.js +63 -0
- package/dist/private/node/ui/components/token-item.js.map +1 -0
- package/dist/private/node/ui/hooks/use-abort-signal.js +5 -4
- package/dist/private/node/ui/hooks/use-abort-signal.js.map +1 -1
- package/dist/private/node/ui/hooks/use-select-state.js +2 -1
- package/dist/private/node/ui/hooks/use-select-state.js.map +1 -1
- package/dist/private/node/ui/utilities.d.ts +6 -6
- package/dist/private/node/ui/utilities.js +1 -1
- package/dist/private/node/ui/utilities.js.map +1 -1
- package/dist/private/node/ui.js +18 -15
- package/dist/private/node/ui.js.map +1 -1
- package/dist/public/common/string.d.ts +1 -1
- package/dist/public/common/string.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/abort.d.ts +14 -6
- package/dist/public/node/abort.js +7 -6
- package/dist/public/node/abort.js.map +1 -1
- package/dist/public/node/analytics.d.ts +1 -0
- package/dist/public/node/analytics.js +79 -32
- package/dist/public/node/analytics.js.map +1 -1
- package/dist/public/node/api/admin.js +1 -1
- package/dist/public/node/api/admin.js.map +1 -1
- package/dist/public/node/base-command.d.ts +10 -0
- package/dist/public/node/base-command.js +31 -8
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/cli.d.ts +13 -0
- package/dist/public/node/cli.js +18 -2
- package/dist/public/node/cli.js.map +1 -1
- package/dist/public/node/context/local.d.ts +3 -1
- package/dist/public/node/context/local.js +14 -7
- package/dist/public/node/context/local.js.map +1 -1
- package/dist/public/node/custom-oclif-loader.js +19 -16
- package/dist/public/node/custom-oclif-loader.js.map +1 -1
- package/dist/public/node/doctor/framework.js +4 -5
- package/dist/public/node/doctor/framework.js.map +1 -1
- package/dist/public/node/environment.js +8 -11
- package/dist/public/node/environment.js.map +1 -1
- package/dist/public/node/error.d.ts +1 -4
- package/dist/public/node/error.js +20 -7
- package/dist/public/node/error.js.map +1 -1
- package/dist/public/node/git.js +7 -5
- package/dist/public/node/git.js.map +1 -1
- package/dist/public/node/global-context.js +1 -1
- package/dist/public/node/global-context.js.map +1 -1
- package/dist/public/node/hidden-folder.js +2 -4
- package/dist/public/node/hidden-folder.js.map +1 -1
- package/dist/public/node/hooks/postrun.js +1 -1
- package/dist/public/node/hooks/postrun.js.map +1 -1
- package/dist/public/node/hooks/prerun.js +5 -1
- package/dist/public/node/hooks/prerun.js.map +1 -1
- package/dist/public/node/local-storage.js +1 -0
- package/dist/public/node/local-storage.js.map +1 -1
- package/dist/public/node/metadata.d.ts +3 -0
- package/dist/public/node/metadata.js.map +1 -1
- package/dist/public/node/mimes.js +1 -3
- package/dist/public/node/mimes.js.map +1 -1
- package/dist/public/node/node-package-manager.js +31 -73
- package/dist/public/node/node-package-manager.js.map +1 -1
- package/dist/public/node/notifications-system.js +19 -3
- package/dist/public/node/notifications-system.js.map +1 -1
- package/dist/public/node/os.js +5 -10
- package/dist/public/node/os.js.map +1 -1
- package/dist/public/node/output.js +11 -20
- package/dist/public/node/output.js.map +1 -1
- package/dist/public/node/plugins/tunnel.d.ts +1 -2
- package/dist/public/node/plugins/tunnel.js +2 -2
- package/dist/public/node/plugins/tunnel.js.map +1 -1
- package/dist/public/node/result.js +4 -2
- package/dist/public/node/result.js.map +1 -1
- package/dist/public/node/serial-batch-processor.js +4 -3
- package/dist/public/node/serial-batch-processor.js.map +1 -1
- package/dist/public/node/store-auth-session.js +1 -1
- package/dist/public/node/store-auth-session.js.map +1 -1
- package/dist/public/node/system.d.ts +15 -1
- package/dist/public/node/system.js +42 -6
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/themes/conf.js +2 -2
- package/dist/public/node/themes/conf.js.map +1 -1
- package/dist/public/node/themes/factories.js +2 -2
- package/dist/public/node/themes/factories.js.map +1 -1
- package/dist/public/node/themes/theme-manager.js +2 -0
- package/dist/public/node/themes/theme-manager.js.map +1 -1
- package/dist/public/node/themes/urls.js +7 -7
- package/dist/public/node/themes/urls.js.map +1 -1
- package/dist/public/node/toml/toml-file.js +12 -13
- package/dist/public/node/toml/toml-file.js.map +1 -1
- package/dist/public/node/ui.d.ts +1 -1
- package/dist/public/node/ui.js +1 -1
- package/dist/public/node/ui.js.map +1 -1
- package/dist/public/node/vendor/dev_server/dev-server-2024.js +14 -2
- package/dist/public/node/vendor/dev_server/dev-server-2024.js.map +1 -1
- package/dist/public/node/vendor/dev_server/dev-server.js +3 -0
- package/dist/public/node/vendor/dev_server/dev-server.js.map +1 -1
- package/dist/public/node/vendor/dev_server/network/index.js +1 -1
- package/dist/public/node/vendor/dev_server/network/index.js.map +1 -1
- package/dist/public/node/vendor/otel-js/export/InstantaneousMetricReader.js +1 -0
- package/dist/public/node/vendor/otel-js/export/InstantaneousMetricReader.js.map +1 -1
- package/dist/public/node/vendor/otel-js/service/BaseOtelService/BaseOtelService.js +5 -2
- package/dist/public/node/vendor/otel-js/service/BaseOtelService/BaseOtelService.js.map +1 -1
- package/dist/public/node/vendor/otel-js/service/types.d.ts +2 -2
- package/dist/public/node/vendor/otel-js/service/types.js.map +1 -1
- package/dist/public/node/vscode.js +12 -11
- package/dist/public/node/vscode.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otel-metrics.js","sourceRoot":"","sources":["../../../src/private/node/otel-metrics.ts"],"names":[],"mappings":"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","sourcesContent":["import {MetricInstrumentType, OtelService} from '../../public/node/vendor/otel-js/service/types.js'\nimport {outputContent, outputDebug, outputToken} from '../../public/node/output.js'\nimport {\n DefaultOtelService,\n DefaultOtelServiceOptions,\n} from '../../public/node/vendor/otel-js/service/DefaultOtelService/DefaultOtelService.js'\nimport {isUnitTest, opentelemetryDomain} from '../../public/node/context/local.js'\nimport {ValueType, diag} from '@opentelemetry/api'\n\ntype MetricRecorder =\n | 'console'\n | {\n type: 'otel'\n otel: Pick<OtelService, 'record'>\n }\n\n// this should be type, not interface\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ntype Labels = {\n exit: string\n job: string\n cli_version: string\n}\n\ninterface Timing {\n active: number\n network: number\n prompt: number\n}\n\nenum Name {\n Counter = 'cli_commands_total',\n Duration = 'cli_commands_duration_ms',\n Elapsed = 'cli_commands_wall_clock_elapsed_ms',\n}\n\ninterface CreateMetricRecorderOptions {\n skipMetricAnalytics: boolean\n otelOptions: Omit<DefaultOtelServiceOptions, 'env' | 'otelEndpoint'>\n}\n\ninterface RecordMetricsOptions {\n /** If true, don't log anything */\n skipMetricAnalytics: boolean\n /** The CLI version running the command */\n cliVersion: string\n /** The plug-in that owns the command */\n owningPlugin: string\n /** The command name, e.g. `app dev` */\n command: string\n /** The exit mode for the command */\n exitMode: string\n}\n\n/**\n * Record reliability metrics.\n */\nexport async function recordMetrics(\n options: RecordMetricsOptions,\n timing: Timing,\n recorderFactory: (options: CreateMetricRecorderOptions) => MetricRecorder = createMetricRecorder,\n) {\n const recorder = recorderFactory({\n skipMetricAnalytics: options.skipMetricAnalytics,\n otelOptions: defaultOtelOptions(),\n })\n\n let regularisedCliVersion = options.cliVersion\n\n if (options.cliVersion.includes('nightly')) {\n regularisedCliVersion = 'nightly'\n } else if (options.cliVersion.includes('pre')) {\n regularisedCliVersion = 'pre'\n }\n const labels = {\n exit: options.exitMode,\n job: `${options.owningPlugin}::${options.command}`,\n cli_version: regularisedCliVersion,\n }\n\n recordCommandCounter(recorder, labels)\n recordCommandTiming(recorder, labels, timing)\n}\n\nconst COMMAND_DURATION_BOUNDARIES_MS = [\n 0, 100, 200, 300, 500, 750, 1000, 1500, 2000, 3000, 5000, 7500, 10_000, 15_000, 20_000, 30_000, 50_000, 70_000,\n 85_000, 100_000,\n]\n\n/**\n * Get the default options for the OTEL service. These are the same across environments.\n */\nfunction defaultOtelOptions(): Omit<DefaultOtelServiceOptions, 'env' | 'otelEndpoint'> {\n return {\n serviceName: 'shopify-cli',\n throttleLimit: 1000,\n prefixMetric: false,\n metrics: {\n [Name.Counter]: {\n type: MetricInstrumentType.Counter,\n description: 'Total number of CLI commands executed',\n valueType: ValueType.INT,\n },\n [Name.Duration]: {\n type: MetricInstrumentType.Histogram,\n description:\n 'Total time spent in execution of CLI commands. Does not include time spent waiting for network, prompts, etc.',\n valueType: ValueType.INT,\n boundaries: COMMAND_DURATION_BOUNDARIES_MS,\n },\n [Name.Elapsed]: {\n type: MetricInstrumentType.Histogram,\n description:\n 'Total time elapsed from start to finish of CLI commands. Includes time spent waiting for network, prompts, etc.',\n valueType: ValueType.INT,\n boundaries: COMMAND_DURATION_BOUNDARIES_MS,\n },\n },\n }\n}\n\n/**\n * Create the metric recorder for this command.\n *\n * If metric logging is disabled, or we are running in a unit test, we record to the console.\n *\n */\nfunction createMetricRecorder(options: CreateMetricRecorderOptions): MetricRecorder {\n let recorder: MetricRecorder = 'console'\n if (!(options.skipMetricAnalytics || isUnitTest())) {\n recorder = {\n type: 'otel',\n otel: globalOtelService(options),\n }\n }\n return recorder\n}\n\nlet _otelService: OtelService | undefined\n\n/**\n * OTEL service singleton.\n *\n * The service is a singleton as it uses a global diagnostic logger that assumes its the only one in the process.\n */\nfunction globalOtelService(options: CreateMetricRecorderOptions): OtelService {\n if (!_otelService) {\n _otelService = new DefaultOtelService({\n ...options.otelOptions,\n env: undefined,\n otelEndpoint: `${opentelemetryDomain()}/v1/metrics`,\n })\n // Suppress OTEL diagnostic output — internal export errors (e.g. retryable failures)\n // should never appear in user-facing CLI output.\n diag.disable()\n }\n return _otelService\n}\n\n/**\n * Log command counter metrics.\n */\nfunction recordCommandCounter(recorder: MetricRecorder, labels: Labels) {\n if (recorder === 'console') {\n outputDebug(outputContent`[OTEL] record ${Name.Counter} counter ${outputToken.json({labels})}`)\n return\n }\n recorder.otel.record(Name.Counter, 1, labels)\n}\n\n/**\n * Log command timing metrics.\n */\nfunction recordCommandTiming(recorder: MetricRecorder, labels: Labels, timing: Timing) {\n if (recorder === 'console') {\n outputDebug(\n outputContent`[OTEL] record ${Name.Duration} histogram ${timing.active.toString()}ms ${outputToken.json({\n labels,\n })}`,\n )\n outputDebug(outputContent`[OTEL] record ${Name.Elapsed} histogram stage=\"active\" ${timing.active.toString()}ms`)\n outputDebug(outputContent`[OTEL] record ${Name.Elapsed} histogram stage=\"network\" ${timing.network.toString()}ms`)\n outputDebug(outputContent`[OTEL] record ${Name.Elapsed} histogram stage=\"prompt\" ${timing.prompt.toString()}ms`)\n return\n }\n\n if (timing.active > 0) {\n recorder.otel.record(Name.Duration, timing.active, labels)\n recorder.otel.record(Name.Elapsed, timing.active, {...labels, stage: 'active'})\n }\n if (timing.network > 0) {\n recorder.otel.record(Name.Elapsed, timing.network, {...labels, stage: 'network'})\n }\n if (timing.prompt > 0) {\n recorder.otel.record(Name.Elapsed, timing.prompt, {...labels, stage: 'prompt'})\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"otel-metrics.js","sourceRoot":"","sources":["../../../src/private/node/otel-metrics.ts"],"names":[],"mappings":"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","sourcesContent":["import {MetricInstrumentType, OtelService} from '../../public/node/vendor/otel-js/service/types.js'\nimport {outputContent, outputDebug, outputToken} from '../../public/node/output.js'\nimport {\n DefaultOtelService,\n DefaultOtelServiceOptions,\n} from '../../public/node/vendor/otel-js/service/DefaultOtelService/DefaultOtelService.js'\nimport {isUnitTest, opentelemetryDomain} from '../../public/node/context/local.js'\nimport {ValueType, diag} from '@opentelemetry/api'\n\ntype MetricRecorder =\n | 'console'\n | {\n type: 'otel'\n otel: Pick<OtelService, 'getMeterProvider' | 'record'>\n }\n\n// this should be type, not interface\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ntype Labels = {\n exit: string\n job: string\n cli_version: string\n}\n\ninterface Timing {\n active: number\n network: number\n prompt: number\n}\n\nenum Name {\n Counter = 'cli_commands_total',\n Duration = 'cli_commands_duration_ms',\n Elapsed = 'cli_commands_wall_clock_elapsed_ms',\n}\n\ninterface CreateMetricRecorderOptions {\n skipMetricAnalytics: boolean\n otelOptions: Omit<DefaultOtelServiceOptions, 'env' | 'otelEndpoint'>\n}\n\ninterface RecordMetricsOptions {\n /** If true, don't log anything */\n skipMetricAnalytics: boolean\n /** The CLI version running the command */\n cliVersion: string\n /** The plug-in that owns the command */\n owningPlugin: string\n /** The command name, e.g. `app dev` */\n command: string\n /** The exit mode for the command */\n exitMode: string\n}\n\n/**\n * Record reliability metrics.\n */\nexport async function recordMetrics(\n options: RecordMetricsOptions,\n timing: Timing,\n recorderFactory: (options: CreateMetricRecorderOptions) => MetricRecorder = createMetricRecorder,\n) {\n const recorder = recorderFactory({\n skipMetricAnalytics: options.skipMetricAnalytics,\n otelOptions: defaultOtelOptions(),\n })\n\n let regularisedCliVersion = options.cliVersion\n\n if (options.cliVersion.includes('nightly')) {\n regularisedCliVersion = 'nightly'\n } else if (options.cliVersion.includes('pre')) {\n regularisedCliVersion = 'pre'\n }\n const labels = {\n exit: options.exitMode,\n job: `${options.owningPlugin}::${options.command}`,\n cli_version: regularisedCliVersion,\n }\n\n recordCommandCounter(recorder, labels)\n recordCommandTiming(recorder, labels, timing)\n if (recorder !== 'console') {\n await recorder.otel.getMeterProvider().forceFlush({})\n }\n}\n\nconst COMMAND_DURATION_BOUNDARIES_MS = [\n 0, 100, 200, 300, 500, 750, 1000, 1500, 2000, 3000, 5000, 7500, 10_000, 15_000, 20_000, 30_000, 50_000, 70_000,\n 85_000, 100_000,\n]\n\n/**\n * Get the default options for the OTEL service. These are the same across environments.\n */\nfunction defaultOtelOptions(): Omit<DefaultOtelServiceOptions, 'env' | 'otelEndpoint'> {\n return {\n serviceName: 'shopify-cli',\n throttleLimit: 1000,\n prefixMetric: false,\n metrics: {\n [Name.Counter]: {\n type: MetricInstrumentType.Counter,\n description: 'Total number of CLI commands executed',\n valueType: ValueType.INT,\n },\n [Name.Duration]: {\n type: MetricInstrumentType.Histogram,\n description:\n 'Total time spent in execution of CLI commands. Does not include time spent waiting for network, prompts, etc.',\n valueType: ValueType.INT,\n boundaries: COMMAND_DURATION_BOUNDARIES_MS,\n },\n [Name.Elapsed]: {\n type: MetricInstrumentType.Histogram,\n description:\n 'Total time elapsed from start to finish of CLI commands. Includes time spent waiting for network, prompts, etc.',\n valueType: ValueType.INT,\n boundaries: COMMAND_DURATION_BOUNDARIES_MS,\n },\n },\n }\n}\n\n/**\n * Create the metric recorder for this command.\n *\n * If metric logging is disabled, or we are running in a unit test, we record to the console.\n *\n */\nfunction createMetricRecorder(options: CreateMetricRecorderOptions): MetricRecorder {\n let recorder: MetricRecorder = 'console'\n if (!(options.skipMetricAnalytics || isUnitTest())) {\n recorder = {\n type: 'otel',\n otel: globalOtelService(options),\n }\n }\n return recorder\n}\n\nlet _otelService: OtelService | undefined\n\n/**\n * OTEL service singleton.\n *\n * The service is a singleton as it uses a global diagnostic logger that assumes its the only one in the process.\n */\nfunction globalOtelService(options: CreateMetricRecorderOptions): OtelService {\n if (!_otelService) {\n _otelService = new DefaultOtelService({\n ...options.otelOptions,\n env: undefined,\n otelEndpoint: `${opentelemetryDomain()}/v1/metrics`,\n })\n // Suppress OTEL diagnostic output — internal export errors (e.g. retryable failures)\n // should never appear in user-facing CLI output.\n diag.disable()\n }\n return _otelService\n}\n\n/**\n * Log command counter metrics.\n */\nfunction recordCommandCounter(recorder: MetricRecorder, labels: Labels) {\n if (recorder === 'console') {\n outputDebug(outputContent`[OTEL] record ${Name.Counter} counter ${outputToken.json({labels})}`)\n return\n }\n recorder.otel.record(Name.Counter, 1, labels)\n}\n\n/**\n * Log command timing metrics.\n */\nfunction recordCommandTiming(recorder: MetricRecorder, labels: Labels, timing: Timing) {\n if (recorder === 'console') {\n outputDebug(\n outputContent`[OTEL] record ${Name.Duration} histogram ${timing.active.toString()}ms ${outputToken.json({\n labels,\n })}`,\n )\n outputDebug(outputContent`[OTEL] record ${Name.Elapsed} histogram stage=\"active\" ${timing.active.toString()}ms`)\n outputDebug(outputContent`[OTEL] record ${Name.Elapsed} histogram stage=\"network\" ${timing.network.toString()}ms`)\n outputDebug(outputContent`[OTEL] record ${Name.Elapsed} histogram stage=\"prompt\" ${timing.prompt.toString()}ms`)\n return\n }\n\n if (timing.active > 0) {\n recorder.otel.record(Name.Duration, timing.active, labels)\n recorder.otel.record(Name.Elapsed, timing.active, {...labels, stage: 'active'})\n }\n if (timing.network > 0) {\n recorder.otel.record(Name.Elapsed, timing.network, {...labels, stage: 'network'})\n }\n if (timing.prompt > 0) {\n recorder.otel.record(Name.Elapsed, timing.prompt, {...labels, stage: 'prompt'})\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The Claude Code plugin hint protocol marker. */
|
|
2
|
+
export declare const SHOPIFY_AI_TOOLKIT_PLUGIN_HINT = "<claude-code-hint v=\"1\" type=\"plugin\" value=\"shopify-ai-toolkit@claude-plugins-official\" />";
|
|
3
|
+
/**
|
|
4
|
+
* Returns whether this process was launched by Claude Code.
|
|
5
|
+
*
|
|
6
|
+
* @param environment - Environment variables to inspect.
|
|
7
|
+
* @returns Whether Claude Code environment markers are truthy.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runningUnderClaudeCode(environment?: NodeJS.ProcessEnv): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Emits the Claude Code plugin hint on every command invocation under Claude Code.
|
|
12
|
+
* Claude Code handles deduplication and persistence.
|
|
13
|
+
*
|
|
14
|
+
* @param environment - Environment variables to inspect.
|
|
15
|
+
*/
|
|
16
|
+
export declare function emitClaudeCodePluginHint(environment?: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// This module is imported statically from the prerun hook, which runs before the CLI's
|
|
2
|
+
// heavy modules load. Keep it dependency-free (isTruthy has no imports) so it adds no
|
|
3
|
+
// measurable startup cost.
|
|
4
|
+
import { isTruthy } from '../../public/node/context/utilities.js';
|
|
5
|
+
/** The Claude Code plugin hint protocol marker. */
|
|
6
|
+
export const SHOPIFY_AI_TOOLKIT_PLUGIN_HINT = '<claude-code-hint v="1" type="plugin" value="shopify-ai-toolkit@claude-plugins-official" />';
|
|
7
|
+
/**
|
|
8
|
+
* Returns whether this process was launched by Claude Code.
|
|
9
|
+
*
|
|
10
|
+
* @param environment - Environment variables to inspect.
|
|
11
|
+
* @returns Whether Claude Code environment markers are truthy.
|
|
12
|
+
*/
|
|
13
|
+
export function runningUnderClaudeCode(environment = process.env) {
|
|
14
|
+
return isTruthy(environment.CLAUDECODE) || isTruthy(environment.CLAUDE_CODE_CHILD_SESSION);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Emits the Claude Code plugin hint on every command invocation under Claude Code.
|
|
18
|
+
* Claude Code handles deduplication and persistence.
|
|
19
|
+
*
|
|
20
|
+
* @param environment - Environment variables to inspect.
|
|
21
|
+
*/
|
|
22
|
+
export function emitClaudeCodePluginHint(environment = process.env) {
|
|
23
|
+
if (!runningUnderClaudeCode(environment))
|
|
24
|
+
return;
|
|
25
|
+
// stderr.write doesn't throw synchronously on supported Node versions; stream failures
|
|
26
|
+
// surface as async 'error' events that a try/catch here couldn't intercept anyway.
|
|
27
|
+
process.stderr.write(`${SHOPIFY_AI_TOOLKIT_PLUGIN_HINT}\n`);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=plugin-hints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-hints.js","sourceRoot":"","sources":["../../../src/private/node/plugin-hints.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,sFAAsF;AACtF,2BAA2B;AAC3B,OAAO,EAAC,QAAQ,EAAC,MAAM,wCAAwC,CAAA;AAE/D,mDAAmD;AACnD,MAAM,CAAC,MAAM,8BAA8B,GACzC,6FAA6F,CAAA;AAE/F;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,cAAiC,OAAO,CAAC,GAAG;IACjF,OAAO,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAA;AAC5F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,cAAiC,OAAO,CAAC,GAAG;IACnF,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC;QAAE,OAAM;IAEhD,uFAAuF;IACvF,mFAAmF;IACnF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,8BAA8B,IAAI,CAAC,CAAA;AAC7D,CAAC","sourcesContent":["// This module is imported statically from the prerun hook, which runs before the CLI's\n// heavy modules load. Keep it dependency-free (isTruthy has no imports) so it adds no\n// measurable startup cost.\nimport {isTruthy} from '../../public/node/context/utilities.js'\n\n/** The Claude Code plugin hint protocol marker. */\nexport const SHOPIFY_AI_TOOLKIT_PLUGIN_HINT =\n '<claude-code-hint v=\"1\" type=\"plugin\" value=\"shopify-ai-toolkit@claude-plugins-official\" />'\n\n/**\n * Returns whether this process was launched by Claude Code.\n *\n * @param environment - Environment variables to inspect.\n * @returns Whether Claude Code environment markers are truthy.\n */\nexport function runningUnderClaudeCode(environment: NodeJS.ProcessEnv = process.env): boolean {\n return isTruthy(environment.CLAUDECODE) || isTruthy(environment.CLAUDE_CODE_CHILD_SESSION)\n}\n\n/**\n * Emits the Claude Code plugin hint on every command invocation under Claude Code.\n * Claude Code handles deduplication and persistence.\n *\n * @param environment - Environment variables to inspect.\n */\nexport function emitClaudeCodePluginHint(environment: NodeJS.ProcessEnv = process.env): void {\n if (!runningUnderClaudeCode(environment)) return\n\n // stderr.write doesn't throw synchronously on supported Node versions; stream failures\n // surface as async 'error' events that a try/catch here couldn't intercept anyway.\n process.stderr.write(`${SHOPIFY_AI_TOOLKIT_PLUGIN_HINT}\\n`)\n}\n"]}
|
|
@@ -3,15 +3,14 @@ export const MAX_REQUEST_IDS = 100;
|
|
|
3
3
|
* Manages collection of request IDs during command execution
|
|
4
4
|
*/
|
|
5
5
|
class RequestIDCollection {
|
|
6
|
-
|
|
7
|
-
this.requestIds = [];
|
|
8
|
-
}
|
|
6
|
+
static instance;
|
|
9
7
|
static getInstance() {
|
|
10
8
|
if (!RequestIDCollection.instance) {
|
|
11
9
|
RequestIDCollection.instance = new RequestIDCollection();
|
|
12
10
|
}
|
|
13
11
|
return RequestIDCollection.instance;
|
|
14
12
|
}
|
|
13
|
+
requestIds = [];
|
|
15
14
|
/**
|
|
16
15
|
* Add a request ID to the collection
|
|
17
16
|
* We only report the first MAX_REQUEST_IDS request IDs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-ids.js","sourceRoot":"","sources":["../../../src/private/node/request-ids.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC;;GAEG;AACH,MAAM,mBAAmB;
|
|
1
|
+
{"version":3,"file":"request-ids.js","sourceRoot":"","sources":["../../../src/private/node/request-ids.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC;;GAEG;AACH,MAAM,mBAAmB;IACf,MAAM,CAAC,QAAQ,CAAqB;IAE5C,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,CAAC;YAClC,mBAAmB,CAAC,QAAQ,GAAG,IAAI,mBAAmB,EAAE,CAAA;QAC1D,CAAC;QACD,OAAO,mBAAmB,CAAC,QAAQ,CAAA;IACrC,CAAC;IAEO,UAAU,GAAa,EAAE,CAAA;IAEjC;;;OAGG;IACH,YAAY,CAAC,SAAoC;QAC/C,IAAI,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;YAC1D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAA;IACxB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;IACtB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,WAAW,EAAE,CAAA","sourcesContent":["export const MAX_REQUEST_IDS = 100\n\n/**\n * Manages collection of request IDs during command execution\n */\nclass RequestIDCollection {\n private static instance: RequestIDCollection\n\n static getInstance(): RequestIDCollection {\n if (!RequestIDCollection.instance) {\n RequestIDCollection.instance = new RequestIDCollection()\n }\n return RequestIDCollection.instance\n }\n\n private requestIds: string[] = []\n\n /**\n * Add a request ID to the collection\n * We only report the first MAX_REQUEST_IDS request IDs.\n */\n addRequestId(requestId: string | undefined | null) {\n if (requestId && this.requestIds.length < MAX_REQUEST_IDS) {\n this.requestIds.push(requestId)\n }\n }\n\n /**\n * Get all collected request IDs\n */\n getRequestIds(): string[] {\n return this.requestIds\n }\n\n /**\n * Clear all stored request IDs\n */\n clear() {\n this.requestIds = []\n }\n}\n\nexport const requestIdsCollection = RequestIDCollection.getInstance()\n"]}
|
|
@@ -44,7 +44,7 @@ export async function requestDeviceAuthorization(scopes) {
|
|
|
44
44
|
const errorMessage = buildAuthorizationParseErrorMessage(response, responseText);
|
|
45
45
|
throw new BugError(errorMessage);
|
|
46
46
|
}
|
|
47
|
-
outputDebug(outputContent `Received device authorization
|
|
47
|
+
outputDebug(outputContent `Received device authorization response (HTTP ${String(response.status)}): interval=${String(jsonResult.interval ?? 'not provided')}, expires_in=${String(jsonResult.expires_in ?? 'not provided')}`);
|
|
48
48
|
if (!jsonResult.device_code || !jsonResult.verification_uri_complete) {
|
|
49
49
|
throw new BugError('Failed to start authorization process');
|
|
50
50
|
}
|
|
@@ -107,9 +107,9 @@ export async function pollForDeviceAuthorization(code, interval = 5) {
|
|
|
107
107
|
case 'expired_token':
|
|
108
108
|
reject(new AbortError(`Device authorization failed: Token expired. Please try again.`));
|
|
109
109
|
return;
|
|
110
|
-
case 'unknown_failure':
|
|
110
|
+
case 'unknown_failure':
|
|
111
|
+
default:
|
|
111
112
|
reject(new Error(`Device authorization failed: ${error}`));
|
|
112
|
-
}
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
const startPolling = () => {
|
|
@@ -120,10 +120,7 @@ export async function pollForDeviceAuthorization(code, interval = 5) {
|
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
function convertRequestToParams(queryParams) {
|
|
123
|
-
return Object.entries(queryParams)
|
|
124
|
-
.map(([key, value]) => value && `${key}=${value}`)
|
|
125
|
-
.filter((hasValue) => Boolean(hasValue))
|
|
126
|
-
.join('&');
|
|
123
|
+
return new URLSearchParams(Object.entries(queryParams).filter(([, value]) => Boolean(value))).toString();
|
|
127
124
|
}
|
|
128
125
|
/**
|
|
129
126
|
* Build a detailed error message for JSON parsing failures from the authorization service.
|
|
@@ -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(outputContent`Received device authorization code: ${outputToken.json(jsonResult)}`)\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 reject(new Error(`Device authorization failed: ${error}`))\n }\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 Object.entries(queryParams)\n .map(([key, value]) => value && `${key}=${value}`)\n .filter((hasValue) => Boolean(hasValue))\n .join('&')\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 {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,10 +1,9 @@
|
|
|
1
1
|
import { ApplicationToken, IdentityToken } from './schema.js';
|
|
2
2
|
import { API } from '../api.js';
|
|
3
3
|
import { Result } from '../../../public/node/result.js';
|
|
4
|
-
|
|
5
|
-
export declare class InvalidGrantError extends ExtendableError {
|
|
4
|
+
export declare class InvalidGrantError extends Error {
|
|
6
5
|
}
|
|
7
|
-
export declare class InvalidRequestError extends
|
|
6
|
+
export declare class InvalidRequestError extends Error {
|
|
8
7
|
}
|
|
9
8
|
export interface ExchangeScopes {
|
|
10
9
|
admin: string[];
|
|
@@ -52,7 +51,8 @@ export declare function exchangeAppAutomationTokenForBusinessPlatformAccessToken
|
|
|
52
51
|
accessToken: string;
|
|
53
52
|
userId: string;
|
|
54
53
|
}>;
|
|
55
|
-
|
|
54
|
+
declare const identityDeviceErrors: readonly ["authorization_pending", "access_denied", "expired_token", "slow_down", "unknown_failure"];
|
|
55
|
+
type IdentityDeviceError = (typeof identityDeviceErrors)[number];
|
|
56
56
|
/**
|
|
57
57
|
* Given a deviceCode obtained after starting a device identity flow, request an identity token.
|
|
58
58
|
* @param deviceCode - The device code obtained after starting a device identity flow
|
|
@@ -3,13 +3,14 @@ import { tokenExchangeScopes } from './scopes.js';
|
|
|
3
3
|
import { identityFqdn } from '../../../public/node/context/fqdn.js';
|
|
4
4
|
import { shopifyFetch } from '../../../public/node/http.js';
|
|
5
5
|
import { err, ok } from '../../../public/node/result.js';
|
|
6
|
-
import { AbortError, BugError
|
|
6
|
+
import { AbortError, BugError } from '../../../public/node/error.js';
|
|
7
7
|
import { setLastSeenAuthMethod, setLastSeenUserIdAfterAuth } from '../session.js';
|
|
8
8
|
import { nonRandomUUID } from '../../../public/node/crypto.js';
|
|
9
|
+
import { outputDebug } from '../../../public/node/output.js';
|
|
9
10
|
import * as jose from 'jose';
|
|
10
|
-
export class InvalidGrantError extends
|
|
11
|
+
export class InvalidGrantError extends Error {
|
|
11
12
|
}
|
|
12
|
-
export class InvalidRequestError extends
|
|
13
|
+
export class InvalidRequestError extends Error {
|
|
13
14
|
}
|
|
14
15
|
class InvalidTargetError extends AbortError {
|
|
15
16
|
}
|
|
@@ -70,6 +71,8 @@ async function exchangeAppAutomationTokenForAccessToken(apiName, token, scopes)
|
|
|
70
71
|
}
|
|
71
72
|
catch (error) {
|
|
72
73
|
const prettyName = apiName.replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
74
|
+
const reason = error instanceof Error ? error.message.slice(0, 200) : 'unknown error';
|
|
75
|
+
outputDebug(`Token exchange for the ${prettyName} API failed: ${reason}`);
|
|
73
76
|
throw new AbortError(`The custom token provided can't be used for the ${prettyName} API.`, 'Ensure the token is correct and not expired.');
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -98,6 +101,13 @@ export async function exchangeAppAutomationTokenForAppManagementAccessToken(toke
|
|
|
98
101
|
export async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token) {
|
|
99
102
|
return exchangeAppAutomationTokenForAccessToken('business-platform', token, tokenExchangeScopes('business-platform'));
|
|
100
103
|
}
|
|
104
|
+
const identityDeviceErrors = [
|
|
105
|
+
'authorization_pending',
|
|
106
|
+
'access_denied',
|
|
107
|
+
'expired_token',
|
|
108
|
+
'slow_down',
|
|
109
|
+
'unknown_failure',
|
|
110
|
+
];
|
|
101
111
|
/**
|
|
102
112
|
* Given a deviceCode obtained after starting a device identity flow, request an identity token.
|
|
103
113
|
* @param deviceCode - The device code obtained after starting a device identity flow
|
|
@@ -113,7 +123,8 @@ export async function exchangeDeviceCodeForAccessToken(deviceCode) {
|
|
|
113
123
|
};
|
|
114
124
|
const tokenResult = await tokenRequest(params);
|
|
115
125
|
if (tokenResult.isErr()) {
|
|
116
|
-
|
|
126
|
+
const deviceError = identityDeviceErrors.find((code) => code === tokenResult.error.error) ?? 'unknown_failure';
|
|
127
|
+
return err(deviceError);
|
|
117
128
|
}
|
|
118
129
|
const identityToken = buildIdentityToken(tokenResult.value);
|
|
119
130
|
return ok(identityToken);
|
|
@@ -180,7 +191,12 @@ async function tokenRequest(params) {
|
|
|
180
191
|
const payload = JSON.parse(responseText);
|
|
181
192
|
if (res.ok)
|
|
182
193
|
return ok(payload);
|
|
183
|
-
|
|
194
|
+
// Normalize malformed OAuth error responses before logging.
|
|
195
|
+
const oauthError = typeof payload.error === 'string' && payload.error !== '' ? payload.error : 'unknown_error';
|
|
196
|
+
const rawDescription = typeof payload.error_description === 'string' ? payload.error_description : '';
|
|
197
|
+
const oauthDescription = rawDescription.slice(0, 200);
|
|
198
|
+
outputDebug(`Token request to Identity failed with status ${res.status}: ${oauthError}${oauthDescription === '' ? '' : ` - ${oauthDescription}`}`);
|
|
199
|
+
return err({ error: oauthError, store: params.store });
|
|
184
200
|
}
|
|
185
201
|
catch (error) {
|
|
186
202
|
if (error instanceof SyntaxError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../../../src/private/node/session/exchange.ts"],"names":[],"mappings":"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","sourcesContent":["import {ApplicationToken, IdentityToken} from './schema.js'\nimport {applicationId, clientId as getIdentityClientId} from './identity.js'\nimport {tokenExchangeScopes} from './scopes.js'\nimport {API} from '../api.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport {shopifyFetch} from '../../../public/node/http.js'\nimport {err, ok, Result} from '../../../public/node/result.js'\nimport {AbortError, BugError, ExtendableError} from '../../../public/node/error.js'\nimport {setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../session.js'\nimport {nonRandomUUID} from '../../../public/node/crypto.js'\n\nimport * as jose from 'jose'\n\nexport class InvalidGrantError extends ExtendableError {}\nexport class InvalidRequestError extends ExtendableError {}\nclass InvalidTargetError extends AbortError {}\n\nexport interface ExchangeScopes {\n admin: string[]\n partners: string[]\n storefront: string[]\n businessPlatform: string[]\n appManagement: string[]\n}\n\n/**\n * Given an identity token, request an application token.\n * @param identityToken - access token obtained in a previous step\n * @param store - the store to use, only needed for admin API\n * @returns An array with the application access tokens.\n */\nexport async function exchangeAccessForApplicationTokens(\n identityToken: IdentityToken,\n scopes: ExchangeScopes,\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const token = identityToken.accessToken\n\n const [partners, storefront, businessPlatform, admin, appManagement] = await Promise.all([\n requestAppToken('partners', token, scopes.partners),\n requestAppToken('storefront-renderer', token, scopes.storefront),\n requestAppToken('business-platform', token, scopes.businessPlatform),\n store ? requestAppToken('admin', token, scopes.admin, store) : {},\n requestAppToken('app-management', token, scopes.appManagement),\n ])\n\n return {\n ...partners,\n ...storefront,\n ...businessPlatform,\n ...admin,\n ...appManagement,\n }\n}\n\n/**\n * Given an expired access token, refresh it to get a new one.\n */\nexport async function refreshAccessToken(currentToken: IdentityToken): Promise<IdentityToken> {\n const clientId = getIdentityClientId()\n const params = {\n grant_type: 'refresh_token',\n access_token: currentToken.accessToken,\n refresh_token: currentToken.refreshToken,\n client_id: clientId,\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n return buildIdentityToken(value, currentToken.userId, currentToken.alias)\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid API access token.\n * @param apiName - The API to exchange for the access token\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @param scopes - The scopes to request with the access token\n * @returns An instance with the application access tokens.\n */\nasync function exchangeAppAutomationTokenForAccessToken(\n apiName: API,\n token: string,\n scopes: string[],\n): Promise<{accessToken: string; userId: string}> {\n const appId = applicationId(apiName)\n try {\n const newToken = await requestAppToken(apiName, token, scopes)\n\n const accessToken = newToken[appId]!.accessToken\n const userId = nonRandomUUID(token)\n setLastSeenUserIdAfterAuth(userId)\n setLastSeenAuthMethod('partners_token')\n return {accessToken, userId}\n } catch (error) {\n const prettyName = apiName.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n throw new AbortError(\n `The custom token provided can't be used for the ${prettyName} API.`,\n 'Ensure the token is correct and not expired.',\n )\n }\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Partners API token.\n * This token does not accept extra scopes, just the cli one.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeCustomPartnerToken(token: string): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('partners', token, tokenExchangeScopes('partners'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid App Management API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForAppManagementAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('app-management', token, tokenExchangeScopes('app-management'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('business-platform', token, tokenExchangeScopes('business-platform'))\n}\n\ntype IdentityDeviceError = 'authorization_pending' | 'access_denied' | 'expired_token' | 'slow_down' | 'unknown_failure'\n\n/**\n * Given a deviceCode obtained after starting a device identity flow, request an identity token.\n * @param deviceCode - The device code obtained after starting a device identity flow\n * @param scopes - The scopes to request\n * @returns An instance with the identity access tokens.\n */\nexport async function exchangeDeviceCodeForAccessToken(\n deviceCode: string,\n): Promise<Result<IdentityToken, IdentityDeviceError>> {\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: clientId,\n }\n\n const tokenResult = await tokenRequest(params)\n if (tokenResult.isErr()) {\n return err(tokenResult.error.error as IdentityDeviceError)\n }\n const identityToken = buildIdentityToken(tokenResult.value)\n return ok(identityToken)\n}\n\nexport async function requestAppToken(\n api: API,\n token: string,\n scopes: string[] = [],\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const appId = applicationId(api)\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',\n requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n client_id: clientId,\n audience: appId,\n scope: scopes.join(' '),\n subject_token: token,\n ...(api === 'admin' && {destination: `https://${store}/admin`, store}),\n }\n\n let identifier = appId\n if (api === 'admin' && store) {\n identifier = `${store}-${appId}`\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n const appToken = buildApplicationToken(value)\n return {[identifier]: appToken}\n}\n\ninterface TokenRequestResult {\n access_token: string\n expires_in: number\n refresh_token: string\n scope: string\n id_token?: string\n}\n\nfunction tokenRequestErrorHandler({error, store}: {error: string; store?: string}) {\n const invalidTargetErrorMessage = `You are not authorized to use the CLI to develop in the provided store${\n store ? `: ${store}` : '.'\n }`\n\n if (error === 'invalid_grant') {\n // There's an scenario when Identity returns \"invalid_grant\" when trying to refresh the token\n // using a valid refresh token. When that happens, we take the user through the authentication flow.\n return new InvalidGrantError()\n }\n if (error === 'invalid_request') {\n // There's an scenario when Identity returns \"invalid_request\" when exchanging an identity token.\n // This means the token is invalid. We clear the session and throw an error to let the caller know.\n return new InvalidRequestError()\n }\n if (error === 'invalid_target') {\n return new InvalidTargetError(invalidTargetErrorMessage, '', [\n 'Ensure you have logged in to the store using the Shopify admin at least once.',\n 'Ensure you are the store owner, or have a staff account if you are attempting to log in to a dev store.',\n 'Ensure you are using the permanent store domain, not a vanity domain.',\n ])\n }\n // eslint-disable-next-line @shopify/cli/no-error-factory-functions\n return new AbortError(error)\n}\n\nasync function tokenRequest(\n params: Record<string, string>,\n): Promise<Result<TokenRequestResult, {error: string; store?: string}>> {\n const fqdn = await identityFqdn()\n const url = `https://${fqdn}/oauth/token`\n\n // Send OAuth parameters in the request body (per RFC 6749) rather than the\n // URL query string. This prevents sensitive credentials (subject_token,\n // refresh_token, device_code, client_id, etc.) from being written to\n // verbose CLI debug output or otherwise leaking through URLs.\n const body = new URLSearchParams(Object.entries(params)).toString()\n\n const res = await shopifyFetch(url, {\n method: 'POST',\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n body,\n })\n try {\n const responseText = await res.text()\n\n const payload = JSON.parse(responseText)\n if (res.ok) return ok(payload)\n\n return err({error: payload.error, store: params.store})\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new AbortError(\n `Received invalid response from authentication service (HTTP ${res.status}).`,\n 'The response could not be parsed as JSON. The service may be temporarily unavailable. Please try again.',\n )\n }\n throw error\n }\n}\n\nfunction buildIdentityToken(\n result: TokenRequestResult,\n existingUserId?: string,\n existingAlias?: string,\n): IdentityToken {\n const userId = existingUserId ?? (result.id_token ? jose.decodeJwt(result.id_token).sub! : undefined)\n\n if (!userId) {\n throw new BugError('Error setting userId for session. No id_token or pre-existing user ID provided.')\n }\n\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n userId,\n alias: existingAlias,\n }\n}\n\nfunction buildApplicationToken(result: TokenRequestResult): ApplicationToken {\n return {\n accessToken: result.access_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../../../src/private/node/session/exchange.ts"],"names":[],"mappings":"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","sourcesContent":["import {ApplicationToken, IdentityToken} from './schema.js'\nimport {applicationId, clientId as getIdentityClientId} from './identity.js'\nimport {tokenExchangeScopes} from './scopes.js'\nimport {API} from '../api.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport {shopifyFetch} from '../../../public/node/http.js'\nimport {err, ok, Result} from '../../../public/node/result.js'\nimport {AbortError, BugError} from '../../../public/node/error.js'\nimport {setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../session.js'\nimport {nonRandomUUID} from '../../../public/node/crypto.js'\nimport {outputDebug} from '../../../public/node/output.js'\n\nimport * as jose from 'jose'\n\nexport class InvalidGrantError extends Error {}\nexport class InvalidRequestError extends Error {}\nclass InvalidTargetError extends AbortError {}\n\nexport interface ExchangeScopes {\n admin: string[]\n partners: string[]\n storefront: string[]\n businessPlatform: string[]\n appManagement: string[]\n}\n\n/**\n * Given an identity token, request an application token.\n * @param identityToken - access token obtained in a previous step\n * @param store - the store to use, only needed for admin API\n * @returns An array with the application access tokens.\n */\nexport async function exchangeAccessForApplicationTokens(\n identityToken: IdentityToken,\n scopes: ExchangeScopes,\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const token = identityToken.accessToken\n\n const [partners, storefront, businessPlatform, admin, appManagement] = await Promise.all([\n requestAppToken('partners', token, scopes.partners),\n requestAppToken('storefront-renderer', token, scopes.storefront),\n requestAppToken('business-platform', token, scopes.businessPlatform),\n store ? requestAppToken('admin', token, scopes.admin, store) : {},\n requestAppToken('app-management', token, scopes.appManagement),\n ])\n\n return {\n ...partners,\n ...storefront,\n ...businessPlatform,\n ...admin,\n ...appManagement,\n }\n}\n\n/**\n * Given an expired access token, refresh it to get a new one.\n */\nexport async function refreshAccessToken(currentToken: IdentityToken): Promise<IdentityToken> {\n const clientId = getIdentityClientId()\n const params = {\n grant_type: 'refresh_token',\n access_token: currentToken.accessToken,\n refresh_token: currentToken.refreshToken,\n client_id: clientId,\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n return buildIdentityToken(value, currentToken.userId, currentToken.alias)\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid API access token.\n * @param apiName - The API to exchange for the access token\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @param scopes - The scopes to request with the access token\n * @returns An instance with the application access tokens.\n */\nasync function exchangeAppAutomationTokenForAccessToken(\n apiName: API,\n token: string,\n scopes: string[],\n): Promise<{accessToken: string; userId: string}> {\n const appId = applicationId(apiName)\n try {\n const newToken = await requestAppToken(apiName, token, scopes)\n\n const accessToken = newToken[appId]!.accessToken\n const userId = nonRandomUUID(token)\n setLastSeenUserIdAfterAuth(userId)\n setLastSeenAuthMethod('partners_token')\n return {accessToken, userId}\n } catch (error) {\n const prettyName = apiName.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n const reason = error instanceof Error ? error.message.slice(0, 200) : 'unknown error'\n outputDebug(`Token exchange for the ${prettyName} API failed: ${reason}`)\n throw new AbortError(\n `The custom token provided can't be used for the ${prettyName} API.`,\n 'Ensure the token is correct and not expired.',\n )\n }\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Partners API token.\n * This token does not accept extra scopes, just the cli one.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeCustomPartnerToken(token: string): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('partners', token, tokenExchangeScopes('partners'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid App Management API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForAppManagementAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('app-management', token, tokenExchangeScopes('app-management'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('business-platform', token, tokenExchangeScopes('business-platform'))\n}\n\nconst identityDeviceErrors = [\n 'authorization_pending',\n 'access_denied',\n 'expired_token',\n 'slow_down',\n 'unknown_failure',\n] as const\n\ntype IdentityDeviceError = (typeof identityDeviceErrors)[number]\n\n/**\n * Given a deviceCode obtained after starting a device identity flow, request an identity token.\n * @param deviceCode - The device code obtained after starting a device identity flow\n * @param scopes - The scopes to request\n * @returns An instance with the identity access tokens.\n */\nexport async function exchangeDeviceCodeForAccessToken(\n deviceCode: string,\n): Promise<Result<IdentityToken, IdentityDeviceError>> {\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: clientId,\n }\n\n const tokenResult = await tokenRequest(params)\n if (tokenResult.isErr()) {\n const deviceError = identityDeviceErrors.find((code) => code === tokenResult.error.error) ?? 'unknown_failure'\n return err(deviceError)\n }\n const identityToken = buildIdentityToken(tokenResult.value)\n return ok(identityToken)\n}\n\nexport async function requestAppToken(\n api: API,\n token: string,\n scopes: string[] = [],\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const appId = applicationId(api)\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',\n requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n client_id: clientId,\n audience: appId,\n scope: scopes.join(' '),\n subject_token: token,\n ...(api === 'admin' && {destination: `https://${store}/admin`, store}),\n }\n\n let identifier = appId\n if (api === 'admin' && store) {\n identifier = `${store}-${appId}`\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n const appToken = buildApplicationToken(value)\n return {[identifier]: appToken}\n}\n\ninterface TokenRequestResult {\n access_token: string\n expires_in: number\n refresh_token: string\n scope: string\n id_token?: string\n}\n\nfunction tokenRequestErrorHandler({error, store}: TokenRequestError) {\n const invalidTargetErrorMessage = `You are not authorized to use the CLI to develop in the provided store${\n store ? `: ${store}` : '.'\n }`\n\n if (error === 'invalid_grant') {\n // There's an scenario when Identity returns \"invalid_grant\" when trying to refresh the token\n // using a valid refresh token. When that happens, we take the user through the authentication flow.\n return new InvalidGrantError()\n }\n if (error === 'invalid_request') {\n // There's an scenario when Identity returns \"invalid_request\" when exchanging an identity token.\n // This means the token is invalid. We clear the session and throw an error to let the caller know.\n return new InvalidRequestError()\n }\n if (error === 'invalid_target') {\n return new InvalidTargetError(invalidTargetErrorMessage, '', [\n 'Ensure you have logged in to the store using the Shopify admin at least once.',\n 'Ensure you are the store owner, or have a staff account if you are attempting to log in to a dev store.',\n 'Ensure you are using the permanent store domain, not a vanity domain.',\n ])\n }\n // eslint-disable-next-line @shopify/cli/no-error-factory-functions\n return new AbortError(error)\n}\n\ninterface TokenRequestError {\n error: string\n store?: string\n}\n\nasync function tokenRequest(params: Record<string, string>): Promise<Result<TokenRequestResult, TokenRequestError>> {\n const fqdn = await identityFqdn()\n const url = `https://${fqdn}/oauth/token`\n\n // Send OAuth parameters in the request body (per RFC 6749) rather than the\n // URL query string. This prevents sensitive credentials (subject_token,\n // refresh_token, device_code, client_id, etc.) from being written to\n // verbose CLI debug output or otherwise leaking through URLs.\n const body = new URLSearchParams(Object.entries(params)).toString()\n\n const res = await shopifyFetch(url, {\n method: 'POST',\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n body,\n })\n try {\n const responseText = await res.text()\n\n const payload = JSON.parse(responseText)\n if (res.ok) return ok(payload)\n\n // Normalize malformed OAuth error responses before logging.\n const oauthError = typeof payload.error === 'string' && payload.error !== '' ? payload.error : 'unknown_error'\n const rawDescription = typeof payload.error_description === 'string' ? payload.error_description : ''\n const oauthDescription = rawDescription.slice(0, 200)\n\n outputDebug(\n `Token request to Identity failed with status ${res.status}: ${oauthError}${\n oauthDescription === '' ? '' : ` - ${oauthDescription}`\n }`,\n )\n\n return err({error: oauthError, store: params.store})\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new AbortError(\n `Received invalid response from authentication service (HTTP ${res.status}).`,\n 'The response could not be parsed as JSON. The service may be temporarily unavailable. Please try again.',\n )\n }\n throw error\n }\n}\n\nfunction buildIdentityToken(\n result: TokenRequestResult,\n existingUserId?: string,\n existingAlias?: string,\n): IdentityToken {\n const userId = existingUserId ?? (result.id_token ? jose.decodeJwt(result.id_token).sub! : undefined)\n\n if (!userId) {\n throw new BugError('Error setting userId for session. No id_token or pre-existing user ID provided.')\n }\n\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n userId,\n alias: existingAlias,\n }\n}\n\nfunction buildApplicationToken(result: TokenRequestResult): ApplicationToken {\n return {\n accessToken: result.access_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n }\n}\n"]}
|
|
@@ -3,35 +3,34 @@ import React from 'react';
|
|
|
3
3
|
import { render as inkRender } from 'ink';
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
5
|
class Stderr extends EventEmitter {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.lastFrame = () => this._lastFrame;
|
|
14
|
-
}
|
|
6
|
+
frames = [];
|
|
7
|
+
_lastFrame;
|
|
8
|
+
write = (frame) => {
|
|
9
|
+
this.frames.push(frame);
|
|
10
|
+
this._lastFrame = frame;
|
|
11
|
+
};
|
|
12
|
+
lastFrame = () => this._lastFrame;
|
|
15
13
|
}
|
|
16
14
|
export class Stdin extends EventEmitter {
|
|
15
|
+
isTTY;
|
|
16
|
+
data = null;
|
|
17
17
|
constructor(options = {}) {
|
|
18
18
|
super();
|
|
19
|
-
this.data = null;
|
|
20
|
-
this.write = (data) => {
|
|
21
|
-
this.data = data;
|
|
22
|
-
this.emit('readable');
|
|
23
|
-
};
|
|
24
|
-
this.read = () => {
|
|
25
|
-
const data = this.data;
|
|
26
|
-
this.data = null;
|
|
27
|
-
return data;
|
|
28
|
-
};
|
|
29
19
|
this.isTTY = options.isTTY ?? true;
|
|
30
20
|
}
|
|
21
|
+
write = (data) => {
|
|
22
|
+
this.data = data;
|
|
23
|
+
this.emit('readable');
|
|
24
|
+
};
|
|
31
25
|
setEncoding() { }
|
|
32
26
|
setRawMode() { }
|
|
33
27
|
ref() { }
|
|
34
28
|
unref() { }
|
|
29
|
+
read = () => {
|
|
30
|
+
const data = this.data;
|
|
31
|
+
this.data = null;
|
|
32
|
+
return data;
|
|
33
|
+
};
|
|
35
34
|
}
|
|
36
35
|
export const render = (tree, options = {}) => {
|
|
37
36
|
const stdout = new Stdout({ columns: 100 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../../src/private/node/testing/ui.ts"],"names":[],"mappings":"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","sourcesContent":["import {Stdout, InkLifecycleRoot} from '../ui.js'\nimport React, {ReactElement} from 'react'\nimport {render as inkRender} from 'ink'\n\nimport {EventEmitter} from 'events'\n\nclass Stderr extends EventEmitter {\n readonly frames: string[] = []\n private _lastFrame?: string\n\n write = (frame: string) => {\n this.frames.push(frame)\n this._lastFrame = frame\n }\n\n lastFrame = () => this._lastFrame\n}\n\nexport class Stdin extends EventEmitter {\n isTTY: boolean\n data: string | null = null\n\n constructor(options: {isTTY?: boolean} = {}) {\n super()\n this.isTTY = options.isTTY ?? true\n }\n\n write = (data: string) => {\n this.data = data\n this.emit('readable')\n }\n\n setEncoding() {}\n setRawMode() {}\n ref() {}\n unref() {}\n read: () => string | null = () => {\n const data = this.data\n\n this.data = null\n\n return data\n }\n}\n\ninterface Instance {\n rerender: (tree: ReactElement) => void\n unmount: () => void\n cleanup: () => void\n stdout: Stdout\n stderr: Stderr\n stdin: Stdin\n frames: string[]\n lastFrame: () => string | undefined\n waitUntilExit: () => TrackedPromise<void>\n}\n\ninterface RenderOptions {\n stdout?: EventEmitter\n stderr?: EventEmitter\n stdin?: EventEmitter\n}\n\nexport const render = (tree: ReactElement, options: RenderOptions = {}): Instance => {\n const stdout = new Stdout({columns: 100})\n const stderr = new Stderr()\n const stdin = new Stdin()\n\n const instance = inkRender(React.createElement(InkLifecycleRoot, null, tree), {\n stdout: options.stdout ?? (stdout as any),\n\n stderr: options.stderr ?? (stderr as any),\n\n stdin: options.stdin ?? (stdin as any),\n debug: true,\n exitOnCtrlC: false,\n patchConsole: false,\n })\n\n return {\n rerender: (tree: ReactElement) => instance.rerender(React.createElement(InkLifecycleRoot, null, tree)),\n unmount: instance.unmount,\n cleanup: instance.cleanup,\n waitUntilExit: () => trackPromise(instance.waitUntilExit().then(() => {})),\n stdout,\n stderr,\n stdin,\n frames: stdout.frames,\n lastFrame: stdout.lastFrame,\n }\n}\n\n/**\n * Wait for the component to be ready to accept input.\n */\nexport function waitForInputsToBeReady() {\n return new Promise((resolve) => setTimeout(resolve, 10))\n}\n\n/**\n * Wait for the last frame to change to anything.\n */\nexport async function waitForChange(func: () => void, getChangingValue: () => string | number | undefined) {\n const initialValue = getChangingValue()\n\n func()\n\n while (getChangingValue() === initialValue) {\n // Yield via setImmediate so React 19's scheduler (which also uses\n // setImmediate in Node.js) can flush batched renders, then yield\n // via setTimeout(0) to let any follow-up microtasks settle.\n // eslint-disable-next-line no-await-in-loop\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n }\n}\n\nexport async function waitFor(func: () => void, condition: () => boolean) {\n func()\n\n while (!condition()) {\n // eslint-disable-next-line no-await-in-loop\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n }\n}\n\n/**\n * Wait for the last frame to contain specific text.\n */\nexport function waitForContent(\n renderInstance: ReturnType<typeof render>,\n content: string,\n func: () => void = () => {},\n) {\n return waitFor(\n () => func(),\n\n () => renderInstance.lastFrame()!.includes(content),\n )\n}\n\n/**\n * Send input and wait for the last frame to change.\n *\n * Useful when you want to send some input and wait for anything to change in the interface.\n * If you need to wait for a specific change instead, you can use sendInputAndWaitForContent.\n */\nexport async function sendInputAndWaitForChange(renderInstance: ReturnType<typeof render>, ...inputs: string[]) {\n await waitForChange(() => inputs.forEach((input) => renderInstance.stdin.write(input)), renderInstance.lastFrame)\n // Yield via setImmediate → setTimeout(0) so React 19's scheduler can flush\n // effects (e.g. re-register useInput handlers with up-to-date closures)\n // before subsequent input is sent.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n}\n\n/** Send input and wait a number of milliseconds.\n *\n * Useful if you know some what will happen after input will take a certain amount of time\n * and it will not cause any visible change so you can't use sendInputAndWaitForChange.\n * This function can also be used if you want to test that nothing changes after some input has been sent.\n */\nexport async function sendInputAndWait(\n renderInstance: ReturnType<typeof render>,\n waitTime: number,\n ...inputs: string[]\n) {\n inputs.forEach((input) => renderInstance.stdin.write(input))\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n}\n\n/**\n * Send input and wait for the last frame to contain specific text.\n *\n * Useful when you want to send some input and wait for a specific change to happen.\n * If you need to wait for any change instead, you can use sendInputAndWaitForChange.\n */\nexport async function sendInputAndWaitForContent(\n renderInstance: ReturnType<typeof render>,\n content: string,\n ...inputs: string[]\n) {\n await waitForContent(renderInstance, content, () => inputs.forEach((input) => renderInstance.stdin.write(input)))\n // Yield so React 19's scheduler can flush effects (e.g. re-register useInput\n // handlers with up-to-date closures) before subsequent input is sent.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n}\n\n/** Function that is useful when you want to check the last frame of a component that unmounted.\n *\n * With Ink 6 / React 19, the output is no longer cleared on unmount,\n * so lastFrame() consistently returns the last rendered content.\n */\nexport function getLastFrameAfterUnmount(renderInstance: ReturnType<typeof render>) {\n return renderInstance.lastFrame()\n}\n\ntype TrackedPromise<T> = Promise<T> & {\n isFulfilled: () => boolean\n isPending: () => boolean\n isRejected: () => boolean\n}\n\nfunction trackPromise<T>(promise: Promise<T>): TrackedPromise<T> {\n let isPending = true\n let isRejected = false\n let isFulfilled = false\n\n const trackedPromise = promise.then(\n function (result) {\n isFulfilled = true\n isPending = false\n return result\n },\n function (error) {\n isRejected = true\n isPending = false\n throw error\n },\n ) as TrackedPromise<T>\n\n trackedPromise.isFulfilled = function () {\n return isFulfilled\n }\n trackedPromise.isPending = function () {\n return isPending\n }\n trackedPromise.isRejected = function () {\n return isRejected\n }\n\n return trackedPromise\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../../src/private/node/testing/ui.ts"],"names":[],"mappings":"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","sourcesContent":["import {Stdout, InkLifecycleRoot} from '../ui.js'\nimport React, {ReactElement} from 'react'\nimport {render as inkRender} from 'ink'\n\nimport {EventEmitter} from 'events'\n\nclass Stderr extends EventEmitter {\n readonly frames: string[] = []\n private _lastFrame?: string\n\n write = (frame: string) => {\n this.frames.push(frame)\n this._lastFrame = frame\n }\n\n lastFrame = () => this._lastFrame\n}\n\nexport class Stdin extends EventEmitter {\n isTTY: boolean\n data: string | null = null\n\n constructor(options: {isTTY?: boolean} = {}) {\n super()\n this.isTTY = options.isTTY ?? true\n }\n\n write = (data: string) => {\n this.data = data\n this.emit('readable')\n }\n\n setEncoding() {}\n setRawMode() {}\n ref() {}\n unref() {}\n read: () => string | null = () => {\n const data = this.data\n\n this.data = null\n\n return data\n }\n}\n\ninterface Instance {\n rerender: (tree: ReactElement) => void\n unmount: () => void\n cleanup: () => void\n stdout: Stdout\n stderr: Stderr\n stdin: Stdin\n frames: string[]\n lastFrame: () => string | undefined\n waitUntilExit: () => TrackedPromise<void>\n}\n\ninterface RenderOptions {\n stdout?: EventEmitter\n stderr?: EventEmitter\n stdin?: EventEmitter\n}\n\nexport const render = (tree: ReactElement, options: RenderOptions = {}): Instance => {\n const stdout = new Stdout({columns: 100})\n const stderr = new Stderr()\n const stdin = new Stdin()\n\n const instance = inkRender(React.createElement(InkLifecycleRoot, null, tree), {\n stdout: options.stdout ?? (stdout as any),\n\n stderr: options.stderr ?? (stderr as any),\n\n stdin: options.stdin ?? (stdin as any),\n debug: true,\n exitOnCtrlC: false,\n patchConsole: false,\n })\n\n return {\n rerender: (tree: ReactElement) => instance.rerender(React.createElement(InkLifecycleRoot, null, tree)),\n unmount: instance.unmount,\n cleanup: instance.cleanup,\n waitUntilExit: () => trackPromise(instance.waitUntilExit().then(() => {})),\n stdout,\n stderr,\n stdin,\n frames: stdout.frames,\n lastFrame: stdout.lastFrame,\n }\n}\n\n/**\n * Wait for the component to be ready to accept input.\n */\nexport function waitForInputsToBeReady() {\n return new Promise((resolve) => setTimeout(resolve, 10))\n}\n\n/**\n * Wait for the last frame to change to anything.\n */\nexport async function waitForChange(func: () => void, getChangingValue: () => string | number | undefined) {\n const initialValue = getChangingValue()\n\n func()\n\n while (getChangingValue() === initialValue) {\n // Yield via setImmediate so React 19's scheduler (which also uses\n // setImmediate in Node.js) can flush batched renders, then yield\n // via setTimeout(0) to let any follow-up microtasks settle.\n // eslint-disable-next-line no-await-in-loop\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n }\n}\n\nexport async function waitFor(func: () => void, condition: () => boolean) {\n func()\n\n while (!condition()) {\n // eslint-disable-next-line no-await-in-loop\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n }\n}\n\n/**\n * Wait for the last frame to contain specific text.\n */\nexport function waitForContent(\n renderInstance: ReturnType<typeof render>,\n content: string,\n func: () => void = () => {},\n) {\n return waitFor(\n () => func(),\n\n () => renderInstance.lastFrame()!.includes(content),\n )\n}\n\n/**\n * Send input and wait for the last frame to change.\n *\n * Useful when you want to send some input and wait for anything to change in the interface.\n * If you need to wait for a specific change instead, you can use sendInputAndWaitForContent.\n */\nexport async function sendInputAndWaitForChange(renderInstance: ReturnType<typeof render>, ...inputs: string[]) {\n await waitForChange(() => inputs.forEach((input) => renderInstance.stdin.write(input)), renderInstance.lastFrame)\n // Yield via setImmediate → setTimeout(0) so React 19's scheduler can flush\n // effects (e.g. re-register useInput handlers with up-to-date closures)\n // before subsequent input is sent.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n}\n\n/** Send input and wait a number of milliseconds.\n *\n * Useful if you know some what will happen after input will take a certain amount of time\n * and it will not cause any visible change so you can't use sendInputAndWaitForChange.\n * This function can also be used if you want to test that nothing changes after some input has been sent.\n */\nexport async function sendInputAndWait(\n renderInstance: ReturnType<typeof render>,\n waitTime: number,\n ...inputs: string[]\n) {\n inputs.forEach((input) => renderInstance.stdin.write(input))\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n}\n\n/**\n * Send input and wait for the last frame to contain specific text.\n *\n * Useful when you want to send some input and wait for a specific change to happen.\n * If you need to wait for any change instead, you can use sendInputAndWaitForChange.\n */\nexport async function sendInputAndWaitForContent(\n renderInstance: ReturnType<typeof render>,\n content: string,\n ...inputs: string[]\n) {\n await waitForContent(renderInstance, content, () => inputs.forEach((input) => renderInstance.stdin.write(input)))\n // Yield so React 19's scheduler can flush effects (e.g. re-register useInput\n // handlers with up-to-date closures) before subsequent input is sent.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)))\n}\n\n/** Function that is useful when you want to check the last frame of a component that unmounted.\n *\n * With Ink 6 / React 19, the output is no longer cleared on unmount,\n * so lastFrame() consistently returns the last rendered content.\n */\nexport function getLastFrameAfterUnmount(renderInstance: ReturnType<typeof render>) {\n return renderInstance.lastFrame()\n}\n\ntype TrackedPromise<T> = Promise<T> & {\n isFulfilled: () => boolean\n isPending: () => boolean\n isRejected: () => boolean\n}\n\nfunction trackPromise<T>(promise: Promise<T>): TrackedPromise<T> {\n let isPending = true\n let isRejected = false\n let isFulfilled = false\n\n const trackedPromise = promise.then(\n function (result) {\n isFulfilled = true\n isPending = false\n return result\n },\n function (error) {\n isRejected = true\n isPending = false\n throw error\n },\n ) as TrackedPromise<T>\n\n trackedPromise.isFulfilled = function () {\n return isFulfilled\n }\n trackedPromise.isPending = function () {\n return isPending\n }\n trackedPromise.isRejected = function () {\n return isRejected\n }\n\n return trackedPromise\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BannerType } from './Banner.js';
|
|
2
|
-
import { BoldToken, InlineToken, LinkToken, TokenItem } from './TokenizedText.js';
|
|
3
2
|
import { TabularDataProps } from './TabularData.js';
|
|
4
3
|
import { FunctionComponent } from 'react';
|
|
4
|
+
import type { BoldToken, InlineToken, LinkToken, TokenItem } from './token-item.js';
|
|
5
5
|
export interface CustomSection {
|
|
6
6
|
title?: string;
|
|
7
7
|
body: TabularDataProps | TokenItem;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Alert.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,
|
|
1
|
+
{"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/Alert.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAa,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAC,WAAW,EAAmB,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAA;AAC7B,OAAO,KAA0B,MAAM,OAAO,CAAA;AAsB9C,MAAM,KAAK,GAAkC,CAAC,EAC5C,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,SAAS,EACT,IAAI,EACJ,cAAc,EACd,gBAAgB,GAAG,KAAK,GACzB,EAAE,EAAE;IACH,OAAO,CACL,oBAAC,MAAM,IAAC,IAAI,EAAE,IAAI;QACf,QAAQ,CAAC,CAAC,CAAC,CACV,oBAAC,IAAI,IAAC,IAAI;YACR,oBAAC,aAAa,IAAC,IAAI,EAAE,QAAQ,GAAI,CAC5B,CACR,CAAC,CAAC,CAAC,IAAI;QAEP,IAAI,CAAC,CAAC,CAAC,oBAAC,aAAa,IAAC,IAAI,EAAE,IAAI,GAAI,CAAC,CAAC,CAAC,IAAI;QAE3C,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACnC,oBAAC,IAAI,IAAC,KAAK,EAAC,YAAY,EAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,GAAI,CACzE,CAAC,CAAC,CAAC,IAAI;QAEP,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,KAAK,EAAC,WAAW,EAAC,KAAK,EAAE,SAAS,GAAI,CAAC,CAAC,CAAC,IAAI;QAEvF,IAAI,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAI,CAAC,CAAC,CAAC,IAAI;QAExD,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAC7C,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,GAAG,EAAE,CAAC,IAC/B,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CACtC,oBAAC,GAAG,IAAC,GAAG,EAAE,KAAK,EAAE,aAAa,EAAC,QAAQ;YACpC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,IAAI,UAAE,OAAO,CAAC,KAAK,CAAQ,CAAC,CAAC,CAAC,IAAI;YACxD,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CACnE,oBAAC,WAAW,OAAK,OAAO,CAAC,IAAI,GAAI,CAClC,CAAC,CAAC,CAAC,CACF,oBAAC,aAAa,IAAC,IAAI,EAAE,OAAO,CAAC,IAAI,GAAI,CACtC,CACG,CACP,CAAC,CACE,CACP,CAAC,CAAC,CAAC,IAAI,CACD,CACV,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAC,KAAK,EAAC,CAAA","sourcesContent":["import {Banner, BannerType} from './Banner.js'\nimport {Link} from './Link.js'\nimport {List} from './List.js'\nimport {TokenizedText} from './TokenizedText.js'\nimport {TabularData, TabularDataProps} from './TabularData.js'\nimport {Box, Text} from 'ink'\nimport React, {FunctionComponent} from 'react'\nimport type {BoldToken, InlineToken, LinkToken, TokenItem} from './token-item.js'\n\nexport interface CustomSection {\n title?: string\n body: TabularDataProps | TokenItem\n}\n\nexport interface AlertProps {\n type: Exclude<BannerType, 'external_error'>\n headline?: TokenItem<Exclude<InlineToken, LinkToken | BoldToken>>\n body?: TokenItem\n nextSteps?: TokenItem<InlineToken>[]\n reference?: TokenItem<InlineToken>[]\n link?: {\n label: string\n url: string\n }\n orderedNextSteps?: boolean\n customSections?: CustomSection[]\n}\n\nconst Alert: FunctionComponent<AlertProps> = ({\n type,\n headline,\n body,\n nextSteps,\n reference,\n link,\n customSections,\n orderedNextSteps = false,\n}) => {\n return (\n <Banner type={type}>\n {headline ? (\n <Text bold>\n <TokenizedText item={headline} />\n </Text>\n ) : null}\n\n {body ? <TokenizedText item={body} /> : null}\n\n {nextSteps && nextSteps.length > 0 ? (\n <List title=\"Next steps\" items={nextSteps} ordered={orderedNextSteps} />\n ) : null}\n\n {reference && reference.length > 0 ? <List title=\"Reference\" items={reference} /> : null}\n\n {link ? <Link url={link.url} label={link.label} /> : null}\n\n {customSections && customSections.length > 0 ? (\n <Box flexDirection=\"column\" gap={1}>\n {customSections.map((section, index) => (\n <Box key={index} flexDirection=\"column\">\n {section.title ? <Text bold>{section.title}</Text> : null}\n {typeof section.body === 'object' && 'tabularData' in section.body ? (\n <TabularData {...section.body} />\n ) : (\n <TokenizedText item={section.body} />\n )}\n </Box>\n ))}\n </Box>\n ) : null}\n </Banner>\n )\n}\n\nexport {Alert}\n"]}
|
|
@@ -8,6 +8,8 @@ import { describe, expect, test } from 'vitest';
|
|
|
8
8
|
* ConcurrentOutput tests are unreliable unless we await a promise that resolves after the process has written to stdout.
|
|
9
9
|
*/
|
|
10
10
|
class Synchronizer {
|
|
11
|
+
resolve;
|
|
12
|
+
promise;
|
|
11
13
|
constructor() {
|
|
12
14
|
this.resolve = () => { };
|
|
13
15
|
this.promise = new Promise((resolve, _reject) => {
|