@devicecloud.dev/dcd 5.6.0-beta.3 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/commands/artifacts.d.ts +1 -1
- package/dist/commands/artifacts.js +1 -1
- package/dist/commands/cloud.d.ts +5 -5
- package/dist/commands/cloud.js +14 -6
- package/dist/commands/switch-org.js +5 -1
- package/dist/commands/upload.js +5 -1
- package/dist/commands/whoami.d.ts +7 -1
- package/dist/commands/whoami.js +26 -2
- package/dist/config/flags/device.flags.d.ts +3 -3
- package/dist/config/flags/device.flags.js +5 -3
- package/dist/config/flags/execution.flags.d.ts +1 -1
- package/dist/config/flags/execution.flags.js +1 -1
- package/dist/config/flags/output.flags.d.ts +1 -1
- package/dist/config/flags/output.flags.js +1 -1
- package/dist/constants.d.ts +5 -5
- package/dist/gateways/api-gateway.js +2 -1
- package/dist/gateways/realtime-gateway.d.ts +7 -0
- package/dist/gateways/realtime-gateway.js +12 -1
- package/dist/mcp/context.d.ts +14 -3
- package/dist/mcp/context.js +15 -3
- package/dist/mcp/tools/download-artifacts.js +9 -6
- package/dist/mcp/tools/list-devices.js +1 -1
- package/dist/mcp/tools/run-cloud-test.js +19 -3
- package/dist/services/device-validation.service.d.ts +17 -2
- package/dist/services/device-validation.service.js +93 -12
- package/dist/services/notices.service.js +3 -0
- package/dist/services/report-download.service.js +9 -3
- package/dist/services/results-polling.service.d.ts +21 -0
- package/dist/services/results-polling.service.js +55 -3
- package/dist/services/test-submission.service.d.ts +11 -0
- package/dist/services/test-submission.service.js +12 -5
- package/dist/types/domain/auth.types.d.ts +6 -0
- package/dist/types/domain/device.types.d.ts +18 -4
- package/dist/types/domain/device.types.js +17 -3
- package/dist/types/generated/schema.types.d.ts +127 -167
- package/dist/utils/auth.d.ts +33 -0
- package/dist/utils/auth.js +61 -2
- package/package.json +2 -2
|
@@ -1649,9 +1649,11 @@ export interface components {
|
|
|
1649
1649
|
/** @enum {string} */
|
|
1650
1650
|
iOSVersion?: "17" | "18" | "26" | "27";
|
|
1651
1651
|
/** @enum {string} */
|
|
1652
|
-
iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen";
|
|
1652
|
+
iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-air" | "iphone-18-pro" | "iphone-18-pro-max" | "ipad-pro-6th-gen" | "ipad-pro-m5-11" | "ipad-pro-m5-13";
|
|
1653
1653
|
platform?: string;
|
|
1654
1654
|
googlePlay?: boolean;
|
|
1655
|
+
/** @description Cancel the still-queued tests of the previous run from the same CI context. The context is derived from the run metadata (gh_repo/bb_repo, gh_branch/gh_pr_number or their bb_ twins, and gh_check_name); without it nothing is cancelled. Tests already running are left to finish. */
|
|
1656
|
+
cancelPrevious?: boolean;
|
|
1655
1657
|
config: string;
|
|
1656
1658
|
name?: string;
|
|
1657
1659
|
/** @enum {string} */
|
|
@@ -1689,9 +1691,11 @@ export interface components {
|
|
|
1689
1691
|
/** @enum {string} */
|
|
1690
1692
|
iOSVersion?: "17" | "18" | "26" | "27";
|
|
1691
1693
|
/** @enum {string} */
|
|
1692
|
-
iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen";
|
|
1694
|
+
iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-air" | "iphone-18-pro" | "iphone-18-pro-max" | "ipad-pro-6th-gen" | "ipad-pro-m5-11" | "ipad-pro-m5-13";
|
|
1693
1695
|
platform?: string;
|
|
1694
1696
|
googlePlay?: boolean;
|
|
1697
|
+
/** @description Cancel the still-queued tests of the previous run from the same CI context. The context is derived from the run metadata (gh_repo/bb_repo, gh_branch/gh_pr_number or their bb_ twins, and gh_check_name); without it nothing is cancelled. Tests already running are left to finish. */
|
|
1698
|
+
cancelPrevious?: boolean;
|
|
1695
1699
|
config: string;
|
|
1696
1700
|
name?: string;
|
|
1697
1701
|
/** @enum {string} */
|
|
@@ -1740,9 +1744,11 @@ export interface components {
|
|
|
1740
1744
|
/** @enum {string} */
|
|
1741
1745
|
iOSVersion?: "17" | "18" | "26" | "27";
|
|
1742
1746
|
/** @enum {string} */
|
|
1743
|
-
iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen";
|
|
1747
|
+
iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-air" | "iphone-18-pro" | "iphone-18-pro-max" | "ipad-pro-6th-gen" | "ipad-pro-m5-11" | "ipad-pro-m5-13";
|
|
1744
1748
|
platform?: string;
|
|
1745
1749
|
googlePlay?: boolean;
|
|
1750
|
+
/** @description Cancel the still-queued tests of the previous run from the same CI context. The context is derived from the run metadata (gh_repo/bb_repo, gh_branch/gh_pr_number or their bb_ twins, and gh_check_name); without it nothing is cancelled. Tests already running are left to finish. */
|
|
1751
|
+
cancelPrevious?: boolean;
|
|
1746
1752
|
config: string;
|
|
1747
1753
|
name?: string;
|
|
1748
1754
|
/** @enum {string} */
|
|
@@ -1878,6 +1884,7 @@ export interface components {
|
|
|
1878
1884
|
retry_of?: number;
|
|
1879
1885
|
fail_reason?: string;
|
|
1880
1886
|
duration_seconds?: number;
|
|
1887
|
+
cancellation_reason?: string;
|
|
1881
1888
|
simulator_name?: string;
|
|
1882
1889
|
config?: Record<string, never>;
|
|
1883
1890
|
};
|
|
@@ -2188,6 +2195,8 @@ export interface operations {
|
|
|
2188
2195
|
message?: string;
|
|
2189
2196
|
success?: boolean;
|
|
2190
2197
|
cancelledCount?: number;
|
|
2198
|
+
/** @description Credits returned for tests that had not started yet. 0 when nothing was refundable. See the billing docs for the rate. */
|
|
2199
|
+
refundedAmount?: number;
|
|
2191
2200
|
};
|
|
2192
2201
|
};
|
|
2193
2202
|
};
|
|
@@ -2949,174 +2958,125 @@ export interface operations {
|
|
|
2949
2958
|
* "statusCode": 200,
|
|
2950
2959
|
* "data": {
|
|
2951
2960
|
* "ios": {
|
|
2952
|
-
* "iphone-14":
|
|
2953
|
-
* "
|
|
2954
|
-
* "
|
|
2955
|
-
*
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
2958
|
-
*
|
|
2959
|
-
*
|
|
2960
|
-
*
|
|
2961
|
-
* "
|
|
2962
|
-
* "
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
2965
|
-
* "
|
|
2966
|
-
*
|
|
2967
|
-
*
|
|
2968
|
-
*
|
|
2969
|
-
* "
|
|
2970
|
-
*
|
|
2971
|
-
*
|
|
2972
|
-
*
|
|
2973
|
-
*
|
|
2974
|
-
*
|
|
2975
|
-
*
|
|
2976
|
-
* "
|
|
2977
|
-
*
|
|
2978
|
-
*
|
|
2979
|
-
*
|
|
2980
|
-
* "
|
|
2981
|
-
*
|
|
2982
|
-
* "iphone-
|
|
2983
|
-
* "
|
|
2984
|
-
* "
|
|
2985
|
-
*
|
|
2986
|
-
*
|
|
2987
|
-
*
|
|
2988
|
-
*
|
|
2989
|
-
*
|
|
2990
|
-
*
|
|
2991
|
-
*
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
2995
|
-
*
|
|
2996
|
-
*
|
|
2997
|
-
*
|
|
2998
|
-
*
|
|
2999
|
-
* "
|
|
3000
|
-
*
|
|
3001
|
-
*
|
|
3002
|
-
*
|
|
3003
|
-
*
|
|
3004
|
-
*
|
|
3005
|
-
* }
|
|
2961
|
+
* "iphone-14": [
|
|
2962
|
+
* "17",
|
|
2963
|
+
* "18"
|
|
2964
|
+
* ],
|
|
2965
|
+
* "iphone-15": [
|
|
2966
|
+
* "17"
|
|
2967
|
+
* ],
|
|
2968
|
+
* "iphone-16": [
|
|
2969
|
+
* "18",
|
|
2970
|
+
* "26",
|
|
2971
|
+
* "27"
|
|
2972
|
+
* ],
|
|
2973
|
+
* "iphone-16-plus": [
|
|
2974
|
+
* "26",
|
|
2975
|
+
* "27"
|
|
2976
|
+
* ],
|
|
2977
|
+
* "iphone-16-pro": [
|
|
2978
|
+
* "18",
|
|
2979
|
+
* "26",
|
|
2980
|
+
* "27"
|
|
2981
|
+
* ],
|
|
2982
|
+
* "iphone-16-pro-max": [
|
|
2983
|
+
* "18",
|
|
2984
|
+
* "26",
|
|
2985
|
+
* "27"
|
|
2986
|
+
* ],
|
|
2987
|
+
* "iphone-17": [
|
|
2988
|
+
* "26",
|
|
2989
|
+
* "27"
|
|
2990
|
+
* ],
|
|
2991
|
+
* "iphone-air": [
|
|
2992
|
+
* "26",
|
|
2993
|
+
* "27"
|
|
2994
|
+
* ],
|
|
2995
|
+
* "iphone-18-pro": [
|
|
2996
|
+
* "27"
|
|
2997
|
+
* ],
|
|
2998
|
+
* "iphone-18-pro-max": [
|
|
2999
|
+
* "27"
|
|
3000
|
+
* ],
|
|
3001
|
+
* "ipad-pro-6th-gen": [
|
|
3002
|
+
* "18",
|
|
3003
|
+
* "26",
|
|
3004
|
+
* "27"
|
|
3005
|
+
* ],
|
|
3006
|
+
* "ipad-pro-m5-11": [
|
|
3007
|
+
* "26",
|
|
3008
|
+
* "27"
|
|
3009
|
+
* ],
|
|
3010
|
+
* "ipad-pro-m5-13": [
|
|
3011
|
+
* "26",
|
|
3012
|
+
* "27"
|
|
3013
|
+
* ]
|
|
3006
3014
|
* },
|
|
3007
3015
|
* "android": {
|
|
3008
|
-
* "pixel-6":
|
|
3009
|
-
* "
|
|
3010
|
-
* "
|
|
3011
|
-
*
|
|
3012
|
-
*
|
|
3013
|
-
*
|
|
3014
|
-
*
|
|
3015
|
-
*
|
|
3016
|
-
*
|
|
3017
|
-
*
|
|
3018
|
-
*
|
|
3019
|
-
*
|
|
3020
|
-
*
|
|
3021
|
-
* "
|
|
3022
|
-
*
|
|
3023
|
-
* "pixel-
|
|
3024
|
-
* "
|
|
3025
|
-
* "
|
|
3026
|
-
*
|
|
3027
|
-
*
|
|
3028
|
-
*
|
|
3029
|
-
*
|
|
3030
|
-
*
|
|
3031
|
-
*
|
|
3032
|
-
* "
|
|
3033
|
-
* "
|
|
3034
|
-
*
|
|
3035
|
-
*
|
|
3036
|
-
*
|
|
3037
|
-
*
|
|
3038
|
-
*
|
|
3039
|
-
*
|
|
3040
|
-
* "
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3044
|
-
* "
|
|
3045
|
-
*
|
|
3046
|
-
*
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
3049
|
-
*
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
*
|
|
3054
|
-
*
|
|
3055
|
-
*
|
|
3056
|
-
*
|
|
3057
|
-
*
|
|
3058
|
-
*
|
|
3059
|
-
*
|
|
3060
|
-
*
|
|
3061
|
-
*
|
|
3062
|
-
*
|
|
3063
|
-
*
|
|
3064
|
-
* "
|
|
3065
|
-
* "
|
|
3066
|
-
*
|
|
3067
|
-
* "37"
|
|
3068
|
-
* ],
|
|
3069
|
-
* "deprecated": false
|
|
3070
|
-
* },
|
|
3071
|
-
* "pixel-10-pro": {
|
|
3072
|
-
* "name": "Pixel 10 Pro",
|
|
3073
|
-
* "apiLevels": [
|
|
3074
|
-
* "36",
|
|
3075
|
-
* "37"
|
|
3076
|
-
* ],
|
|
3077
|
-
* "deprecated": false
|
|
3078
|
-
* },
|
|
3079
|
-
* "pixel-10-pro-xl": {
|
|
3080
|
-
* "name": "Pixel 10 Pro XL",
|
|
3081
|
-
* "apiLevels": [
|
|
3082
|
-
* "36",
|
|
3083
|
-
* "37"
|
|
3084
|
-
* ],
|
|
3085
|
-
* "deprecated": false
|
|
3086
|
-
* },
|
|
3087
|
-
* "pixel-10-pro-fold": {
|
|
3088
|
-
* "name": "Pixel 10 Pro Fold",
|
|
3089
|
-
* "apiLevels": [
|
|
3090
|
-
* "36",
|
|
3091
|
-
* "37"
|
|
3092
|
-
* ],
|
|
3093
|
-
* "deprecated": false
|
|
3094
|
-
* },
|
|
3095
|
-
* "pixel-11": {
|
|
3096
|
-
* "name": "Pixel 11",
|
|
3097
|
-
* "apiLevels": [
|
|
3098
|
-
* "37"
|
|
3099
|
-
* ],
|
|
3100
|
-
* "deprecated": false
|
|
3101
|
-
* },
|
|
3102
|
-
* "generic-tablet": {
|
|
3103
|
-
* "name": "Generic Tablet",
|
|
3104
|
-
* "apiLevels": [
|
|
3105
|
-
* "33",
|
|
3106
|
-
* "36",
|
|
3107
|
-
* "37"
|
|
3108
|
-
* ],
|
|
3109
|
-
* "deprecated": false
|
|
3110
|
-
* }
|
|
3016
|
+
* "pixel-6": [
|
|
3017
|
+
* "29",
|
|
3018
|
+
* "30",
|
|
3019
|
+
* "31",
|
|
3020
|
+
* "32",
|
|
3021
|
+
* "33",
|
|
3022
|
+
* "34",
|
|
3023
|
+
* "35",
|
|
3024
|
+
* "36",
|
|
3025
|
+
* "37"
|
|
3026
|
+
* ],
|
|
3027
|
+
* "pixel-6-pro": [
|
|
3028
|
+
* "33",
|
|
3029
|
+
* "35"
|
|
3030
|
+
* ],
|
|
3031
|
+
* "pixel-7": [
|
|
3032
|
+
* "33",
|
|
3033
|
+
* "34",
|
|
3034
|
+
* "35",
|
|
3035
|
+
* "36",
|
|
3036
|
+
* "37"
|
|
3037
|
+
* ],
|
|
3038
|
+
* "pixel-7-pro": [
|
|
3039
|
+
* "33",
|
|
3040
|
+
* "34",
|
|
3041
|
+
* "35",
|
|
3042
|
+
* "36",
|
|
3043
|
+
* "37"
|
|
3044
|
+
* ],
|
|
3045
|
+
* "pixel-8": [
|
|
3046
|
+
* "34",
|
|
3047
|
+
* "35",
|
|
3048
|
+
* "36",
|
|
3049
|
+
* "37"
|
|
3050
|
+
* ],
|
|
3051
|
+
* "pixel-10": [
|
|
3052
|
+
* "36",
|
|
3053
|
+
* "37"
|
|
3054
|
+
* ],
|
|
3055
|
+
* "pixel-10-pro": [
|
|
3056
|
+
* "36",
|
|
3057
|
+
* "37"
|
|
3058
|
+
* ],
|
|
3059
|
+
* "pixel-10-pro-xl": [
|
|
3060
|
+
* "36",
|
|
3061
|
+
* "37"
|
|
3062
|
+
* ],
|
|
3063
|
+
* "pixel-10-pro-fold": [
|
|
3064
|
+
* "36",
|
|
3065
|
+
* "37"
|
|
3066
|
+
* ],
|
|
3067
|
+
* "pixel-11": [
|
|
3068
|
+
* "37"
|
|
3069
|
+
* ],
|
|
3070
|
+
* "generic-tablet": [
|
|
3071
|
+
* "33",
|
|
3072
|
+
* "36",
|
|
3073
|
+
* "37"
|
|
3074
|
+
* ]
|
|
3111
3075
|
* },
|
|
3112
3076
|
* "androidPlay": {
|
|
3113
|
-
* "pixel-7":
|
|
3114
|
-
* "
|
|
3115
|
-
*
|
|
3116
|
-
* "34"
|
|
3117
|
-
* ],
|
|
3118
|
-
* "deprecated": false
|
|
3119
|
-
* }
|
|
3077
|
+
* "pixel-7": [
|
|
3078
|
+
* "34"
|
|
3079
|
+
* ]
|
|
3120
3080
|
* },
|
|
3121
3081
|
* "maestro": {
|
|
3122
3082
|
* "supportedVersions": [
|
package/dist/utils/auth.d.ts
CHANGED
|
@@ -10,7 +10,40 @@ export interface ResolveAuthOptions {
|
|
|
10
10
|
*/
|
|
11
11
|
sessionOnly?: boolean;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Which API key, if any, outranks the stored session — the precedence
|
|
15
|
+
* resolveAuth applies. For the session-only commands (`whoami`, `switch-org`)
|
|
16
|
+
* to say that what they show is not what other commands will authenticate as.
|
|
17
|
+
*/
|
|
18
|
+
export declare function apiKeyOverride(apiKeyFlag?: string): '--api-key' | 'DEVICE_CLOUD_API_KEY' | undefined;
|
|
13
19
|
export declare function resolveAuth(opts: ResolveAuthOptions): Promise<AuthContext>;
|
|
20
|
+
/**
|
|
21
|
+
* True when a `dcd login` (bearer) context's access token has expired or will
|
|
22
|
+
* within the refresh window. API-key contexts never expire.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isAuthExpiring(auth: AuthContext, nowSeconds?: number): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Keep a long-running command's credential usable. An API-key context, or a
|
|
27
|
+
* session that isn't near expiry, comes back unchanged. Otherwise the stored
|
|
28
|
+
* session is re-read and, if it is expiring too, refreshed under the same lock
|
|
29
|
+
* resolveAuth uses — so a session another `dcd` process has already rotated
|
|
30
|
+
* is picked up rather than raced for.
|
|
31
|
+
*
|
|
32
|
+
* Only the token changes: the org the command started with is kept, so an
|
|
33
|
+
* org switched in another terminal mid-run can't redirect an in-flight poll
|
|
34
|
+
* at an upload that org doesn't own.
|
|
35
|
+
*
|
|
36
|
+
* @throws SessionRefreshError when Supabase refuses the refresh (`definitive`
|
|
37
|
+
* when only `dcd login` can fix it), or CliError when there is no stored
|
|
38
|
+
* session any more (e.g. after `dcd logout`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function refreshAuth(auth: AuthContext): Promise<AuthContext>;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a refreshAuth failure is final for this process — the refresh token
|
|
43
|
+
* was refused, or the session is gone — as opposed to a network blip that the
|
|
44
|
+
* next attempt may get past.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isDefinitiveAuthFailure(error: unknown): boolean;
|
|
14
47
|
/**
|
|
15
48
|
* Remove only the (definitively dead) session from the stored config, keeping
|
|
16
49
|
* env/api_url/org so a re-login lands back in the same environment. With the
|
package/dist/utils/auth.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Supabase session (refreshed if near expiry). Throws CliError if none found.
|
|
5
5
|
*
|
|
6
6
|
* The refresh path writes the rotated tokens back to disk atomically. Callers
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* resolve once per invocation, not per request — except where a process can
|
|
8
|
+
* outlive the session's access token (the MCP server, a long `dcd cloud`
|
|
9
|
+
* poll), which re-checks with isAuthExpiring / refreshAuth below.
|
|
9
10
|
*/
|
|
10
11
|
import { closeSync, openSync, rmSync, statSync } from 'node:fs';
|
|
11
12
|
import { ENVIRONMENTS } from '../config/environments.js';
|
|
@@ -19,6 +20,18 @@ const REFRESH_SKEW_SECONDS = 60;
|
|
|
19
20
|
const LOCK_STALE_MS = 10_000;
|
|
20
21
|
const LOCK_WAIT_MS = 10_000;
|
|
21
22
|
const LOCK_POLL_MS = 250;
|
|
23
|
+
/**
|
|
24
|
+
* Which API key, if any, outranks the stored session — the precedence
|
|
25
|
+
* resolveAuth applies. For the session-only commands (`whoami`, `switch-org`)
|
|
26
|
+
* to say that what they show is not what other commands will authenticate as.
|
|
27
|
+
*/
|
|
28
|
+
export function apiKeyOverride(apiKeyFlag) {
|
|
29
|
+
if (apiKeyFlag?.trim())
|
|
30
|
+
return '--api-key';
|
|
31
|
+
if (process.env.DEVICE_CLOUD_API_KEY?.trim())
|
|
32
|
+
return 'DEVICE_CLOUD_API_KEY';
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
22
35
|
export async function resolveAuth(opts) {
|
|
23
36
|
if (!opts.sessionOnly) {
|
|
24
37
|
const flag = opts.apiKeyFlag?.trim();
|
|
@@ -59,6 +72,7 @@ export async function resolveAuth(opts) {
|
|
|
59
72
|
mode: 'bearer',
|
|
60
73
|
accessToken: session.access_token,
|
|
61
74
|
env: config.env,
|
|
75
|
+
expiresAt: session.expires_at,
|
|
62
76
|
orgId: config.current_org_id,
|
|
63
77
|
userEmail: session.user_email,
|
|
64
78
|
headers: {
|
|
@@ -69,6 +83,51 @@ export async function resolveAuth(opts) {
|
|
|
69
83
|
telemetry.configure({ auth, apiUrl: config.api_url });
|
|
70
84
|
return auth;
|
|
71
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* True when a `dcd login` (bearer) context's access token has expired or will
|
|
88
|
+
* within the refresh window. API-key contexts never expire.
|
|
89
|
+
*/
|
|
90
|
+
export function isAuthExpiring(auth, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
91
|
+
return (auth.mode === 'bearer' &&
|
|
92
|
+
auth.expiresAt !== undefined &&
|
|
93
|
+
auth.expiresAt <= nowSeconds + REFRESH_SKEW_SECONDS);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Keep a long-running command's credential usable. An API-key context, or a
|
|
97
|
+
* session that isn't near expiry, comes back unchanged. Otherwise the stored
|
|
98
|
+
* session is re-read and, if it is expiring too, refreshed under the same lock
|
|
99
|
+
* resolveAuth uses — so a session another `dcd` process has already rotated
|
|
100
|
+
* is picked up rather than raced for.
|
|
101
|
+
*
|
|
102
|
+
* Only the token changes: the org the command started with is kept, so an
|
|
103
|
+
* org switched in another terminal mid-run can't redirect an in-flight poll
|
|
104
|
+
* at an upload that org doesn't own.
|
|
105
|
+
*
|
|
106
|
+
* @throws SessionRefreshError when Supabase refuses the refresh (`definitive`
|
|
107
|
+
* when only `dcd login` can fix it), or CliError when there is no stored
|
|
108
|
+
* session any more (e.g. after `dcd logout`).
|
|
109
|
+
*/
|
|
110
|
+
export async function refreshAuth(auth) {
|
|
111
|
+
if (!isAuthExpiring(auth))
|
|
112
|
+
return auth;
|
|
113
|
+
const fresh = await resolveAuth({ apiKeyFlag: undefined, sessionOnly: true });
|
|
114
|
+
if (!auth.orgId || fresh.orgId === auth.orgId)
|
|
115
|
+
return fresh;
|
|
116
|
+
return {
|
|
117
|
+
...fresh,
|
|
118
|
+
orgId: auth.orgId,
|
|
119
|
+
headers: { ...fresh.headers, 'x-dcd-org': auth.orgId },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Whether a refreshAuth failure is final for this process — the refresh token
|
|
124
|
+
* was refused, or the session is gone — as opposed to a network blip that the
|
|
125
|
+
* next attempt may get past.
|
|
126
|
+
*/
|
|
127
|
+
export function isDefinitiveAuthFailure(error) {
|
|
128
|
+
return ((error instanceof SessionRefreshError && error.definitive) ||
|
|
129
|
+
error instanceof CliError);
|
|
130
|
+
}
|
|
72
131
|
/**
|
|
73
132
|
* Refresh the stored session under a config-adjacent lockfile so two
|
|
74
133
|
* concurrent `dcd` invocations (CI matrices) can't both consume the same
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"chai": "^6.2.2",
|
|
32
32
|
"eslint": "^10.5.0",
|
|
33
33
|
"eslint-config-prettier": "^10.1.8",
|
|
34
|
-
"eslint-plugin-unicorn": "^
|
|
34
|
+
"eslint-plugin-unicorn": "^76.0.0",
|
|
35
35
|
"husky": "^9.1.7",
|
|
36
36
|
"mocha": "^12.0.0",
|
|
37
37
|
"prettier": "^3.8.4",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"test:unit": "node scripts/test-runner.mjs --unit",
|
|
70
70
|
"typecheck": "tsc --noEmit -p tsconfig.test.json"
|
|
71
71
|
},
|
|
72
|
-
"version": "5.6.0
|
|
72
|
+
"version": "5.6.0",
|
|
73
73
|
"bugs": {
|
|
74
74
|
"url": "https://discord.gg/gm3mJwcNw8"
|
|
75
75
|
},
|