@devicecloud.dev/dcd 5.0.0-beta.0 → 5.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/commands/artifacts.d.ts +28 -28
- package/dist/commands/artifacts.js +20 -23
- package/dist/commands/cloud.d.ts +57 -57
- package/dist/commands/cloud.js +224 -192
- package/dist/commands/list.d.ts +22 -22
- package/dist/commands/list.js +43 -40
- package/dist/commands/live.js +134 -127
- package/dist/commands/login.d.ts +11 -11
- package/dist/commands/login.js +46 -44
- package/dist/commands/logout.js +16 -18
- package/dist/commands/status.d.ts +11 -11
- package/dist/commands/status.js +53 -44
- package/dist/commands/switch-org.d.ts +7 -7
- package/dist/commands/switch-org.js +19 -21
- package/dist/commands/upgrade.js +41 -33
- package/dist/commands/upload.d.ts +10 -10
- package/dist/commands/upload.js +42 -43
- package/dist/commands/whoami.js +17 -20
- package/dist/config/environments.js +6 -12
- package/dist/config/flags/api.flags.js +1 -4
- package/dist/config/flags/binary.flags.js +1 -4
- package/dist/config/flags/device.flags.js +6 -9
- package/dist/config/flags/environment.flags.js +1 -4
- package/dist/config/flags/execution.flags.js +1 -4
- package/dist/config/flags/github.flags.js +1 -4
- package/dist/config/flags/output.flags.js +1 -4
- package/dist/constants.js +15 -18
- package/dist/gateways/api-gateway.d.ts +31 -6
- package/dist/gateways/api-gateway.js +70 -16
- package/dist/gateways/cli-auth-gateway.d.ts +1 -1
- package/dist/gateways/cli-auth-gateway.js +3 -6
- package/dist/gateways/realtime-gateway.d.ts +32 -0
- package/dist/gateways/realtime-gateway.js +103 -0
- package/dist/gateways/supabase-gateway.d.ts +1 -1
- package/dist/gateways/supabase-gateway.js +10 -14
- package/dist/index.js +41 -38
- package/dist/mcp/context.d.ts +33 -0
- package/dist/mcp/context.js +33 -0
- package/dist/mcp/helpers.d.ts +16 -0
- package/dist/mcp/helpers.js +34 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +24 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +27 -0
- package/dist/mcp/tools/download-artifacts.d.ts +11 -0
- package/dist/mcp/tools/download-artifacts.js +84 -0
- package/dist/mcp/tools/get-status.d.ts +7 -0
- package/dist/mcp/tools/get-status.js +39 -0
- package/dist/mcp/tools/list-devices.d.ts +7 -0
- package/dist/mcp/tools/list-devices.js +27 -0
- package/dist/mcp/tools/list-runs.d.ts +3 -0
- package/dist/mcp/tools/list-runs.js +60 -0
- package/dist/mcp/tools/run-cloud-test.d.ts +14 -0
- package/dist/mcp/tools/run-cloud-test.js +233 -0
- package/dist/methods.d.ts +32 -1
- package/dist/methods.js +133 -79
- package/dist/services/device-validation.service.d.ts +1 -1
- package/dist/services/device-validation.service.js +1 -5
- package/dist/services/execution-plan.service.js +14 -17
- package/dist/services/execution-plan.utils.js +15 -23
- package/dist/services/flow-paths.d.ts +17 -0
- package/dist/services/flow-paths.js +52 -0
- package/dist/services/metadata-extractor.service.js +22 -25
- package/dist/services/moropo.service.js +18 -20
- package/dist/services/report-download.service.d.ts +1 -1
- package/dist/services/report-download.service.js +5 -9
- package/dist/services/results-polling.service.d.ts +18 -3
- package/dist/services/results-polling.service.js +211 -108
- package/dist/services/telemetry.service.d.ts +10 -1
- package/dist/services/telemetry.service.js +40 -18
- package/dist/services/test-submission.service.d.ts +21 -4
- package/dist/services/test-submission.service.js +51 -34
- package/dist/services/version.service.d.ts +30 -7
- package/dist/services/version.service.js +88 -32
- package/dist/types/domain/auth.types.d.ts +8 -0
- package/dist/types/domain/auth.types.js +1 -2
- package/dist/types/domain/device.types.js +8 -11
- package/dist/types/domain/live.types.js +1 -2
- package/dist/types/generated/schema.types.js +1 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -18
- package/dist/types.js +1 -2
- package/dist/utils/auth.d.ts +1 -1
- package/dist/utils/auth.js +27 -28
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +39 -0
- package/dist/utils/cli.d.ts +16 -2
- package/dist/utils/cli.js +57 -29
- package/dist/utils/compatibility.d.ts +1 -1
- package/dist/utils/compatibility.js +5 -7
- package/dist/utils/config-store.js +33 -43
- package/dist/utils/connectivity.js +1 -4
- package/dist/utils/expo.js +15 -21
- package/dist/utils/orgs.js +8 -12
- package/dist/utils/paths.js +2 -5
- package/dist/utils/progress.d.ts +3 -0
- package/dist/utils/progress.js +47 -8
- package/dist/utils/styling.d.ts +35 -37
- package/dist/utils/styling.js +52 -86
- package/dist/utils/ui.d.ts +41 -0
- package/dist/utils/ui.js +95 -0
- package/package.json +27 -24
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort detection of non-interactive / CI environments. Used to suppress
|
|
3
|
+
* interactive niceties (e.g. the `dcd login` nudge) that only make sense for a
|
|
4
|
+
* human at a terminal. Dependency-free on purpose — these are the env vars the
|
|
5
|
+
* major providers set, plus a TTY check for piped/redirected output.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Returns true when running under CI or otherwise non-interactively (no TTY on
|
|
9
|
+
* stdout, e.g. output piped to a file). A truthy value for any known CI env var
|
|
10
|
+
* counts — providers set `CI=true`, but a bare presence check is the safe net.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isCI(): boolean;
|
package/dist/utils/ci.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort detection of non-interactive / CI environments. Used to suppress
|
|
3
|
+
* interactive niceties (e.g. the `dcd login` nudge) that only make sense for a
|
|
4
|
+
* human at a terminal. Dependency-free on purpose — these are the env vars the
|
|
5
|
+
* major providers set, plus a TTY check for piped/redirected output.
|
|
6
|
+
*/
|
|
7
|
+
// Generic + per-provider markers. `CI` is set by virtually every provider;
|
|
8
|
+
// the rest cover platforms that historically didn't set it.
|
|
9
|
+
const CI_ENV_VARS = [
|
|
10
|
+
'CI',
|
|
11
|
+
'CONTINUOUS_INTEGRATION',
|
|
12
|
+
'BUILD_NUMBER',
|
|
13
|
+
'GITHUB_ACTIONS',
|
|
14
|
+
'GITLAB_CI',
|
|
15
|
+
'CIRCLECI',
|
|
16
|
+
'TRAVIS',
|
|
17
|
+
'BUILDKITE',
|
|
18
|
+
'DRONE',
|
|
19
|
+
'TEAMCITY_VERSION',
|
|
20
|
+
'TF_BUILD', // Azure Pipelines
|
|
21
|
+
'JENKINS_URL',
|
|
22
|
+
'BITBUCKET_BUILD_NUMBER',
|
|
23
|
+
'APPVEYOR',
|
|
24
|
+
'CODEBUILD_BUILD_ID',
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Returns true when running under CI or otherwise non-interactively (no TTY on
|
|
28
|
+
* stdout, e.g. output piped to a file). A truthy value for any known CI env var
|
|
29
|
+
* counts — providers set `CI=true`, but a bare presence check is the safe net.
|
|
30
|
+
*/
|
|
31
|
+
export function isCI() {
|
|
32
|
+
for (const name of CI_ENV_VARS) {
|
|
33
|
+
const value = process.env[name];
|
|
34
|
+
if (value !== undefined && value !== '' && value !== 'false' && value !== '0') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return !process.stdout.isTTY;
|
|
39
|
+
}
|
package/dist/utils/cli.d.ts
CHANGED
|
@@ -24,10 +24,24 @@ export declare function validateEnum<T extends string>(value: string | undefined
|
|
|
24
24
|
/**
|
|
25
25
|
* Coerce a flag value (possibly a single string, array, or undefined) into a
|
|
26
26
|
* flat string array. Comma-separated values inside each entry are split out.
|
|
27
|
-
*
|
|
28
|
-
* surfaces a string (single use) or string[] (repeated).
|
|
27
|
+
* Pair with {@link collectRepeatedFlag} for repeatable flags.
|
|
29
28
|
*/
|
|
30
29
|
export declare function coerceArray(value: string | string[] | undefined, split?: boolean): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Collect every occurrence of a repeatable flag from raw argv, in order.
|
|
32
|
+
*
|
|
33
|
+
* citty 0.2.2 delegates to Node's `parseArgs`, which — without `multiple: true`
|
|
34
|
+
* (unsupported by citty's ArgsDef) — keeps only the LAST value of a repeated
|
|
35
|
+
* `type: 'string'` flag. So `-e A=1 -e B=2` collapses to just `B=2`. We recover
|
|
36
|
+
* all occurrences by scanning rawArgs ourselves (same approach as
|
|
37
|
+
* `recoverFlagValue` in commands/live.ts).
|
|
38
|
+
*
|
|
39
|
+
* `names` lists every spelling of one logical flag, e.g. ['--env', '-e'].
|
|
40
|
+
* Handles both `--flag value` (consuming the next token, so values starting
|
|
41
|
+
* with `-` survive) and `--flag=value`. Feed the result through
|
|
42
|
+
* {@link coerceArray} for comma-splitting where appropriate.
|
|
43
|
+
*/
|
|
44
|
+
export declare function collectRepeatedFlag(rawArgs: string[], names: string[]): string[];
|
|
31
45
|
/**
|
|
32
46
|
* Parse an integer flag. Returns undefined if the value is undefined/empty.
|
|
33
47
|
* Throws CliError if the value is not a valid integer.
|
package/dist/utils/cli.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.logger = exports.CliError = void 0;
|
|
4
|
-
exports.getCliVersion = getCliVersion;
|
|
5
|
-
exports.getInstallMethod = getInstallMethod;
|
|
6
|
-
exports.getUpgradeCommand = getUpgradeCommand;
|
|
7
|
-
exports.validateEnum = validateEnum;
|
|
8
|
-
exports.coerceArray = coerceArray;
|
|
9
|
-
exports.parseIntFlag = parseIntFlag;
|
|
10
1
|
/**
|
|
11
2
|
* Shared helpers for command files.
|
|
12
3
|
* - Version lookup from package.json
|
|
@@ -15,13 +6,22 @@ exports.parseIntFlag = parseIntFlag;
|
|
|
15
6
|
* - A minimal Logger mirroring the oclif Command log/warn/error shape so call
|
|
16
7
|
* sites ported from oclif keep working.
|
|
17
8
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { telemetry } from '../services/telemetry.service.js';
|
|
11
|
+
import { symbols } from './styling.js';
|
|
12
|
+
// Resolve version at runtime. The bun-compiled binary can't read package.json
|
|
13
|
+
// off disk (it isn't bundled next to the embedded module), so the build stamps
|
|
14
|
+
// the version in via `bun --define __DCD_CLI_VERSION__` (see
|
|
15
|
+
// scripts/build-binaries.mjs). Prefer that constant; on the npm/tsx path the
|
|
16
|
+
// identifier was never defined, so `typeof` is 'undefined' (no ReferenceError)
|
|
17
|
+
// and we fall back to reading package.json.
|
|
18
|
+
export function getCliVersion() {
|
|
19
|
+
if (typeof __DCD_CLI_VERSION__ === 'string' &&
|
|
20
|
+
__DCD_CLI_VERSION__.length > 0) {
|
|
21
|
+
return __DCD_CLI_VERSION__;
|
|
22
|
+
}
|
|
22
23
|
try {
|
|
23
|
-
|
|
24
|
-
const pkg = require('../../package.json');
|
|
24
|
+
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
25
25
|
return pkg.version;
|
|
26
26
|
}
|
|
27
27
|
catch {
|
|
@@ -30,17 +30,17 @@ function getCliVersion() {
|
|
|
30
30
|
}
|
|
31
31
|
// The Bun runtime sets process.versions.bun; bun-compiled standalone binaries
|
|
32
32
|
// inherit this. Node-run installs (npm/pnpm/npx/tsx) don't expose it.
|
|
33
|
-
function getInstallMethod() {
|
|
33
|
+
export function getInstallMethod() {
|
|
34
34
|
return typeof process.versions.bun === 'string'
|
|
35
35
|
? 'binary'
|
|
36
36
|
: 'npm';
|
|
37
37
|
}
|
|
38
|
-
function getUpgradeCommand() {
|
|
38
|
+
export function getUpgradeCommand() {
|
|
39
39
|
return getInstallMethod() === 'binary'
|
|
40
40
|
? 'dcd upgrade'
|
|
41
41
|
: 'npm install -g @devicecloud.dev/dcd@latest';
|
|
42
42
|
}
|
|
43
|
-
class CliError extends Error {
|
|
43
|
+
export class CliError extends Error {
|
|
44
44
|
exitCode;
|
|
45
45
|
constructor(message, exitCode = 1) {
|
|
46
46
|
super(message);
|
|
@@ -48,15 +48,14 @@ class CliError extends Error {
|
|
|
48
48
|
this.name = 'CliError';
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
exports.logger = {
|
|
51
|
+
export const logger = {
|
|
53
52
|
log(message) {
|
|
54
53
|
// eslint-disable-next-line no-console
|
|
55
54
|
console.log(message);
|
|
56
55
|
},
|
|
57
56
|
warn(message) {
|
|
58
57
|
// eslint-disable-next-line no-console
|
|
59
|
-
console.warn(
|
|
58
|
+
console.warn(symbols.warning + ' ' + message);
|
|
60
59
|
},
|
|
61
60
|
error(message, opts = {}) {
|
|
62
61
|
const text = message instanceof Error ? message.message : message;
|
|
@@ -70,14 +69,14 @@ exports.logger = {
|
|
|
70
69
|
// The literal "Error:" prefix is important for tests and for grep-friendly
|
|
71
70
|
// CI logs — it survives color stripping and matches /error/i assertions.
|
|
72
71
|
// eslint-disable-next-line no-console
|
|
73
|
-
console.error(
|
|
72
|
+
console.error(symbols.error + ' Error: ' + text);
|
|
74
73
|
}
|
|
75
74
|
// process.exit bypasses beforeExit, so async fetch in telemetry.flush()
|
|
76
75
|
// would be killed mid-flight. flushSync uses curl to ship synchronously
|
|
77
76
|
// before we exit; it's a no-op if telemetry never reached configure().
|
|
78
77
|
const exitCode = opts.exit ?? 1;
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
telemetry.recordCommandFailure({ error: message, exitCode });
|
|
79
|
+
telemetry.flushSync();
|
|
81
80
|
process.exit(exitCode);
|
|
82
81
|
},
|
|
83
82
|
exit(code = 0) {
|
|
@@ -88,7 +87,7 @@ exports.logger = {
|
|
|
88
87
|
* Validate that a string flag value is one of the allowed options.
|
|
89
88
|
* Returns the value untouched on success; throws CliError otherwise.
|
|
90
89
|
*/
|
|
91
|
-
function validateEnum(value, allowed, flagName) {
|
|
90
|
+
export function validateEnum(value, allowed, flagName) {
|
|
92
91
|
if (value === undefined || value === null || value === '')
|
|
93
92
|
return undefined;
|
|
94
93
|
if (!allowed.includes(value)) {
|
|
@@ -99,10 +98,9 @@ function validateEnum(value, allowed, flagName) {
|
|
|
99
98
|
/**
|
|
100
99
|
* Coerce a flag value (possibly a single string, array, or undefined) into a
|
|
101
100
|
* flat string array. Comma-separated values inside each entry are split out.
|
|
102
|
-
*
|
|
103
|
-
* surfaces a string (single use) or string[] (repeated).
|
|
101
|
+
* Pair with {@link collectRepeatedFlag} for repeatable flags.
|
|
104
102
|
*/
|
|
105
|
-
function coerceArray(value, split = true) {
|
|
103
|
+
export function coerceArray(value, split = true) {
|
|
106
104
|
if (value === undefined)
|
|
107
105
|
return [];
|
|
108
106
|
const arr = Array.isArray(value) ? value : [value];
|
|
@@ -110,11 +108,41 @@ function coerceArray(value, split = true) {
|
|
|
110
108
|
return arr;
|
|
111
109
|
return arr.flatMap((v) => v.split(','));
|
|
112
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Collect every occurrence of a repeatable flag from raw argv, in order.
|
|
113
|
+
*
|
|
114
|
+
* citty 0.2.2 delegates to Node's `parseArgs`, which — without `multiple: true`
|
|
115
|
+
* (unsupported by citty's ArgsDef) — keeps only the LAST value of a repeated
|
|
116
|
+
* `type: 'string'` flag. So `-e A=1 -e B=2` collapses to just `B=2`. We recover
|
|
117
|
+
* all occurrences by scanning rawArgs ourselves (same approach as
|
|
118
|
+
* `recoverFlagValue` in commands/live.ts).
|
|
119
|
+
*
|
|
120
|
+
* `names` lists every spelling of one logical flag, e.g. ['--env', '-e'].
|
|
121
|
+
* Handles both `--flag value` (consuming the next token, so values starting
|
|
122
|
+
* with `-` survive) and `--flag=value`. Feed the result through
|
|
123
|
+
* {@link coerceArray} for comma-splitting where appropriate.
|
|
124
|
+
*/
|
|
125
|
+
export function collectRepeatedFlag(rawArgs, names) {
|
|
126
|
+
const out = [];
|
|
127
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
128
|
+
const arg = rawArgs[i];
|
|
129
|
+
const eqName = names.find((n) => arg.startsWith(`${n}=`));
|
|
130
|
+
if (eqName) {
|
|
131
|
+
out.push(arg.slice(eqName.length + 1));
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (names.includes(arg) && i + 1 < rawArgs.length) {
|
|
135
|
+
out.push(rawArgs[i + 1]);
|
|
136
|
+
i++; // consume the value so a leading-dash value isn't re-read as a flag
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
113
141
|
/**
|
|
114
142
|
* Parse an integer flag. Returns undefined if the value is undefined/empty.
|
|
115
143
|
* Throws CliError if the value is not a valid integer.
|
|
116
144
|
*/
|
|
117
|
-
function parseIntFlag(value, flagName) {
|
|
145
|
+
export function parseIntFlag(value, flagName) {
|
|
118
146
|
if (value === undefined || value === null || value === '')
|
|
119
147
|
return undefined;
|
|
120
148
|
// All integer flags (limit/offset/retry) are non-negative; also rejects
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchCompatibilityData = fetchCompatibilityData;
|
|
4
|
-
exports.clearCompatibilityCache = clearCompatibilityCache;
|
|
5
1
|
let cachedCompatibilityData = null;
|
|
6
|
-
async function fetchCompatibilityData(apiUrl, auth) {
|
|
2
|
+
export async function fetchCompatibilityData(apiUrl, auth) {
|
|
7
3
|
if (cachedCompatibilityData) {
|
|
8
4
|
return cachedCompatibilityData;
|
|
9
5
|
}
|
|
@@ -27,9 +23,11 @@ async function fetchCompatibilityData(apiUrl, auth) {
|
|
|
27
23
|
}
|
|
28
24
|
catch (error) {
|
|
29
25
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
30
|
-
throw new Error(`Failed to fetch compatibility data from API: ${errorMessage}
|
|
26
|
+
throw new Error(`Failed to fetch compatibility data from API: ${errorMessage}`, {
|
|
27
|
+
cause: error,
|
|
28
|
+
});
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
|
-
function clearCompatibilityCache() {
|
|
31
|
+
export function clearCompatibilityCache() {
|
|
34
32
|
cachedCompatibilityData = null;
|
|
35
33
|
}
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CONFIG_SCHEMA_VERSION = void 0;
|
|
4
|
-
exports.getConfigDir = getConfigDir;
|
|
5
|
-
exports.getConfigPath = getConfigPath;
|
|
6
|
-
exports.readConfig = readConfig;
|
|
7
|
-
exports.resolveApiUrl = resolveApiUrl;
|
|
8
|
-
exports.writeConfig = writeConfig;
|
|
9
|
-
exports.clearConfig = clearConfig;
|
|
10
|
-
exports.configFileMode = configFileMode;
|
|
11
1
|
/**
|
|
12
2
|
* Persistent CLI config: Supabase session tokens + chosen org.
|
|
13
3
|
*
|
|
@@ -16,31 +6,31 @@ exports.configFileMode = configFileMode;
|
|
|
16
6
|
* Writes are atomic (tmp file + rename) so a crash mid-refresh can't
|
|
17
7
|
* leave a corrupt config behind that logs the user out.
|
|
18
8
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function getConfigDir() {
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { ENVIRONMENTS } from '../config/environments.js';
|
|
14
|
+
export const CONFIG_SCHEMA_VERSION = 1;
|
|
15
|
+
export function getConfigDir() {
|
|
26
16
|
if (process.env.DCD_CONFIG_DIR)
|
|
27
17
|
return process.env.DCD_CONFIG_DIR;
|
|
28
18
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
29
19
|
if (xdg && xdg.trim().length > 0)
|
|
30
20
|
return path.join(xdg, 'dcd');
|
|
31
|
-
return path.join(
|
|
21
|
+
return path.join(homedir(), '.dcd');
|
|
32
22
|
}
|
|
33
|
-
function getConfigPath() {
|
|
23
|
+
export function getConfigPath() {
|
|
34
24
|
return path.join(getConfigDir(), 'config.json');
|
|
35
25
|
}
|
|
36
|
-
function readConfig() {
|
|
26
|
+
export function readConfig() {
|
|
37
27
|
const p = getConfigPath();
|
|
38
|
-
if (!
|
|
28
|
+
if (!existsSync(p))
|
|
39
29
|
return null;
|
|
40
30
|
try {
|
|
41
|
-
const raw =
|
|
31
|
+
const raw = readFileSync(p, 'utf8');
|
|
42
32
|
const parsed = JSON.parse(raw);
|
|
43
|
-
if (parsed.version !==
|
|
33
|
+
if (parsed.version !== CONFIG_SCHEMA_VERSION) {
|
|
44
34
|
// eslint-disable-next-line no-console
|
|
45
35
|
console.warn(`Warning: config at ${p} was written by an incompatible CLI version (config version ${parsed.version}); ignoring it. Run \`dcd login\` to recreate it.`);
|
|
46
36
|
return null;
|
|
@@ -65,20 +55,20 @@ function readConfig() {
|
|
|
65
55
|
* token is rejected with a misleading "Invalid or expired JWT". `switch-org`
|
|
66
56
|
* has always done this; this helper extends it to every command.
|
|
67
57
|
*/
|
|
68
|
-
function resolveApiUrl(flag) {
|
|
58
|
+
export function resolveApiUrl(flag) {
|
|
69
59
|
const explicit = flag?.trim();
|
|
70
60
|
if (explicit)
|
|
71
61
|
return explicit;
|
|
72
|
-
return readConfig()?.api_url ??
|
|
62
|
+
return readConfig()?.api_url ?? ENVIRONMENTS.prod.apiUrl;
|
|
73
63
|
}
|
|
74
|
-
function writeConfig(config) {
|
|
64
|
+
export function writeConfig(config) {
|
|
75
65
|
const dir = getConfigDir();
|
|
76
|
-
if (!
|
|
77
|
-
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
78
68
|
}
|
|
79
69
|
else {
|
|
80
70
|
try {
|
|
81
|
-
|
|
71
|
+
chmodSync(dir, 0o700);
|
|
82
72
|
}
|
|
83
73
|
catch { /* best effort */ }
|
|
84
74
|
}
|
|
@@ -88,38 +78,38 @@ function writeConfig(config) {
|
|
|
88
78
|
// writeFileSync and renameSync right now.
|
|
89
79
|
try {
|
|
90
80
|
const base = path.basename(finalPath);
|
|
91
|
-
for (const entry of
|
|
81
|
+
for (const entry of readdirSync(dir)) {
|
|
92
82
|
if (!entry.startsWith(`${base}.`) || !entry.endsWith('.tmp'))
|
|
93
83
|
continue;
|
|
94
84
|
const tmp = path.join(dir, entry);
|
|
95
85
|
try {
|
|
96
|
-
if (Date.now() -
|
|
97
|
-
|
|
86
|
+
if (Date.now() - statSync(tmp).mtimeMs > 60_000)
|
|
87
|
+
unlinkSync(tmp);
|
|
98
88
|
}
|
|
99
89
|
catch { /* best effort */ }
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
92
|
catch { /* best effort */ }
|
|
103
|
-
const tmpPath = `${finalPath}.${
|
|
104
|
-
|
|
93
|
+
const tmpPath = `${finalPath}.${randomBytes(6).toString('hex')}.tmp`;
|
|
94
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
105
95
|
try {
|
|
106
|
-
|
|
96
|
+
chmodSync(tmpPath, 0o600);
|
|
107
97
|
}
|
|
108
98
|
catch { /* best effort on platforms w/o chmod */ }
|
|
109
|
-
|
|
99
|
+
renameSync(tmpPath, finalPath);
|
|
110
100
|
try {
|
|
111
|
-
|
|
101
|
+
chmodSync(finalPath, 0o600);
|
|
112
102
|
}
|
|
113
103
|
catch { /* best effort */ }
|
|
114
104
|
}
|
|
115
|
-
function clearConfig() {
|
|
105
|
+
export function clearConfig() {
|
|
116
106
|
const p = getConfigPath();
|
|
117
|
-
if (
|
|
118
|
-
|
|
107
|
+
if (existsSync(p))
|
|
108
|
+
unlinkSync(p);
|
|
119
109
|
}
|
|
120
|
-
function configFileMode() {
|
|
110
|
+
export function configFileMode() {
|
|
121
111
|
const p = getConfigPath();
|
|
122
|
-
if (!
|
|
112
|
+
if (!existsSync(p))
|
|
123
113
|
return null;
|
|
124
|
-
return
|
|
114
|
+
return statSync(p).mode & 0o777;
|
|
125
115
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Utility for checking internet connectivity using third-party endpoints
|
|
4
3
|
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.checkInternetConnectivity = checkInternetConnectivity;
|
|
7
4
|
/**
|
|
8
5
|
* Check if the system has internet connectivity by testing against
|
|
9
6
|
* multiple reliable third-party endpoints with detailed diagnostics.
|
|
10
7
|
*
|
|
11
8
|
* @returns Promise<ConnectivityCheckResult> - Detailed connectivity check results
|
|
12
9
|
*/
|
|
13
|
-
async function checkInternetConnectivity() {
|
|
10
|
+
export async function checkInternetConnectivity() {
|
|
14
11
|
// Use multiple reliable endpoints to test connectivity
|
|
15
12
|
const testEndpoints = [
|
|
16
13
|
{ url: 'https://www.google.com/generate_204', description: 'Google' },
|
package/dist/utils/expo.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const fsp = require("node:fs/promises");
|
|
10
|
-
const os = require("node:os");
|
|
11
|
-
const path = require("node:path");
|
|
12
|
-
const node_stream_1 = require("node:stream");
|
|
13
|
-
const promises_1 = require("node:stream/promises");
|
|
14
|
-
const tar = require("tar");
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as fsp from 'node:fs/promises';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { Readable } from 'node:stream';
|
|
7
|
+
import { pipeline } from 'node:stream/promises';
|
|
8
|
+
import * as tar from 'tar';
|
|
15
9
|
const DOWNLOAD_RETRY_ATTEMPTS = 3;
|
|
16
10
|
const DOWNLOAD_RETRY_DELAY_MS = 2000;
|
|
17
11
|
/**
|
|
@@ -19,7 +13,7 @@ const DOWNLOAD_RETRY_DELAY_MS = 2000;
|
|
|
19
13
|
* @param input - String to test
|
|
20
14
|
* @returns True if the string begins with http:// or https://
|
|
21
15
|
*/
|
|
22
|
-
function isUrl(input) {
|
|
16
|
+
export function isUrl(input) {
|
|
23
17
|
return input.startsWith('http://') || input.startsWith('https://');
|
|
24
18
|
}
|
|
25
19
|
/**
|
|
@@ -31,8 +25,8 @@ function isUrl(input) {
|
|
|
31
25
|
* @param debug - Whether to emit debug log lines
|
|
32
26
|
* @returns Absolute path to the downloaded temp file
|
|
33
27
|
*/
|
|
34
|
-
async function downloadExpoUrl(url, debug) {
|
|
35
|
-
const destPath = path.join(os.tmpdir(), `dcd-expo-${
|
|
28
|
+
export async function downloadExpoUrl(url, debug) {
|
|
29
|
+
const destPath = path.join(os.tmpdir(), `dcd-expo-${randomUUID()}.tar.gz`);
|
|
36
30
|
for (let attempt = 1; attempt <= DOWNLOAD_RETRY_ATTEMPTS; attempt++) {
|
|
37
31
|
if (debug) {
|
|
38
32
|
console.log(`[DEBUG] Downloading Expo URL (attempt ${attempt}/${DOWNLOAD_RETRY_ATTEMPTS}): ${url}`);
|
|
@@ -60,7 +54,7 @@ async function downloadExpoUrl(url, debug) {
|
|
|
60
54
|
}
|
|
61
55
|
// Stream to disk using pipeline to handle backpressure and avoid loading
|
|
62
56
|
// the entire archive in memory
|
|
63
|
-
await
|
|
57
|
+
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(destPath));
|
|
64
58
|
if (debug) {
|
|
65
59
|
const stat = await fsp.stat(destPath);
|
|
66
60
|
console.log(`[DEBUG] Downloaded ${(stat.size / 1024 / 1024).toFixed(2)} MB to ${destPath}`);
|
|
@@ -91,8 +85,8 @@ async function downloadExpoUrl(url, debug) {
|
|
|
91
85
|
* @param debug - Whether to emit debug log lines
|
|
92
86
|
* @returns Absolute path to the newly created extract directory
|
|
93
87
|
*/
|
|
94
|
-
async function extractTarGz(tarPath, debug) {
|
|
95
|
-
const extractDir = path.join(os.tmpdir(), `dcd-expo-${
|
|
88
|
+
export async function extractTarGz(tarPath, debug) {
|
|
89
|
+
const extractDir = path.join(os.tmpdir(), `dcd-expo-${randomUUID()}`);
|
|
96
90
|
await fsp.mkdir(extractDir, { recursive: true });
|
|
97
91
|
if (debug) {
|
|
98
92
|
console.log(`[DEBUG] Extracting ${tarPath} to ${extractDir}`);
|
|
@@ -109,7 +103,7 @@ async function extractTarGz(tarPath, debug) {
|
|
|
109
103
|
* @param dir - Directory to search within
|
|
110
104
|
* @returns Absolute path to the .app directory
|
|
111
105
|
*/
|
|
112
|
-
async function findAppBundle(dir) {
|
|
106
|
+
export async function findAppBundle(dir) {
|
|
113
107
|
const candidates = [];
|
|
114
108
|
async function walk(current, depth) {
|
|
115
109
|
const entries = await fsp.readdir(current, { withFileTypes: true });
|
package/dist/utils/orgs.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchOrgs = fetchOrgs;
|
|
4
|
-
exports.pickOrg = pickOrg;
|
|
5
1
|
/**
|
|
6
2
|
* Shared helpers for fetching and picking from /me/orgs. Used by both
|
|
7
3
|
* `dcd login` (after the PKCE claim returns a session) and `dcd switch-org`.
|
|
8
4
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
async function fetchOrgs(apiUrl, headers) {
|
|
5
|
+
import * as p from '@clack/prompts';
|
|
6
|
+
import { CliError } from './cli.js';
|
|
7
|
+
export async function fetchOrgs(apiUrl, headers) {
|
|
12
8
|
const res = await fetch(`${apiUrl.replace(/\/$/, '')}/me/orgs`, { headers });
|
|
13
9
|
if (!res.ok) {
|
|
14
10
|
const body = await res.text().catch(() => '');
|
|
15
|
-
throw new
|
|
11
|
+
throw new CliError(`Failed to list organizations: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
|
|
16
12
|
}
|
|
17
13
|
const body = (await res.json());
|
|
18
14
|
return body.orgs ?? [];
|
|
@@ -21,9 +17,9 @@ async function fetchOrgs(apiUrl, headers) {
|
|
|
21
17
|
* Interactive org picker. Auto-selects when there's only one org (returns it
|
|
22
18
|
* without prompting). Throws on zero orgs or user cancellation.
|
|
23
19
|
*/
|
|
24
|
-
async function pickOrg(orgs, message = 'Pick an organization') {
|
|
20
|
+
export async function pickOrg(orgs, message = 'Pick an organization') {
|
|
25
21
|
if (orgs.length === 0) {
|
|
26
|
-
throw new
|
|
22
|
+
throw new CliError('No organizations found for this user.');
|
|
27
23
|
}
|
|
28
24
|
if (orgs.length === 1)
|
|
29
25
|
return orgs[0];
|
|
@@ -32,9 +28,9 @@ async function pickOrg(orgs, message = 'Pick an organization') {
|
|
|
32
28
|
options: orgs.map((o) => ({ value: o.id, label: o.name })),
|
|
33
29
|
});
|
|
34
30
|
if (p.isCancel(picked))
|
|
35
|
-
throw new
|
|
31
|
+
throw new CliError('Cancelled.');
|
|
36
32
|
const chosen = orgs.find((o) => o.id === picked);
|
|
37
33
|
if (!chosen)
|
|
38
|
-
throw new
|
|
34
|
+
throw new CliError('No organization selected.');
|
|
39
35
|
return chosen;
|
|
40
36
|
}
|
package/dist/utils/paths.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toPortableRelativePath = toPortableRelativePath;
|
|
4
|
-
const path = require("node:path");
|
|
1
|
+
import * as path from 'node:path';
|
|
5
2
|
/**
|
|
6
3
|
* Convert an absolute path into the portable './'-prefixed, forward-slash
|
|
7
4
|
* relative form used as flow keys across submission, metadata maps, and
|
|
@@ -12,7 +9,7 @@ const path = require("node:path");
|
|
|
12
9
|
* Replaces the old `replaceAll(commonRoot, '.')` pattern, which corrupted
|
|
13
10
|
* paths when the root substring recurred mid-path or collapsed to ''.
|
|
14
11
|
*/
|
|
15
|
-
function toPortableRelativePath(absolutePath, commonRoot) {
|
|
12
|
+
export function toPortableRelativePath(absolutePath, commonRoot) {
|
|
16
13
|
let relative = absolutePath;
|
|
17
14
|
if (commonRoot && absolutePath.startsWith(commonRoot)) {
|
|
18
15
|
relative = absolutePath.slice(commonRoot.length);
|
package/dist/utils/progress.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
declare class Action {
|
|
2
2
|
private current;
|
|
3
3
|
private _status;
|
|
4
|
+
private _lastPrinted;
|
|
5
|
+
private interactive;
|
|
4
6
|
start(title: string, initialStatus?: string, _opts?: unknown): void;
|
|
5
7
|
stop(message?: string): void;
|
|
6
8
|
set status(value: string);
|
|
7
9
|
get status(): string;
|
|
10
|
+
private print;
|
|
8
11
|
}
|
|
9
12
|
export declare const ux: {
|
|
10
13
|
action: Action;
|