@shopify/cli-kit 3.87.4 → 3.88.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/api.d.ts +2 -0
- package/dist/private/node/api.js +9 -0
- package/dist/private/node/api.js.map +1 -1
- package/dist/private/node/constants.d.ts +1 -0
- package/dist/private/node/constants.js +1 -0
- package/dist/private/node/constants.js.map +1 -1
- package/dist/private/node/ui/components/SingleTask.d.ts +6 -5
- package/dist/private/node/ui/components/SingleTask.js +7 -5
- package/dist/private/node/ui/components/SingleTask.js.map +1 -1
- package/dist/private/node/ui/components/SingleTask.test.js +87 -35
- package/dist/private/node/ui/components/SingleTask.test.js.map +1 -1
- package/dist/private/node/ui/components/Tasks.d.ts +2 -1
- package/dist/private/node/ui/components/Tasks.js +2 -1
- package/dist/private/node/ui/components/Tasks.js.map +1 -1
- package/dist/private/node/ui/components/Tasks.test.js +21 -1
- package/dist/private/node/ui/components/Tasks.test.js.map +1 -1
- package/dist/public/common/string.d.ts +8 -0
- package/dist/public/common/string.js +23 -0
- package/dist/public/common/string.js.map +1 -1
- package/dist/public/common/version.d.ts +1 -1
- package/dist/public/common/version.js +1 -1
- package/dist/public/common/version.js.map +1 -1
- package/dist/public/node/context/fqdn.js +1 -1
- package/dist/public/node/context/fqdn.js.map +1 -1
- package/dist/public/node/environment.d.ts +1 -1
- package/dist/public/node/environment.js +11 -2
- package/dist/public/node/environment.js.map +1 -1
- package/dist/public/node/fs.d.ts +7 -0
- package/dist/public/node/fs.js +23 -1
- package/dist/public/node/fs.js.map +1 -1
- package/dist/public/node/session.d.ts +11 -0
- package/dist/public/node/session.js +35 -1
- package/dist/public/node/session.js.map +1 -1
- package/dist/public/node/system.d.ts +19 -0
- package/dist/public/node/system.js +39 -0
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/themes/api.js +1 -0
- package/dist/public/node/themes/api.js.map +1 -1
- package/dist/public/node/ui.d.ts +12 -9
- package/dist/public/node/ui.js +11 -14
- package/dist/public/node/ui.js.map +1 -1
- package/dist/public/node/vendor/dev_server/network/index.js +1 -1
- package/dist/public/node/vendor/dev_server/network/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -4,8 +4,10 @@ export declare const allAPIs: API[];
|
|
|
4
4
|
export type NetworkRetryBehaviour = {
|
|
5
5
|
useNetworkLevelRetry: true;
|
|
6
6
|
maxRetryTimeMs: number;
|
|
7
|
+
recordCommandRetries?: boolean;
|
|
7
8
|
} | {
|
|
8
9
|
useNetworkLevelRetry: false;
|
|
10
|
+
recordCommandRetries?: boolean;
|
|
9
11
|
};
|
|
10
12
|
type RequestOptions<T> = {
|
|
11
13
|
request: () => Promise<T>;
|
package/dist/private/node/api.js
CHANGED
|
@@ -2,6 +2,7 @@ import { sanitizedHeadersOutput } from './api/headers.js';
|
|
|
2
2
|
import { sanitizeURL } from './api/urls.js';
|
|
3
3
|
import { sleepWithBackoffUntil } from './sleep-with-backoff.js';
|
|
4
4
|
import { outputDebug } from '../../public/node/output.js';
|
|
5
|
+
import { recordRetry } from '../../public/node/analytics.js';
|
|
5
6
|
import { ClientError } from 'graphql-request';
|
|
6
7
|
import { performance } from 'perf_hooks';
|
|
7
8
|
export const allAPIs = ['admin', 'storefront-renderer', 'partners', 'business-platform', 'app-management'];
|
|
@@ -92,6 +93,10 @@ async function runRequestWithNetworkLevelRetry(requestOptions) {
|
|
|
92
93
|
if (!isTransientNetworkError(err)) {
|
|
93
94
|
throw err;
|
|
94
95
|
}
|
|
96
|
+
// Record command retries
|
|
97
|
+
if (requestOptions.recordCommandRetries) {
|
|
98
|
+
recordRetry(requestOptions.url, `network-retry:${err.message}`);
|
|
99
|
+
}
|
|
95
100
|
outputDebug(`Retrying request to ${requestOptions.url} due to network error ${err}`);
|
|
96
101
|
}
|
|
97
102
|
}
|
|
@@ -300,6 +305,10 @@ ${result.sanitizedHeaders}
|
|
|
300
305
|
}
|
|
301
306
|
}
|
|
302
307
|
retriesUsed += 1;
|
|
308
|
+
// Record command retries
|
|
309
|
+
if (requestOptions.recordCommandRetries) {
|
|
310
|
+
recordRetry(requestOptions.url, `http-retry-${retriesUsed}:${result.status}:`);
|
|
311
|
+
}
|
|
303
312
|
// prefer to wait based on a header if given; the caller's preference if not; and a default if neither.
|
|
304
313
|
const retryDelayMs = result.delayMs ?? retryOptions.defaultDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
305
314
|
outputDebug(`Scheduling retry request #${retriesUsed} to ${result.sanitizedUrl} in ${retryDelayMs} ms`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/private/node/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,sBAAsB,EAAC,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AACzC,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAC,WAAW,EAAC,MAAM,6BAA6B,CAAA;AAEvD,OAAO,EAAC,WAAW,EAAC,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAC,WAAW,EAAC,MAAM,YAAY,CAAA;AAItC,MAAM,CAAC,MAAM,OAAO,GAAU,CAAC,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,mBAAmB,EAAE,gBAAgB,CAAC,CAAA;AAEjH,MAAM,sBAAsB,GAAG,IAAI,CAAA;AACnC,MAAM,mBAAmB,GAAG,EAAE,CAAA;AAgB9B,MAAM,0BAA0B,GAAG,IAAI,GAAG,CAAC;IACzC,eAAe;IACf,cAAc;IACd,MAAM;IACN,cAAc;IACd,eAAe;IACf,aAAa;CACd,CAAC,CAAA;AAEF,SAAS,2BAA2B,CAAC,MAAc;IACjD,OAAO,0BAA0B,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAC/C,CAAC;AA8BD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,sBAAsB,GAAG;YAC7B,gBAAgB;YAChB,YAAY;YACZ,cAAc;YACd,WAAW;YACX,aAAa;YACb,6BAA6B;YAC7B,WAAW;YACX,cAAc;YACd,WAAW;YACX,OAAO;YACP,2BAA2B;YAC3B,SAAS;YACT,iBAAiB;YACjB,aAAa;SACd,CAAA;QACD,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;QAChD,MAAM,UAAU,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAA;QACrG,MAAM,aAAa,GAAG,oCAAoC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAC7E,OAAO,UAAU,IAAI,aAAa,CAAA;IACpC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,gDAAgD;IAChD,IAAI,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,uEAAuE;IACvE,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,6BAA6B,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAA;QACvF,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;QAChD,OAAO,6BAA6B,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAA;IAClG,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,+BAA+B,CAC5C,cAAiC;IAEjC,IAAI,CAAC,cAAc,CAAC,oBAAoB,EAAE,CAAC;QACzC,OAAO,cAAc,CAAC,OAAO,EAAE,CAAA;IACjC,CAAC;IAED,IAAI,aAAsB,CAAA;IAE1B,IAAI,KAAK,EAAE,MAAM,QAAQ,IAAI,qBAAqB,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,CAAC;QAClF,IAAI,CAAC;YACH,OAAO,MAAM,cAAc,CAAC,OAAO,EAAE,CAAA;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,aAAa,GAAG,GAAG,CAAA;YACnB,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClC,MAAM,GAAG,CAAA;YACX,CAAC;YACD,WAAW,CAAC,uBAAuB,cAAc,CAAC,GAAG,yBAAyB,GAAG,EAAE,CAAC,CAAA;QACtF,CAAC;IACH,CAAC;IACD,MAAM,aAAa,CAAA;AACrB,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,cAAiC;IAEjC,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;IAC5B,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,MAAM,eAAe,GAA4B,EAAE,CAAA;IACnD,MAAM,YAAY,GAAG,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;IACpD,IAAI,QAAQ,GAAM,EAAO,CAAA;IACzB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,+BAA+B,CAAC,cAAc,CAAC,CAAA;QAChE,8DAA8D;QAC9D,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAU,EAAE,GAAQ,EAAE,EAAE;YAChD,IAAI,2BAA2B,CAAC,GAAG,CAAC;gBAAE,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACpE,CAAC,CAAC,CAAA;QACF,qDAAqD;IACvD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAC5B,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;QAE9B,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACzB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAqC,EAAE,CAAC;oBAC9E,IAAI,2BAA2B,CAAC,GAAG,CAAC;wBAAE,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;gBACpE,CAAC;YACH,CAAC;YACD,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAA;YAEhE,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChC,IAAI,OAA2B,CAAA;gBAE/B,IAAI,CAAC;oBACH,OAAO,GAAG,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;oBAC1G,qDAAqD;gBACvD,CAAC;gBAAC,MAAM,CAAC;oBACP,iDAAiD;gBACnD,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,WAAW;oBACnB,WAAW,EAAE,GAAG;oBAChB,QAAQ;oBACR,gBAAgB;oBAChB,YAAY;oBACZ,SAAS,EAAE,eAAe,CAAC,cAAc,CAAC;oBAC1C,OAAO;iBACR,CAAA;YACH,CAAC;iBAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvC,OAAO;oBACL,MAAM,EAAE,cAAc;oBACtB,WAAW,EAAE,GAAG;oBAChB,QAAQ;oBACR,gBAAgB;oBAChB,YAAY;oBACZ,SAAS,EAAE,eAAe,CAAC,cAAc,CAAC;oBAC1C,OAAO,EAAE,GAAG;iBACb,CAAA;YACH,CAAC;YAED,OAAO;gBACL,MAAM,EAAE,cAAc;gBACtB,WAAW,EAAE,GAAG;gBAChB,QAAQ;gBACR,gBAAgB;gBAChB,YAAY;gBACZ,SAAS,EAAE,eAAe,CAAC,cAAc,CAAC;aAC3C,CAAA;QACH,CAAC;QACD,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,KAAK,EAAE,GAAG;YACV,QAAQ;YACR,gBAAgB,EAAE,sBAAsB,CAAC,eAAe,CAAC;YACzD,YAAY;YACZ,SAAS,EAAE,eAAe,CAAC,cAAc,CAAC;SAC3C,CAAA;IACH,CAAC;IACD,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;IAC5B,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;IAC9B,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,QAAQ;QACR,QAAQ;QACR,gBAAgB,EAAE,sBAAsB,CAAC,eAAe,CAAC;QACzD,YAAY;QACZ,SAAS,EAAE,eAAe,CAAC,cAAc,CAAC;KAC3C,CAAA;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAkB;IAChD,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAClC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,oEAAoE;IACpE,oEAAoE;IACpE,IAAI,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,CAAA;AAC1F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,cAAiC,EACjC,YAAyE;IAEzE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,cAAc,CAAC,CAAA;IAEvD,WAAW,CAAC,cAAc,MAAM,CAAC,YAAY,iBAAiB,MAAM,CAAC,QAAQ;;EAE7E,MAAM,CAAC,gBAAgB;KACpB,CAAC,CAAA;IAEJ,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,OAAO,MAAM,CAAC,QAAQ,CAAA;QACxB,CAAC;QACD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YAC1D,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,WAAW,CAAA;YAC1B,CAAC;QACH,CAAC;QACD,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YACpD,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,KAAK,CAAA;YACpB,CAAC;QACH,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YAC1D,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,WAAW,CAAA;YAC1B,CAAC;QACH,CAAC;QACD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YAC1D,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,WAAW,CAAA;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,cAAiC,EACjC,YAAyE,EACzE,eAII;IACF,aAAa,EAAE,UAAU;CAC1B;IAED,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,MAAM,cAAc,GAAG,YAAY,CAAC,cAAc,IAAI,mBAAmB,CAAA;IAEzE,IAAI,MAAM,GAAG,MAAM,kBAAkB,CAAC,cAAc,CAAC,CAAA;IAErD,WAAW,CAAC,cAAc,MAAM,CAAC,YAAY,iBAAiB,MAAM,CAAC,QAAQ;;EAE7E,MAAM,CAAC,gBAAgB;KACpB,CAAC,CAAA;IAEJ,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAC3B,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;gBACpB,WAAW,CAAC,cAAc,MAAM,CAAC,YAAY,oBAAoB,WAAW,UAAU,CAAC,CAAA;YACzF,CAAC;YACD,OAAO,MAAM,CAAC,QAAQ,CAAA;QACxB,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YAC5C,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YAC1D,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,WAAW,CAAA;YAC1B,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YAC7C,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YACpD,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,KAAK,CAAA;YACpB,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YAC5C,MAAM,MAAM,CAAC,WAAW,CAAA;QAC1B,CAAC;QAED,IAAI,cAAc,IAAI,WAAW,EAAE,CAAC;YAClC,WAAW,CAAC,GAAG,cAAc,qCAAqC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;YACxF,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;YAC1D,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,CAAC,WAAW,CAAA;YAC1B,CAAC;QACH,CAAC;QACD,WAAW,IAAI,CAAC,CAAA;QAEhB,uGAAuG;QACvG,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,IAAI,YAAY,CAAC,cAAc,IAAI,sBAAsB,CAAA;QAC5F,WAAW,CAAC,6BAA6B,WAAW,OAAO,MAAM,CAAC,YAAY,OAAO,YAAY,KAAK,CAAC,CAAA;QAEvG,4CAA4C;QAC5C,MAAM,GAAG,MAAM,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,EAAE;YACzD,YAAY,CAAC,aAAa,CAAC,GAAG,EAAE;gBAC9B,OAAO,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAA;YAC7C,CAAC,EAAE,YAAY,CAAC,CAAA;QAClB,CAAC,CAAC,CAAA;IACJ,CAAC;AACH,CAAC","sourcesContent":["import {sanitizedHeadersOutput} from './api/headers.js'\nimport {sanitizeURL} from './api/urls.js'\nimport {sleepWithBackoffUntil} from './sleep-with-backoff.js'\nimport {outputDebug} from '../../public/node/output.js'\nimport {Headers} from 'form-data'\nimport {ClientError} from 'graphql-request'\nimport {performance} from 'perf_hooks'\n\nexport type API = 'admin' | 'storefront-renderer' | 'partners' | 'business-platform' | 'app-management'\n\nexport const allAPIs: API[] = ['admin', 'storefront-renderer', 'partners', 'business-platform', 'app-management']\n\nconst DEFAULT_RETRY_DELAY_MS = 1000\nconst DEFAULT_RETRY_LIMIT = 10\n\nexport type NetworkRetryBehaviour =\n | {\n useNetworkLevelRetry: true\n maxRetryTimeMs: number\n }\n | {\n useNetworkLevelRetry: false\n }\n\ntype RequestOptions<T> = {\n request: () => Promise<T>\n url: string\n} & NetworkRetryBehaviour\n\nconst interestingResponseHeaders = new Set([\n 'cache-control',\n 'content-type',\n 'etag',\n 'x-request-id',\n 'server-timing',\n 'retry-after',\n])\n\nfunction responseHeaderIsInteresting(header: string): boolean {\n return interestingResponseHeaders.has(header)\n}\n\ninterface CommonResponse {\n duration: number\n sanitizedHeaders: string\n sanitizedUrl: string\n requestId?: string\n}\n\ntype OkResponse<T> = CommonResponse & {status: 'ok'; response: T}\ntype ClientErrorResponse = CommonResponse & {status: 'client-error'; clientError: ClientError}\ntype UnknownErrorResponse = CommonResponse & {status: 'unknown-error'; error: unknown}\ntype CanRetryErrorResponse = CommonResponse & {\n status: 'can-retry'\n clientError: ClientError\n delayMs: number | undefined\n}\ntype UnauthorizedErrorResponse = CommonResponse & {\n status: 'unauthorized'\n clientError: ClientError\n delayMs: number | undefined\n}\n\ntype VerboseResponse<T> =\n | OkResponse<T>\n | ClientErrorResponse\n | UnknownErrorResponse\n | CanRetryErrorResponse\n | UnauthorizedErrorResponse\n\n/**\n * Checks if an error is a transient network error that is likely to recover with retries.\n *\n * Use this function for retry logic. Use isNetworkError for error classification.\n *\n * Examples of transient errors (worth retrying):\n * - Connection timeouts, resets, and aborts\n * - DNS failures (enotfound, getaddrinfo, eai_again) - can be temporary\n * - Socket disconnects and hang ups\n * - Premature connection closes\n */\nexport function isTransientNetworkError(error: unknown): boolean {\n if (error instanceof Error) {\n const transientErrorMessages = [\n 'socket hang up',\n 'econnreset',\n 'econnaborted',\n 'enotfound',\n 'enetunreach',\n 'network socket disconnected',\n 'etimedout',\n 'econnrefused',\n 'eai_again',\n 'epipe',\n 'the operation was aborted',\n 'timeout',\n 'premature close',\n 'getaddrinfo',\n ]\n const errorMessage = error.message.toLowerCase()\n const anyMatches = transientErrorMessages.some((issueMessage) => errorMessage.includes(issueMessage))\n const missingReason = /^request to .* failed, reason:\\s*$/.test(errorMessage)\n return anyMatches || missingReason\n }\n return false\n}\n\n/**\n * Checks if an error is any kind of network-related error (connection issues, timeouts, DNS failures,\n * TLS/certificate errors, etc.) rather than an application logic error.\n *\n * These errors should be reported as user-facing errors (AbortError) rather than bugs (BugError),\n * regardless of whether they are transient or permanent.\n *\n * Examples include:\n * - Transient: connection timeouts, socket hang ups, temporary DNS failures\n * - Permanent: certificate validation failures, misconfigured SSL\n */\nexport function isNetworkError(error: unknown): boolean {\n // First check if it's a transient network error\n if (isTransientNetworkError(error)) {\n return true\n }\n\n // Then check for permanent network errors (SSL/TLS/certificate issues)\n if (error instanceof Error) {\n const permanentNetworkErrorMessages = ['certificate', 'cert', 'tls', 'ssl', 'altnames']\n const errorMessage = error.message.toLowerCase()\n return permanentNetworkErrorMessages.some((issueMessage) => errorMessage.includes(issueMessage))\n }\n\n return false\n}\n\nasync function runRequestWithNetworkLevelRetry<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n): Promise<T> {\n if (!requestOptions.useNetworkLevelRetry) {\n return requestOptions.request()\n }\n\n let lastSeenError: unknown\n\n for await (const _delayMs of sleepWithBackoffUntil(requestOptions.maxRetryTimeMs)) {\n try {\n return await requestOptions.request()\n } catch (err) {\n lastSeenError = err\n if (!isTransientNetworkError(err)) {\n throw err\n }\n outputDebug(`Retrying request to ${requestOptions.url} due to network error ${err}`)\n }\n }\n throw lastSeenError\n}\n\nasync function makeVerboseRequest<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n): Promise<VerboseResponse<T>> {\n const t0 = performance.now()\n let duration = 0\n const responseHeaders: {[key: string]: string} = {}\n const sanitizedUrl = sanitizeURL(requestOptions.url)\n let response: T = {} as T\n try {\n response = await runRequestWithNetworkLevelRetry(requestOptions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n response.headers.forEach((value: any, key: any) => {\n if (responseHeaderIsInteresting(key)) responseHeaders[key] = value\n })\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (err) {\n const t1 = performance.now()\n duration = Math.round(t1 - t0)\n\n if (err instanceof ClientError) {\n if (err.response.headers) {\n for (const [key, value] of err.response.headers as Iterable<[string, string]>) {\n if (responseHeaderIsInteresting(key)) responseHeaders[key] = value\n }\n }\n const sanitizedHeaders = sanitizedHeadersOutput(responseHeaders)\n\n if (errorsIncludeStatus429(err)) {\n let delayMs: number | undefined\n\n try {\n delayMs = responseHeaders['retry-after'] ? Number.parseInt(responseHeaders['retry-after'], 10) : undefined\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch {\n // ignore errors in extracting retry-after header\n }\n return {\n status: 'can-retry',\n clientError: err,\n duration,\n sanitizedHeaders,\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n delayMs,\n }\n } else if (err.response.status === 401) {\n return {\n status: 'unauthorized',\n clientError: err,\n duration,\n sanitizedHeaders,\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n delayMs: 500,\n }\n }\n\n return {\n status: 'client-error',\n clientError: err,\n duration,\n sanitizedHeaders,\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n }\n }\n return {\n status: 'unknown-error',\n error: err,\n duration,\n sanitizedHeaders: sanitizedHeadersOutput(responseHeaders),\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n }\n }\n const t1 = performance.now()\n duration = Math.round(t1 - t0)\n return {\n status: 'ok',\n response,\n duration,\n sanitizedHeaders: sanitizedHeadersOutput(responseHeaders),\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n }\n}\n\nfunction errorsIncludeStatus429(error: ClientError): boolean {\n if (error.response.status === 429) {\n return true\n }\n\n // GraphQL returns a 401 with a string error message when auth fails\n // Therefore error.response.errors can be a string or GraphQLError[]\n if (typeof error.response.errors === 'string') {\n return false\n }\n return error.response.errors?.some((error) => error.extensions?.code === '429') ?? false\n}\n\nexport async function simpleRequestWithDebugLog<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n errorHandler?: (error: unknown, requestId: string | undefined) => unknown,\n): Promise<T> {\n const result = await makeVerboseRequest(requestOptions)\n\n outputDebug(`Request to ${result.sanitizedUrl} completed in ${result.duration} ms\nWith response headers:\n${result.sanitizedHeaders}\n `)\n\n switch (result.status) {\n case 'ok': {\n return result.response\n }\n case 'client-error': {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n case 'unknown-error': {\n if (errorHandler) {\n throw errorHandler(result.error, result.requestId)\n } else {\n throw result.error\n }\n }\n case 'can-retry': {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n case 'unauthorized': {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n }\n}\n\n/**\n * Makes a HTTP request to some API, retrying if response headers indicate a retryable error.\n *\n * If a request fails with a 429, the retry-after header determines a delay before an automatic retry is performed.\n *\n * If unauthorizedHandler is provided, then it will be called in the case of a 401 and a retry performed. This allows\n * for a token refresh for instance.\n *\n * If there's a network error, e.g. DNS fails to resolve, then API calls are automatically retried.\n *\n * @param request - A function that returns a promise of the response\n * @param url - The URL to request\n * @param errorHandler - A function that handles errors\n * @param unauthorizedHandler - A function that handles unauthorized errors\n * @param retryOptions - Options for the retry\n * @returns The response from the request\n */\nexport async function retryAwareRequest<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n errorHandler?: (error: unknown, requestId: string | undefined) => unknown,\n retryOptions: {\n limitRetriesTo?: number\n defaultDelayMs?: number\n scheduleDelay: (fn: () => void, delay: number) => void\n } = {\n scheduleDelay: setTimeout,\n },\n): Promise<T> {\n let retriesUsed = 0\n const limitRetriesTo = retryOptions.limitRetriesTo ?? DEFAULT_RETRY_LIMIT\n\n let result = await makeVerboseRequest(requestOptions)\n\n outputDebug(`Request to ${result.sanitizedUrl} completed in ${result.duration} ms\nWith response headers:\n${result.sanitizedHeaders}\n `)\n\n while (true) {\n if (result.status === 'ok') {\n if (retriesUsed > 0) {\n outputDebug(`Request to ${result.sanitizedUrl} succeeded after ${retriesUsed} retries`)\n }\n return result.response\n } else if (result.status === 'client-error') {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n } else if (result.status === 'unknown-error') {\n if (errorHandler) {\n throw errorHandler(result.error, result.requestId)\n } else {\n throw result.error\n }\n } else if (result.status === 'unauthorized') {\n throw result.clientError\n }\n\n if (limitRetriesTo <= retriesUsed) {\n outputDebug(`${limitRetriesTo} retries exhausted for request to ${result.sanitizedUrl}`)\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n retriesUsed += 1\n\n // prefer to wait based on a header if given; the caller's preference if not; and a default if neither.\n const retryDelayMs = result.delayMs ?? retryOptions.defaultDelayMs ?? DEFAULT_RETRY_DELAY_MS\n outputDebug(`Scheduling retry request #${retriesUsed} to ${result.sanitizedUrl} in ${retryDelayMs} ms`)\n\n // eslint-disable-next-line no-await-in-loop\n result = await new Promise<VerboseResponse<T>>((resolve) => {\n retryOptions.scheduleDelay(() => {\n resolve(makeVerboseRequest(requestOptions))\n }, retryDelayMs)\n })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/private/node/api.ts"],"names":[],"mappings":"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","sourcesContent":["import {sanitizedHeadersOutput} from './api/headers.js'\nimport {sanitizeURL} from './api/urls.js'\nimport {sleepWithBackoffUntil} from './sleep-with-backoff.js'\nimport {outputDebug} from '../../public/node/output.js'\nimport {recordRetry} from '../../public/node/analytics.js'\nimport {Headers} from 'form-data'\nimport {ClientError} from 'graphql-request'\nimport {performance} from 'perf_hooks'\n\nexport type API = 'admin' | 'storefront-renderer' | 'partners' | 'business-platform' | 'app-management'\n\nexport const allAPIs: API[] = ['admin', 'storefront-renderer', 'partners', 'business-platform', 'app-management']\n\nconst DEFAULT_RETRY_DELAY_MS = 1000\nconst DEFAULT_RETRY_LIMIT = 10\n\nexport type NetworkRetryBehaviour =\n | {\n useNetworkLevelRetry: true\n maxRetryTimeMs: number\n recordCommandRetries?: boolean\n }\n | {\n useNetworkLevelRetry: false\n recordCommandRetries?: boolean\n }\n\ntype RequestOptions<T> = {\n request: () => Promise<T>\n url: string\n} & NetworkRetryBehaviour\n\nconst interestingResponseHeaders = new Set([\n 'cache-control',\n 'content-type',\n 'etag',\n 'x-request-id',\n 'server-timing',\n 'retry-after',\n])\n\nfunction responseHeaderIsInteresting(header: string): boolean {\n return interestingResponseHeaders.has(header)\n}\n\ninterface CommonResponse {\n duration: number\n sanitizedHeaders: string\n sanitizedUrl: string\n requestId?: string\n}\n\ntype OkResponse<T> = CommonResponse & {status: 'ok'; response: T}\ntype ClientErrorResponse = CommonResponse & {status: 'client-error'; clientError: ClientError}\ntype UnknownErrorResponse = CommonResponse & {status: 'unknown-error'; error: unknown}\ntype CanRetryErrorResponse = CommonResponse & {\n status: 'can-retry'\n clientError: ClientError\n delayMs: number | undefined\n}\ntype UnauthorizedErrorResponse = CommonResponse & {\n status: 'unauthorized'\n clientError: ClientError\n delayMs: number | undefined\n}\n\ntype VerboseResponse<T> =\n | OkResponse<T>\n | ClientErrorResponse\n | UnknownErrorResponse\n | CanRetryErrorResponse\n | UnauthorizedErrorResponse\n\n/**\n * Checks if an error is a transient network error that is likely to recover with retries.\n *\n * Use this function for retry logic. Use isNetworkError for error classification.\n *\n * Examples of transient errors (worth retrying):\n * - Connection timeouts, resets, and aborts\n * - DNS failures (enotfound, getaddrinfo, eai_again) - can be temporary\n * - Socket disconnects and hang ups\n * - Premature connection closes\n */\nexport function isTransientNetworkError(error: unknown): boolean {\n if (error instanceof Error) {\n const transientErrorMessages = [\n 'socket hang up',\n 'econnreset',\n 'econnaborted',\n 'enotfound',\n 'enetunreach',\n 'network socket disconnected',\n 'etimedout',\n 'econnrefused',\n 'eai_again',\n 'epipe',\n 'the operation was aborted',\n 'timeout',\n 'premature close',\n 'getaddrinfo',\n ]\n const errorMessage = error.message.toLowerCase()\n const anyMatches = transientErrorMessages.some((issueMessage) => errorMessage.includes(issueMessage))\n const missingReason = /^request to .* failed, reason:\\s*$/.test(errorMessage)\n return anyMatches || missingReason\n }\n return false\n}\n\n/**\n * Checks if an error is any kind of network-related error (connection issues, timeouts, DNS failures,\n * TLS/certificate errors, etc.) rather than an application logic error.\n *\n * These errors should be reported as user-facing errors (AbortError) rather than bugs (BugError),\n * regardless of whether they are transient or permanent.\n *\n * Examples include:\n * - Transient: connection timeouts, socket hang ups, temporary DNS failures\n * - Permanent: certificate validation failures, misconfigured SSL\n */\nexport function isNetworkError(error: unknown): boolean {\n // First check if it's a transient network error\n if (isTransientNetworkError(error)) {\n return true\n }\n\n // Then check for permanent network errors (SSL/TLS/certificate issues)\n if (error instanceof Error) {\n const permanentNetworkErrorMessages = ['certificate', 'cert', 'tls', 'ssl', 'altnames']\n const errorMessage = error.message.toLowerCase()\n return permanentNetworkErrorMessages.some((issueMessage) => errorMessage.includes(issueMessage))\n }\n\n return false\n}\n\nasync function runRequestWithNetworkLevelRetry<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n): Promise<T> {\n if (!requestOptions.useNetworkLevelRetry) {\n return requestOptions.request()\n }\n\n let lastSeenError: unknown\n\n for await (const _delayMs of sleepWithBackoffUntil(requestOptions.maxRetryTimeMs)) {\n try {\n return await requestOptions.request()\n } catch (err) {\n lastSeenError = err\n if (!isTransientNetworkError(err)) {\n throw err\n }\n\n // Record command retries\n if (requestOptions.recordCommandRetries) {\n recordRetry(requestOptions.url, `network-retry:${(err as Error).message}`)\n }\n\n outputDebug(`Retrying request to ${requestOptions.url} due to network error ${err}`)\n }\n }\n throw lastSeenError\n}\n\nasync function makeVerboseRequest<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n): Promise<VerboseResponse<T>> {\n const t0 = performance.now()\n let duration = 0\n const responseHeaders: {[key: string]: string} = {}\n const sanitizedUrl = sanitizeURL(requestOptions.url)\n let response: T = {} as T\n try {\n response = await runRequestWithNetworkLevelRetry(requestOptions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n response.headers.forEach((value: any, key: any) => {\n if (responseHeaderIsInteresting(key)) responseHeaders[key] = value\n })\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (err) {\n const t1 = performance.now()\n duration = Math.round(t1 - t0)\n\n if (err instanceof ClientError) {\n if (err.response.headers) {\n for (const [key, value] of err.response.headers as Iterable<[string, string]>) {\n if (responseHeaderIsInteresting(key)) responseHeaders[key] = value\n }\n }\n const sanitizedHeaders = sanitizedHeadersOutput(responseHeaders)\n\n if (errorsIncludeStatus429(err)) {\n let delayMs: number | undefined\n\n try {\n delayMs = responseHeaders['retry-after'] ? Number.parseInt(responseHeaders['retry-after'], 10) : undefined\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch {\n // ignore errors in extracting retry-after header\n }\n return {\n status: 'can-retry',\n clientError: err,\n duration,\n sanitizedHeaders,\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n delayMs,\n }\n } else if (err.response.status === 401) {\n return {\n status: 'unauthorized',\n clientError: err,\n duration,\n sanitizedHeaders,\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n delayMs: 500,\n }\n }\n\n return {\n status: 'client-error',\n clientError: err,\n duration,\n sanitizedHeaders,\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n }\n }\n return {\n status: 'unknown-error',\n error: err,\n duration,\n sanitizedHeaders: sanitizedHeadersOutput(responseHeaders),\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n }\n }\n const t1 = performance.now()\n duration = Math.round(t1 - t0)\n return {\n status: 'ok',\n response,\n duration,\n sanitizedHeaders: sanitizedHeadersOutput(responseHeaders),\n sanitizedUrl,\n requestId: responseHeaders['x-request-id'],\n }\n}\n\nfunction errorsIncludeStatus429(error: ClientError): boolean {\n if (error.response.status === 429) {\n return true\n }\n\n // GraphQL returns a 401 with a string error message when auth fails\n // Therefore error.response.errors can be a string or GraphQLError[]\n if (typeof error.response.errors === 'string') {\n return false\n }\n return error.response.errors?.some((error) => error.extensions?.code === '429') ?? false\n}\n\nexport async function simpleRequestWithDebugLog<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n errorHandler?: (error: unknown, requestId: string | undefined) => unknown,\n): Promise<T> {\n const result = await makeVerboseRequest(requestOptions)\n\n outputDebug(`Request to ${result.sanitizedUrl} completed in ${result.duration} ms\nWith response headers:\n${result.sanitizedHeaders}\n `)\n\n switch (result.status) {\n case 'ok': {\n return result.response\n }\n case 'client-error': {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n case 'unknown-error': {\n if (errorHandler) {\n throw errorHandler(result.error, result.requestId)\n } else {\n throw result.error\n }\n }\n case 'can-retry': {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n case 'unauthorized': {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n }\n}\n\n/**\n * Makes a HTTP request to some API, retrying if response headers indicate a retryable error.\n *\n * If a request fails with a 429, the retry-after header determines a delay before an automatic retry is performed.\n *\n * If unauthorizedHandler is provided, then it will be called in the case of a 401 and a retry performed. This allows\n * for a token refresh for instance.\n *\n * If there's a network error, e.g. DNS fails to resolve, then API calls are automatically retried.\n *\n * @param request - A function that returns a promise of the response\n * @param url - The URL to request\n * @param errorHandler - A function that handles errors\n * @param unauthorizedHandler - A function that handles unauthorized errors\n * @param retryOptions - Options for the retry\n * @returns The response from the request\n */\nexport async function retryAwareRequest<T extends {headers: Headers; status: number}>(\n requestOptions: RequestOptions<T>,\n errorHandler?: (error: unknown, requestId: string | undefined) => unknown,\n retryOptions: {\n limitRetriesTo?: number\n defaultDelayMs?: number\n scheduleDelay: (fn: () => void, delay: number) => void\n } = {\n scheduleDelay: setTimeout,\n },\n): Promise<T> {\n let retriesUsed = 0\n const limitRetriesTo = retryOptions.limitRetriesTo ?? DEFAULT_RETRY_LIMIT\n\n let result = await makeVerboseRequest(requestOptions)\n\n outputDebug(`Request to ${result.sanitizedUrl} completed in ${result.duration} ms\nWith response headers:\n${result.sanitizedHeaders}\n `)\n\n while (true) {\n if (result.status === 'ok') {\n if (retriesUsed > 0) {\n outputDebug(`Request to ${result.sanitizedUrl} succeeded after ${retriesUsed} retries`)\n }\n return result.response\n } else if (result.status === 'client-error') {\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n } else if (result.status === 'unknown-error') {\n if (errorHandler) {\n throw errorHandler(result.error, result.requestId)\n } else {\n throw result.error\n }\n } else if (result.status === 'unauthorized') {\n throw result.clientError\n }\n\n if (limitRetriesTo <= retriesUsed) {\n outputDebug(`${limitRetriesTo} retries exhausted for request to ${result.sanitizedUrl}`)\n if (errorHandler) {\n throw errorHandler(result.clientError, result.requestId)\n } else {\n throw result.clientError\n }\n }\n retriesUsed += 1\n\n // Record command retries\n if (requestOptions.recordCommandRetries) {\n recordRetry(requestOptions.url, `http-retry-${retriesUsed}:${result.status}:`)\n }\n\n // prefer to wait based on a header if given; the caller's preference if not; and a default if neither.\n const retryDelayMs = result.delayMs ?? retryOptions.defaultDelayMs ?? DEFAULT_RETRY_DELAY_MS\n outputDebug(`Scheduling retry request #${retriesUsed} to ${result.sanitizedUrl} in ${retryDelayMs} ms`)\n\n // eslint-disable-next-line no-await-in-loop\n result = await new Promise<VerboseResponse<T>>((resolve) => {\n retryOptions.scheduleDelay(() => {\n resolve(makeVerboseRequest(requestOptions))\n }, retryDelayMs)\n })\n }\n}\n"]}
|
|
@@ -40,6 +40,7 @@ export const environmentVariables = {
|
|
|
40
40
|
themeKitAccessDomain: 'SHOPIFY_CLI_THEME_KIT_ACCESS_DOMAIN',
|
|
41
41
|
json: 'SHOPIFY_FLAG_JSON',
|
|
42
42
|
neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',
|
|
43
|
+
usePartnersApi: 'SHOPIFY_CLI_USE_PARTNERS_API',
|
|
43
44
|
skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',
|
|
44
45
|
maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',
|
|
45
46
|
};
|
|
@@ -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,iBAAiB,EAAE,iCAAiC;IACpD,GAAG,EAAE,iBAAiB;IACtB,aAAa,EAAE,oBAAoB;IACnC,WAAW,EAAE,0BAA0B;IACvC,aAAa,EAAE,4BAA4B;IAC3C,SAAS,EAAE,qBAAqB;IAChC,UAAU,EAAE,qBAAqB;IACjC,eAAe,EAAE,+BAA+B;IAChD,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE,yBAAyB;IACrC,QAAQ,EAAE,mBAAmB;IAC7B,OAAO,EAAE,sBAAsB;IAC/B,mEAAmE;IACnE,UAAU,EAAE,YAAY;IACxB,aAAa,EAAE,gBAAgB;IAC/B,6BAA6B,EAAE,0CAA0C;IACzE,MAAM,EAAE,sBAAsB;IAC9B,UAAU,EAAE,aAAa;IACzB,WAAW,EAAE,aAAa;IAC1B,WAAW,EAAE,eAAe;IAC5B,YAAY,EAAE,0BAA0B;IACxC,aAAa,EAAE,4BAA4B;IAC3C,YAAY,EAAE,2BAA2B;IACzC,OAAO,EAAE,yCAAyC;IAClD,oBAAoB,EAAE,qCAAqC;IAC3D,IAAI,EAAE,mBAAmB;IACzB,mBAAmB,EAAE,oCAAoC;IACzD,qBAAqB,EAAE,sCAAsC;IAC7D,6BAA6B,EAAE,gDAAgD;CAChF,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,kCAAkC,CAAA;AAE7E,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,WAAW,EAAE,cAAc;CAC5B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,WAAW,EAAE;QACX,GAAG,EAAE,kBAAkB;KACxB;IACD,WAAW,EAAE;QACX,KAAK,EAAE;YACL,IAAI,EAAE,GAAG,EAAE;gBACT,OAAO,WAAW,EAAE,CAAA;YACtB,CAAC;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,GAAG,EAAE;oBACT,OAAO,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAA;gBAC1C,CAAC;gBACD,QAAQ,EAAE,GAAG,EAAE;oBACb,OAAO,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;gBACtD,CAAC;aACF;SACF;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,6BAA6B,EAAE,CAAC;CACjC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,kCAAkC,CAAA;AAE/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAC,IAAI,EAAE,CAAC,EAAC,EAAC,CAAA;AAElE,MAAM,CAAC,MAAM,oBAAoB,GAC/B,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,IAAI,2BAA2B,CAAA","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 enableCliRedirect: 'SHOPIFY_CLI_ENABLE_CLI_REDIRECT',\n env: 'SHOPIFY_CLI_ENV',\n firstPartyDev: 'SHOPIFY_CLI_1P_DEV',\n noAnalytics: 'SHOPIFY_CLI_NO_ANALYTICS',\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 neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',\n skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',\n maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',\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"]}
|
|
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,iBAAiB,EAAE,iCAAiC;IACpD,GAAG,EAAE,iBAAiB;IACtB,aAAa,EAAE,oBAAoB;IACnC,WAAW,EAAE,0BAA0B;IACvC,aAAa,EAAE,4BAA4B;IAC3C,SAAS,EAAE,qBAAqB;IAChC,UAAU,EAAE,qBAAqB;IACjC,eAAe,EAAE,+BAA+B;IAChD,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE,yBAAyB;IACrC,QAAQ,EAAE,mBAAmB;IAC7B,OAAO,EAAE,sBAAsB;IAC/B,mEAAmE;IACnE,UAAU,EAAE,YAAY;IACxB,aAAa,EAAE,gBAAgB;IAC/B,6BAA6B,EAAE,0CAA0C;IACzE,MAAM,EAAE,sBAAsB;IAC9B,UAAU,EAAE,aAAa;IACzB,WAAW,EAAE,aAAa;IAC1B,WAAW,EAAE,eAAe;IAC5B,YAAY,EAAE,0BAA0B;IACxC,aAAa,EAAE,4BAA4B;IAC3C,YAAY,EAAE,2BAA2B;IACzC,OAAO,EAAE,yCAAyC;IAClD,oBAAoB,EAAE,qCAAqC;IAC3D,IAAI,EAAE,mBAAmB;IACzB,mBAAmB,EAAE,oCAAoC;IACzD,cAAc,EAAE,8BAA8B;IAC9C,qBAAqB,EAAE,sCAAsC;IAC7D,6BAA6B,EAAE,gDAAgD;CAChF,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,kCAAkC,CAAA;AAE7E,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,WAAW,EAAE,cAAc;CAC5B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,WAAW,EAAE;QACX,GAAG,EAAE,kBAAkB;KACxB;IACD,WAAW,EAAE;QACX,KAAK,EAAE;YACL,IAAI,EAAE,GAAG,EAAE;gBACT,OAAO,WAAW,EAAE,CAAA;YACtB,CAAC;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,GAAG,EAAE;oBACT,OAAO,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAA;gBAC1C,CAAC;gBACD,QAAQ,EAAE,GAAG,EAAE;oBACb,OAAO,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;gBACtD,CAAC;aACF;SACF;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,6BAA6B,EAAE,CAAC;CACjC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,kCAAkC,CAAA;AAE/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAC,IAAI,EAAE,CAAC,EAAC,EAAC,CAAA;AAElE,MAAM,CAAC,MAAM,oBAAoB,GAC/B,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,IAAI,2BAA2B,CAAA","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 enableCliRedirect: 'SHOPIFY_CLI_ENABLE_CLI_REDIRECT',\n env: 'SHOPIFY_CLI_ENV',\n firstPartyDev: 'SHOPIFY_CLI_1P_DEV',\n noAnalytics: 'SHOPIFY_CLI_NO_ANALYTICS',\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 neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',\n usePartnersApi: 'SHOPIFY_CLI_USE_PARTNERS_API',\n skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',\n maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',\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"]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
interface SingleTaskProps {
|
|
3
|
-
title:
|
|
4
|
-
|
|
1
|
+
import { TokenizedString } from '../../../../public/node/output.js';
|
|
2
|
+
interface SingleTaskProps<T> {
|
|
3
|
+
title: TokenizedString;
|
|
4
|
+
task: (updateStatus: (status: TokenizedString) => void) => Promise<T>;
|
|
5
|
+
onComplete?: (result: T) => void;
|
|
5
6
|
noColor?: boolean;
|
|
6
7
|
}
|
|
7
|
-
declare const SingleTask: ({
|
|
8
|
+
declare const SingleTask: <T>({ task, title, onComplete, noColor }: SingleTaskProps<T>) => JSX.Element | null;
|
|
8
9
|
export { SingleTask };
|
|
@@ -2,26 +2,28 @@ import { LoadingBar } from './LoadingBar.js';
|
|
|
2
2
|
import { useExitOnCtrlC } from '../hooks/use-exit-on-ctrl-c.js';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
4
|
import { useApp } from 'ink';
|
|
5
|
-
const SingleTask = ({
|
|
5
|
+
const SingleTask = ({ task, title, onComplete, noColor }) => {
|
|
6
|
+
const [status, setStatus] = useState(title);
|
|
6
7
|
const [isDone, setIsDone] = useState(false);
|
|
7
8
|
const { exit: unmountInk } = useApp();
|
|
8
9
|
useExitOnCtrlC();
|
|
9
10
|
useEffect(() => {
|
|
10
|
-
|
|
11
|
-
.then(() => {
|
|
11
|
+
task(setStatus)
|
|
12
|
+
.then((result) => {
|
|
12
13
|
setIsDone(true);
|
|
14
|
+
onComplete?.(result);
|
|
13
15
|
unmountInk();
|
|
14
16
|
})
|
|
15
17
|
.catch((error) => {
|
|
16
18
|
setIsDone(true);
|
|
17
19
|
unmountInk(error);
|
|
18
20
|
});
|
|
19
|
-
}, [
|
|
21
|
+
}, [task, unmountInk, onComplete]);
|
|
20
22
|
if (isDone) {
|
|
21
23
|
// clear things once done
|
|
22
24
|
return null;
|
|
23
25
|
}
|
|
24
|
-
return React.createElement(LoadingBar, { title:
|
|
26
|
+
return React.createElement(LoadingBar, { title: status.value, noColor: noColor });
|
|
25
27
|
};
|
|
26
28
|
export { SingleTask };
|
|
27
29
|
//# sourceMappingURL=SingleTask.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SingleTask.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SingleTask.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAC,cAAc,EAAC,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"SingleTask.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SingleTask.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAC,cAAc,EAAC,MAAM,gCAAgC,CAAA;AAE7D,OAAO,KAAK,EAAE,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAChD,OAAO,EAAC,MAAM,EAAC,MAAM,KAAK,CAAA;AAS1B,MAAM,UAAU,GAAG,CAAK,EAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAqB,EAAE,EAAE;IAChF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,EAAC,IAAI,EAAE,UAAU,EAAC,GAAG,MAAM,EAAE,CAAA;IACnC,cAAc,EAAE,CAAA;IAEhB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,CAAC;aACZ,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,SAAS,CAAC,IAAI,CAAC,CAAA;YACf,UAAU,EAAE,CAAC,MAAM,CAAC,CAAA;YACpB,UAAU,EAAE,CAAA;QACd,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,SAAS,CAAC,IAAI,CAAC,CAAA;YACf,UAAU,CAAC,KAAK,CAAC,CAAA;QACnB,CAAC,CAAC,CAAA;IACN,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA;IAElC,IAAI,MAAM,EAAE,CAAC;QACX,yBAAyB;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,oBAAC,UAAU,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CAAA;AAC9D,CAAC,CAAA;AAED,OAAO,EAAC,UAAU,EAAC,CAAA","sourcesContent":["import {LoadingBar} from './LoadingBar.js'\nimport {useExitOnCtrlC} from '../hooks/use-exit-on-ctrl-c.js'\nimport {TokenizedString} from '../../../../public/node/output.js'\nimport React, {useEffect, useState} from 'react'\nimport {useApp} from 'ink'\n\ninterface SingleTaskProps<T> {\n title: TokenizedString\n task: (updateStatus: (status: TokenizedString) => void) => Promise<T>\n onComplete?: (result: T) => void\n noColor?: boolean\n}\n\nconst SingleTask = <T,>({task, title, onComplete, noColor}: SingleTaskProps<T>) => {\n const [status, setStatus] = useState(title)\n const [isDone, setIsDone] = useState(false)\n const {exit: unmountInk} = useApp()\n useExitOnCtrlC()\n\n useEffect(() => {\n task(setStatus)\n .then((result) => {\n setIsDone(true)\n onComplete?.(result)\n unmountInk()\n })\n .catch((error) => {\n setIsDone(true)\n unmountInk(error)\n })\n }, [task, unmountInk, onComplete])\n\n if (isDone) {\n // clear things once done\n return null\n }\n\n return <LoadingBar title={status.value} noColor={noColor} />\n}\n\nexport {SingleTask}\n"]}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { SingleTask } from './SingleTask.js';
|
|
2
2
|
import { render } from '../../testing/ui.js';
|
|
3
|
+
import { TokenizedString } from '../../../../public/node/output.js';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import { describe, expect, test } from 'vitest';
|
|
5
6
|
describe('SingleTask', () => {
|
|
6
7
|
test('unmounts when promise resolves successfully', async () => {
|
|
7
8
|
// Given
|
|
8
|
-
const title = 'Uploading files';
|
|
9
|
+
const title = new TokenizedString('Uploading files');
|
|
9
10
|
let resolvePromise;
|
|
10
|
-
const
|
|
11
|
+
const task = () => new Promise((resolve) => {
|
|
11
12
|
resolvePromise = resolve;
|
|
12
13
|
});
|
|
13
14
|
// When
|
|
14
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
15
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
|
|
15
16
|
// Wait for initial render
|
|
16
17
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
17
18
|
// Resolve the promise
|
|
@@ -23,13 +24,13 @@ describe('SingleTask', () => {
|
|
|
23
24
|
});
|
|
24
25
|
test('unmounts when promise rejects', async () => {
|
|
25
26
|
// Given
|
|
26
|
-
const title = 'Failed task';
|
|
27
|
+
const title = new TokenizedString('Failed task');
|
|
27
28
|
let rejectPromise;
|
|
28
|
-
const
|
|
29
|
+
const task = () => new Promise((resolve, reject) => {
|
|
29
30
|
rejectPromise = reject;
|
|
30
31
|
});
|
|
31
32
|
// When
|
|
32
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
33
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
|
|
33
34
|
// Wait for initial render
|
|
34
35
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
35
36
|
// Reject the promise and expect waitUntilExit to reject
|
|
@@ -39,53 +40,60 @@ describe('SingleTask', () => {
|
|
|
39
40
|
});
|
|
40
41
|
test('handles promise that resolves immediately', async () => {
|
|
41
42
|
// Given
|
|
42
|
-
const title = 'Instant task';
|
|
43
|
-
const
|
|
43
|
+
const title = new TokenizedString('Instant task');
|
|
44
|
+
const task = () => Promise.resolve('immediate success');
|
|
44
45
|
// When
|
|
45
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
46
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
|
|
46
47
|
await renderInstance.waitUntilExit();
|
|
47
48
|
// Then - component should complete successfully
|
|
48
49
|
expect(renderInstance.lastFrame()).toBeDefined();
|
|
49
50
|
});
|
|
50
51
|
test('handles promise that rejects immediately', async () => {
|
|
51
52
|
// Given
|
|
52
|
-
const title = 'Instant failure';
|
|
53
|
-
const
|
|
53
|
+
const title = new TokenizedString('Instant failure');
|
|
54
|
+
const task = () => Promise.reject(new Error('Immediate error'));
|
|
54
55
|
// When
|
|
55
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
56
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
|
|
56
57
|
// Then - should exit with error
|
|
57
58
|
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Immediate error');
|
|
58
59
|
});
|
|
59
60
|
test('handles different types of promise return values', async () => {
|
|
60
61
|
// Test with string
|
|
61
|
-
|
|
62
|
-
const
|
|
62
|
+
let stringResult;
|
|
63
|
+
const stringTask = () => Promise.resolve('task completed');
|
|
64
|
+
const stringRender = render(React.createElement(SingleTask, { title: new TokenizedString('String task'), task: stringTask, onComplete: (result) => (stringResult = result) }));
|
|
63
65
|
await stringRender.waitUntilExit();
|
|
64
66
|
expect(stringRender.lastFrame()).toBeDefined();
|
|
67
|
+
expect(stringResult).toBe('task completed');
|
|
65
68
|
// Test with object
|
|
66
|
-
|
|
67
|
-
const
|
|
69
|
+
let objectResult;
|
|
70
|
+
const objectTask = () => Promise.resolve({ id: 1, name: 'test' });
|
|
71
|
+
const objectRender = render(React.createElement(SingleTask, { title: new TokenizedString('Object task'), task: objectTask, onComplete: (result) => (objectResult = result) }));
|
|
68
72
|
await objectRender.waitUntilExit();
|
|
69
73
|
expect(objectRender.lastFrame()).toBeDefined();
|
|
70
74
|
// Test with number
|
|
71
|
-
|
|
72
|
-
const
|
|
75
|
+
let numberResult;
|
|
76
|
+
const numberTask = () => Promise.resolve(42);
|
|
77
|
+
const numberRender = render(React.createElement(SingleTask, { title: new TokenizedString('Number task'), task: numberTask, onComplete: (result) => (numberResult = result) }));
|
|
73
78
|
await numberRender.waitUntilExit();
|
|
74
79
|
expect(numberRender.lastFrame()).toBeDefined();
|
|
80
|
+
expect(numberResult).toBe(42);
|
|
75
81
|
// Test with boolean
|
|
76
|
-
|
|
77
|
-
const
|
|
82
|
+
let booleanResult;
|
|
83
|
+
const booleanTask = () => Promise.resolve(true);
|
|
84
|
+
const booleanRender = render(React.createElement(SingleTask, { title: new TokenizedString('Boolean task'), task: booleanTask, onComplete: (result) => (booleanResult = result) }));
|
|
78
85
|
await booleanRender.waitUntilExit();
|
|
79
86
|
expect(booleanRender.lastFrame()).toBeDefined();
|
|
87
|
+
expect(booleanResult).toBe(true);
|
|
80
88
|
});
|
|
81
89
|
test('handles promise with delayed resolution', async () => {
|
|
82
90
|
// Given
|
|
83
|
-
const title = 'Delayed task';
|
|
84
|
-
const
|
|
91
|
+
const title = new TokenizedString('Delayed task');
|
|
92
|
+
const task = () => new Promise((resolve) => {
|
|
85
93
|
setTimeout(() => resolve('completed'), 100);
|
|
86
94
|
});
|
|
87
95
|
// When
|
|
88
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
96
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
|
|
89
97
|
// Wait for completion
|
|
90
98
|
await renderInstance.waitUntilExit();
|
|
91
99
|
// Then
|
|
@@ -93,12 +101,12 @@ describe('SingleTask', () => {
|
|
|
93
101
|
});
|
|
94
102
|
test('handles promise with delayed rejection', async () => {
|
|
95
103
|
// Given
|
|
96
|
-
const title = 'Delayed failure';
|
|
97
|
-
const
|
|
104
|
+
const title = new TokenizedString('Delayed failure');
|
|
105
|
+
const task = () => new Promise((resolve, reject) => {
|
|
98
106
|
setTimeout(() => reject(new Error('delayed error')), 100);
|
|
99
107
|
});
|
|
100
108
|
// When
|
|
101
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
109
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task }));
|
|
102
110
|
// Wait for completion - should throw error
|
|
103
111
|
await expect(renderInstance.waitUntilExit()).rejects.toThrow('delayed error');
|
|
104
112
|
});
|
|
@@ -112,19 +120,19 @@ describe('SingleTask', () => {
|
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
122
|
const customError = new CustomError('Custom error message', 'CUSTOM_CODE');
|
|
115
|
-
const
|
|
123
|
+
const task = () => Promise.reject(customError);
|
|
116
124
|
// When
|
|
117
|
-
const renderInstance = render(React.createElement(SingleTask, { title:
|
|
125
|
+
const renderInstance = render(React.createElement(SingleTask, { title: new TokenizedString('Custom error task'), task: task }));
|
|
118
126
|
// Then - should preserve the exact error
|
|
119
127
|
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Custom error message');
|
|
120
128
|
});
|
|
121
129
|
test('handles concurrent promise operations', async () => {
|
|
122
130
|
// Given - Multiple SingleTask components with different promises
|
|
123
|
-
const fastPromise = new Promise((resolve) => setTimeout(() => resolve('fast'), 50));
|
|
124
|
-
const slowPromise = new Promise((resolve) => setTimeout(() => resolve('slow'), 150));
|
|
131
|
+
const fastPromise = () => new Promise((resolve) => setTimeout(() => resolve('fast'), 50));
|
|
132
|
+
const slowPromise = () => new Promise((resolve) => setTimeout(() => resolve('slow'), 150));
|
|
125
133
|
// When
|
|
126
|
-
const fastRender = render(React.createElement(SingleTask, { title:
|
|
127
|
-
const slowRender = render(React.createElement(SingleTask, { title:
|
|
134
|
+
const fastRender = render(React.createElement(SingleTask, { title: new TokenizedString('Fast task'), task: fastPromise }));
|
|
135
|
+
const slowRender = render(React.createElement(SingleTask, { title: new TokenizedString('Slow task'), task: slowPromise }));
|
|
128
136
|
// Then - Both should complete successfully
|
|
129
137
|
await fastRender.waitUntilExit();
|
|
130
138
|
await slowRender.waitUntilExit();
|
|
@@ -133,13 +141,57 @@ describe('SingleTask', () => {
|
|
|
133
141
|
});
|
|
134
142
|
test('passes noColor prop to LoadingBar component', async () => {
|
|
135
143
|
// Given
|
|
136
|
-
const title = 'No color task';
|
|
137
|
-
const
|
|
144
|
+
const title = new TokenizedString('No color task');
|
|
145
|
+
const task = () => Promise.resolve();
|
|
138
146
|
// When - Test that noColor prop doesn't break the component
|
|
139
|
-
const renderInstance = render(React.createElement(SingleTask, { title: title,
|
|
147
|
+
const renderInstance = render(React.createElement(SingleTask, { title: title, task: task, noColor: true }));
|
|
140
148
|
await renderInstance.waitUntilExit();
|
|
141
149
|
// Then - Component should complete successfully with noColor prop
|
|
142
150
|
expect(renderInstance.lastFrame()).toBeDefined();
|
|
143
151
|
});
|
|
152
|
+
test('updates status message during task execution', async () => {
|
|
153
|
+
// Given
|
|
154
|
+
const initialTitle = new TokenizedString('Starting task');
|
|
155
|
+
let step1Resolve;
|
|
156
|
+
let step2Resolve;
|
|
157
|
+
let step3Resolve;
|
|
158
|
+
const step1Promise = new Promise((resolve) => {
|
|
159
|
+
step1Resolve = resolve;
|
|
160
|
+
});
|
|
161
|
+
const step2Promise = new Promise((resolve) => {
|
|
162
|
+
step2Resolve = resolve;
|
|
163
|
+
});
|
|
164
|
+
const step3Promise = new Promise((resolve) => {
|
|
165
|
+
step3Resolve = resolve;
|
|
166
|
+
});
|
|
167
|
+
const task = async (updateStatus) => {
|
|
168
|
+
updateStatus(new TokenizedString('Running (1 complete)...'));
|
|
169
|
+
await step1Promise;
|
|
170
|
+
updateStatus(new TokenizedString('Running (2 complete)...'));
|
|
171
|
+
await step2Promise;
|
|
172
|
+
updateStatus(new TokenizedString('Running (3 complete)...'));
|
|
173
|
+
await step3Promise;
|
|
174
|
+
return 'completed';
|
|
175
|
+
};
|
|
176
|
+
// When
|
|
177
|
+
const renderInstance = render(React.createElement(SingleTask, { title: initialTitle, task: task }));
|
|
178
|
+
// Wait for component to render with first status
|
|
179
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
180
|
+
const frame1 = renderInstance.lastFrame();
|
|
181
|
+
expect(frame1).toContain('1 complete');
|
|
182
|
+
// Progress to step 2
|
|
183
|
+
step1Resolve();
|
|
184
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
185
|
+
const frame2 = renderInstance.lastFrame();
|
|
186
|
+
expect(frame2).toContain('2 complete');
|
|
187
|
+
// Progress to step 3
|
|
188
|
+
step2Resolve();
|
|
189
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
190
|
+
const frame3 = renderInstance.lastFrame();
|
|
191
|
+
expect(frame3).toContain('3 complete');
|
|
192
|
+
// Complete the task
|
|
193
|
+
step3Resolve();
|
|
194
|
+
await renderInstance.waitUntilExit();
|
|
195
|
+
});
|
|
144
196
|
});
|
|
145
197
|
//# sourceMappingURL=SingleTask.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SingleTask.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SingleTask.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAC,MAAM,EAAC,MAAM,qBAAqB,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAE7C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,IAAI,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC7D,QAAQ;QACR,MAAM,KAAK,GAAG,iBAAiB,CAAA;QAC/B,IAAI,cAAuC,CAAA;QAC3C,MAAM,WAAW,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YAClD,cAAc,GAAG,OAAO,CAAA;QAC1B,CAAC,CAAC,CAAA;QAEF,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAErF,0BAA0B;QAC1B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;QAEvD,sBAAsB;QACtB,cAAe,CAAC,SAAS,CAAC,CAAA;QAE1B,2CAA2C;QAC3C,MAAM,cAAc,CAAC,aAAa,EAAE,CAAA;QAEpC,0CAA0C;QAC1C,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC/C,QAAQ;QACR,MAAM,KAAK,GAAG,aAAa,CAAA;QAC3B,IAAI,aAAqC,CAAA;QACzC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1D,aAAa,GAAG,MAAM,CAAA;QACxB,CAAC,CAAC,CAAA;QAEF,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAErF,0BAA0B;QAC1B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;QAEvD,wDAAwD;QACxD,aAAc,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,CAAA;QAExC,2CAA2C;QAC3C,MAAM,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QAC3D,QAAQ;QACR,MAAM,KAAK,GAAG,cAAc,CAAA;QAC5B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;QAExD,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QACrF,MAAM,cAAc,CAAC,aAAa,EAAE,CAAA;QAEpC,gDAAgD;QAChD,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QAC1D,QAAQ;QACR,MAAM,KAAK,GAAG,iBAAiB,CAAA;QAC/B,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAA;QAEhE,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAErF,gCAAgC;QAChC,MAAM,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAClE,mBAAmB;QACnB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;QACpD,MAAM,YAAY,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,aAAa,EAAC,WAAW,EAAE,UAAU,GAAI,CAAC,CAAA;QACxF,MAAM,YAAY,CAAC,aAAa,EAAE,CAAA;QAClC,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QAE9C,mBAAmB;QACnB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,EAAC,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAC,CAAC,CAAA;QACzD,MAAM,YAAY,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,aAAa,EAAC,WAAW,EAAE,UAAU,GAAI,CAAC,CAAA;QACxF,MAAM,YAAY,CAAC,aAAa,EAAE,CAAA;QAClC,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QAE9C,mBAAmB;QACnB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACtC,MAAM,YAAY,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,aAAa,EAAC,WAAW,EAAE,UAAU,GAAI,CAAC,CAAA;QACxF,MAAM,YAAY,CAAC,aAAa,EAAE,CAAA;QAClC,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QAE9C,oBAAoB;QACpB,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACzC,MAAM,aAAa,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,cAAc,EAAC,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAC3F,MAAM,aAAa,CAAC,aAAa,EAAE,CAAA;QACnC,MAAM,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACzD,QAAQ;QACR,MAAM,KAAK,GAAG,cAAc,CAAA;QAC5B,MAAM,WAAW,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YAClD,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,CAAA;QAC7C,CAAC,CAAC,CAAA;QAEF,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAErF,sBAAsB;QACtB,MAAM,cAAc,CAAC,aAAa,EAAE,CAAA;QAEpC,OAAO;QACP,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACxD,QAAQ;QACR,MAAM,KAAK,GAAG,iBAAiB,CAAA;QAC/B,MAAM,WAAW,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1D,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QAC3D,CAAC,CAAC,CAAA;QAEF,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAErF,2CAA2C;QAC3C,MAAM,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QACpD,yBAAyB;QACzB,MAAM,WAAY,SAAQ,KAAK;YAC7B,YAAY,OAAe,EAAS,IAAY;gBAC9C,KAAK,CAAC,OAAO,CAAC,CAAA;gBADoB,SAAI,GAAJ,IAAI,CAAQ;gBAE9C,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;YAC3B,CAAC;SACF;QAED,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,sBAAsB,EAAE,aAAa,CAAC,CAAA;QAC1E,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;QAE/C,OAAO;QACP,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,mBAAmB,EAAC,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAEjG,yCAAyC;QACzC,MAAM,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACtF,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACvD,iEAAiE;QACjE,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;QACnF,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QAEpF,OAAO;QACP,MAAM,UAAU,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,WAAW,EAAC,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QACrF,MAAM,UAAU,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAC,WAAW,EAAC,WAAW,EAAE,WAAW,GAAI,CAAC,CAAA;QAErF,2CAA2C;QAC3C,MAAM,UAAU,CAAC,aAAa,EAAE,CAAA;QAChC,MAAM,UAAU,CAAC,aAAa,EAAE,CAAA;QAEhC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QAC5C,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC7D,QAAQ;QACR,MAAM,KAAK,GAAG,eAAe,CAAA;QAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA;QAErC,4DAA4D;QAC5D,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,SAAG,CAAC,CAAA;QAC7F,MAAM,cAAc,CAAC,aAAa,EAAE,CAAA;QAEpC,kEAAkE;QAClE,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import {SingleTask} from './SingleTask.js'\nimport {render} from '../../testing/ui.js'\nimport React from 'react'\nimport {describe, expect, test} from 'vitest'\n\ndescribe('SingleTask', () => {\n test('unmounts when promise resolves successfully', async () => {\n // Given\n const title = 'Uploading files'\n let resolvePromise: (value: string) => void\n const taskPromise = new Promise<string>((resolve) => {\n resolvePromise = resolve\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} />)\n\n // Wait for initial render\n await new Promise((resolve) => setTimeout(resolve, 10))\n\n // Resolve the promise\n resolvePromise!('success')\n\n // Wait for component to update and unmount\n await renderInstance.waitUntilExit()\n\n // Then - component should unmount cleanly\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('unmounts when promise rejects', async () => {\n // Given\n const title = 'Failed task'\n let rejectPromise: (error: Error) => void\n const taskPromise = new Promise<string>((resolve, reject) => {\n rejectPromise = reject\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} />)\n\n // Wait for initial render\n await new Promise((resolve) => setTimeout(resolve, 10))\n\n // Reject the promise and expect waitUntilExit to reject\n rejectPromise!(new Error('Task failed'))\n\n // The component should exit with the error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('Task failed')\n })\n\n test('handles promise that resolves immediately', async () => {\n // Given\n const title = 'Instant task'\n const taskPromise = Promise.resolve('immediate success')\n\n // When\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} />)\n await renderInstance.waitUntilExit()\n\n // Then - component should complete successfully\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('handles promise that rejects immediately', async () => {\n // Given\n const title = 'Instant failure'\n const taskPromise = Promise.reject(new Error('Immediate error'))\n\n // When\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} />)\n\n // Then - should exit with error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('Immediate error')\n })\n\n test('handles different types of promise return values', async () => {\n // Test with string\n const stringTask = Promise.resolve('task completed')\n const stringRender = render(<SingleTask title=\"String task\" taskPromise={stringTask} />)\n await stringRender.waitUntilExit()\n expect(stringRender.lastFrame()).toBeDefined()\n\n // Test with object\n const objectTask = Promise.resolve({id: 1, name: 'test'})\n const objectRender = render(<SingleTask title=\"Object task\" taskPromise={objectTask} />)\n await objectRender.waitUntilExit()\n expect(objectRender.lastFrame()).toBeDefined()\n\n // Test with number\n const numberTask = Promise.resolve(42)\n const numberRender = render(<SingleTask title=\"Number task\" taskPromise={numberTask} />)\n await numberRender.waitUntilExit()\n expect(numberRender.lastFrame()).toBeDefined()\n\n // Test with boolean\n const booleanTask = Promise.resolve(true)\n const booleanRender = render(<SingleTask title=\"Boolean task\" taskPromise={booleanTask} />)\n await booleanRender.waitUntilExit()\n expect(booleanRender.lastFrame()).toBeDefined()\n })\n\n test('handles promise with delayed resolution', async () => {\n // Given\n const title = 'Delayed task'\n const taskPromise = new Promise<string>((resolve) => {\n setTimeout(() => resolve('completed'), 100)\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} />)\n\n // Wait for completion\n await renderInstance.waitUntilExit()\n\n // Then\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('handles promise with delayed rejection', async () => {\n // Given\n const title = 'Delayed failure'\n const taskPromise = new Promise<string>((resolve, reject) => {\n setTimeout(() => reject(new Error('delayed error')), 100)\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} />)\n\n // Wait for completion - should throw error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('delayed error')\n })\n\n test('preserves error types and messages', async () => {\n // Test with custom error\n class CustomError extends Error {\n constructor(message: string, public code: string) {\n super(message)\n this.name = 'CustomError'\n }\n }\n\n const customError = new CustomError('Custom error message', 'CUSTOM_CODE')\n const taskPromise = Promise.reject(customError)\n\n // When\n const renderInstance = render(<SingleTask title=\"Custom error task\" taskPromise={taskPromise} />)\n\n // Then - should preserve the exact error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('Custom error message')\n })\n\n test('handles concurrent promise operations', async () => {\n // Given - Multiple SingleTask components with different promises\n const fastPromise = new Promise((resolve) => setTimeout(() => resolve('fast'), 50))\n const slowPromise = new Promise((resolve) => setTimeout(() => resolve('slow'), 150))\n\n // When\n const fastRender = render(<SingleTask title=\"Fast task\" taskPromise={fastPromise} />)\n const slowRender = render(<SingleTask title=\"Slow task\" taskPromise={slowPromise} />)\n\n // Then - Both should complete successfully\n await fastRender.waitUntilExit()\n await slowRender.waitUntilExit()\n\n expect(fastRender.lastFrame()).toBeDefined()\n expect(slowRender.lastFrame()).toBeDefined()\n })\n\n test('passes noColor prop to LoadingBar component', async () => {\n // Given\n const title = 'No color task'\n const taskPromise = Promise.resolve()\n\n // When - Test that noColor prop doesn't break the component\n const renderInstance = render(<SingleTask title={title} taskPromise={taskPromise} noColor />)\n await renderInstance.waitUntilExit()\n\n // Then - Component should complete successfully with noColor prop\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"SingleTask.test.js","sourceRoot":"","sources":["../../../../../src/private/node/ui/components/SingleTask.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import {SingleTask} from './SingleTask.js'\nimport {render} from '../../testing/ui.js'\nimport {TokenizedString} from '../../../../public/node/output.js'\nimport React from 'react'\nimport {describe, expect, test} from 'vitest'\n\ndescribe('SingleTask', () => {\n test('unmounts when promise resolves successfully', async () => {\n // Given\n const title = new TokenizedString('Uploading files')\n let resolvePromise: (value: string) => void\n const task = () =>\n new Promise<string>((resolve) => {\n resolvePromise = resolve\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} task={task} />)\n\n // Wait for initial render\n await new Promise((resolve) => setTimeout(resolve, 10))\n\n // Resolve the promise\n resolvePromise!('success')\n\n // Wait for component to update and unmount\n await renderInstance.waitUntilExit()\n\n // Then - component should unmount cleanly\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('unmounts when promise rejects', async () => {\n // Given\n const title = new TokenizedString('Failed task')\n let rejectPromise: (error: Error) => void\n const task = () =>\n new Promise<string>((resolve, reject) => {\n rejectPromise = reject\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} task={task} />)\n\n // Wait for initial render\n await new Promise((resolve) => setTimeout(resolve, 10))\n\n // Reject the promise and expect waitUntilExit to reject\n rejectPromise!(new Error('Task failed'))\n\n // The component should exit with the error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('Task failed')\n })\n\n test('handles promise that resolves immediately', async () => {\n // Given\n const title = new TokenizedString('Instant task')\n const task = () => Promise.resolve('immediate success')\n\n // When\n const renderInstance = render(<SingleTask title={title} task={task} />)\n await renderInstance.waitUntilExit()\n\n // Then - component should complete successfully\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('handles promise that rejects immediately', async () => {\n // Given\n const title = new TokenizedString('Instant failure')\n const task = () => Promise.reject(new Error('Immediate error'))\n\n // When\n const renderInstance = render(<SingleTask title={title} task={task} />)\n\n // Then - should exit with error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('Immediate error')\n })\n\n test('handles different types of promise return values', async () => {\n // Test with string\n let stringResult: string | undefined\n const stringTask = () => Promise.resolve('task completed')\n const stringRender = render(\n <SingleTask\n title={new TokenizedString('String task')}\n task={stringTask}\n onComplete={(result) => (stringResult = result)}\n />,\n )\n await stringRender.waitUntilExit()\n expect(stringRender.lastFrame()).toBeDefined()\n expect(stringResult).toBe('task completed')\n\n // Test with object\n let objectResult: {id: number; name: string} | undefined\n const objectTask = () => Promise.resolve({id: 1, name: 'test'})\n const objectRender = render(\n <SingleTask\n title={new TokenizedString('Object task')}\n task={objectTask}\n onComplete={(result) => (objectResult = result)}\n />,\n )\n await objectRender.waitUntilExit()\n expect(objectRender.lastFrame()).toBeDefined()\n\n // Test with number\n let numberResult: number | undefined\n const numberTask = () => Promise.resolve(42)\n const numberRender = render(\n <SingleTask\n title={new TokenizedString('Number task')}\n task={numberTask}\n onComplete={(result) => (numberResult = result)}\n />,\n )\n await numberRender.waitUntilExit()\n expect(numberRender.lastFrame()).toBeDefined()\n expect(numberResult).toBe(42)\n\n // Test with boolean\n let booleanResult: boolean | undefined\n const booleanTask = () => Promise.resolve(true)\n const booleanRender = render(\n <SingleTask\n title={new TokenizedString('Boolean task')}\n task={booleanTask}\n onComplete={(result) => (booleanResult = result)}\n />,\n )\n await booleanRender.waitUntilExit()\n expect(booleanRender.lastFrame()).toBeDefined()\n expect(booleanResult).toBe(true)\n })\n\n test('handles promise with delayed resolution', async () => {\n // Given\n const title = new TokenizedString('Delayed task')\n const task = () =>\n new Promise<string>((resolve) => {\n setTimeout(() => resolve('completed'), 100)\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} task={task} />)\n\n // Wait for completion\n await renderInstance.waitUntilExit()\n\n // Then\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('handles promise with delayed rejection', async () => {\n // Given\n const title = new TokenizedString('Delayed failure')\n const task = () =>\n new Promise<string>((resolve, reject) => {\n setTimeout(() => reject(new Error('delayed error')), 100)\n })\n\n // When\n const renderInstance = render(<SingleTask title={title} task={task} />)\n\n // Wait for completion - should throw error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('delayed error')\n })\n\n test('preserves error types and messages', async () => {\n // Test with custom error\n class CustomError extends Error {\n constructor(message: string, public code: string) {\n super(message)\n this.name = 'CustomError'\n }\n }\n\n const customError = new CustomError('Custom error message', 'CUSTOM_CODE')\n const task = () => Promise.reject(customError)\n\n // When\n const renderInstance = render(<SingleTask title={new TokenizedString('Custom error task')} task={task} />)\n\n // Then - should preserve the exact error\n await expect(renderInstance.waitUntilExit()).rejects.toThrow('Custom error message')\n })\n\n test('handles concurrent promise operations', async () => {\n // Given - Multiple SingleTask components with different promises\n const fastPromise = () => new Promise((resolve) => setTimeout(() => resolve('fast'), 50))\n const slowPromise = () => new Promise((resolve) => setTimeout(() => resolve('slow'), 150))\n\n // When\n const fastRender = render(<SingleTask title={new TokenizedString('Fast task')} task={fastPromise} />)\n const slowRender = render(<SingleTask title={new TokenizedString('Slow task')} task={slowPromise} />)\n\n // Then - Both should complete successfully\n await fastRender.waitUntilExit()\n await slowRender.waitUntilExit()\n\n expect(fastRender.lastFrame()).toBeDefined()\n expect(slowRender.lastFrame()).toBeDefined()\n })\n\n test('passes noColor prop to LoadingBar component', async () => {\n // Given\n const title = new TokenizedString('No color task')\n const task = () => Promise.resolve()\n\n // When - Test that noColor prop doesn't break the component\n const renderInstance = render(<SingleTask title={title} task={task} noColor />)\n await renderInstance.waitUntilExit()\n\n // Then - Component should complete successfully with noColor prop\n expect(renderInstance.lastFrame()).toBeDefined()\n })\n\n test('updates status message during task execution', async () => {\n // Given\n const initialTitle = new TokenizedString('Starting task')\n let step1Resolve: () => void\n let step2Resolve: () => void\n let step3Resolve: () => void\n\n const step1Promise = new Promise<void>((resolve) => {\n step1Resolve = resolve\n })\n const step2Promise = new Promise<void>((resolve) => {\n step2Resolve = resolve\n })\n const step3Promise = new Promise<void>((resolve) => {\n step3Resolve = resolve\n })\n\n const task = async (updateStatus: (status: TokenizedString) => void) => {\n updateStatus(new TokenizedString('Running (1 complete)...'))\n await step1Promise\n\n updateStatus(new TokenizedString('Running (2 complete)...'))\n await step2Promise\n\n updateStatus(new TokenizedString('Running (3 complete)...'))\n await step3Promise\n\n return 'completed'\n }\n\n // When\n const renderInstance = render(<SingleTask title={initialTitle} task={task} />)\n\n // Wait for component to render with first status\n await new Promise((resolve) => setTimeout(resolve, 10))\n const frame1 = renderInstance.lastFrame()\n expect(frame1).toContain('1 complete')\n\n // Progress to step 2\n step1Resolve!()\n await new Promise((resolve) => setTimeout(resolve, 10))\n const frame2 = renderInstance.lastFrame()\n expect(frame2).toContain('2 complete')\n\n // Progress to step 3\n step2Resolve!()\n await new Promise((resolve) => setTimeout(resolve, 10))\n const frame3 = renderInstance.lastFrame()\n expect(frame3).toContain('3 complete')\n\n // Complete the task\n step3Resolve!()\n await renderInstance.waitUntilExit()\n })\n})\n"]}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AbortSignal } from '../../../../public/node/abort.js';
|
|
2
|
+
import { TokenizedString } from '../../../../public/node/output.js';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
export interface Task<TContext = unknown> {
|
|
4
|
-
title: string;
|
|
5
|
+
title: string | TokenizedString;
|
|
5
6
|
task: (ctx: TContext, task: Task<TContext>) => Promise<void | Task<TContext>[]>;
|
|
6
7
|
retry?: number;
|
|
7
8
|
retryCount?: number;
|
|
@@ -70,7 +70,8 @@ function Tasks({ tasks, silent = isUnitTest(), onComplete = noop, abortSignal, n
|
|
|
70
70
|
if (silent) {
|
|
71
71
|
return null;
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
const title = typeof currentTask.title === 'string' ? currentTask.title : currentTask.title.value;
|
|
74
|
+
return state === TasksState.Loading && !isAborted ? (React.createElement(LoadingBar, { title: title, noColor: noColor, noProgressBar: noProgressBar })) : null;
|
|
74
75
|
}
|
|
75
76
|
export { Tasks };
|
|
76
77
|
//# sourceMappingURL=Tasks.js.map
|