@shopify/cli-kit 4.5.1 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/private/node/analytics/error-grouping.d.ts +57 -0
- package/dist/private/node/analytics/error-grouping.js +191 -0
- package/dist/private/node/analytics/error-grouping.js.map +1 -0
- package/dist/private/node/analytics/graphql-error-codes.d.ts +39 -0
- package/dist/private/node/analytics/graphql-error-codes.js +73 -0
- package/dist/private/node/analytics/graphql-error-codes.js.map +1 -0
- package/dist/private/node/api/headers.js +1 -2
- package/dist/private/node/api/headers.js.map +1 -1
- package/dist/private/node/constants.d.ts +0 -2
- package/dist/private/node/constants.js +0 -2
- package/dist/private/node/constants.js.map +1 -1
- package/dist/private/node/content-tokens.js +2 -4
- package/dist/private/node/content-tokens.js.map +1 -1
- package/dist/private/node/session/scopes.js +6 -5
- package/dist/private/node/session/scopes.js.map +1 -1
- package/dist/private/node/session/validate.js +1 -2
- package/dist/private/node/session/validate.js.map +1 -1
- package/dist/private/node/session.d.ts +1 -0
- package/dist/private/node/session.js +9 -8
- package/dist/private/node/session.js.map +1 -1
- package/dist/public/common/object.d.ts +8 -0
- package/dist/public/common/object.js +24 -0
- package/dist/public/common/object.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/analytics.js +4 -1
- package/dist/public/node/analytics.js.map +1 -1
- package/dist/public/node/api/partners.d.ts +0 -17
- package/dist/public/node/api/partners.js +1 -48
- package/dist/public/node/api/partners.js.map +1 -1
- package/dist/public/node/base-command.d.ts +1 -0
- package/dist/public/node/base-command.js +2 -0
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/cli.d.ts +3 -0
- package/dist/public/node/cli.js +6 -0
- package/dist/public/node/cli.js.map +1 -1
- package/dist/public/node/context/local.d.ts +0 -7
- package/dist/public/node/context/local.js +0 -9
- package/dist/public/node/context/local.js.map +1 -1
- package/dist/public/node/environment.d.ts +0 -6
- package/dist/public/node/environment.js +0 -8
- package/dist/public/node/environment.js.map +1 -1
- package/dist/public/node/error-handler.js +35 -14
- package/dist/public/node/error-handler.js.map +1 -1
- package/dist/public/node/error.js +34 -0
- package/dist/public/node/error.js.map +1 -1
- package/dist/public/node/graphiql/server.js +2 -2
- package/dist/public/node/graphiql/server.js.map +1 -1
- package/dist/public/node/local-storage.js +10 -1
- package/dist/public/node/local-storage.js.map +1 -1
- package/dist/public/node/node-package-manager.js +2 -13
- package/dist/public/node/node-package-manager.js.map +1 -1
- package/dist/public/node/session.d.ts +13 -0
- package/dist/public/node/session.js +26 -1
- package/dist/public/node/session.js.map +1 -1
- package/dist/public/node/system.js +2 -3
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/themes/api.js +9 -2
- package/dist/public/node/themes/api.js.map +1 -1
- package/dist/public/node/tree-kill.js +12 -25
- package/dist/public/node/tree-kill.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured signals extracted from an error, used to group it into a meaningful Bugsnag bucket.
|
|
3
|
+
*
|
|
4
|
+
* These are read from the *original* typed error (before it is flattened to a generic `Error`
|
|
5
|
+
* for reporting), so we group on facts the error already carries rather than by re-parsing a
|
|
6
|
+
* stringified message.
|
|
7
|
+
*/
|
|
8
|
+
interface ErrorGroupingSignals {
|
|
9
|
+
httpStatus?: number;
|
|
10
|
+
code?: string;
|
|
11
|
+
errorClass?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The fully resolved grouping decision for an error: the Bugsnag grouping hash (or `undefined` to
|
|
15
|
+
* fall back to stack-trace grouping), the semantic category, and the structured signals that drove
|
|
16
|
+
* the decision. The reporter uses a single resolution for both `event.groupingHash` and the
|
|
17
|
+
* `error_grouping` metadata so they can never disagree.
|
|
18
|
+
*/
|
|
19
|
+
interface ResolvedErrorGrouping {
|
|
20
|
+
hash?: string;
|
|
21
|
+
category?: string;
|
|
22
|
+
signals: ErrorGroupingSignals;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extracts structured grouping signals from an error object.
|
|
26
|
+
*
|
|
27
|
+
* Only assigns a field when a value is actually present, so the result can be safely merged over
|
|
28
|
+
* message-derived signals without an explicit `undefined` erasing a recovered value.
|
|
29
|
+
*
|
|
30
|
+
* @param error - The original error (any type).
|
|
31
|
+
* @returns The HTTP status, GraphQL error code, and error class name when available.
|
|
32
|
+
*/
|
|
33
|
+
export declare function errorGroupingSignals(error: unknown): ErrorGroupingSignals;
|
|
34
|
+
/**
|
|
35
|
+
* Resolves the full grouping decision for an error: hash, category, and the signals behind it.
|
|
36
|
+
*
|
|
37
|
+
* Categories are resolved structured-first (HTTP status / GraphQL code), falling back to the
|
|
38
|
+
* shared keyword categorizer only for untyped errors. When no meaningful category can be
|
|
39
|
+
* resolved, `hash` is `undefined` so the caller leaves `event.groupingHash` unset and Bugsnag's
|
|
40
|
+
* default stack-trace grouping applies — this avoids merging genuinely distinct unknown bugs.
|
|
41
|
+
*
|
|
42
|
+
* @param error - The original error (any type).
|
|
43
|
+
* @param sliceName - The product slice (`app`, `theme`, `store`, `hydrogen`, or `cli`).
|
|
44
|
+
* @returns The resolved hash (or `undefined`), category, and structured signals.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveErrorGrouping(error: unknown, sliceName: string): ResolvedErrorGrouping;
|
|
47
|
+
/**
|
|
48
|
+
* Builds a Bugsnag grouping hash of the form `${slice}:${category}:${signature}`.
|
|
49
|
+
*
|
|
50
|
+
* Thin wrapper over {@link resolveErrorGrouping} for callers that only need the hash.
|
|
51
|
+
*
|
|
52
|
+
* @param error - The original error (any type).
|
|
53
|
+
* @param sliceName - The product slice (`app`, `theme`, `store`, `hydrogen`, or `cli`).
|
|
54
|
+
* @returns The grouping hash, or `undefined` to fall back to stack-trace grouping.
|
|
55
|
+
*/
|
|
56
|
+
export declare function errorGroupingHash(error: unknown, sliceName: string): string | undefined;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { categorizeError, ErrorCategory, formatErrorMessage } from './error-categorizer.js';
|
|
2
|
+
import { graphQLErrorCodes, isPermissionCode, isRateLimitCode } from './graphql-error-codes.js';
|
|
3
|
+
import { GraphQLClientError } from '../api/headers.js';
|
|
4
|
+
import { slugify } from '../../../public/common/string.js';
|
|
5
|
+
import { ClientError } from 'graphql-request';
|
|
6
|
+
/**
|
|
7
|
+
* Extracts structured grouping signals from an error object.
|
|
8
|
+
*
|
|
9
|
+
* Only assigns a field when a value is actually present, so the result can be safely merged over
|
|
10
|
+
* message-derived signals without an explicit `undefined` erasing a recovered value.
|
|
11
|
+
*
|
|
12
|
+
* @param error - The original error (any type).
|
|
13
|
+
* @returns The HTTP status, GraphQL error code, and error class name when available.
|
|
14
|
+
*/
|
|
15
|
+
export function errorGroupingSignals(error) {
|
|
16
|
+
const signals = {};
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
signals.errorClass = error.constructor.name;
|
|
19
|
+
}
|
|
20
|
+
// GraphQLClientError (handleErrors: true, status < 500) preserves the status and errors array.
|
|
21
|
+
if (error instanceof GraphQLClientError) {
|
|
22
|
+
if (error.statusCode !== undefined)
|
|
23
|
+
signals.httpStatus = error.statusCode;
|
|
24
|
+
const code = routableCode(error.errors);
|
|
25
|
+
if (code !== undefined)
|
|
26
|
+
signals.code = code;
|
|
27
|
+
return signals;
|
|
28
|
+
}
|
|
29
|
+
// Raw graphql-request ClientError (handleErrors: false) exposes the full response.
|
|
30
|
+
if (error instanceof ClientError) {
|
|
31
|
+
const status = error.response?.status;
|
|
32
|
+
if (status !== undefined)
|
|
33
|
+
signals.httpStatus = status;
|
|
34
|
+
const code = routableCode(error.response?.errors);
|
|
35
|
+
if (code !== undefined)
|
|
36
|
+
signals.code = code;
|
|
37
|
+
}
|
|
38
|
+
return signals;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolves the full grouping decision for an error: hash, category, and the signals behind it.
|
|
42
|
+
*
|
|
43
|
+
* Categories are resolved structured-first (HTTP status / GraphQL code), falling back to the
|
|
44
|
+
* shared keyword categorizer only for untyped errors. When no meaningful category can be
|
|
45
|
+
* resolved, `hash` is `undefined` so the caller leaves `event.groupingHash` unset and Bugsnag's
|
|
46
|
+
* default stack-trace grouping applies — this avoids merging genuinely distinct unknown bugs.
|
|
47
|
+
*
|
|
48
|
+
* @param error - The original error (any type).
|
|
49
|
+
* @param sliceName - The product slice (`app`, `theme`, `store`, `hydrogen`, or `cli`).
|
|
50
|
+
* @returns The resolved hash (or `undefined`), category, and structured signals.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveErrorGrouping(error, sliceName) {
|
|
53
|
+
const message = errorMessage(error);
|
|
54
|
+
// Object signals are authoritative; message-derived signals fill gaps (e.g. 5xx errors that the
|
|
55
|
+
// API layer flattens into an AbortError, dropping the structured status field). Merge field by
|
|
56
|
+
// field so an absent object signal never clobbers one recovered from the message.
|
|
57
|
+
const fromError = errorGroupingSignals(error);
|
|
58
|
+
const fromMessage = signalsFromMessage(message);
|
|
59
|
+
const signals = {
|
|
60
|
+
httpStatus: fromError.httpStatus ?? fromMessage.httpStatus,
|
|
61
|
+
code: fromError.code ?? fromMessage.code,
|
|
62
|
+
errorClass: fromError.errorClass ?? fromMessage.errorClass,
|
|
63
|
+
};
|
|
64
|
+
const structuredCategory = categoryFromSignals(signals);
|
|
65
|
+
if (structuredCategory) {
|
|
66
|
+
return {
|
|
67
|
+
hash: `${sliceName}:${structuredCategory}:${structuredSignature(signals)}`,
|
|
68
|
+
category: structuredCategory,
|
|
69
|
+
signals,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const groupingError = new Error(stripJsonDump(message));
|
|
73
|
+
const category = categorizeError(groupingError);
|
|
74
|
+
if (category === ErrorCategory.Unknown) {
|
|
75
|
+
return { hash: undefined, category: undefined, signals };
|
|
76
|
+
}
|
|
77
|
+
const categoryName = category.toLowerCase();
|
|
78
|
+
return {
|
|
79
|
+
hash: `${sliceName}:${categoryName}:${formatErrorMessage(groupingError, category)}`,
|
|
80
|
+
category: categoryName,
|
|
81
|
+
signals,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Builds a Bugsnag grouping hash of the form `${slice}:${category}:${signature}`.
|
|
86
|
+
*
|
|
87
|
+
* Thin wrapper over {@link resolveErrorGrouping} for callers that only need the hash.
|
|
88
|
+
*
|
|
89
|
+
* @param error - The original error (any type).
|
|
90
|
+
* @param sliceName - The product slice (`app`, `theme`, `store`, `hydrogen`, or `cli`).
|
|
91
|
+
* @returns The grouping hash, or `undefined` to fall back to stack-trace grouping.
|
|
92
|
+
*/
|
|
93
|
+
export function errorGroupingHash(error, sliceName) {
|
|
94
|
+
return resolveErrorGrouping(error, sliceName).hash;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolves a semantic category from structured signals using an explicit decision table.
|
|
98
|
+
*
|
|
99
|
+
* Precedence is deliberate:
|
|
100
|
+
* - rate limit (`THROTTLED`/`429` code or HTTP 429) wins first — it is the most actionable bucket.
|
|
101
|
+
* - HTTP 401 is authoritative for `authentication`: a request the server rejected as
|
|
102
|
+
* unauthenticated is an authentication problem even if a GraphQL code also reports access-denied.
|
|
103
|
+
* `ACCESS_DENIED` in practice pairs with HTTP 403, which is handled by the permission branch.
|
|
104
|
+
* - 403 / `ACCESS_DENIED` (incl. nested App Management `access_denied`) → `permission`.
|
|
105
|
+
*
|
|
106
|
+
* Returns `undefined` when no structured signal is present.
|
|
107
|
+
*/
|
|
108
|
+
function categoryFromSignals(signals) {
|
|
109
|
+
const { httpStatus, code } = signals;
|
|
110
|
+
if (isRateLimitCode(code) || httpStatus === 429)
|
|
111
|
+
return 'rate_limit';
|
|
112
|
+
if (httpStatus === 401)
|
|
113
|
+
return 'authentication';
|
|
114
|
+
if (httpStatus === 403 || isPermissionCode(code))
|
|
115
|
+
return 'permission';
|
|
116
|
+
if (httpStatus !== undefined && httpStatus >= 500)
|
|
117
|
+
return 'server';
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Builds a stable, low-cardinality signature slug from structured signals.
|
|
122
|
+
*/
|
|
123
|
+
function structuredSignature(signals) {
|
|
124
|
+
const parts = [];
|
|
125
|
+
if (signals.httpStatus !== undefined)
|
|
126
|
+
parts.push(`http-${signals.httpStatus}`);
|
|
127
|
+
if (signals.code)
|
|
128
|
+
parts.push(signals.code);
|
|
129
|
+
if (parts.length === 0 && signals.errorClass)
|
|
130
|
+
parts.push(signals.errorClass);
|
|
131
|
+
return slugify(parts.join('-')).slice(0, 50);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Recovers structured signals from an error message string. Handles both error shapes seen from
|
|
135
|
+
* the API layer: graphql-request's `GraphQL Error (Code: NNN): {json}` and the cli-kit wrapper's
|
|
136
|
+
* `... responded unsuccessfully with the HTTP status NNN ...`.
|
|
137
|
+
*/
|
|
138
|
+
function signalsFromMessage(message) {
|
|
139
|
+
const signals = {};
|
|
140
|
+
const statusMatch = message.match(/HTTP status (\d{3})/i) ?? message.match(/\(Code: (\d{3})\)/i);
|
|
141
|
+
if (statusMatch?.[1])
|
|
142
|
+
signals.httpStatus = Number(statusMatch[1]);
|
|
143
|
+
const payload = parseEmbeddedJson(message);
|
|
144
|
+
if (signals.httpStatus === undefined && payload?.response?.status !== undefined) {
|
|
145
|
+
signals.httpStatus = payload.response.status;
|
|
146
|
+
}
|
|
147
|
+
const code = routableCode(payload?.response?.errors);
|
|
148
|
+
if (code !== undefined)
|
|
149
|
+
signals.code = code;
|
|
150
|
+
return signals;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parses the JSON blob embedded in a graphql-request ClientError message, if present.
|
|
154
|
+
*/
|
|
155
|
+
function parseEmbeddedJson(message) {
|
|
156
|
+
const jsonStart = message.indexOf('{');
|
|
157
|
+
if (jsonStart === -1)
|
|
158
|
+
return undefined;
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(message.slice(jsonStart));
|
|
161
|
+
// eslint-disable-next-line no-catch-all/no-catch-all
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Picks the most routing-relevant code from a GraphQL `errors` value.
|
|
169
|
+
*
|
|
170
|
+
* Scans every error (not just the first) and prefers a code we route on — a rate-limit code, then
|
|
171
|
+
* a permission code — so a benign leading code can't mask a `THROTTLED` or `ACCESS_DENIED` further
|
|
172
|
+
* down the array. Falls back to the first code present for visibility.
|
|
173
|
+
*/
|
|
174
|
+
function routableCode(errors) {
|
|
175
|
+
const codes = graphQLErrorCodes(errors);
|
|
176
|
+
return codes.find(isRateLimitCode) ?? codes.find(isPermissionCode) ?? codes[0];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Strips a trailing JSON dump (`...: {json}`) from a message so the keyword categorizer sees the
|
|
180
|
+
* human-readable prefix rather than the serialized request/response (which contains the literal
|
|
181
|
+
* `request`, mis-routing to the network category).
|
|
182
|
+
*/
|
|
183
|
+
function stripJsonDump(message) {
|
|
184
|
+
return message.split(': {')[0] ?? message;
|
|
185
|
+
}
|
|
186
|
+
function errorMessage(error) {
|
|
187
|
+
if (error instanceof Error)
|
|
188
|
+
return error.message;
|
|
189
|
+
return typeof error === 'string' ? error : '';
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=error-grouping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-grouping.js","sourceRoot":"","sources":["../../../../src/private/node/analytics/error-grouping.ts"],"names":[],"mappings":"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","sourcesContent":["import {categorizeError, ErrorCategory, formatErrorMessage} from './error-categorizer.js'\nimport {graphQLErrorCodes, isPermissionCode, isRateLimitCode} from './graphql-error-codes.js'\nimport {GraphQLClientError} from '../api/headers.js'\nimport {slugify} from '../../../public/common/string.js'\nimport {ClientError} from 'graphql-request'\n\n/**\n * Structured signals extracted from an error, used to group it into a meaningful Bugsnag bucket.\n *\n * These are read from the *original* typed error (before it is flattened to a generic `Error`\n * for reporting), so we group on facts the error already carries rather than by re-parsing a\n * stringified message.\n */\ninterface ErrorGroupingSignals {\n httpStatus?: number\n code?: string\n errorClass?: string\n}\n\n/**\n * The fully resolved grouping decision for an error: the Bugsnag grouping hash (or `undefined` to\n * fall back to stack-trace grouping), the semantic category, and the structured signals that drove\n * the decision. The reporter uses a single resolution for both `event.groupingHash` and the\n * `error_grouping` metadata so they can never disagree.\n */\ninterface ResolvedErrorGrouping {\n hash?: string\n category?: string\n signals: ErrorGroupingSignals\n}\n\n/**\n * Extracts structured grouping signals from an error object.\n *\n * Only assigns a field when a value is actually present, so the result can be safely merged over\n * message-derived signals without an explicit `undefined` erasing a recovered value.\n *\n * @param error - The original error (any type).\n * @returns The HTTP status, GraphQL error code, and error class name when available.\n */\nexport function errorGroupingSignals(error: unknown): ErrorGroupingSignals {\n const signals: ErrorGroupingSignals = {}\n\n if (error instanceof Error) {\n signals.errorClass = error.constructor.name\n }\n\n // GraphQLClientError (handleErrors: true, status < 500) preserves the status and errors array.\n if (error instanceof GraphQLClientError) {\n if (error.statusCode !== undefined) signals.httpStatus = error.statusCode\n const code = routableCode(error.errors)\n if (code !== undefined) signals.code = code\n return signals\n }\n\n // Raw graphql-request ClientError (handleErrors: false) exposes the full response.\n if (error instanceof ClientError) {\n const status = error.response?.status\n if (status !== undefined) signals.httpStatus = status\n const code = routableCode(error.response?.errors)\n if (code !== undefined) signals.code = code\n }\n\n return signals\n}\n\n/**\n * Resolves the full grouping decision for an error: hash, category, and the signals behind it.\n *\n * Categories are resolved structured-first (HTTP status / GraphQL code), falling back to the\n * shared keyword categorizer only for untyped errors. When no meaningful category can be\n * resolved, `hash` is `undefined` so the caller leaves `event.groupingHash` unset and Bugsnag's\n * default stack-trace grouping applies — this avoids merging genuinely distinct unknown bugs.\n *\n * @param error - The original error (any type).\n * @param sliceName - The product slice (`app`, `theme`, `store`, `hydrogen`, or `cli`).\n * @returns The resolved hash (or `undefined`), category, and structured signals.\n */\nexport function resolveErrorGrouping(error: unknown, sliceName: string): ResolvedErrorGrouping {\n const message = errorMessage(error)\n // Object signals are authoritative; message-derived signals fill gaps (e.g. 5xx errors that the\n // API layer flattens into an AbortError, dropping the structured status field). Merge field by\n // field so an absent object signal never clobbers one recovered from the message.\n const fromError = errorGroupingSignals(error)\n const fromMessage = signalsFromMessage(message)\n const signals: ErrorGroupingSignals = {\n httpStatus: fromError.httpStatus ?? fromMessage.httpStatus,\n code: fromError.code ?? fromMessage.code,\n errorClass: fromError.errorClass ?? fromMessage.errorClass,\n }\n\n const structuredCategory = categoryFromSignals(signals)\n if (structuredCategory) {\n return {\n hash: `${sliceName}:${structuredCategory}:${structuredSignature(signals)}`,\n category: structuredCategory,\n signals,\n }\n }\n\n const groupingError = new Error(stripJsonDump(message))\n const category = categorizeError(groupingError)\n if (category === ErrorCategory.Unknown) {\n return {hash: undefined, category: undefined, signals}\n }\n\n const categoryName = category.toLowerCase()\n return {\n hash: `${sliceName}:${categoryName}:${formatErrorMessage(groupingError, category)}`,\n category: categoryName,\n signals,\n }\n}\n\n/**\n * Builds a Bugsnag grouping hash of the form `${slice}:${category}:${signature}`.\n *\n * Thin wrapper over {@link resolveErrorGrouping} for callers that only need the hash.\n *\n * @param error - The original error (any type).\n * @param sliceName - The product slice (`app`, `theme`, `store`, `hydrogen`, or `cli`).\n * @returns The grouping hash, or `undefined` to fall back to stack-trace grouping.\n */\nexport function errorGroupingHash(error: unknown, sliceName: string): string | undefined {\n return resolveErrorGrouping(error, sliceName).hash\n}\n\n/**\n * Resolves a semantic category from structured signals using an explicit decision table.\n *\n * Precedence is deliberate:\n * - rate limit (`THROTTLED`/`429` code or HTTP 429) wins first — it is the most actionable bucket.\n * - HTTP 401 is authoritative for `authentication`: a request the server rejected as\n * unauthenticated is an authentication problem even if a GraphQL code also reports access-denied.\n * `ACCESS_DENIED` in practice pairs with HTTP 403, which is handled by the permission branch.\n * - 403 / `ACCESS_DENIED` (incl. nested App Management `access_denied`) → `permission`.\n *\n * Returns `undefined` when no structured signal is present.\n */\nfunction categoryFromSignals(signals: ErrorGroupingSignals): string | undefined {\n const {httpStatus, code} = signals\n\n if (isRateLimitCode(code) || httpStatus === 429) return 'rate_limit'\n if (httpStatus === 401) return 'authentication'\n if (httpStatus === 403 || isPermissionCode(code)) return 'permission'\n if (httpStatus !== undefined && httpStatus >= 500) return 'server'\n\n return undefined\n}\n\n/**\n * Builds a stable, low-cardinality signature slug from structured signals.\n */\nfunction structuredSignature(signals: ErrorGroupingSignals): string {\n const parts: string[] = []\n if (signals.httpStatus !== undefined) parts.push(`http-${signals.httpStatus}`)\n if (signals.code) parts.push(signals.code)\n if (parts.length === 0 && signals.errorClass) parts.push(signals.errorClass)\n\n return slugify(parts.join('-')).slice(0, 50)\n}\n\n/**\n * Recovers structured signals from an error message string. Handles both error shapes seen from\n * the API layer: graphql-request's `GraphQL Error (Code: NNN): {json}` and the cli-kit wrapper's\n * `... responded unsuccessfully with the HTTP status NNN ...`.\n */\nfunction signalsFromMessage(message: string): ErrorGroupingSignals {\n const signals: ErrorGroupingSignals = {}\n\n const statusMatch = message.match(/HTTP status (\\d{3})/i) ?? message.match(/\\(Code: (\\d{3})\\)/i)\n if (statusMatch?.[1]) signals.httpStatus = Number(statusMatch[1])\n\n const payload = parseEmbeddedJson(message)\n if (signals.httpStatus === undefined && payload?.response?.status !== undefined) {\n signals.httpStatus = payload.response.status\n }\n const code = routableCode(payload?.response?.errors)\n if (code !== undefined) signals.code = code\n\n return signals\n}\n\ninterface EmbeddedGraphQLPayload {\n response?: {\n status?: number\n errors?: unknown\n }\n}\n\n/**\n * Parses the JSON blob embedded in a graphql-request ClientError message, if present.\n */\nfunction parseEmbeddedJson(message: string): EmbeddedGraphQLPayload | undefined {\n const jsonStart = message.indexOf('{')\n if (jsonStart === -1) return undefined\n\n try {\n return JSON.parse(message.slice(jsonStart)) as EmbeddedGraphQLPayload\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch {\n return undefined\n }\n}\n\n/**\n * Picks the most routing-relevant code from a GraphQL `errors` value.\n *\n * Scans every error (not just the first) and prefers a code we route on — a rate-limit code, then\n * a permission code — so a benign leading code can't mask a `THROTTLED` or `ACCESS_DENIED` further\n * down the array. Falls back to the first code present for visibility.\n */\nfunction routableCode(errors: unknown): string | undefined {\n const codes = graphQLErrorCodes(errors)\n return codes.find(isRateLimitCode) ?? codes.find(isPermissionCode) ?? codes[0]\n}\n\n/**\n * Strips a trailing JSON dump (`...: {json}`) from a message so the keyword categorizer sees the\n * human-readable prefix rather than the serialized request/response (which contains the literal\n * `request`, mis-routing to the network category).\n */\nfunction stripJsonDump(message: string): string {\n return message.split(': {')[0] ?? message\n}\n\nfunction errorMessage(error: unknown): string {\n if (error instanceof Error) return error.message\n return typeof error === 'string' ? error : ''\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for reading routing-relevant codes out of a GraphQL error response.
|
|
3
|
+
*
|
|
4
|
+
* This module is intentionally dependency-free: it is imported by both the grouping logic
|
|
5
|
+
* (`error-grouping.ts`) and the crash-report suppression logic (`../../public/node/error.ts`).
|
|
6
|
+
* `error.ts` cannot import `error-grouping.ts` directly — that would create an
|
|
7
|
+
* `error.ts → headers.ts → error.ts` import cycle — so the shared scanning logic lives here,
|
|
8
|
+
* where it imports nothing from cli-kit.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Collects every routing-relevant code from a GraphQL `errors` value.
|
|
12
|
+
*
|
|
13
|
+
* Scans *all* errors (not just the first), reading both the top-level `extensions.code` and the
|
|
14
|
+
* nested App Management shape `extensions.app_errors.errors[].category`. The API can also return
|
|
15
|
+
* `errors` as a string (e.g. some 401s), which yields no codes.
|
|
16
|
+
*
|
|
17
|
+
* @param errors - The `errors` value from a GraphQL response (array, string, or undefined).
|
|
18
|
+
* @returns Every string code/category found, in document order.
|
|
19
|
+
*/
|
|
20
|
+
export declare function graphQLErrorCodes(errors: unknown): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Whether a single code is a rate-limit signal (`THROTTLED` or `429`).
|
|
23
|
+
*
|
|
24
|
+
* Mirrors the established shape detected by `errorsIncludeStatus429` in `private/node/api.ts`,
|
|
25
|
+
* where `extensions.code === '429'` signals rate limiting even at HTTP 200.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isRateLimitCode(code: string | undefined): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether a single code/category is a permission / access-denied signal.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isPermissionCode(code: string | undefined): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether any GraphQL error carries a rate-limit code. Convenience for suppression logic that only
|
|
34
|
+
* needs a boolean over the raw `errors` value.
|
|
35
|
+
*
|
|
36
|
+
* @param errors - The `errors` value from a GraphQL response.
|
|
37
|
+
* @returns True when a rate-limit code is present in any error.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasRateLimitCode(errors: unknown): boolean;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for reading routing-relevant codes out of a GraphQL error response.
|
|
3
|
+
*
|
|
4
|
+
* This module is intentionally dependency-free: it is imported by both the grouping logic
|
|
5
|
+
* (`error-grouping.ts`) and the crash-report suppression logic (`../../public/node/error.ts`).
|
|
6
|
+
* `error.ts` cannot import `error-grouping.ts` directly — that would create an
|
|
7
|
+
* `error.ts → headers.ts → error.ts` import cycle — so the shared scanning logic lives here,
|
|
8
|
+
* where it imports nothing from cli-kit.
|
|
9
|
+
*/
|
|
10
|
+
/** GraphQL `extensions.code` values we treat as rate limiting. */
|
|
11
|
+
const RATE_LIMIT_CODES = new Set(['THROTTLED', '429']);
|
|
12
|
+
/**
|
|
13
|
+
* GraphQL `extensions.code` values (and nested App Management `app_errors` categories) we treat as
|
|
14
|
+
* a permission / access-denied signal. The top-level admin code is `ACCESS_DENIED`; the App
|
|
15
|
+
* Management nested shape uses the lowercase category `access_denied`.
|
|
16
|
+
*/
|
|
17
|
+
const PERMISSION_CODES = new Set(['ACCESS_DENIED', 'access_denied']);
|
|
18
|
+
/**
|
|
19
|
+
* Collects every routing-relevant code from a GraphQL `errors` value.
|
|
20
|
+
*
|
|
21
|
+
* Scans *all* errors (not just the first), reading both the top-level `extensions.code` and the
|
|
22
|
+
* nested App Management shape `extensions.app_errors.errors[].category`. The API can also return
|
|
23
|
+
* `errors` as a string (e.g. some 401s), which yields no codes.
|
|
24
|
+
*
|
|
25
|
+
* @param errors - The `errors` value from a GraphQL response (array, string, or undefined).
|
|
26
|
+
* @returns Every string code/category found, in document order.
|
|
27
|
+
*/
|
|
28
|
+
export function graphQLErrorCodes(errors) {
|
|
29
|
+
if (!Array.isArray(errors))
|
|
30
|
+
return [];
|
|
31
|
+
return errors.flatMap((entry) => {
|
|
32
|
+
const found = [];
|
|
33
|
+
const error = entry;
|
|
34
|
+
const code = error?.extensions?.code;
|
|
35
|
+
if (typeof code === 'string')
|
|
36
|
+
found.push(code);
|
|
37
|
+
const appErrors = error?.extensions?.app_errors?.errors;
|
|
38
|
+
if (Array.isArray(appErrors)) {
|
|
39
|
+
for (const appError of appErrors) {
|
|
40
|
+
const category = appError?.category;
|
|
41
|
+
if (typeof category === 'string')
|
|
42
|
+
found.push(category);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return found;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether a single code is a rate-limit signal (`THROTTLED` or `429`).
|
|
50
|
+
*
|
|
51
|
+
* Mirrors the established shape detected by `errorsIncludeStatus429` in `private/node/api.ts`,
|
|
52
|
+
* where `extensions.code === '429'` signals rate limiting even at HTTP 200.
|
|
53
|
+
*/
|
|
54
|
+
export function isRateLimitCode(code) {
|
|
55
|
+
return code !== undefined && RATE_LIMIT_CODES.has(code);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Whether a single code/category is a permission / access-denied signal.
|
|
59
|
+
*/
|
|
60
|
+
export function isPermissionCode(code) {
|
|
61
|
+
return code !== undefined && PERMISSION_CODES.has(code);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether any GraphQL error carries a rate-limit code. Convenience for suppression logic that only
|
|
65
|
+
* needs a boolean over the raw `errors` value.
|
|
66
|
+
*
|
|
67
|
+
* @param errors - The `errors` value from a GraphQL response.
|
|
68
|
+
* @returns True when a rate-limit code is present in any error.
|
|
69
|
+
*/
|
|
70
|
+
export function hasRateLimitCode(errors) {
|
|
71
|
+
return graphQLErrorCodes(errors).some(isRateLimitCode);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=graphql-error-codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql-error-codes.js","sourceRoot":"","sources":["../../../../src/private/node/analytics/graphql-error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,kEAAkE;AAClE,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAA;AAEtD;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAA;AAEpE;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAe;IAC/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAA;IAErC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,MAAM,KAAK,GAAG,KAAqF,CAAA;QAEnG,MAAM,IAAI,GAAG,KAAK,EAAE,UAAU,EAAE,IAAI,CAAA;QACpC,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE9C,MAAM,SAAS,GAAG,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAA;QACvD,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,MAAM,QAAQ,GAAI,QAA6C,EAAE,QAAQ,CAAA;gBACzE,IAAI,OAAO,QAAQ,KAAK,QAAQ;oBAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACxD,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,IAAwB;IACtD,OAAO,IAAI,KAAK,SAAS,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAwB;IACvD,OAAO,IAAI,KAAK,SAAS,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;AACxD,CAAC","sourcesContent":["/**\n * Pure helpers for reading routing-relevant codes out of a GraphQL error response.\n *\n * This module is intentionally dependency-free: it is imported by both the grouping logic\n * (`error-grouping.ts`) and the crash-report suppression logic (`../../public/node/error.ts`).\n * `error.ts` cannot import `error-grouping.ts` directly — that would create an\n * `error.ts → headers.ts → error.ts` import cycle — so the shared scanning logic lives here,\n * where it imports nothing from cli-kit.\n */\n\n/** GraphQL `extensions.code` values we treat as rate limiting. */\nconst RATE_LIMIT_CODES = new Set(['THROTTLED', '429'])\n\n/**\n * GraphQL `extensions.code` values (and nested App Management `app_errors` categories) we treat as\n * a permission / access-denied signal. The top-level admin code is `ACCESS_DENIED`; the App\n * Management nested shape uses the lowercase category `access_denied`.\n */\nconst PERMISSION_CODES = new Set(['ACCESS_DENIED', 'access_denied'])\n\n/**\n * Collects every routing-relevant code from a GraphQL `errors` value.\n *\n * Scans *all* errors (not just the first), reading both the top-level `extensions.code` and the\n * nested App Management shape `extensions.app_errors.errors[].category`. The API can also return\n * `errors` as a string (e.g. some 401s), which yields no codes.\n *\n * @param errors - The `errors` value from a GraphQL response (array, string, or undefined).\n * @returns Every string code/category found, in document order.\n */\nexport function graphQLErrorCodes(errors: unknown): string[] {\n if (!Array.isArray(errors)) return []\n\n return errors.flatMap((entry) => {\n const found: string[] = []\n const error = entry as {extensions?: {code?: unknown; app_errors?: {errors?: unknown}}} | undefined\n\n const code = error?.extensions?.code\n if (typeof code === 'string') found.push(code)\n\n const appErrors = error?.extensions?.app_errors?.errors\n if (Array.isArray(appErrors)) {\n for (const appError of appErrors) {\n const category = (appError as {category?: unknown} | undefined)?.category\n if (typeof category === 'string') found.push(category)\n }\n }\n\n return found\n })\n}\n\n/**\n * Whether a single code is a rate-limit signal (`THROTTLED` or `429`).\n *\n * Mirrors the established shape detected by `errorsIncludeStatus429` in `private/node/api.ts`,\n * where `extensions.code === '429'` signals rate limiting even at HTTP 200.\n */\nexport function isRateLimitCode(code: string | undefined): boolean {\n return code !== undefined && RATE_LIMIT_CODES.has(code)\n}\n\n/**\n * Whether a single code/category is a permission / access-denied signal.\n */\nexport function isPermissionCode(code: string | undefined): boolean {\n return code !== undefined && PERMISSION_CODES.has(code)\n}\n\n/**\n * Whether any GraphQL error carries a rate-limit code. Convenience for suppression logic that only\n * needs a boolean over the raw `errors` value.\n *\n * @param errors - The `errors` value from a GraphQL response.\n * @returns True when a rate-limit code is present in any error.\n */\nexport function hasRateLimitCode(errors: unknown): boolean {\n return graphQLErrorCodes(errors).some(isRateLimitCode)\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CLI_KIT_VERSION } from '../../../public/common/version.js';
|
|
2
|
-
import {
|
|
2
|
+
import { isUnitTest, isVerbose } from '../../../public/node/context/local.js';
|
|
3
3
|
import { AbortError } from '../../../public/node/error.js';
|
|
4
4
|
import https from 'https';
|
|
5
5
|
class RequestClientError extends AbortError {
|
|
@@ -49,7 +49,6 @@ export function buildHeaders(token) {
|
|
|
49
49
|
// 'Sec-CH-UA': secCHUA, This header requires the Git sha.
|
|
50
50
|
'Sec-CH-UA-PLATFORM': process.platform,
|
|
51
51
|
'Content-Type': 'application/json',
|
|
52
|
-
...(firstPartyDev() && { 'X-Shopify-Cli-Employee': '1' }),
|
|
53
52
|
};
|
|
54
53
|
if (token) {
|
|
55
54
|
const authString = token.match(/^shp(at|ua|ca|tka)/) ? token : `Bearer ${token}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../../src/private/node/api/headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,mCAAmC,CAAA;AACjE,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../../src/private/node/api/headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,mCAAmC,CAAA;AACjE,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,uCAAuC,CAAA;AAC3E,OAAO,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAA;AACxD,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,MAAM,kBAAmB,SAAQ,UAAU;IAEzC,YAAmB,OAAe,EAAE,UAAkB;QACpD,MAAM,UAAU,GACd,UAAU,KAAK,GAAG;YAChB,CAAC,CAAC,oFAAoF;YACtF,CAAC,CAAC,SAAS,CAAA;QACf,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAC1B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;CACF;AACD,MAAM,OAAO,kBAAmB,SAAQ,kBAAkB;IAIxD,8DAA8D;IAC9D,YAAmB,OAAe,EAAE,UAAkB,EAAE,MAAc;QACpE,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;IACxB,CAAC;CACF;AAED,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;AAE5F;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAA+B;IACpE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClC,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,SAAS,GAA2B,EAAE,CAAA;IAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QACtC,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC9F,SAAS,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAE,CAAA;QACtC,CAAC;IACH,CAAC,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;SAC1B,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACd,OAAO,MAAM,MAAM,KAAK,SAAS,CAAC,MAAM,CAAC,EAAE,CAAA;IAC7C,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,MAAM,SAAS,GAAG,kBAAkB,eAAe,EAAE,CAAA;IAErD,MAAM,OAAO,GAA2B;QACtC,YAAY,EAAE,SAAS;QACvB,YAAY,EAAE,YAAY;QAC1B,0DAA0D;QAC1D,oBAAoB,EAAE,OAAO,CAAC,QAAQ;QACtC,cAAc,EAAE,kBAAkB;KACnC,CAAA;IACD,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAA;QAEhF,OAAO,CAAC,aAAa,GAAG,UAAU,CAAA;QAClC,OAAO,CAAC,wBAAwB,CAAC,GAAG,UAAU,CAAA;IAChD,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;QACrB,kBAAkB,EAAE,IAAI;QACxB,SAAS,EAAE,IAAI;KAChB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import {CLI_KIT_VERSION} from '../../../public/common/version.js'\nimport {isUnitTest, isVerbose} from '../../../public/node/context/local.js'\nimport {AbortError} from '../../../public/node/error.js'\nimport https from 'https'\n\nclass RequestClientError extends AbortError {\n statusCode: number\n public constructor(message: string, statusCode: number) {\n const tryMessage =\n statusCode === 403\n ? 'Ensure you are using the correct account. You can switch with `shopify auth login`'\n : undefined\n super(message, tryMessage)\n this.statusCode = statusCode\n }\n}\nexport class GraphQLClientError extends RequestClientError {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n errors?: any[]\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public constructor(message: string, statusCode: number, errors?: any[]) {\n super(message, statusCode)\n this.errors = errors\n this.stack = undefined\n }\n}\n\nconst SENSITIVE_HEADERS = ['token', 'authorization', 'subject_token', 'cookie', 'signature']\n\n/**\n * Removes the sensitive data from the headers and outputs them as a string.\n * @param headers - HTTP headers.\n * @returns A sanitized version of the headers as a string.\n */\nexport function sanitizedHeadersOutput(headers: Record<string, string>): string {\n if (!isVerbose() && !isUnitTest()) {\n return ''\n }\n\n const sanitized: Record<string, string> = {}\n Object.keys(headers).forEach((header) => {\n if (SENSITIVE_HEADERS.find((keyword) => header.toLowerCase().includes(keyword)) === undefined) {\n sanitized[header] = headers[header]!\n }\n })\n return Object.keys(sanitized)\n .map((header) => {\n return ` - ${header}: ${sanitized[header]}`\n })\n .join('\\n')\n}\n\nexport function buildHeaders(token?: string): Record<string, string> {\n const userAgent = `Shopify CLI; v=${CLI_KIT_VERSION}`\n\n const headers: Record<string, string> = {\n 'User-Agent': userAgent,\n 'Keep-Alive': 'timeout=30',\n // 'Sec-CH-UA': secCHUA, This header requires the Git sha.\n 'Sec-CH-UA-PLATFORM': process.platform,\n 'Content-Type': 'application/json',\n }\n if (token) {\n const authString = token.match(/^shp(at|ua|ca|tka)/) ? token : `Bearer ${token}`\n\n headers.authorization = authString\n headers['X-Shopify-Access-Token'] = authString\n }\n\n return headers\n}\n\n/**\n * This utility function returns the https.Agent to use for a given service.\n */\nexport async function httpsAgent(): Promise<https.Agent> {\n return new https.Agent({\n rejectUnauthorized: true,\n keepAlive: true,\n })\n}\n"]}
|
|
@@ -6,7 +6,6 @@ export declare const environmentVariables: {
|
|
|
6
6
|
doctor: string;
|
|
7
7
|
enableCliRedirect: string;
|
|
8
8
|
env: string;
|
|
9
|
-
firstPartyDev: string;
|
|
10
9
|
noAnalytics: string;
|
|
11
10
|
optOutInstrumentation: string;
|
|
12
11
|
appAutomationToken: string;
|
|
@@ -31,7 +30,6 @@ export declare const environmentVariables: {
|
|
|
31
30
|
otelURL: string;
|
|
32
31
|
themeKitAccessDomain: string;
|
|
33
32
|
json: string;
|
|
34
|
-
neverUsePartnersApi: string;
|
|
35
33
|
skipNetworkLevelRetry: string;
|
|
36
34
|
maxRequestTimeForNetworkCalls: string;
|
|
37
35
|
disableImportScanning: string;
|
|
@@ -16,7 +16,6 @@ export const environmentVariables = {
|
|
|
16
16
|
doctor: 'SHOPIFY_CLI_DOCTOR',
|
|
17
17
|
enableCliRedirect: 'SHOPIFY_CLI_ENABLE_CLI_REDIRECT',
|
|
18
18
|
env: 'SHOPIFY_CLI_ENV',
|
|
19
|
-
firstPartyDev: 'SHOPIFY_CLI_1P_DEV',
|
|
20
19
|
noAnalytics: 'SHOPIFY_CLI_NO_ANALYTICS',
|
|
21
20
|
optOutInstrumentation: 'OPT_OUT_INSTRUMENTATION',
|
|
22
21
|
appAutomationToken: 'SHOPIFY_APP_AUTOMATION_TOKEN',
|
|
@@ -42,7 +41,6 @@ export const environmentVariables = {
|
|
|
42
41
|
otelURL: 'SHOPIFY_CLI_OTEL_EXPORTER_OTLP_ENDPOINT',
|
|
43
42
|
themeKitAccessDomain: 'SHOPIFY_CLI_THEME_KIT_ACCESS_DOMAIN',
|
|
44
43
|
json: 'SHOPIFY_FLAG_JSON',
|
|
45
|
-
neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',
|
|
46
44
|
skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',
|
|
47
45
|
maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',
|
|
48
46
|
disableImportScanning: 'SHOPIFY_CLI_DISABLE_IMPORT_SCANNING',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/private/node/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAA;AAClD,OAAO,QAAQ,MAAM,WAAW,CAAA;AAEhC,MAAM,UAAU,GAAG,aAAa,CAAA;AAEhC,MAAM,WAAW,GAAG,GAAG,EAAE;IACvB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IACjE,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,KAAK,CAAA;AACnC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,EAAE;IAC7B,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,CAAA;AACjC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,kBAAkB,EAAE,kCAAkC;IACtD,gBAAgB,EAAE,gCAAgC;IAClD,UAAU,EAAE,yBAAyB;IACrC,MAAM,EAAE,oBAAoB;IAC5B,iBAAiB,EAAE,iCAAiC;IACpD,GAAG,EAAE,iBAAiB;IACtB,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/private/node/constants.ts"],"names":[],"mappings":"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","sourcesContent":["import {joinPath} from '../../public/node/path.js'\nimport envPaths from 'env-paths'\n\nconst identifier = 'shopify-cli'\n\nconst cacheFolder = () => {\n if (process.env.XDG_CACHE_HOME) return process.env.XDG_CACHE_HOME\n return envPaths(identifier).cache\n}\n\nexport const logsFolder = () => {\n return envPaths(identifier).log\n}\n\nexport const environmentVariables = {\n alwaysLogAnalytics: 'SHOPIFY_CLI_ALWAYS_LOG_ANALYTICS',\n alwaysLogMetrics: 'SHOPIFY_CLI_ALWAYS_LOG_METRICS',\n deviceAuth: 'SHOPIFY_CLI_DEVICE_AUTH',\n doctor: 'SHOPIFY_CLI_DOCTOR',\n enableCliRedirect: 'SHOPIFY_CLI_ENABLE_CLI_REDIRECT',\n env: 'SHOPIFY_CLI_ENV',\n noAnalytics: 'SHOPIFY_CLI_NO_ANALYTICS',\n optOutInstrumentation: 'OPT_OUT_INSTRUMENTATION',\n appAutomationToken: 'SHOPIFY_APP_AUTOMATION_TOKEN',\n partnersToken: 'SHOPIFY_CLI_PARTNERS_TOKEN',\n runAsUser: 'SHOPIFY_RUN_AS_USER',\n serviceEnv: 'SHOPIFY_SERVICE_ENV',\n skipCliRedirect: 'SHOPIFY_CLI_SKIP_CLI_REDIRECT',\n spinInstance: 'SPIN_INSTANCE',\n themeToken: 'SHOPIFY_CLI_THEME_TOKEN',\n unitTest: 'SHOPIFY_UNIT_TEST',\n verbose: 'SHOPIFY_FLAG_VERBOSE',\n // Variables to detect if the CLI is running in a cloud environment\n codespaces: 'CODESPACES',\n codespaceName: 'CODESPACE_NAME',\n codespacePortForwardingDomain: 'GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN',\n gitpod: 'GITPOD_WORKSPACE_URL',\n cloudShell: 'CLOUD_SHELL',\n spinAppPort: 'SERVER_PORT',\n spinAppHost: 'SPIN_APP_HOST',\n organization: 'SHOPIFY_CLI_ORGANIZATION',\n identityToken: 'SHOPIFY_CLI_IDENTITY_TOKEN',\n refreshToken: 'SHOPIFY_CLI_REFRESH_TOKEN',\n otelURL: 'SHOPIFY_CLI_OTEL_EXPORTER_OTLP_ENDPOINT',\n themeKitAccessDomain: 'SHOPIFY_CLI_THEME_KIT_ACCESS_DOMAIN',\n json: 'SHOPIFY_FLAG_JSON',\n skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',\n maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',\n disableImportScanning: 'SHOPIFY_CLI_DISABLE_IMPORT_SCANNING',\n}\n\nexport const defaultThemeKitAccessDomain = 'theme-kit-access.shopifyapps.com'\n\nexport const systemEnvironmentVariables = {\n backendPort: 'BACKEND_PORT',\n}\n\nexport const pathConstants = {\n executables: {\n dev: '/opt/dev/bin/dev',\n },\n directories: {\n cache: {\n path: () => {\n return cacheFolder()\n },\n vendor: {\n path: () => {\n return joinPath(cacheFolder(), 'vendor')\n },\n binaries: () => {\n return joinPath(cacheFolder(), 'vendor', 'binaries')\n },\n },\n },\n },\n}\n\nexport const sessionConstants = {\n expirationTimeMarginInMinutes: 4,\n}\n\nexport const bugsnagApiKey = '9e1e6889176fd0c795d5c659225e0fae'\n\nexport const reportingRateLimit = {limit: 300, timeout: {days: 1}}\n\nexport const themeKitAccessDomain =\n process.env[environmentVariables.themeKitAccessDomain] ?? defaultThemeKitAccessDomain\n"]}
|
|
@@ -49,8 +49,7 @@ export class JsonContentToken extends ContentToken {
|
|
|
49
49
|
}
|
|
50
50
|
export class LinesDiffContentToken extends ContentToken {
|
|
51
51
|
output() {
|
|
52
|
-
return this.value
|
|
53
|
-
.map((part) => {
|
|
52
|
+
return this.value.flatMap((part) => {
|
|
54
53
|
if (part.added) {
|
|
55
54
|
return part.value
|
|
56
55
|
.split(/\n/)
|
|
@@ -70,8 +69,7 @@ export class LinesDiffContentToken extends ContentToken {
|
|
|
70
69
|
else {
|
|
71
70
|
return part.value;
|
|
72
71
|
}
|
|
73
|
-
})
|
|
74
|
-
.flat();
|
|
72
|
+
});
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
export class ColorContentToken extends ContentToken {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-tokens.js","sourceRoot":"","sources":["../../../src/private/node/content-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAgB,gBAAgB,EAAC,MAAM,6BAA6B,CAAA;AAC3E,OAAO,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAA;AACxD,OAAO,WAAW,MAAM,cAAc,CAAA;AACtC,OAAO,kBAAkB,MAAM,qBAAqB,CAAA;AACpD,OAAO,GAAG,MAAM,YAAY,CAAA;AAG5B,MAAM,OAAgB,YAAY;IAGhC,YAAY,KAAQ;QAClB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CAGF;AAED,MAAM,OAAO,eAAgB,SAAQ,YAAoB;IACvD,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,YAA2B;IAI/D,YAAY,KAAoB,EAAE,IAAa,EAAE,QAAiB;QAChE,KAAK,CAAC,KAAK,CAAC,CAAA;QACZ,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAA;QAC3C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;QAC3B,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,GAAG,IAAI,CAAA;QAC9E,IAAI,kBAAkB,CAAC,MAAM,EAAE,CAAC;YAC9B,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QACpC,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,IAAI,eAAe,CAAA;IACzC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,YAA2B;IAClE,MAAM;QACJ,OAAO,KAAK,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAA;IACpE,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,OAAO,gBAAiB,SAAQ,YAAiB;IACrD,MAAM;QACJ,IAAI,CAAC;YACH,OAAO,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;YAC9C,qDAAqD;QACvD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,YAAsB;IAC/D,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK
|
|
1
|
+
{"version":3,"file":"content-tokens.js","sourceRoot":"","sources":["../../../src/private/node/content-tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import colors from '../../public/node/colors.js'\nimport {OutputMessage, stringifyMessage} from '../../public/node/output.js'\nimport {relativizePath} from '../../public/node/path.js'\nimport ansiEscapes from 'ansi-escapes'\nimport supportsHyperlinks from 'supports-hyperlinks'\nimport cjs from 'color-json'\nimport type {Change} from 'diff'\n\nexport abstract class ContentToken<T> {\n value: T\n\n constructor(value: T) {\n this.value = value\n }\n\n abstract output(): string | string[]\n}\n\nexport class RawContentToken extends ContentToken<string> {\n output(): string {\n return this.value\n }\n}\n\nexport class LinkContentToken extends ContentToken<OutputMessage> {\n link: string\n fallback: string | undefined\n\n constructor(value: OutputMessage, link?: string, fallback?: string) {\n super(value)\n this.link = link ?? stringifyMessage(value)\n this.fallback = fallback\n }\n\n output() {\n const text = colors.green(stringifyMessage(this.value))\n const url = this.link ?? ''\n const defaultFallback = this.value === this.link ? text : `${text} ( ${url} )`\n if (supportsHyperlinks.stdout) {\n return ansiEscapes.link(text, url)\n }\n return this.fallback ?? defaultFallback\n }\n}\n\nexport class CommandContentToken extends ContentToken<OutputMessage> {\n output(): string {\n return `\\`${colors.magentaBright(stringifyMessage(this.value))}\\``\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class JsonContentToken extends ContentToken<any> {\n output(): string {\n try {\n return cjs(stringifyMessage(this.value) ?? {})\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (_) {\n return JSON.stringify(stringifyMessage(this.value) ?? {}, null, 2)\n }\n }\n}\n\nexport class LinesDiffContentToken extends ContentToken<Change[]> {\n output(): string[] {\n return this.value.flatMap((part) => {\n if (part.added) {\n return part.value\n .split(/\\n/)\n .filter((line) => line !== '')\n .map((line) => {\n return colors.green(`+ ${line}\\n`)\n })\n } else if (part.removed) {\n return part.value\n .split(/\\n/)\n .filter((line) => line !== '')\n .map((line) => {\n return colors.magenta(`- ${line}\\n`)\n })\n } else {\n return part.value\n }\n })\n }\n}\n\nexport class ColorContentToken extends ContentToken<OutputMessage> {\n color: (text: string) => string\n\n constructor(value: OutputMessage, color: (text: string) => string) {\n super(value)\n this.color = color\n }\n\n output(): string {\n return this.color(stringifyMessage(this.value))\n }\n}\n\nexport class ErrorContentToken extends ContentToken<OutputMessage> {\n output(): string {\n return colors.bold.redBright(stringifyMessage(this.value))\n }\n}\n\nexport class PathContentToken extends ContentToken<OutputMessage> {\n output(): string {\n return relativizePath(stringifyMessage(this.value))\n }\n}\n\nexport class HeadingContentToken extends ContentToken<OutputMessage> {\n output(): string {\n return colors.bold.underline(stringifyMessage(this.value))\n }\n}\n\nexport class SubHeadingContentToken extends ContentToken<OutputMessage> {\n output(): string {\n return colors.underline(stringifyMessage(this.value))\n }\n}\n\nexport class ItalicContentToken extends ContentToken<OutputMessage> {\n output(): string {\n return colors.italic(stringifyMessage(this.value))\n }\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { allAPIs } from '../api.js';
|
|
2
2
|
import { BugError } from '../../../public/node/error.js';
|
|
3
|
+
import { uniq } from '../../../public/common/array.js';
|
|
3
4
|
/**
|
|
4
5
|
* Generate a flat array with all the default scopes for all the APIs plus
|
|
5
6
|
* any custom scope defined by the user.
|
|
@@ -7,9 +8,9 @@ import { BugError } from '../../../public/node/error.js';
|
|
|
7
8
|
* @returns Array of scopes
|
|
8
9
|
*/
|
|
9
10
|
export function allDefaultScopes(extraScopes = []) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return
|
|
11
|
+
const scopes = allAPIs.flatMap((api) => defaultApiScopes(api));
|
|
12
|
+
const allScopes = ['openid', ...scopes, ...extraScopes].map(scopeTransform);
|
|
13
|
+
return uniq(allScopes);
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Generate a flat array with the default scopes for the given API plus
|
|
@@ -19,8 +20,8 @@ export function allDefaultScopes(extraScopes = []) {
|
|
|
19
20
|
* @returns Array of scopes
|
|
20
21
|
*/
|
|
21
22
|
export function apiScopes(api, extraScopes = []) {
|
|
22
|
-
const scopes = [...defaultApiScopes(api), ...extraScopes
|
|
23
|
-
return
|
|
23
|
+
const scopes = [...defaultApiScopes(api), ...extraScopes].map(scopeTransform);
|
|
24
|
+
return uniq(scopes);
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Returns specific scopes required for token exchange with the given API.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../../../src/private/node/session/scopes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAM,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,QAAQ,EAAC,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../../../src/private/node/session/scopes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAM,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,QAAQ,EAAC,MAAM,+BAA+B,CAAA;AACtD,OAAO,EAAC,IAAI,EAAC,MAAM,iCAAiC,CAAA;AAEpD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,cAAwB,EAAE;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9D,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,GAAG,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAC3E,OAAO,IAAI,CAAC,SAAS,CAAC,CAAA;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAQ,EAAE,cAAwB,EAAE;IAC5D,MAAM,MAAM,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAC7E,OAAO,IAAI,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAQ;IAC1C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,UAAU;YACb,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAA;QAChC,KAAK,gBAAgB;YACnB,OAAO,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAA;QAC3C,KAAK,mBAAmB;YACtB,OAAO,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAA;QACzC,KAAK,OAAO,CAAC;QACb,KAAK,qBAAqB;YACxB,MAAM,IAAI,QAAQ,CAAC,yCAAyC,GAAG,EAAE,CAAC,CAAA;IACtE,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAQ;IAChC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAA;QAC9C,KAAK,qBAAqB;YACxB,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;QAChC,KAAK,UAAU;YACb,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,KAAK,mBAAmB;YACtB,OAAO,CAAC,cAAc,EAAE,kBAAkB,EAAE,uBAAuB,CAAC,CAAA;QACtE,KAAK,gBAAgB;YACnB,OAAO,CAAC,gBAAgB,CAAC,CAAA;QAC3B;YACE,MAAM,IAAI,QAAQ,CAAC,gBAAgB,GAAG,EAAE,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,SAAS;YACZ,OAAO,iDAAiD,CAAA;QAC1D,KAAK,QAAQ;YACX,OAAO,gDAAgD,CAAA;QACzD,KAAK,cAAc;YACjB,OAAO,2EAA2E,CAAA;QACpF,KAAK,KAAK;YACR,OAAO,sDAAsD,CAAA;QAC/D,KAAK,UAAU;YACb,OAAO,gEAAgE,CAAA;QACzE,KAAK,cAAc;YACjB,OAAO,oDAAoD,CAAA;QAC7D,KAAK,kBAAkB;YACrB,OAAO,4DAA4D,CAAA;QACrE,KAAK,uBAAuB;YAC1B,OAAO,iEAAiE,CAAA;QAC1E,KAAK,gBAAgB;YACnB,OAAO,uDAAuD,CAAA;QAChE;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC","sourcesContent":["import {allAPIs, API} from '../api.js'\nimport {BugError} from '../../../public/node/error.js'\nimport {uniq} from '../../../public/common/array.js'\n\n/**\n * Generate a flat array with all the default scopes for all the APIs plus\n * any custom scope defined by the user.\n * @param extraScopes - custom user-defined scopes\n * @returns Array of scopes\n */\nexport function allDefaultScopes(extraScopes: string[] = []): string[] {\n const scopes = allAPIs.flatMap((api) => defaultApiScopes(api))\n const allScopes = ['openid', ...scopes, ...extraScopes].map(scopeTransform)\n return uniq(allScopes)\n}\n\n/**\n * Generate a flat array with the default scopes for the given API plus\n * any custom scope defined by the user\n * @param api - API to get the scopes for\n * @param extraScopes - custom user-defined scopes\n * @returns Array of scopes\n */\nexport function apiScopes(api: API, extraScopes: string[] = []): string[] {\n const scopes = [...defaultApiScopes(api), ...extraScopes].map(scopeTransform)\n return uniq(scopes)\n}\n\n/**\n * Returns specific scopes required for token exchange with the given API.\n * @param api - API to get the scopes for\n * @returns Array of transformed scopes\n */\nexport function tokenExchangeScopes(api: API): string[] {\n switch (api) {\n case 'partners':\n return [scopeTransform('cli')]\n case 'app-management':\n return [scopeTransform('app-management')]\n case 'business-platform':\n return [scopeTransform('destinations')]\n case 'admin':\n case 'storefront-renderer':\n throw new BugError(`API not supported for token exchange: ${api}`)\n }\n}\n\nfunction defaultApiScopes(api: API): string[] {\n switch (api) {\n case 'admin':\n return ['graphql', 'themes', 'collaborator']\n case 'storefront-renderer':\n return ['devtools', 'graphql']\n case 'partners':\n return ['cli']\n case 'business-platform':\n return ['destinations', 'store-management', 'on-demand-user-access']\n case 'app-management':\n return ['app-management']\n default:\n throw new BugError(`Unknown API: ${api}`)\n }\n}\n\nfunction scopeTransform(scope: string): string {\n switch (scope) {\n case 'graphql':\n return 'https://api.shopify.com/auth/shop.admin.graphql'\n case 'themes':\n return 'https://api.shopify.com/auth/shop.admin.themes'\n case 'collaborator':\n return 'https://api.shopify.com/auth/partners.collaborator-relationships.readonly'\n case 'cli':\n return 'https://api.shopify.com/auth/partners.app.cli.access'\n case 'devtools':\n return 'https://api.shopify.com/auth/shop.storefront-renderer.devtools'\n case 'destinations':\n return 'https://api.shopify.com/auth/destinations.readonly'\n case 'store-management':\n return 'https://api.shopify.com/auth/organization.store-management'\n case 'on-demand-user-access':\n return 'https://api.shopify.com/auth/organization.on-demand-user-access'\n case 'app-management':\n return 'https://api.shopify.com/auth/organization.apps.manage'\n default:\n return scope\n }\n}\n"]}
|