@shopify/cli-kit 4.1.0 → 4.3.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/assets/graphiql/favicon.ico +0 -0
- package/assets/graphiql/style.css +58 -0
- package/dist/private/common/array.js +2 -1
- package/dist/private/common/array.js.map +1 -1
- package/dist/private/node/api/headers.js +6 -3
- package/dist/private/node/api/headers.js.map +1 -1
- package/dist/private/node/session/device-authorization.js +4 -16
- package/dist/private/node/session/device-authorization.js.map +1 -1
- package/dist/private/node/session/store.js +10 -1
- package/dist/private/node/session/store.js.map +1 -1
- package/dist/private/node/ui.js +4 -1
- package/dist/private/node/ui.js.map +1 -1
- package/dist/public/common/gid.d.ts +24 -0
- package/dist/public/common/gid.js +32 -0
- package/dist/public/common/gid.js.map +1 -0
- package/dist/public/common/url.d.ts +16 -0
- package/dist/public/common/url.js +39 -0
- package/dist/public/common/url.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 +3 -5
- package/dist/public/node/analytics.js.map +1 -1
- package/dist/public/node/cli.d.ts +13 -0
- package/dist/public/node/cli.js +12 -0
- package/dist/public/node/cli.js.map +1 -1
- package/dist/public/node/context/fqdn.js +1 -1
- package/dist/public/node/context/fqdn.js.map +1 -1
- package/dist/public/node/error-handler.js +1 -1
- package/dist/public/node/error-handler.js.map +1 -1
- package/dist/public/node/graphiql/server.d.ts +80 -0
- package/dist/public/node/graphiql/server.js +234 -0
- package/dist/public/node/graphiql/server.js.map +1 -0
- package/dist/public/node/graphiql/templates/graphiql.d.ts +12 -0
- package/dist/public/node/graphiql/templates/graphiql.js +314 -0
- package/dist/public/node/graphiql/templates/graphiql.js.map +1 -0
- package/dist/public/node/graphiql/templates/unauthorized.d.ts +5 -0
- package/dist/public/node/graphiql/templates/unauthorized.js +111 -0
- package/dist/public/node/graphiql/templates/unauthorized.js.map +1 -0
- package/dist/public/node/graphiql/utilities.d.ts +12 -0
- package/dist/public/node/graphiql/utilities.js +44 -0
- package/dist/public/node/graphiql/utilities.js.map +1 -0
- package/dist/public/node/graphql.d.ts +19 -0
- package/dist/public/node/graphql.js +41 -0
- package/dist/public/node/graphql.js.map +1 -0
- package/dist/public/node/hooks/postrun.js +12 -2
- package/dist/public/node/hooks/postrun.js.map +1 -1
- package/dist/public/node/http.js +27 -31
- package/dist/public/node/http.js.map +1 -1
- package/dist/public/node/json-schema.js +22 -6
- package/dist/public/node/json-schema.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/monorail.d.ts +3 -1
- package/dist/public/node/monorail.js +1 -1
- package/dist/public/node/monorail.js.map +1 -1
- package/dist/public/node/output.js +20 -11
- package/dist/public/node/output.js.map +1 -1
- package/dist/public/node/session.d.ts +2 -1
- package/dist/public/node/session.js +3 -2
- package/dist/public/node/session.js.map +1 -1
- package/dist/public/node/system.js +3 -0
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/tcp.js +11 -3
- package/dist/public/node/tcp.js.map +1 -1
- package/dist/public/node/themes/api.js +76 -4
- package/dist/public/node/themes/api.js.map +1 -1
- package/dist/public/node/toml/toml-file.d.ts +3 -2
- package/dist/public/node/toml/toml-file.js +3 -2
- package/dist/public/node/toml/toml-file.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +34 -29
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
html {
|
|
2
|
+
font-family: -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
|
|
3
|
+
text-size-adjust: 100%;
|
|
4
|
+
text-rendering: optimizeLegibility;
|
|
5
|
+
-webkit-font-smoothing: antialiased;
|
|
6
|
+
-moz-osx-font-smoothing: grayscale;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-size: 26px;
|
|
11
|
+
line-height: normal;
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
button, input, optgroup, select, textarea {
|
|
17
|
+
font-family: inherit;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
h1 {
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
font-size: 1em;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
p {
|
|
26
|
+
font-weight: 400;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.body-success {
|
|
30
|
+
color: #F6F6F7;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.body-error {
|
|
34
|
+
color: #202223;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.app-success {
|
|
38
|
+
width: 100vw;
|
|
39
|
+
height: 100vh;
|
|
40
|
+
background-color: #054A49;
|
|
41
|
+
display: flex;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.app-error {
|
|
45
|
+
width: 100vw;
|
|
46
|
+
height: 100vh;
|
|
47
|
+
background-color: #F6F6F7;
|
|
48
|
+
display: flex;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.container {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
width: 100%;
|
|
56
|
+
height: 100%;
|
|
57
|
+
padding-left: 7.5em;
|
|
58
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { uniq } from '../../public/common/array.js';
|
|
1
2
|
export function unionArrayStrategy(destinationArray, sourceArray) {
|
|
2
|
-
return
|
|
3
|
+
return uniq([...destinationArray, ...sourceArray]);
|
|
3
4
|
}
|
|
4
5
|
//# sourceMappingURL=array.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array.js","sourceRoot":"","sources":["../../../src/private/common/array.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,gBAA2B,EAAE,WAAsB;IACpF,OAAO,
|
|
1
|
+
{"version":3,"file":"array.js","sourceRoot":"","sources":["../../../src/private/common/array.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,8BAA8B,CAAA;AAEjD,MAAM,UAAU,kBAAkB,CAAC,gBAA2B,EAAE,WAAsB;IACpF,OAAO,IAAI,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,WAAW,CAAC,CAAC,CAAA;AACpD,CAAC","sourcesContent":["import {uniq} from '../../public/common/array.js'\n\nexport function unionArrayStrategy(destinationArray: unknown[], sourceArray: unknown[]): unknown[] {\n return uniq([...destinationArray, ...sourceArray])\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CLI_KIT_VERSION } from '../../../public/common/version.js';
|
|
2
|
-
import { firstPartyDev } from '../../../public/node/context/local.js';
|
|
2
|
+
import { firstPartyDev, 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 {
|
|
@@ -19,16 +19,19 @@ export class GraphQLClientError extends RequestClientError {
|
|
|
19
19
|
this.stack = undefined;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
const SENSITIVE_HEADERS = ['token', 'authorization', 'subject_token', 'cookie'];
|
|
22
23
|
/**
|
|
23
24
|
* Removes the sensitive data from the headers and outputs them as a string.
|
|
24
25
|
* @param headers - HTTP headers.
|
|
25
26
|
* @returns A sanitized version of the headers as a string.
|
|
26
27
|
*/
|
|
27
28
|
export function sanitizedHeadersOutput(headers) {
|
|
29
|
+
if (!isVerbose() && !isUnitTest()) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
28
32
|
const sanitized = {};
|
|
29
|
-
const keywords = ['token', 'authorization', 'subject_token', 'cookie'];
|
|
30
33
|
Object.keys(headers).forEach((header) => {
|
|
31
|
-
if (
|
|
34
|
+
if (SENSITIVE_HEADERS.find((keyword) => header.toLowerCase().includes(keyword)) === undefined) {
|
|
32
35
|
sanitized[header] = headers[header];
|
|
33
36
|
}
|
|
34
37
|
});
|
|
@@ -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,aAAa,EAAC,MAAM,uCAAuC,CAAA;
|
|
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,aAAa,EAAE,UAAU,EAAE,SAAS,EAAC,MAAM,uCAAuC,CAAA;AAC1F,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,CAAC,CAAA;AAE/E;;;;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;QAClC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAC,wBAAwB,EAAE,GAAG,EAAC,CAAC;KACxD,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 {firstPartyDev, 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']\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 ...(firstPartyDev() && {'X-Shopify-Cli-Employee': '1'}),\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"]}
|
|
@@ -4,9 +4,7 @@ import { identityFqdn } from '../../../public/node/context/fqdn.js';
|
|
|
4
4
|
import { shopifyFetch } from '../../../public/node/http.js';
|
|
5
5
|
import { outputContent, outputDebug, outputInfo, outputToken } from '../../../public/node/output.js';
|
|
6
6
|
import { AbortError, BugError } from '../../../public/node/error.js';
|
|
7
|
-
import { isCloudEnvironment } from '../../../public/node/context/local.js';
|
|
8
7
|
import { isCI, openURL } from '../../../public/node/system.js';
|
|
9
|
-
import { isTTY, keypress } from '../../../public/node/ui.js';
|
|
10
8
|
/**
|
|
11
9
|
* Initiate a device authorization flow.
|
|
12
10
|
* This will return a DeviceAuthorizationResponse containing the URL where user
|
|
@@ -56,22 +54,12 @@ export async function requestDeviceAuthorization(scopes) {
|
|
|
56
54
|
}
|
|
57
55
|
outputInfo(outputContent `User verification code: ${jsonResult.user_code}`);
|
|
58
56
|
const linkToken = outputToken.link(jsonResult.verification_uri_complete);
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (isCloudEnvironment() || !isTTY()) {
|
|
63
|
-
cloudMessage();
|
|
57
|
+
const opened = await openURL(jsonResult.verification_uri_complete);
|
|
58
|
+
if (opened) {
|
|
59
|
+
outputInfo(outputContent `Opened link to start the auth process: ${linkToken}`);
|
|
64
60
|
}
|
|
65
61
|
else {
|
|
66
|
-
outputInfo(
|
|
67
|
-
await keypress();
|
|
68
|
-
const opened = await openURL(jsonResult.verification_uri_complete);
|
|
69
|
-
if (opened) {
|
|
70
|
-
outputInfo(outputContent `Opened link to start the auth process: ${linkToken}`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
cloudMessage();
|
|
74
|
-
}
|
|
62
|
+
outputInfo(outputContent `👉 Open this link to start the auth process: ${linkToken}`);
|
|
75
63
|
}
|
|
76
64
|
return {
|
|
77
65
|
deviceCode: jsonResult.device_code,
|
|
@@ -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 {isCloudEnvironment} from '../../../public/node/context/local.js'\nimport {isCI, openURL} from '../../../public/node/system.js'\nimport {isTTY, keypress} from '../../../public/node/ui.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 cloudMessage = () => {\n outputInfo(outputContent`👉 Open this link to start the auth process: ${linkToken}`)\n }\n\n if (isCloudEnvironment() || !isTTY()) {\n cloudMessage()\n } else {\n outputInfo('👉 Press any key to open the login page on your browser')\n await keypress()\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 cloudMessage()\n }\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(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"]}
|
|
@@ -19,7 +19,16 @@ export async function fetch() {
|
|
|
19
19
|
if (!content) {
|
|
20
20
|
return undefined;
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
let contentJson;
|
|
23
|
+
try {
|
|
24
|
+
contentJson = JSON.parse(content);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (!(error instanceof SyntaxError))
|
|
28
|
+
throw error;
|
|
29
|
+
await remove();
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
23
32
|
const parsedSessions = await SessionsSchema.safeParseAsync(contentJson);
|
|
24
33
|
if (parsedSessions.success) {
|
|
25
34
|
return parsedSessions.data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/private/node/session/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAC,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAC,WAAW,EAAE,sBAAsB,EAAE,cAAc,EAAE,WAAW,EAAC,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAA;AAGjE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAkB;IAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IAC7C,WAAW,CAAC,YAAY,CAAC,CAAA;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;IAE7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAA;IAClB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/private/node/session/store.ts"],"names":[],"mappings":"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","sourcesContent":["import {SessionsSchema} from './schema.js'\nimport {getSessions, removeCurrentSessionId, removeSessions, setSessions} from '../conf-store.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport type {Session, Sessions} from './schema.js'\n\n/**\n * Serializes the session as a JSON and stores it in the system.\n * @param session - the session to store.\n */\nexport async function store(sessions: Sessions) {\n const jsonSessions = JSON.stringify(sessions)\n setSessions(jsonSessions)\n}\n\n/**\n * Fetches the sessions from the local storage and returns it.\n * If the format of the object is invalid, the method will discard it.\n * @returns Returns a promise that resolves with the sessions object if it exists and is valid.\n */\nexport async function fetch(): Promise<Sessions | undefined> {\n const content = getSessions()\n\n if (!content) {\n return undefined\n }\n\n let contentJson: unknown\n try {\n contentJson = JSON.parse(content)\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error\n await remove()\n return undefined\n }\n\n const parsedSessions = await SessionsSchema.safeParseAsync(contentJson)\n if (parsedSessions.success) {\n return parsedSessions.data\n } else {\n await remove()\n return undefined\n }\n}\n\n/**\n * Removes a session from the system.\n */\nexport async function remove() {\n removeSessions()\n removeCurrentSessionId()\n}\n\n/**\n * Gets the session alias for a given user ID.\n *\n * @param userId - The user ID of the session to get the alias for.\n * @returns The alias for the session if it exists, otherwise undefined.\n */\nexport async function getSessionAlias(userId: string): Promise<string | undefined> {\n const sessions = await fetch()\n if (!sessions) return undefined\n\n const fqdn = await identityFqdn()\n if (!sessions[fqdn] || !sessions[fqdn][userId]) return undefined\n return sessions[fqdn][userId].identity.alias\n}\n\n/**\n * Sets the alias for a given user's session and persists it.\n *\n * @param userId - The user ID of the session to update.\n * @param alias - The new alias to set.\n */\nexport async function setSessionAlias(userId: string, alias: string): Promise<void> {\n const sessions = await fetch()\n if (!sessions) return\n\n const fqdn = await identityFqdn()\n const session: Session | undefined = sessions[fqdn]?.[userId]\n if (!session) return\n\n session.identity.alias = alias\n await store(sessions)\n}\n\n/**\n * Finds a session by its alias.\n *\n * @param alias - The alias to search for\n * @returns The user ID if found, otherwise undefined\n */\nexport async function findSessionByAlias(alias: string): Promise<string | undefined> {\n const sessions = await fetch()\n if (!sessions) return undefined\n\n const fqdn = await identityFqdn()\n const fqdnSessions = sessions[fqdn]\n if (!fqdnSessions) return undefined\n\n for (const [userId, session] of Object.entries(fqdnSessions)) {\n if (session.identity.alias === alias || userId === alias) {\n return userId\n }\n }\n\n return undefined\n}\n"]}
|
package/dist/private/node/ui.js
CHANGED
|
@@ -45,7 +45,10 @@ export function renderOnce(element, { logLevel = 'info', renderOptions }) {
|
|
|
45
45
|
return renderedString;
|
|
46
46
|
}
|
|
47
47
|
export async function render(element, options) {
|
|
48
|
-
const { waitUntilExit } = inkRender(React.createElement(InkLifecycleRoot, null, element),
|
|
48
|
+
const { waitUntilExit } = inkRender(React.createElement(InkLifecycleRoot, null, element), {
|
|
49
|
+
patchConsole: !isUnitTest(),
|
|
50
|
+
...options,
|
|
51
|
+
});
|
|
49
52
|
await waitUntilExit();
|
|
50
53
|
}
|
|
51
54
|
export class Stdout extends EventEmitter {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/private/node/ui.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAA;AAElC,OAAO,EAAC,UAAU,EAAC,MAAM,oCAAoC,CAAA;AAC7D,OAAO,EAAC,QAAQ,EAAC,MAAM,gCAAgC,CAAA;AAEvD,OAAO,KAAK,EAAE,EAAe,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AACtG,OAAO,EAAM,MAAM,IAAI,SAAS,EAAiB,MAAM,EAAC,MAAM,KAAK,CAAA;AAEnE,OAAO,EAAC,YAAY,EAAC,MAAM,QAAQ,CAAA;AAEnC,MAAM,iBAAiB,GAAG,aAAa,CAAmC,IAAI,CAAC,CAAA;AAE/E;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAA;IAC9C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAC,QAAQ,EAA8B;IACtE,MAAM,EAAC,IAAI,EAAC,GAAG,MAAM,EAAE,CAAA;IACvB,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAyB,IAAI,CAAC,CAAA;IAE1E,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,EAAE;QAC7C,aAAa,CAAC,EAAC,KAAK,EAAC,CAAC,CAAA;IACxB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAA;IAEtB,OAAO,oBAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,IAAG,QAAQ,CAA8B,CAAA;AAC7F,CAAC;AAQD,MAAM,UAAU,UAAU,CAAC,OAAoB,EAAE,EAAC,QAAQ,GAAG,MAAM,EAAE,aAAa,EAAoB;IACpG,MAAM,EAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAC,GAAG,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAE9E,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAA;IAClC,CAAC;IAED,OAAO,EAAE,CAAA;IAET,OAAO,cAAc,CAAA;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAoB,EAAE,OAAuB;IACxE,MAAM,EAAC,aAAa,EAAC,GAAG,SAAS,CAAC,oBAAC,gBAAgB,QAAE,OAAO,CAAoB,EAAE,OAAO,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/private/node/ui.tsx"],"names":[],"mappings":"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","sourcesContent":["import {output} from './output.js'\nimport {Logger, LogLevel} from '../../public/node/output.js'\nimport {isUnitTest} from '../../public/node/context/local.js'\nimport {treeKill} from '../../public/node/tree-kill.js'\n\nimport React, {ReactElement, createContext, useCallback, useContext, useEffect, useState} from 'react'\nimport {Key, render as inkRender, RenderOptions, useApp} from 'ink'\n\nimport {EventEmitter} from 'events'\n\nconst CompletionContext = createContext<((error?: Error) => void) | null>(null)\n\n/**\n * Signal that the current Ink tree is done. Must be called within an\n * InkLifecycleRoot — throws if the provider is missing so lifecycle\n * bugs surface immediately instead of silently hanging.\n */\nexport function useComplete(): (error?: Error) => void {\n const complete = useContext(CompletionContext)\n if (!complete) {\n throw new Error('useComplete() called outside InkLifecycleRoot')\n }\n return complete\n}\n\n/**\n * Root wrapper for Ink trees. Owns the single `exit()` call site — children\n * signal completion via `useComplete()`, which sets state here. The `useEffect`\n * fires post-render, guaranteeing all batched state updates have been flushed\n * before the tree is torn down.\n */\nexport function InkLifecycleRoot({children}: {children: React.ReactNode}) {\n const {exit} = useApp()\n const [exitResult, setExitResult] = useState<{error?: Error} | null>(null)\n\n const complete = useCallback((error?: Error) => {\n setExitResult({error})\n }, [])\n\n useEffect(() => {\n if (exitResult !== null) {\n exit(exitResult.error)\n }\n }, [exitResult, exit])\n\n return <CompletionContext.Provider value={complete}>{children}</CompletionContext.Provider>\n}\n\ninterface RenderOnceOptions {\n logLevel?: LogLevel\n logger?: Logger\n renderOptions?: RenderOptions\n}\n\nexport function renderOnce(element: JSX.Element, {logLevel = 'info', renderOptions}: RenderOnceOptions) {\n const {output: renderedString, unmount} = renderString(element, renderOptions)\n\n if (renderedString) {\n output(renderedString, logLevel)\n }\n\n unmount()\n\n return renderedString\n}\n\nexport async function render(element: JSX.Element, options?: RenderOptions) {\n const {waitUntilExit} = inkRender(<InkLifecycleRoot>{element}</InkLifecycleRoot>, {\n patchConsole: !isUnitTest(),\n ...options,\n })\n await waitUntilExit()\n}\n\ninterface Instance {\n output: string | undefined\n unmount: () => void\n}\n\nexport class Stdout extends EventEmitter {\n columns: number\n rows: number\n readonly frames: string[] = []\n private _lastFrame?: string\n\n constructor(options: {columns?: number; rows?: number}) {\n super()\n this.columns = options.columns ?? 80\n this.rows = options.rows ?? 80\n }\n\n write = (frame: string) => {\n this.frames.push(frame)\n // Ink writes `this.lastOutput + '\\n'` to stdout during unmount when\n // running in a CI environment (detected via `is-in-ci`). In debug\n // mode (which tests use), `lastOutput` is never updated, so the write\n // is just '\\n', clobbering the last real rendered frame. Skip it so\n // that `lastFrame()` keeps returning the final rendered content.\n if (frame !== '\\n') {\n this._lastFrame = frame\n }\n }\n\n lastFrame = () => {\n return this._lastFrame\n }\n}\n\nconst renderString = (element: ReactElement, renderOptions?: RenderOptions): Instance => {\n const columns = isUnitTest() ? 80 : process.stdout.columns\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stdout = (renderOptions?.stdout as any) ?? new Stdout({columns})\n\n const instance = inkRender(element, {\n stdout,\n debug: true,\n exitOnCtrlC: false,\n patchConsole: false,\n })\n\n return {\n output: stdout.lastFrame(),\n unmount: instance.unmount,\n }\n}\n\nexport function handleCtrlC(\n input: string,\n key: Key,\n exit = () => {\n treeKill(process.pid, 'SIGINT')\n },\n) {\n if (input === 'c' && key.ctrl) {\n // Exceptions thrown in hooks aren't caught by our errorHandler.\n exit()\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the trailing numeric id from a plain GraphQL global id like
|
|
3
|
+
* `gid://shopify/Product/123`.
|
|
4
|
+
*
|
|
5
|
+
* @param gid - A plain GraphQL global id string.
|
|
6
|
+
* @returns The trailing numeric id, or undefined when the string does not end with `/<digits>`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function numericIdFromGid(gid: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Decodes a base64-encoded GraphQL global id (for example, the form
|
|
11
|
+
* Business Platform APIs return) and returns the trailing numeric id.
|
|
12
|
+
*
|
|
13
|
+
* @param gid - A base64-encoded GraphQL global id.
|
|
14
|
+
* @returns The trailing numeric id, or undefined when the decoded string does not end with `/<digits>`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function numericIdFromEncodedGid(gid: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Encodes a plain GraphQL global id (`gid://...`) as base64, which is the
|
|
19
|
+
* form some Business Platform endpoints require.
|
|
20
|
+
*
|
|
21
|
+
* @param gid - A plain GraphQL global id string to encode.
|
|
22
|
+
* @returns The base64-encoded gid.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeGid(gid: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the trailing numeric id from a plain GraphQL global id like
|
|
3
|
+
* `gid://shopify/Product/123`.
|
|
4
|
+
*
|
|
5
|
+
* @param gid - A plain GraphQL global id string.
|
|
6
|
+
* @returns The trailing numeric id, or undefined when the string does not end with `/<digits>`.
|
|
7
|
+
*/
|
|
8
|
+
export function numericIdFromGid(gid) {
|
|
9
|
+
const match = gid.match(/\/(\d+)$/);
|
|
10
|
+
return match ? match[1] : undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Decodes a base64-encoded GraphQL global id (for example, the form
|
|
14
|
+
* Business Platform APIs return) and returns the trailing numeric id.
|
|
15
|
+
*
|
|
16
|
+
* @param gid - A base64-encoded GraphQL global id.
|
|
17
|
+
* @returns The trailing numeric id, or undefined when the decoded string does not end with `/<digits>`.
|
|
18
|
+
*/
|
|
19
|
+
export function numericIdFromEncodedGid(gid) {
|
|
20
|
+
return numericIdFromGid(Buffer.from(gid, 'base64').toString('utf8'));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encodes a plain GraphQL global id (`gid://...`) as base64, which is the
|
|
24
|
+
* form some Business Platform endpoints require.
|
|
25
|
+
*
|
|
26
|
+
* @param gid - A plain GraphQL global id string to encode.
|
|
27
|
+
* @returns The base64-encoded gid.
|
|
28
|
+
*/
|
|
29
|
+
export function encodeGid(gid) {
|
|
30
|
+
return Buffer.from(gid).toString('base64');
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=gid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gid.js","sourceRoot":"","sources":["../../../src/public/common/gid.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IACnC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAW;IACjD,OAAO,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAC5C,CAAC","sourcesContent":["/**\n * Extracts the trailing numeric id from a plain GraphQL global id like\n * `gid://shopify/Product/123`.\n *\n * @param gid - A plain GraphQL global id string.\n * @returns The trailing numeric id, or undefined when the string does not end with `/<digits>`.\n */\nexport function numericIdFromGid(gid: string): string | undefined {\n const match = gid.match(/\\/(\\d+)$/)\n return match ? match[1] : undefined\n}\n\n/**\n * Decodes a base64-encoded GraphQL global id (for example, the form\n * Business Platform APIs return) and returns the trailing numeric id.\n *\n * @param gid - A base64-encoded GraphQL global id.\n * @returns The trailing numeric id, or undefined when the decoded string does not end with `/<digits>`.\n */\nexport function numericIdFromEncodedGid(gid: string): string | undefined {\n return numericIdFromGid(Buffer.from(gid, 'base64').toString('utf8'))\n}\n\n/**\n * Encodes a plain GraphQL global id (`gid://...`) as base64, which is the\n * form some Business Platform endpoints require.\n *\n * @param gid - A plain GraphQL global id string to encode.\n * @returns The base64-encoded gid.\n */\nexport function encodeGid(gid: string): string {\n return Buffer.from(gid).toString('base64')\n}\n"]}
|
|
@@ -13,3 +13,19 @@ export declare function isValidURL(url: string): boolean;
|
|
|
13
13
|
* @returns A URL object if the parsing is successful, undefined otherwise.
|
|
14
14
|
*/
|
|
15
15
|
export declare function safeParseURL(url: string): URL | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Extracts the lowercased hostname from a URL-shaped string. Tolerates
|
|
18
|
+
* bare hosts (without a scheme) and inputs that come back from APIs as
|
|
19
|
+
* either `https://shop.myshopify.com` or `shop.myshopify.com`.
|
|
20
|
+
*
|
|
21
|
+
* @param value - A URL or bare host string, possibly null/undefined.
|
|
22
|
+
* @returns The lowercased hostname, or undefined when the input is empty.
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractHost(value: string | null | undefined): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Extracts the subdomain handle from a `*.myshopify.com` URL or host.
|
|
27
|
+
*
|
|
28
|
+
* @param value - A URL or host string, possibly null/undefined.
|
|
29
|
+
* @returns The myshopify subdomain handle, or undefined when the input isn't a `*.myshopify.com` URL.
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractMyshopifyHandle(value: string | null | undefined): string | undefined;
|
|
@@ -30,4 +30,43 @@ export function safeParseURL(url) {
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Extracts the lowercased hostname from a URL-shaped string. Tolerates
|
|
35
|
+
* bare hosts (without a scheme) and inputs that come back from APIs as
|
|
36
|
+
* either `https://shop.myshopify.com` or `shop.myshopify.com`.
|
|
37
|
+
*
|
|
38
|
+
* @param value - A URL or bare host string, possibly null/undefined.
|
|
39
|
+
* @returns The lowercased hostname, or undefined when the input is empty.
|
|
40
|
+
*/
|
|
41
|
+
export function extractHost(value) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return undefined;
|
|
44
|
+
const lowered = value.toLowerCase();
|
|
45
|
+
// A bare `host:port` (e.g. `my-shop.shop.dev:9292`) parses as an opaque URL whose hostname is
|
|
46
|
+
// empty (the host is read as the scheme), so try parsing with an explicit scheme as well and only
|
|
47
|
+
// accept a non-empty hostname.
|
|
48
|
+
for (const candidate of [lowered, `https://${lowered}`]) {
|
|
49
|
+
const hostname = safeParseURL(candidate)?.hostname;
|
|
50
|
+
if (hostname)
|
|
51
|
+
return hostname;
|
|
52
|
+
}
|
|
53
|
+
// Never return an empty string: callers using `extractHost(value) ?? value` must keep the input.
|
|
54
|
+
const fallback = lowered.replace(/^https?:\/\//, '').split('/')[0];
|
|
55
|
+
if (fallback)
|
|
56
|
+
return fallback;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extracts the subdomain handle from a `*.myshopify.com` URL or host.
|
|
61
|
+
*
|
|
62
|
+
* @param value - A URL or host string, possibly null/undefined.
|
|
63
|
+
* @returns The myshopify subdomain handle, or undefined when the input isn't a `*.myshopify.com` URL.
|
|
64
|
+
*/
|
|
65
|
+
export function extractMyshopifyHandle(value) {
|
|
66
|
+
const host = extractHost(value);
|
|
67
|
+
if (!host)
|
|
68
|
+
return undefined;
|
|
69
|
+
const match = host.match(/^([^.]+)\.myshopify\.com$/);
|
|
70
|
+
return match ? match[1] : undefined;
|
|
71
|
+
}
|
|
33
72
|
//# sourceMappingURL=url.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/public/common/url.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,SAAS;YAAE,OAAO,KAAK,CAAA;QAC5C,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QACnB,qDAAqD;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/**\n * Check if the format of a URL is valid or not.\n *\n * @param url - URL to be checked.\n * @returns True if the URL is valid, false otherwise.\n * @throws An error if URL's constructor throws an error other than `TypeError`.\n */\nexport function isValidURL(url: string): boolean {\n try {\n return Boolean(new URL(url))\n } catch (error: unknown) {\n if (error instanceof TypeError) return false\n throw error\n }\n}\n\n/**\n * Safely parse a string into a URL.\n *\n * @param url - The string to parse into a URL.\n * @returns A URL object if the parsing is successful, undefined otherwise.\n */\nexport function safeParseURL(url: string): URL | undefined {\n try {\n return new URL(url)\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n return undefined\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/public/common/url.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,SAAS;YAAE,OAAO,KAAK,CAAA;QAC5C,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QACnB,qDAAqD;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,KAAgC;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,CAAA;IACnC,8FAA8F;IAC9F,kGAAkG;IAClG,+BAA+B;IAC/B,KAAK,MAAM,SAAS,IAAI,CAAC,OAAO,EAAE,WAAW,OAAO,EAAE,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAA;QAClD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;IAC/B,CAAC;IACD,iGAAiG;IACjG,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAgC;IACrE,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;IAC/B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAA;IACrD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACrC,CAAC","sourcesContent":["/**\n * Check if the format of a URL is valid or not.\n *\n * @param url - URL to be checked.\n * @returns True if the URL is valid, false otherwise.\n * @throws An error if URL's constructor throws an error other than `TypeError`.\n */\nexport function isValidURL(url: string): boolean {\n try {\n return Boolean(new URL(url))\n } catch (error: unknown) {\n if (error instanceof TypeError) return false\n throw error\n }\n}\n\n/**\n * Safely parse a string into a URL.\n *\n * @param url - The string to parse into a URL.\n * @returns A URL object if the parsing is successful, undefined otherwise.\n */\nexport function safeParseURL(url: string): URL | undefined {\n try {\n return new URL(url)\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n return undefined\n }\n}\n\n/**\n * Extracts the lowercased hostname from a URL-shaped string. Tolerates\n * bare hosts (without a scheme) and inputs that come back from APIs as\n * either `https://shop.myshopify.com` or `shop.myshopify.com`.\n *\n * @param value - A URL or bare host string, possibly null/undefined.\n * @returns The lowercased hostname, or undefined when the input is empty.\n */\nexport function extractHost(value: string | null | undefined): string | undefined {\n if (!value) return undefined\n const lowered = value.toLowerCase()\n // A bare `host:port` (e.g. `my-shop.shop.dev:9292`) parses as an opaque URL whose hostname is\n // empty (the host is read as the scheme), so try parsing with an explicit scheme as well and only\n // accept a non-empty hostname.\n for (const candidate of [lowered, `https://${lowered}`]) {\n const hostname = safeParseURL(candidate)?.hostname\n if (hostname) return hostname\n }\n // Never return an empty string: callers using `extractHost(value) ?? value` must keep the input.\n const fallback = lowered.replace(/^https?:\\/\\//, '').split('/')[0]\n if (fallback) return fallback\n return undefined\n}\n\n/**\n * Extracts the subdomain handle from a `*.myshopify.com` URL or host.\n *\n * @param value - A URL or host string, possibly null/undefined.\n * @returns The myshopify subdomain handle, or undefined when the input isn't a `*.myshopify.com` URL.\n */\nexport function extractMyshopifyHandle(value: string | null | undefined): string | undefined {\n const host = extractHost(value)\n if (!host) return undefined\n const match = host.match(/^([^.]+)\\.myshopify\\.com$/)\n return match ? match[1] : undefined\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_KIT_VERSION = "4.
|
|
1
|
+
export declare const CLI_KIT_VERSION = "4.3.0";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const CLI_KIT_VERSION = '4.
|
|
1
|
+
export const CLI_KIT_VERSION = '4.3.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/public/common/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA","sourcesContent":["export const CLI_KIT_VERSION = '4.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/public/common/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA","sourcesContent":["export const CLI_KIT_VERSION = '4.3.0'\n"]}
|
|
@@ -85,8 +85,8 @@ async function buildPayload({ config, errorMessage, exitMode }) {
|
|
|
85
85
|
outputDebug('Unable to log analytics event - no information on executed command');
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
|
-
const { startCommand, startArgs, startTime } = commandStartOptions;
|
|
89
|
-
const currentTime = new Date().getTime();
|
|
88
|
+
const { startCommand, startArgs, startTime, endTime } = commandStartOptions;
|
|
89
|
+
const currentTime = endTime ?? new Date().getTime();
|
|
90
90
|
// All bundled plugins appear as `@shopify/cli` in the payload
|
|
91
91
|
const { '@shopify/cli': internalPluginsPublic, ...externalPluginsPublic } = await fanoutHooks(config, 'public_command_metadata', {});
|
|
92
92
|
const { '@shopify/cli': internalPluginsSensitive, ...externalPluginsSensitive } = await fanoutHooks(config, 'sensitive_command_metadata', {});
|
|
@@ -104,7 +104,7 @@ async function buildPayload({ config, errorMessage, exitMode }) {
|
|
|
104
104
|
}, 0);
|
|
105
105
|
const wallClockElapsed = currentTime - startTime;
|
|
106
106
|
const totalTimeWithoutSubtimers = wallClockElapsed - totalTimeFromSubtimers;
|
|
107
|
-
|
|
107
|
+
const payload = {
|
|
108
108
|
public: {
|
|
109
109
|
command: startCommand,
|
|
110
110
|
time_start: startTime,
|
|
@@ -146,8 +146,6 @@ async function buildPayload({ config, errorMessage, exitMode }) {
|
|
|
146
146
|
payload.public[metric] = Math.floor(current);
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
|
-
// strip undefined fields -- they make up the majority of payloads due to wide metadata structure.
|
|
150
|
-
payload = JSON.parse(JSON.stringify(payload));
|
|
151
149
|
return sanitizePayload(payload);
|
|
152
150
|
}
|
|
153
151
|
function sanitizePayload(payload) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/public/node/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import {alwaysLogAnalytics, alwaysLogMetrics, analyticsDisabled, isShopify} from './context/local.js'\nimport * as metadata from './metadata.js'\nimport {publishMonorailEvent, MONORAIL_COMMAND_TOPIC} from './monorail.js'\nimport {fanoutHooks} from './plugins.js'\nimport {sendErrorToBugsnag} from './error-handler.js'\nimport {outputContent, outputDebug, outputToken} from './output.js'\nimport {\n recordTiming as storageRecordTiming,\n recordError as storageRecordError,\n recordRetry as storageRecordRetry,\n recordEvent as storageRecordEvent,\n compileData as storageCompileData,\n RuntimeData,\n} from '../../private/node/analytics/storage.js'\nimport {getEnvironmentData, getSensitiveEnvironmentData} from '../../private/node/analytics.js'\nimport {CLI_KIT_VERSION} from '../common/version.js'\nimport {recordMetrics} from '../../private/node/otel-metrics.js'\nimport {runWithRateLimit} from '../../private/node/conf-store.js'\nimport {reportingRateLimit} from '../../private/node/constants.js'\nimport {getLastSeenUserIdAfterAuth} from '../../private/node/session.js'\nimport {requestIdsCollection} from '../../private/node/request-ids.js'\n\nimport {Interfaces} from '@oclif/core'\n\nexport type CommandExitMode =\n // The command completed successfully\n | 'ok'\n // The command exited for some unexpected reason -- i.e. a bug\n | 'unexpected_error'\n // The command exited with an error, but its one we expect and doesn't point to a bug -- i.e. malformed config files\n | 'expected_error'\n\ninterface ReportAnalyticsEventOptions {\n config: Interfaces.Config\n errorMessage?: string\n exitMode: CommandExitMode\n}\n\n/**\n * Report an analytics event, sending it off to Monorail -- Shopify's internal analytics service.\n *\n * The payload for an event includes both generic data, and data gathered from installed plug-ins.\n *\n */\nexport async function reportAnalyticsEvent(options: ReportAnalyticsEventOptions): Promise<void> {\n try {\n const payload = await buildPayload(options)\n if (payload === undefined) {\n // Nothing to log\n return\n }\n\n let withinRateLimit = false\n await runWithRateLimit({\n key: 'report-analytics-event',\n ...reportingRateLimit,\n task: async () => {\n withinRateLimit = true\n },\n })\n if (!withinRateLimit) {\n outputDebug(outputContent`Skipping command analytics due to rate limiting, payload: ${outputToken.json(payload)}`)\n return\n }\n\n const skipMonorailAnalytics = !alwaysLogAnalytics() && analyticsDisabled()\n const skipMetricAnalytics = !alwaysLogMetrics() && analyticsDisabled()\n if (skipMonorailAnalytics || skipMetricAnalytics) {\n outputDebug(outputContent`Skipping command analytics, payload: ${outputToken.json(payload)}`)\n }\n\n const doMonorail = async () => {\n if (skipMonorailAnalytics) {\n return\n }\n const response = await publishMonorailEvent(MONORAIL_COMMAND_TOPIC, payload.public, payload.sensitive)\n if (response.type === 'error') {\n outputDebug(response.message)\n }\n }\n const doOpenTelemetry = async () => {\n const active = payload.public.cmd_all_timing_active_ms ?? 0\n const network = payload.public.cmd_all_timing_network_ms ?? 0\n const prompt = payload.public.cmd_all_timing_prompts_ms ?? 0\n\n return recordMetrics(\n {\n skipMetricAnalytics,\n cliVersion: payload.public.cli_version,\n owningPlugin: payload.public.cmd_all_plugin ?? '@shopify/cli',\n command: payload.public.command,\n exitMode: options.exitMode,\n },\n {\n active,\n network,\n prompt,\n },\n )\n }\n await Promise.all([doMonorail(), doOpenTelemetry()])\n\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n let message = 'Failed to report usage analytics'\n if (error instanceof Error) {\n message = message.concat(`: ${error.message}`)\n }\n outputDebug(message)\n await sendErrorToBugsnag(error, 'expected_error')\n }\n}\n\nasync function buildPayload({config, errorMessage, exitMode}: ReportAnalyticsEventOptions) {\n const {commandStartOptions, environmentFlags, ...sensitiveMetadata} = metadata.getAllSensitiveMetadata()\n if (commandStartOptions === undefined) {\n outputDebug('Unable to log analytics event - no information on executed command')\n return\n }\n const {startCommand, startArgs, startTime} = commandStartOptions\n const currentTime = new Date().getTime()\n\n // All bundled plugins appear as `@shopify/cli` in the payload\n const {'@shopify/cli': internalPluginsPublic, ...externalPluginsPublic} = await fanoutHooks(\n config,\n 'public_command_metadata',\n {},\n )\n const {'@shopify/cli': internalPluginsSensitive, ...externalPluginsSensitive} = await fanoutHooks(\n config,\n 'sensitive_command_metadata',\n {},\n )\n\n const environmentData = await getEnvironmentData(config)\n const sensitiveEnvironmentData = await getSensitiveEnvironmentData(config)\n const publicMetadata = metadata.getAllPublicMetadata()\n\n // Automatically calculate the total time spent in the command, excluding time spent in subtimers.\n const subTimers = ['cmd_all_timing_network_ms', 'cmd_all_timing_prompts_ms'] as const\n const totalTimeFromSubtimers = subTimers.reduce((total, timer) => {\n const value = publicMetadata[timer]\n if (value !== undefined) {\n return total + value\n }\n return total\n }, 0)\n const wallClockElapsed = currentTime - startTime\n const totalTimeWithoutSubtimers = wallClockElapsed - totalTimeFromSubtimers\n\n let payload = {\n public: {\n command: startCommand,\n time_start: startTime,\n time_end: currentTime,\n total_time: wallClockElapsed,\n success: exitMode === 'ok' && errorMessage === undefined,\n cli_version: CLI_KIT_VERSION,\n ruby_version: '',\n node_version: process.version.replace('v', ''),\n is_employee: await isShopify(),\n ...environmentData,\n ...internalPluginsPublic,\n ...publicMetadata,\n cmd_all_timing_active_ms: totalTimeWithoutSubtimers,\n cmd_all_exit: exitMode,\n user_id: await getLastSeenUserIdAfterAuth(),\n request_ids: requestIdsCollection.getRequestIds(),\n },\n sensitive: {\n args: startArgs.join(' '),\n cmd_all_environment_flags: environmentFlags,\n error_message: errorMessage,\n ...internalPluginsSensitive,\n ...sensitiveEnvironmentData,\n metadata: JSON.stringify({\n ...sensitiveMetadata,\n extraPublic: {\n ...externalPluginsPublic,\n },\n extraSensitive: {...externalPluginsSensitive},\n }),\n },\n }\n\n // round down timing metrics\n const timingMetrics = ['cmd_all_timing_active_ms', 'cmd_all_timing_network_ms', 'cmd_all_timing_prompts_ms'] as const\n timingMetrics.forEach((metric) => {\n const current = payload.public[metric]\n if (current !== undefined) {\n payload.public[metric] = Math.floor(current)\n }\n })\n\n // strip undefined fields -- they make up the majority of payloads due to wide metadata structure.\n payload = JSON.parse(JSON.stringify(payload))\n\n return sanitizePayload(payload)\n}\n\nfunction sanitizePayload<T>(payload: T): T {\n const payloadString = JSON.stringify(payload)\n // Remove Theme Access passwords from the payload\n const sanitizedPayloadString = payloadString.replace(/shptka_\\w*/g, '*****')\n return JSON.parse(sanitizedPayloadString)\n}\n\n/**\n * Records timing data for performance monitoring. Call twice with the same\n * event name to start and stop timing. First call starts the timer, second\n * call stops it and records the duration.\n *\n * @example\n * ```ts\n * recordTiming('theme-upload') // Start timing\n * // ... do work ...\n * recordTiming('theme-upload') // Stop timing and record duration\n * ```\n *\n * @param eventName - Unique identifier for the timing event\n */\nexport function recordTiming(eventName: string): void {\n storageRecordTiming(eventName)\n}\n\n/**\n * Records error information for debugging and monitoring. Use this to track\n * any exceptions or error conditions that occur during theme operations.\n * Errors are automatically categorized for easier analysis.\n *\n * @example\n * ```ts\n * try {\n * // ... risky operation ...\n * } catch (error) {\n * recordError(error)\n * }\n * ```\n *\n * @param error - Error object or message to record\n */\nexport function recordError<T>(error: T): T {\n storageRecordError(error)\n return error\n}\n\n/**\n * Records retry attempts for network operations. Use this to track when\n * operations are retried due to transient failures. Helps identify\n * problematic endpoints or operations that frequently fail.\n *\n * @example\n * ```ts\n * recordRetry('https://api.shopify.com/themes', 'upload')\n * ```\n *\n * @param url - The URL or endpoint being retried\n * @param operation - Description of the operation being retried\n */\nexport function recordRetry(url: string, operation: string): void {\n storageRecordRetry(url, operation)\n}\n\n/**\n * Records custom events for tracking specific user actions or system events.\n * Use this for important milestones, user interactions, or significant\n * state changes in the application.\n *\n * @example\n * ```ts\n * recordEvent('theme-dev-started')\n * recordEvent('file-watcher-connected')\n * ```\n *\n * @param eventName - Descriptive name for the event\n */\nexport function recordEvent(eventName: string): void {\n storageRecordEvent(eventName)\n}\n\n/**\n * Compiles and returns all runtime analytics data collected during the session.\n * This includes timing measurements, error records, retry attempts, and custom\n * events. Use this to retrieve a complete snapshot of analytics data for\n * reporting or debugging purposes.\n *\n * @example\n * ```ts\n * const analyticsData = compileData()\n * console.log(`Recorded ${analyticsData.timings.length} timing events`)\n * console.log(`Recorded ${analyticsData.errors.length} errors`)\n * ```\n *\n * @returns Object containing all collected analytics data including timings, errors, retries, and events\n */\nexport function compileData(): RuntimeData {\n return storageCompileData()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/public/node/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import {alwaysLogAnalytics, alwaysLogMetrics, analyticsDisabled, isShopify} from './context/local.js'\nimport * as metadata from './metadata.js'\nimport {publishMonorailEvent, MONORAIL_COMMAND_TOPIC} from './monorail.js'\nimport {fanoutHooks} from './plugins.js'\nimport {sendErrorToBugsnag} from './error-handler.js'\nimport {outputContent, outputDebug, outputToken} from './output.js'\nimport {\n recordTiming as storageRecordTiming,\n recordError as storageRecordError,\n recordRetry as storageRecordRetry,\n recordEvent as storageRecordEvent,\n compileData as storageCompileData,\n RuntimeData,\n} from '../../private/node/analytics/storage.js'\nimport {getEnvironmentData, getSensitiveEnvironmentData} from '../../private/node/analytics.js'\nimport {CLI_KIT_VERSION} from '../common/version.js'\nimport {recordMetrics} from '../../private/node/otel-metrics.js'\nimport {runWithRateLimit} from '../../private/node/conf-store.js'\nimport {reportingRateLimit} from '../../private/node/constants.js'\nimport {getLastSeenUserIdAfterAuth} from '../../private/node/session.js'\nimport {requestIdsCollection} from '../../private/node/request-ids.js'\n\nimport {Interfaces} from '@oclif/core'\n\nexport type CommandExitMode =\n // The command completed successfully\n | 'ok'\n // The command exited for some unexpected reason -- i.e. a bug\n | 'unexpected_error'\n // The command exited with an error, but its one we expect and doesn't point to a bug -- i.e. malformed config files\n | 'expected_error'\n\ninterface ReportAnalyticsEventOptions {\n config: Interfaces.Config\n errorMessage?: string\n exitMode: CommandExitMode\n}\n\n/**\n * Report an analytics event, sending it off to Monorail -- Shopify's internal analytics service.\n *\n * The payload for an event includes both generic data, and data gathered from installed plug-ins.\n *\n */\nexport async function reportAnalyticsEvent(options: ReportAnalyticsEventOptions): Promise<void> {\n try {\n const payload = await buildPayload(options)\n if (payload === undefined) {\n // Nothing to log\n return\n }\n\n let withinRateLimit = false\n await runWithRateLimit({\n key: 'report-analytics-event',\n ...reportingRateLimit,\n task: async () => {\n withinRateLimit = true\n },\n })\n if (!withinRateLimit) {\n outputDebug(outputContent`Skipping command analytics due to rate limiting, payload: ${outputToken.json(payload)}`)\n return\n }\n\n const skipMonorailAnalytics = !alwaysLogAnalytics() && analyticsDisabled()\n const skipMetricAnalytics = !alwaysLogMetrics() && analyticsDisabled()\n if (skipMonorailAnalytics || skipMetricAnalytics) {\n outputDebug(outputContent`Skipping command analytics, payload: ${outputToken.json(payload)}`)\n }\n\n const doMonorail = async () => {\n if (skipMonorailAnalytics) {\n return\n }\n const response = await publishMonorailEvent(MONORAIL_COMMAND_TOPIC, payload.public, payload.sensitive)\n if (response.type === 'error') {\n outputDebug(response.message)\n }\n }\n const doOpenTelemetry = async () => {\n const active = payload.public.cmd_all_timing_active_ms ?? 0\n const network = payload.public.cmd_all_timing_network_ms ?? 0\n const prompt = payload.public.cmd_all_timing_prompts_ms ?? 0\n\n return recordMetrics(\n {\n skipMetricAnalytics,\n cliVersion: payload.public.cli_version,\n owningPlugin: payload.public.cmd_all_plugin ?? '@shopify/cli',\n command: payload.public.command,\n exitMode: options.exitMode,\n },\n {\n active,\n network,\n prompt,\n },\n )\n }\n await Promise.all([doMonorail(), doOpenTelemetry()])\n\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n let message = 'Failed to report usage analytics'\n if (error instanceof Error) {\n message = message.concat(`: ${error.message}`)\n }\n outputDebug(message)\n await sendErrorToBugsnag(error, 'expected_error')\n }\n}\n\nasync function buildPayload({config, errorMessage, exitMode}: ReportAnalyticsEventOptions) {\n const {commandStartOptions, environmentFlags, ...sensitiveMetadata} = metadata.getAllSensitiveMetadata()\n if (commandStartOptions === undefined) {\n outputDebug('Unable to log analytics event - no information on executed command')\n return\n }\n const {startCommand, startArgs, startTime, endTime} = commandStartOptions\n const currentTime = endTime ?? new Date().getTime()\n\n // All bundled plugins appear as `@shopify/cli` in the payload\n const {'@shopify/cli': internalPluginsPublic, ...externalPluginsPublic} = await fanoutHooks(\n config,\n 'public_command_metadata',\n {},\n )\n const {'@shopify/cli': internalPluginsSensitive, ...externalPluginsSensitive} = await fanoutHooks(\n config,\n 'sensitive_command_metadata',\n {},\n )\n\n const environmentData = await getEnvironmentData(config)\n const sensitiveEnvironmentData = await getSensitiveEnvironmentData(config)\n const publicMetadata = metadata.getAllPublicMetadata()\n\n // Automatically calculate the total time spent in the command, excluding time spent in subtimers.\n const subTimers = ['cmd_all_timing_network_ms', 'cmd_all_timing_prompts_ms'] as const\n const totalTimeFromSubtimers = subTimers.reduce((total, timer) => {\n const value = publicMetadata[timer]\n if (value !== undefined) {\n return total + value\n }\n return total\n }, 0)\n const wallClockElapsed = currentTime - startTime\n const totalTimeWithoutSubtimers = wallClockElapsed - totalTimeFromSubtimers\n\n const payload = {\n public: {\n command: startCommand,\n time_start: startTime,\n time_end: currentTime,\n total_time: wallClockElapsed,\n success: exitMode === 'ok' && errorMessage === undefined,\n cli_version: CLI_KIT_VERSION,\n ruby_version: '',\n node_version: process.version.replace('v', ''),\n is_employee: await isShopify(),\n ...environmentData,\n ...internalPluginsPublic,\n ...publicMetadata,\n cmd_all_timing_active_ms: totalTimeWithoutSubtimers,\n cmd_all_exit: exitMode,\n user_id: await getLastSeenUserIdAfterAuth(),\n request_ids: requestIdsCollection.getRequestIds(),\n },\n sensitive: {\n args: startArgs.join(' '),\n cmd_all_environment_flags: environmentFlags,\n error_message: errorMessage,\n ...internalPluginsSensitive,\n ...sensitiveEnvironmentData,\n metadata: JSON.stringify({\n ...sensitiveMetadata,\n extraPublic: {\n ...externalPluginsPublic,\n },\n extraSensitive: {...externalPluginsSensitive},\n }),\n },\n }\n\n // round down timing metrics\n const timingMetrics = ['cmd_all_timing_active_ms', 'cmd_all_timing_network_ms', 'cmd_all_timing_prompts_ms'] as const\n timingMetrics.forEach((metric) => {\n const current = payload.public[metric]\n if (current !== undefined) {\n payload.public[metric] = Math.floor(current)\n }\n })\n\n return sanitizePayload(payload)\n}\n\nfunction sanitizePayload<T>(payload: T): T {\n const payloadString = JSON.stringify(payload)\n // Remove Theme Access passwords from the payload\n const sanitizedPayloadString = payloadString.replace(/shptka_\\w*/g, '*****')\n return JSON.parse(sanitizedPayloadString)\n}\n\n/**\n * Records timing data for performance monitoring. Call twice with the same\n * event name to start and stop timing. First call starts the timer, second\n * call stops it and records the duration.\n *\n * @example\n * ```ts\n * recordTiming('theme-upload') // Start timing\n * // ... do work ...\n * recordTiming('theme-upload') // Stop timing and record duration\n * ```\n *\n * @param eventName - Unique identifier for the timing event\n */\nexport function recordTiming(eventName: string): void {\n storageRecordTiming(eventName)\n}\n\n/**\n * Records error information for debugging and monitoring. Use this to track\n * any exceptions or error conditions that occur during theme operations.\n * Errors are automatically categorized for easier analysis.\n *\n * @example\n * ```ts\n * try {\n * // ... risky operation ...\n * } catch (error) {\n * recordError(error)\n * }\n * ```\n *\n * @param error - Error object or message to record\n */\nexport function recordError<T>(error: T): T {\n storageRecordError(error)\n return error\n}\n\n/**\n * Records retry attempts for network operations. Use this to track when\n * operations are retried due to transient failures. Helps identify\n * problematic endpoints or operations that frequently fail.\n *\n * @example\n * ```ts\n * recordRetry('https://api.shopify.com/themes', 'upload')\n * ```\n *\n * @param url - The URL or endpoint being retried\n * @param operation - Description of the operation being retried\n */\nexport function recordRetry(url: string, operation: string): void {\n storageRecordRetry(url, operation)\n}\n\n/**\n * Records custom events for tracking specific user actions or system events.\n * Use this for important milestones, user interactions, or significant\n * state changes in the application.\n *\n * @example\n * ```ts\n * recordEvent('theme-dev-started')\n * recordEvent('file-watcher-connected')\n * ```\n *\n * @param eventName - Descriptive name for the event\n */\nexport function recordEvent(eventName: string): void {\n storageRecordEvent(eventName)\n}\n\n/**\n * Compiles and returns all runtime analytics data collected during the session.\n * This includes timing measurements, error records, retry attempts, and custom\n * events. Use this to retrieve a complete snapshot of analytics data for\n * reporting or debugging purposes.\n *\n * @example\n * ```ts\n * const analyticsData = compileData()\n * console.log(`Recorded ${analyticsData.timings.length} timing events`)\n * console.log(`Recorded ${analyticsData.errors.length} errors`)\n * ```\n *\n * @returns Object containing all collected analytics data including timings, errors, retries, and events\n */\nexport function compileData(): RuntimeData {\n return storageCompileData()\n}\n"]}
|