@devicecloud.dev/dcd 4.0.0 → 4.0.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/dist/commands/cloud.d.ts +1 -7
- package/dist/commands/cloud.js +58 -18
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +7 -3
- package/dist/gateways/api-gateway.d.ts +3 -2
- package/dist/gateways/api-gateway.js +21 -0
- package/dist/methods.js +11 -2
- package/dist/plan.d.ts +1 -0
- package/dist/plan.js +22 -4
- package/dist/types/schema.types.d.ts +310 -313
- package/dist/types/schema.types.js +1 -4
- package/oclif.manifest.json +15 -4
- package/package.json +3 -3
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export default class Cloud extends Command {
|
|
|
18
18
|
'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
19
|
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
20
|
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
22
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
23
|
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
24
|
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
@@ -53,11 +54,4 @@ export default class Cloud extends Command {
|
|
|
53
54
|
};
|
|
54
55
|
private versionCheck;
|
|
55
56
|
run(): Promise<any>;
|
|
56
|
-
/**
|
|
57
|
-
* Generate the JSON output file path based on upload ID or custom filename
|
|
58
|
-
* @param uploadId - Upload ID to use if custom filename is not provided
|
|
59
|
-
* @param jsonFileName - Optional custom filename (can include relative path)
|
|
60
|
-
* @returns Path to the JSON output file
|
|
61
|
-
*/
|
|
62
|
-
private getJsonOutputPath;
|
|
63
57
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -64,7 +64,7 @@ class Cloud extends core_1.Command {
|
|
|
64
64
|
let jsonFile = false;
|
|
65
65
|
try {
|
|
66
66
|
const { args, flags, raw } = await this.parse(Cloud);
|
|
67
|
-
let { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, 'dry-run': dryRun, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'ignore-sha-check': ignoreShaCheck, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, json, 'json-file-name': jsonFileName, 'maestro-version': maestroVersion, metadata, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, name, orientation, quiet, report, retry, 'runner-type': runnerType, 'x86-arch': x86Arch, ...rest } = flags;
|
|
67
|
+
let { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, 'junit-path': junitPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, 'dry-run': dryRun, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'ignore-sha-check': ignoreShaCheck, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, json, 'json-file-name': jsonFileName, 'maestro-version': maestroVersion, metadata, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, name, orientation, quiet, report, retry, 'runner-type': runnerType, 'x86-arch': x86Arch, ...rest } = flags;
|
|
68
68
|
// Resolve "latest" maestro version to actual version
|
|
69
69
|
const resolvedMaestroVersion = (0, constants_1.resolveMaestroVersion)(maestroVersion);
|
|
70
70
|
// Store debug flag for use in catch block
|
|
@@ -228,6 +228,9 @@ class Cloud extends core_1.Command {
|
|
|
228
228
|
if (runnerType === 'm1') {
|
|
229
229
|
this.log('Note: runnerType m1 is experimental and currently supports Android (Pixel 7, API Level 34) only.');
|
|
230
230
|
}
|
|
231
|
+
if (runnerType === 'gpu1') {
|
|
232
|
+
this.log('Note: runnerType gpu1 is Android-only and requires contacting support to enable. Without support enablement, your runner type will revert to default.');
|
|
233
|
+
}
|
|
231
234
|
const additionalAppBinaryIds = nonFlatAdditionalAppBinaryIds?.flat();
|
|
232
235
|
const additionalAppFiles = nonFlatAdditionalAppFiles?.flat();
|
|
233
236
|
const { firstFile, secondFile } = args;
|
|
@@ -320,7 +323,7 @@ class Cloud extends core_1.Command {
|
|
|
320
323
|
}
|
|
321
324
|
throw error;
|
|
322
325
|
}
|
|
323
|
-
const { allExcludeTags, allIncludeTags, flowMetadata, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
|
|
326
|
+
const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
|
|
324
327
|
if (debug) {
|
|
325
328
|
this.log(`DEBUG: All include tags: ${allIncludeTags?.join(', ') || 'none'}`);
|
|
326
329
|
this.log(`DEBUG: All exclude tags: ${allExcludeTags?.join(', ') || 'none'}`);
|
|
@@ -370,6 +373,23 @@ class Cloud extends core_1.Command {
|
|
|
370
373
|
flagLogs.push(`${k}: ${v}`);
|
|
371
374
|
}
|
|
372
375
|
}
|
|
376
|
+
// Format overrides information
|
|
377
|
+
const overridesEntries = Object.entries(flowOverrides);
|
|
378
|
+
const hasOverrides = overridesEntries.some(([, overrides]) => Object.keys(overrides).length > 0);
|
|
379
|
+
let overridesLog = '';
|
|
380
|
+
if (hasOverrides) {
|
|
381
|
+
overridesLog = '\n With overrides';
|
|
382
|
+
for (const [flowPath, overrides] of overridesEntries) {
|
|
383
|
+
if (Object.keys(overrides).length > 0) {
|
|
384
|
+
const relativePath = flowPath.replace(process.cwd(), '.');
|
|
385
|
+
overridesLog += `\n → ${relativePath}:`;
|
|
386
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
387
|
+
overridesLog += `\n ${key}: ${value}`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
overridesLog += '\n';
|
|
392
|
+
}
|
|
373
393
|
this.log(`
|
|
374
394
|
|
|
375
395
|
Submitting new job
|
|
@@ -381,7 +401,7 @@ class Cloud extends core_1.Command {
|
|
|
381
401
|
|
|
382
402
|
With options
|
|
383
403
|
→ ${flagLogs.join(`
|
|
384
|
-
→ `)}
|
|
404
|
+
→ `)}${overridesLog}
|
|
385
405
|
|
|
386
406
|
`);
|
|
387
407
|
if (dryRun) {
|
|
@@ -478,6 +498,10 @@ class Cloud extends core_1.Command {
|
|
|
478
498
|
key.replaceAll(commonRoot, '.').split(path.sep).join('/'),
|
|
479
499
|
value,
|
|
480
500
|
]))));
|
|
501
|
+
testFormData.set('testFileOverrides', JSON.stringify(Object.fromEntries(Object.entries(flowOverrides).map(([key, value]) => [
|
|
502
|
+
key.replaceAll(commonRoot, '.').split(path.sep).join('/'),
|
|
503
|
+
value,
|
|
504
|
+
]))));
|
|
481
505
|
testFormData.set('sequentialFlows', JSON.stringify(sequentialFlows.map((t) => t.replaceAll(commonRoot, '.').split(path.sep).join('/'))));
|
|
482
506
|
testFormData.set('env', JSON.stringify(envObject));
|
|
483
507
|
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
|
|
@@ -568,7 +592,7 @@ class Cloud extends core_1.Command {
|
|
|
568
592
|
uploadId: results[0].test_upload_id,
|
|
569
593
|
};
|
|
570
594
|
if (flags['json-file']) {
|
|
571
|
-
const jsonFilePath =
|
|
595
|
+
const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`;
|
|
572
596
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
573
597
|
}
|
|
574
598
|
if (json) {
|
|
@@ -660,6 +684,34 @@ class Cloud extends core_1.Command {
|
|
|
660
684
|
this.warn('Failed to download artifacts');
|
|
661
685
|
}
|
|
662
686
|
}
|
|
687
|
+
// Handle report download separately if --report junit is specified
|
|
688
|
+
if (report === 'junit') {
|
|
689
|
+
try {
|
|
690
|
+
if (debug) {
|
|
691
|
+
this.log('DEBUG: Downloading JUNIT report');
|
|
692
|
+
}
|
|
693
|
+
const reportPath = path.resolve(process.cwd(), junitPath || 'report.xml');
|
|
694
|
+
await api_gateway_1.ApiGateway.downloadReport(apiUrl, apiKey, results[0].test_upload_id, reportPath);
|
|
695
|
+
this.log('\n');
|
|
696
|
+
this.log(`JUNIT test report has been downloaded to ${reportPath}`);
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
if (debug) {
|
|
700
|
+
this.log(`DEBUG: Error downloading JUNIT report: ${error}`);
|
|
701
|
+
}
|
|
702
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
703
|
+
this.warn(`Failed to download JUNIT report: ${errorMessage}`);
|
|
704
|
+
if (errorMessage.includes('404')) {
|
|
705
|
+
this.warn('No JUNIT reports found for this upload. Make sure your tests were run with --report junit flag.');
|
|
706
|
+
}
|
|
707
|
+
else if (errorMessage.includes('EACCES') || errorMessage.includes('EPERM')) {
|
|
708
|
+
this.warn('Permission denied. Check write permissions for the current directory.');
|
|
709
|
+
}
|
|
710
|
+
else if (errorMessage.includes('ENOENT')) {
|
|
711
|
+
this.warn('Directory does not exist. Make sure you have write access to the current directory.');
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
663
715
|
const resultsWithoutEarlierTries = updatedResults.filter((result) => {
|
|
664
716
|
const originalTryId = result.retry_of || result.id;
|
|
665
717
|
const tries = updatedResults.filter((r) => r.retry_of === originalTryId || r.id === originalTryId);
|
|
@@ -683,7 +735,7 @@ class Cloud extends core_1.Command {
|
|
|
683
735
|
uploadId: results[0].test_upload_id,
|
|
684
736
|
};
|
|
685
737
|
if (flags['json-file']) {
|
|
686
|
-
const jsonFilePath =
|
|
738
|
+
const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`;
|
|
687
739
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
688
740
|
}
|
|
689
741
|
if (json) {
|
|
@@ -709,7 +761,7 @@ class Cloud extends core_1.Command {
|
|
|
709
761
|
uploadId: results[0].test_upload_id,
|
|
710
762
|
};
|
|
711
763
|
if (flags['json-file']) {
|
|
712
|
-
const jsonFilePath =
|
|
764
|
+
const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`;
|
|
713
765
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
714
766
|
}
|
|
715
767
|
if (json) {
|
|
@@ -759,17 +811,5 @@ class Cloud extends core_1.Command {
|
|
|
759
811
|
}
|
|
760
812
|
}
|
|
761
813
|
}
|
|
762
|
-
/**
|
|
763
|
-
* Generate the JSON output file path based on upload ID or custom filename
|
|
764
|
-
* @param uploadId - Upload ID to use if custom filename is not provided
|
|
765
|
-
* @param jsonFileName - Optional custom filename (can include relative path)
|
|
766
|
-
* @returns Path to the JSON output file
|
|
767
|
-
*/
|
|
768
|
-
getJsonOutputPath(uploadId, jsonFileName) {
|
|
769
|
-
if (jsonFileName) {
|
|
770
|
-
return jsonFileName;
|
|
771
|
-
}
|
|
772
|
-
return `${uploadId}_dcd.json`;
|
|
773
|
-
}
|
|
774
814
|
}
|
|
775
815
|
exports.default = Cloud;
|
package/dist/constants.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare const flags: {
|
|
|
12
12
|
'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
13
|
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
14
|
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
16
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
17
|
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
18
|
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
package/dist/constants.js
CHANGED
|
@@ -73,6 +73,10 @@ exports.flags = {
|
|
|
73
73
|
dependsOn: ['download-artifacts'],
|
|
74
74
|
description: 'Custom file path for downloaded artifacts (default: ./artifacts.zip)',
|
|
75
75
|
}),
|
|
76
|
+
'junit-path': core_1.Flags.string({
|
|
77
|
+
dependsOn: ['report'],
|
|
78
|
+
description: 'Custom file path for downloaded JUnit report (default: ./report.xml)',
|
|
79
|
+
}),
|
|
76
80
|
async: core_1.Flags.boolean({
|
|
77
81
|
description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)',
|
|
78
82
|
}),
|
|
@@ -87,7 +91,7 @@ exports.flags = {
|
|
|
87
91
|
description: 'Locale that will be set to a device, ISO-639-1 code and uppercase ISO-3166-1 code e.g. "de_DE" for Germany',
|
|
88
92
|
}),
|
|
89
93
|
'download-artifacts': core_1.Flags.string({
|
|
90
|
-
description: 'Download a zip containing the logs, screenshots and videos for each result in this run. You will debited a $0.01 egress fee for each result.
|
|
94
|
+
description: 'Download a zip containing the logs, screenshots and videos for each result in this run. You will be debited a $0.01 egress fee for each result. Options: ALL (everything), FAILED (failures only).',
|
|
91
95
|
options: ['ALL', 'FAILED'],
|
|
92
96
|
}),
|
|
93
97
|
'dry-run': core_1.Flags.boolean({
|
|
@@ -198,8 +202,8 @@ exports.flags = {
|
|
|
198
202
|
}),
|
|
199
203
|
'runner-type': core_1.Flags.string({
|
|
200
204
|
default: 'default',
|
|
201
|
-
description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information',
|
|
202
|
-
options: ['default', 'm4', 'm1'],
|
|
205
|
+
description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information. gpu1 is Android-only and requires contacting support to enable, otherwise reverts to default.',
|
|
206
|
+
options: ['default', 'm4', 'm1', 'gpu1'],
|
|
203
207
|
}),
|
|
204
208
|
'show-crosshairs': core_1.Flags.boolean({
|
|
205
209
|
default: false,
|
|
@@ -4,17 +4,18 @@ export declare const ApiGateway: {
|
|
|
4
4
|
appBinaryId: string;
|
|
5
5
|
exists: boolean;
|
|
6
6
|
}>;
|
|
7
|
+
downloadReport(baseUrl: string, apiKey: string, uploadId: string, reportPath?: string): Promise<void>;
|
|
7
8
|
downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
|
|
8
9
|
finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
|
|
9
10
|
getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
|
|
10
|
-
id: string;
|
|
11
11
|
message: string;
|
|
12
12
|
path: string;
|
|
13
13
|
token: string;
|
|
14
|
+
id: string;
|
|
14
15
|
}>;
|
|
15
16
|
getResultsForUpload(baseUrl: string, apiKey: string, uploadId: string): Promise<{
|
|
16
|
-
results?: import("../types/schema.types").components["schemas"]["TResultResponse"][];
|
|
17
17
|
statusCode?: number;
|
|
18
|
+
results?: import("../types/schema.types").components["schemas"]["TResultResponse"][];
|
|
18
19
|
}>;
|
|
19
20
|
getUploadStatus(baseUrl: string, apiKey: string, options: {
|
|
20
21
|
name?: string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ApiGateway = void 0;
|
|
4
|
+
const fs = require("node:fs/promises");
|
|
5
|
+
const path = require("node:path");
|
|
4
6
|
exports.ApiGateway = {
|
|
5
7
|
async checkForExistingUpload(baseUrl, apiKey, sha) {
|
|
6
8
|
const res = await fetch(`${baseUrl}/uploads/checkForExistingUpload`, {
|
|
@@ -13,6 +15,25 @@ exports.ApiGateway = {
|
|
|
13
15
|
});
|
|
14
16
|
return res.json();
|
|
15
17
|
},
|
|
18
|
+
async downloadReport(baseUrl, apiKey, uploadId, reportPath) {
|
|
19
|
+
const finalReportPath = reportPath || path.resolve(process.cwd(), `report-${uploadId}.xml`);
|
|
20
|
+
const url = `${baseUrl}/results/${uploadId}/report`;
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
headers: {
|
|
23
|
+
'x-app-api-key': apiKey,
|
|
24
|
+
},
|
|
25
|
+
method: 'GET',
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const errorText = await res.text();
|
|
29
|
+
if (res.status === 404) {
|
|
30
|
+
throw new Error(`Upload ID '${uploadId}' not found or no results available for this upload`);
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Failed to download report: ${res.status} ${errorText}`);
|
|
33
|
+
}
|
|
34
|
+
const buffer = await res.arrayBuffer();
|
|
35
|
+
await fs.writeFile(finalReportPath, Buffer.from(buffer));
|
|
36
|
+
},
|
|
16
37
|
async downloadArtifactsZip(baseUrl, apiKey, uploadId, results, artifactsPath = './artifacts.zip') {
|
|
17
38
|
const res = await fetch(`${baseUrl}/results/${uploadId}/download`, {
|
|
18
39
|
body: JSON.stringify({ results }),
|
package/dist/methods.js
CHANGED
|
@@ -251,9 +251,18 @@ const writeJSONFile = (filePath, data, logger) => {
|
|
|
251
251
|
logger.log(`JSON output written to: ${path.resolve(filePath)}`);
|
|
252
252
|
}
|
|
253
253
|
catch (error) {
|
|
254
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
255
|
+
const isPermissionError = errorMessage.includes('EACCES') || errorMessage.includes('EPERM');
|
|
256
|
+
const isNoSuchFileError = errorMessage.includes('ENOENT');
|
|
254
257
|
logger.warn(`Failed to write JSON output to file: ${filePath}`);
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
if (isPermissionError) {
|
|
259
|
+
logger.warn('Permission denied - check file/directory write permissions');
|
|
260
|
+
logger.warn('Try running with appropriate permissions or choose a different output location');
|
|
261
|
+
}
|
|
262
|
+
else if (isNoSuchFileError) {
|
|
263
|
+
logger.warn('Directory does not exist - create the directory first or choose an existing path');
|
|
264
|
+
}
|
|
265
|
+
logger.warn(`Error details: ${errorMessage}`);
|
|
257
266
|
}
|
|
258
267
|
};
|
|
259
268
|
exports.writeJSONFile = writeJSONFile;
|
package/dist/plan.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ interface IExecutionPlan {
|
|
|
16
16
|
allExcludeTags?: null | string[];
|
|
17
17
|
allIncludeTags?: null | string[];
|
|
18
18
|
flowMetadata: Record<string, Record<string, unknown>>;
|
|
19
|
+
flowOverrides: Record<string, Record<string, unknown>>;
|
|
19
20
|
flowsToRun: string[];
|
|
20
21
|
referencedFiles: string[];
|
|
21
22
|
sequence?: IFlowSequence | null;
|
package/dist/plan.js
CHANGED
|
@@ -42,16 +42,28 @@ function filterFlowFiles(unfilteredFlowFiles, excludeFlows) {
|
|
|
42
42
|
return unfilteredFlowFiles;
|
|
43
43
|
}
|
|
44
44
|
function getWorkspaceConfig(input, unfilteredFlowFiles) {
|
|
45
|
-
const possibleConfigPaths = new Set([
|
|
46
|
-
path.join(input, 'config.yaml'),
|
|
47
|
-
path.join(input, 'config.yml'),
|
|
48
|
-
].map((p) => path.normalize(p)));
|
|
45
|
+
const possibleConfigPaths = new Set([path.join(input, 'config.yaml'), path.join(input, 'config.yml')].map((p) => path.normalize(p)));
|
|
49
46
|
const configFilePath = unfilteredFlowFiles.find((file) => possibleConfigPaths.has(path.normalize(file)));
|
|
50
47
|
const config = configFilePath
|
|
51
48
|
? (0, planMethods_1.readYamlFileAsJson)(configFilePath)
|
|
52
49
|
: {};
|
|
53
50
|
return config;
|
|
54
51
|
}
|
|
52
|
+
function extractDeviceCloudOverrides(config) {
|
|
53
|
+
if (!config || !config.env || typeof config.env !== 'object') {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const overrides = {};
|
|
57
|
+
const envVars = config.env;
|
|
58
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
59
|
+
if (key.startsWith('DEVICECLOUD_OVERRIDE_')) {
|
|
60
|
+
// Remove the DEVICECLOUD_OVERRIDE_ prefix and use the rest as the override key
|
|
61
|
+
const overrideKey = key.replace('DEVICECLOUD_OVERRIDE_', '');
|
|
62
|
+
overrides[overrideKey] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return overrides;
|
|
66
|
+
}
|
|
55
67
|
async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
|
|
56
68
|
const normalizedInput = path.normalize(input);
|
|
57
69
|
const flowMetadata = {};
|
|
@@ -64,12 +76,15 @@ async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
|
|
|
64
76
|
throw new Error('If using config.yaml, pass the workspace folder path, not the config file or a custom path via --config');
|
|
65
77
|
}
|
|
66
78
|
const { config } = (0, planMethods_1.readTestYamlFileAsJson)(normalizedInput);
|
|
79
|
+
const flowOverrides = {};
|
|
67
80
|
if (config) {
|
|
68
81
|
flowMetadata[normalizedInput] = config;
|
|
82
|
+
flowOverrides[normalizedInput] = extractDeviceCloudOverrides(config);
|
|
69
83
|
}
|
|
70
84
|
const checkedDependancies = await checkDependencies(normalizedInput);
|
|
71
85
|
return {
|
|
72
86
|
flowMetadata,
|
|
87
|
+
flowOverrides,
|
|
73
88
|
flowsToRun: [normalizedInput],
|
|
74
89
|
referencedFiles: [...new Set(checkedDependancies)],
|
|
75
90
|
totalFlowFiles: 1,
|
|
@@ -151,11 +166,13 @@ async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
|
|
|
151
166
|
...excludeTags,
|
|
152
167
|
...(workspaceConfig.excludeTags || []),
|
|
153
168
|
];
|
|
169
|
+
const flowOverrides = {};
|
|
154
170
|
const allFlows = unfilteredFlowFiles.filter((filePath) => {
|
|
155
171
|
const config = configPerFlowFile[filePath];
|
|
156
172
|
const tags = config?.tags || [];
|
|
157
173
|
if (config) {
|
|
158
174
|
flowMetadata[filePath] = config;
|
|
175
|
+
flowOverrides[filePath] = extractDeviceCloudOverrides(config);
|
|
159
176
|
}
|
|
160
177
|
return ((allIncludeTags.length === 0 ||
|
|
161
178
|
tags.some((tag) => allIncludeTags.includes(tag))) &&
|
|
@@ -185,6 +202,7 @@ async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
|
|
|
185
202
|
allExcludeTags,
|
|
186
203
|
allIncludeTags,
|
|
187
204
|
flowMetadata,
|
|
205
|
+
flowOverrides,
|
|
188
206
|
flowsToRun: normalFlows,
|
|
189
207
|
referencedFiles: [...new Set(allFiles)],
|
|
190
208
|
sequence: {
|