@devicecloud.dev/dcd 0.0.1-alpha.1 → 0.0.1-alpha.4
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 +2 -1
- package/dist/commands/cloud.js +108 -27
- package/dist/plan.d.ts +10 -0
- package/dist/plan.js +152 -0
- package/oclif.manifest.json +22 -3
- package/package.json +6 -2
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class Cloud extends Command {
|
|
3
3
|
static args: {
|
|
4
|
-
firstFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
4
|
+
firstFile: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
5
|
secondFile: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
6
|
};
|
|
7
7
|
static description: string;
|
|
@@ -13,6 +13,7 @@ export default class Cloud extends Command {
|
|
|
13
13
|
appBinaryId: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
14
|
appFile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
15
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
|
+
flows: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
17
|
iOSVersion: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
18
|
};
|
|
18
19
|
run(): Promise<void>;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/* eslint-disable complexity */
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const archiver = require("archiver");
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
6
|
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_stream_1 = require("node:stream");
|
|
8
|
+
const plan_1 = require("../plan");
|
|
7
9
|
const mimeTypeLookupByExtension = {
|
|
8
10
|
apk: 'application/vnd.android.package-archive',
|
|
9
11
|
yaml: 'application/x-yaml',
|
|
10
12
|
zip: 'application/zip',
|
|
11
13
|
};
|
|
14
|
+
const PERMITTED_EXTENSIONS = new Set([
|
|
15
|
+
'yml',
|
|
16
|
+
'yaml',
|
|
17
|
+
'png',
|
|
18
|
+
'jpg',
|
|
19
|
+
'jpeg',
|
|
20
|
+
'gif',
|
|
21
|
+
'mp4',
|
|
22
|
+
'js',
|
|
23
|
+
]);
|
|
12
24
|
const typeSafeFetch = async (baseUrl, path, init) => {
|
|
13
25
|
const res = await fetch(baseUrl + path, init);
|
|
14
26
|
if (!res.ok) {
|
|
@@ -16,17 +28,48 @@ const typeSafeFetch = async (baseUrl, path, init) => {
|
|
|
16
28
|
}
|
|
17
29
|
return res.json();
|
|
18
30
|
};
|
|
19
|
-
const
|
|
20
|
-
const
|
|
31
|
+
const toBuffer = async (archive) => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
const writable = new node_stream_1.Writable();
|
|
34
|
+
writable._write = (chunk, _, callback) => {
|
|
35
|
+
// save to array to concatenate later
|
|
36
|
+
chunks.push(chunk);
|
|
37
|
+
callback();
|
|
38
|
+
};
|
|
39
|
+
// pipe to writable
|
|
40
|
+
archive.pipe(writable);
|
|
41
|
+
await archive.finalize();
|
|
42
|
+
// once done, concatenate chunks
|
|
43
|
+
return Buffer.concat(chunks);
|
|
44
|
+
};
|
|
45
|
+
const compressDir = async (sourceDir) => {
|
|
46
|
+
// const output = createWriteStream(zipTargetPath);
|
|
21
47
|
const archive = archiver('zip', {
|
|
22
48
|
zlib: { level: 9 },
|
|
23
49
|
});
|
|
24
50
|
archive.on('error', (err) => {
|
|
25
51
|
throw err;
|
|
26
52
|
});
|
|
27
|
-
archive.
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
archive.directory(sourceDir, false, (data) => {
|
|
54
|
+
if (PERMITTED_EXTENSIONS.has(data.name.split('.').pop())) {
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
const buffer = await toBuffer(archive);
|
|
60
|
+
return buffer;
|
|
61
|
+
};
|
|
62
|
+
const compressFile = async (sourceFile) => {
|
|
63
|
+
const archive = archiver('zip', {
|
|
64
|
+
zlib: { level: 9 },
|
|
65
|
+
});
|
|
66
|
+
archive.on('error', (err) => {
|
|
67
|
+
throw err;
|
|
68
|
+
});
|
|
69
|
+
archive.file(sourceFile, { name: sourceFile });
|
|
70
|
+
const buffer = await toBuffer(archive);
|
|
71
|
+
// await writeFile('./my-zip.zip', buffer);
|
|
72
|
+
return buffer;
|
|
30
73
|
};
|
|
31
74
|
class Cloud extends core_1.Command {
|
|
32
75
|
static args = {
|
|
@@ -34,7 +77,6 @@ class Cloud extends core_1.Command {
|
|
|
34
77
|
description: 'The binary file of the app to run your flow against, e.g. app.apk for android or app.zip for ios',
|
|
35
78
|
hidden: true,
|
|
36
79
|
name: 'App file',
|
|
37
|
-
required: true,
|
|
38
80
|
}),
|
|
39
81
|
secondFile: core_1.Args.string({
|
|
40
82
|
description: 'The flow file to run against the app, e.g. test.yaml',
|
|
@@ -48,12 +90,14 @@ class Cloud extends core_1.Command {
|
|
|
48
90
|
androidApiLevel: core_1.Flags.integer({
|
|
49
91
|
aliases: ['android-api-level'],
|
|
50
92
|
description: 'Android API level to run your flow against',
|
|
93
|
+
options: ['32', '33', '34'],
|
|
51
94
|
}),
|
|
52
95
|
apiKey: core_1.Flags.string({ aliases: ['api-key'], description: 'API key' }),
|
|
53
96
|
apiUrl: core_1.Flags.string({
|
|
54
97
|
aliases: ['api-url'],
|
|
55
98
|
default: 'https://api.devicecloud.dev',
|
|
56
99
|
description: 'API base URL',
|
|
100
|
+
hidden: true,
|
|
57
101
|
}),
|
|
58
102
|
appBinaryId: core_1.Flags.string({
|
|
59
103
|
aliases: ['app-binary-id'],
|
|
@@ -69,42 +113,50 @@ class Cloud extends core_1.Command {
|
|
|
69
113
|
description: 'One or more environment variables to inject into your Flows',
|
|
70
114
|
multiple: true,
|
|
71
115
|
}),
|
|
116
|
+
flows: core_1.Flags.string({
|
|
117
|
+
aliases: ['flows'],
|
|
118
|
+
description: 'The path to the flow file or folder containing your Flows',
|
|
119
|
+
}),
|
|
72
120
|
iOSVersion: core_1.Flags.string({
|
|
73
121
|
aliases: ['ios-version'],
|
|
74
122
|
description: 'iOS version to run your flow against',
|
|
123
|
+
options: ['16.4', '17.2'],
|
|
75
124
|
}),
|
|
76
125
|
};
|
|
77
126
|
async run() {
|
|
78
127
|
const { args, flags } = await this.parse(Cloud);
|
|
79
|
-
const { apiKey, apiUrl, appBinaryId, ...rest } = flags;
|
|
128
|
+
const { apiKey, apiUrl, appBinaryId, appFile, env, flows, ...rest } = flags;
|
|
80
129
|
console.log({ args });
|
|
81
130
|
const { firstFile, secondFile } = args;
|
|
82
131
|
let finalBinaryId = appBinaryId;
|
|
83
|
-
const
|
|
84
|
-
let flowFile = secondFile;
|
|
132
|
+
const finalAppFile = appFile ?? firstFile;
|
|
133
|
+
let flowFile = flows ?? secondFile;
|
|
85
134
|
if (appBinaryId) {
|
|
86
135
|
if (secondFile) {
|
|
87
136
|
throw new Error('You cannot provide both an appBinaryId and a binary file');
|
|
88
137
|
}
|
|
89
|
-
flowFile = firstFile;
|
|
90
|
-
this.log(`you want to run the flow ${flowFile} against the binary with id ${appBinaryId} with the following flags: ${JSON.stringify(flags)}`);
|
|
138
|
+
flowFile = flows ?? firstFile;
|
|
139
|
+
this.log(`you want to run the flow(s) ${flowFile} against the binary with id ${appBinaryId} with the following flags: ${JSON.stringify(flags)}`);
|
|
91
140
|
}
|
|
92
141
|
else {
|
|
93
|
-
if (!
|
|
94
|
-
throw new Error('App file must be a .apk or .zip file');
|
|
95
|
-
}
|
|
96
|
-
if (!(flowFile && appFile)) {
|
|
142
|
+
if (!(flowFile && finalAppFile)) {
|
|
97
143
|
throw new Error('You must provide a flow file and an app binary id');
|
|
98
144
|
}
|
|
99
|
-
|
|
145
|
+
if (!finalAppFile.endsWith('.apk') && !finalAppFile.endsWith('.zip')) {
|
|
146
|
+
throw new Error('App file must be a .apk or .zip file');
|
|
147
|
+
}
|
|
148
|
+
this.log(`you want to run the flow(s) ${flowFile} against the app ${finalAppFile} with the following flags: ${JSON.stringify(flags)}`);
|
|
149
|
+
}
|
|
150
|
+
if (!flowFile) {
|
|
151
|
+
throw new Error('You must provide a flow file');
|
|
100
152
|
}
|
|
101
153
|
if (!finalBinaryId) {
|
|
102
154
|
const binaryFormData = new FormData();
|
|
103
|
-
const binaryBlob = new Blob([await (0, promises_1.readFile)(
|
|
104
|
-
type: mimeTypeLookupByExtension[
|
|
155
|
+
const binaryBlob = new Blob([await (0, promises_1.readFile)(finalAppFile)], {
|
|
156
|
+
type: mimeTypeLookupByExtension[finalAppFile.split('.').pop()],
|
|
105
157
|
});
|
|
106
|
-
console.log(mimeTypeLookupByExtension[
|
|
107
|
-
binaryFormData.set('file', binaryBlob,
|
|
158
|
+
console.log(mimeTypeLookupByExtension[finalAppFile.split('.').pop()]);
|
|
159
|
+
binaryFormData.set('file', binaryBlob, finalAppFile);
|
|
108
160
|
const options = {
|
|
109
161
|
body: binaryFormData,
|
|
110
162
|
headers: { 'x-app-api-key': apiKey },
|
|
@@ -116,17 +168,46 @@ class Cloud extends core_1.Command {
|
|
|
116
168
|
this.log(message);
|
|
117
169
|
finalBinaryId = binaryId;
|
|
118
170
|
}
|
|
171
|
+
const testFileNames = [];
|
|
172
|
+
let flowFileDirectory = flowFile;
|
|
173
|
+
if (!flowFile?.endsWith('.yaml') && !flowFile?.endsWith('.yml')) {
|
|
174
|
+
try {
|
|
175
|
+
const executionPlan = await (0, plan_1.plan)(flowFile, [], []);
|
|
176
|
+
for (const file of executionPlan.flowsToRun) {
|
|
177
|
+
testFileNames.push(file);
|
|
178
|
+
}
|
|
179
|
+
for (const file of executionPlan.sequence?.flows ?? []) {
|
|
180
|
+
// todo: handle continueOnFailure and other sequence properties
|
|
181
|
+
testFileNames.push(file);
|
|
182
|
+
}
|
|
183
|
+
console.log(executionPlan);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// we are working with a single file
|
|
191
|
+
flowFileDirectory = flowFile.split('/').slice(0, -1).join('/');
|
|
192
|
+
testFileNames.push(flowFile.split('/').pop());
|
|
193
|
+
}
|
|
119
194
|
const testFormData = new FormData();
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
195
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
196
|
+
const envObject = (env ?? []).reduce((acc, cur) => {
|
|
197
|
+
const [key, value] = cur.split('=');
|
|
198
|
+
acc[key] = value;
|
|
199
|
+
return acc;
|
|
200
|
+
}, {});
|
|
201
|
+
const buffer = flowFileDirectory?.length
|
|
202
|
+
? await compressDir(flowFileDirectory)
|
|
203
|
+
: await compressFile(flowFile);
|
|
204
|
+
const blob = new Blob([buffer], {
|
|
125
205
|
type: mimeTypeLookupByExtension.zip,
|
|
126
206
|
});
|
|
127
207
|
testFormData.set('file', blob, 'flowFile.zip');
|
|
128
208
|
testFormData.set('appBinaryId', finalBinaryId);
|
|
129
|
-
testFormData.set('
|
|
209
|
+
testFormData.set('testFileNames', JSON.stringify(testFileNames));
|
|
210
|
+
testFormData.set('env', JSON.stringify(envObject));
|
|
130
211
|
for (const [key, value] of Object.entries(rest)) {
|
|
131
212
|
if (value) {
|
|
132
213
|
testFormData.set(key, value);
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface IExecutionPlan {
|
|
2
|
+
flowsToRun: string[];
|
|
3
|
+
sequence?: IFlowSequence | null;
|
|
4
|
+
}
|
|
5
|
+
interface IFlowSequence {
|
|
6
|
+
continueOnFailure?: boolean;
|
|
7
|
+
flows: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function plan(input: string, includeTags: string[], excludeTags: string[]): Promise<IExecutionPlan>;
|
|
10
|
+
export {};
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.plan = void 0;
|
|
4
|
+
const globToRegExp = require("glob-to-regexp");
|
|
5
|
+
const yaml = require("js-yaml");
|
|
6
|
+
const fs = require("node:fs");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
function getFlowsToRunInSequence(paths, flowOrder) {
|
|
9
|
+
if (flowOrder.length === 0)
|
|
10
|
+
return [];
|
|
11
|
+
const orderSet = new Set(flowOrder);
|
|
12
|
+
const namesInOrder = Object.keys(paths).filter((key) => orderSet.has(key));
|
|
13
|
+
if (namesInOrder.length === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const result = [...orderSet].filter((item) => namesInOrder.includes(item));
|
|
16
|
+
if (result.length === 0) {
|
|
17
|
+
throw new Error(`Could not find flows needed for execution in order: ${[...orderSet]
|
|
18
|
+
.filter((item) => !namesInOrder.includes(item))
|
|
19
|
+
.join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
else if (flowOrder
|
|
22
|
+
.slice(0, result.length)
|
|
23
|
+
.every((value, index) => value === result[index])) {
|
|
24
|
+
return result.map((item) => paths[item]);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isFlowFile(filePath) {
|
|
31
|
+
return filePath.endsWith('.yaml') || filePath.endsWith('.yml');
|
|
32
|
+
}
|
|
33
|
+
const readYamlFileAsJson = (filePath) => {
|
|
34
|
+
const yamlText = fs.readFileSync(filePath, 'utf8');
|
|
35
|
+
return yaml.load(yamlText);
|
|
36
|
+
};
|
|
37
|
+
const readConfigFromYamlFileAsJson = (filePath) => {
|
|
38
|
+
const yamlText = fs.readFileSync(filePath, 'utf8');
|
|
39
|
+
if (yamlText.includes('\n---\n')) {
|
|
40
|
+
const yamlTexts = yamlText.split('\n---\n');
|
|
41
|
+
const config = yaml.load(yamlTexts[0]);
|
|
42
|
+
if (Object.keys(config).filter((key) => key === 'appId').length > 1) {
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
async function walk(dir, filterFunction) {
|
|
49
|
+
const readDirResult = await fs.promises.readdir(dir);
|
|
50
|
+
const files = await Promise.all(readDirResult.map(async (file) => {
|
|
51
|
+
const filePath = path.join(dir, file);
|
|
52
|
+
const stats = await fs.promises.stat(filePath);
|
|
53
|
+
if (stats.isDirectory())
|
|
54
|
+
return walk(filePath);
|
|
55
|
+
if (stats.isFile())
|
|
56
|
+
if (filterFunction) {
|
|
57
|
+
if (filterFunction(filePath))
|
|
58
|
+
return filePath;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return filePath;
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
return files.flat().filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
async function plan(input, includeTags, excludeTags) {
|
|
67
|
+
if (!fs.existsSync(input)) {
|
|
68
|
+
throw new Error(`Flow path does not exist: ${path.resolve(input)}`);
|
|
69
|
+
}
|
|
70
|
+
if (fs.lstatSync(input).isFile()) {
|
|
71
|
+
return { flowsToRun: [input] };
|
|
72
|
+
}
|
|
73
|
+
let unfilteredFlowFiles = await walk(input, isFlowFile);
|
|
74
|
+
if (unfilteredFlowFiles.length === 0) {
|
|
75
|
+
throw new Error(`Flow directory does not contain any Flow files: ${path.resolve(input)}`);
|
|
76
|
+
}
|
|
77
|
+
const configFilePath = unfilteredFlowFiles.find((file) => file.endsWith('config.yaml') || file.endsWith('config.yml'));
|
|
78
|
+
if (configFilePath)
|
|
79
|
+
unfilteredFlowFiles = unfilteredFlowFiles.filter((file) => file !== configFilePath);
|
|
80
|
+
const cleanPath = path.normalize(input);
|
|
81
|
+
const relativeFilePaths = unfilteredFlowFiles.map((file) => file.replace(cleanPath, ''));
|
|
82
|
+
const topLevelFlowFiles = relativeFilePaths.filter((file) => !file.includes('/'));
|
|
83
|
+
const workspaceConfig = configFilePath
|
|
84
|
+
? readYamlFileAsJson(configFilePath)
|
|
85
|
+
: {};
|
|
86
|
+
let unsortedFlowFiles = topLevelFlowFiles;
|
|
87
|
+
if (workspaceConfig.flows) {
|
|
88
|
+
const matchers = workspaceConfig.flows.map((glob) => glob === '*' ? /^((?!\/).)*$/ : globToRegExp(glob));
|
|
89
|
+
unsortedFlowFiles = relativeFilePaths.filter((filePath) => matchers.some((matcher) => matcher.test(filePath)));
|
|
90
|
+
}
|
|
91
|
+
if (unsortedFlowFiles.length === 0) {
|
|
92
|
+
const error = workspaceConfig.flows
|
|
93
|
+
? new Error(`Flow inclusion pattern(s) did not match any Flow files:\n${workspaceConfig.flows.join('\n')}`)
|
|
94
|
+
: new Error(`Top-level directory does not contain any Flows: ${path.resolve(input)}`);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
98
|
+
const configPerFlowFile = unsortedFlowFiles.reduce((acc, filePath) => {
|
|
99
|
+
acc[filePath] = readConfigFromYamlFileAsJson(cleanPath + filePath);
|
|
100
|
+
return acc;
|
|
101
|
+
}, {});
|
|
102
|
+
const allIncludeTags = [
|
|
103
|
+
...includeTags,
|
|
104
|
+
...(workspaceConfig.includeTags || []),
|
|
105
|
+
];
|
|
106
|
+
const allExcludeTags = [
|
|
107
|
+
...excludeTags,
|
|
108
|
+
...(workspaceConfig.excludeTags || []),
|
|
109
|
+
];
|
|
110
|
+
const allFlows = unsortedFlowFiles.filter((filePath) => {
|
|
111
|
+
const config = configPerFlowFile[filePath];
|
|
112
|
+
const tags = config?.tags || [];
|
|
113
|
+
return ((allIncludeTags.length === 0 ||
|
|
114
|
+
tags.some((tag) => allIncludeTags.includes(tag))) &&
|
|
115
|
+
(allExcludeTags.length === 0 ||
|
|
116
|
+
!tags.some((tag) => allExcludeTags.includes(tag))));
|
|
117
|
+
});
|
|
118
|
+
if (allFlows.length === 0) {
|
|
119
|
+
throw new Error(`Include / Exclude tags did not match any Flows:\n\nInclude Tags:\n${allIncludeTags.join('\n')}\n\nExclude Tags:\n${allExcludeTags.join('\n')}`);
|
|
120
|
+
}
|
|
121
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
122
|
+
const pathsByName = allFlows.reduce((acc, filePath) => {
|
|
123
|
+
const config = configPerFlowFile[filePath];
|
|
124
|
+
acc[config?.name || path.parse(filePath).name] = filePath;
|
|
125
|
+
return acc;
|
|
126
|
+
}, {});
|
|
127
|
+
const flowsToRunInSequence = workspaceConfig.executionOrder?.flowsOrder
|
|
128
|
+
?.map((flowOrder) => getFlowsToRunInSequence(pathsByName, [flowOrder]))
|
|
129
|
+
.flat() || [];
|
|
130
|
+
const normalFlows = allFlows.filter((flow) => !flowsToRunInSequence.includes(flow));
|
|
131
|
+
// for (const filePath of allFlows) {
|
|
132
|
+
// const commands = YamlCommandReader.readCommands(filePath).filter(
|
|
133
|
+
// (command) => command.addMediaCommand,
|
|
134
|
+
// );
|
|
135
|
+
// const mediaPaths = commands.flatMap(
|
|
136
|
+
// (command) => command.addMediaCommand.mediaPaths,
|
|
137
|
+
// );
|
|
138
|
+
// YamlCommandsPathValidator.validatePathsExistInWorkspace(
|
|
139
|
+
// input,
|
|
140
|
+
// filePath,
|
|
141
|
+
// mediaPaths,
|
|
142
|
+
// );
|
|
143
|
+
// }
|
|
144
|
+
return {
|
|
145
|
+
flowsToRun: normalFlows,
|
|
146
|
+
sequence: {
|
|
147
|
+
continueOnFailure: workspaceConfig.executionOrder?.continueOnFailure,
|
|
148
|
+
flows: flowsToRunInSequence,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
exports.plan = plan;
|
package/oclif.manifest.json
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
"firstFile": {
|
|
7
7
|
"description": "The binary file of the app to run your flow against, e.g. app.apk for android or app.zip for ios",
|
|
8
8
|
"hidden": true,
|
|
9
|
-
"name": "firstFile"
|
|
10
|
-
"required": true
|
|
9
|
+
"name": "firstFile"
|
|
11
10
|
},
|
|
12
11
|
"secondFile": {
|
|
13
12
|
"description": "The flow file to run against the app, e.g. test.yaml",
|
|
@@ -28,6 +27,11 @@
|
|
|
28
27
|
"name": "androidApiLevel",
|
|
29
28
|
"hasDynamicHelp": false,
|
|
30
29
|
"multiple": false,
|
|
30
|
+
"options": [
|
|
31
|
+
"32",
|
|
32
|
+
"33",
|
|
33
|
+
"34"
|
|
34
|
+
],
|
|
31
35
|
"type": "option"
|
|
32
36
|
},
|
|
33
37
|
"apiKey": {
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
"api-url"
|
|
46
50
|
],
|
|
47
51
|
"description": "API base URL",
|
|
52
|
+
"hidden": true,
|
|
48
53
|
"name": "apiUrl",
|
|
49
54
|
"default": "https://api.devicecloud.dev",
|
|
50
55
|
"hasDynamicHelp": false,
|
|
@@ -82,6 +87,16 @@
|
|
|
82
87
|
"multiple": true,
|
|
83
88
|
"type": "option"
|
|
84
89
|
},
|
|
90
|
+
"flows": {
|
|
91
|
+
"aliases": [
|
|
92
|
+
"flows"
|
|
93
|
+
],
|
|
94
|
+
"description": "The path to the flow file or folder containing your Flows",
|
|
95
|
+
"name": "flows",
|
|
96
|
+
"hasDynamicHelp": false,
|
|
97
|
+
"multiple": false,
|
|
98
|
+
"type": "option"
|
|
99
|
+
},
|
|
85
100
|
"iOSVersion": {
|
|
86
101
|
"aliases": [
|
|
87
102
|
"ios-version"
|
|
@@ -90,6 +105,10 @@
|
|
|
90
105
|
"name": "iOSVersion",
|
|
91
106
|
"hasDynamicHelp": false,
|
|
92
107
|
"multiple": false,
|
|
108
|
+
"options": [
|
|
109
|
+
"16.4",
|
|
110
|
+
"17.2"
|
|
111
|
+
],
|
|
93
112
|
"type": "option"
|
|
94
113
|
}
|
|
95
114
|
},
|
|
@@ -109,5 +128,5 @@
|
|
|
109
128
|
]
|
|
110
129
|
}
|
|
111
130
|
},
|
|
112
|
-
"version": "0.0.1-alpha.
|
|
131
|
+
"version": "0.0.1-alpha.4"
|
|
113
132
|
}
|
package/package.json
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"@oclif/plugin-plugins": "^4",
|
|
12
12
|
"@oclif/plugin-update": "^4.1.11",
|
|
13
13
|
"@oclif/plugin-warn-if-update-available": "^3.0.10",
|
|
14
|
-
"archiver": "^6.0.1"
|
|
14
|
+
"archiver": "^6.0.1",
|
|
15
|
+
"glob-to-regexp": "^0.4.1",
|
|
16
|
+
"js-yaml": "^4.1.0"
|
|
15
17
|
},
|
|
16
18
|
"description": "Better cloud maestro testing",
|
|
17
19
|
"devDependencies": {
|
|
@@ -19,6 +21,8 @@
|
|
|
19
21
|
"@oclif/test": "^3",
|
|
20
22
|
"@types/archiver": "^6.0.2",
|
|
21
23
|
"@types/chai": "^4",
|
|
24
|
+
"@types/glob-to-regexp": "^0.4.4",
|
|
25
|
+
"@types/js-yaml": "^4.0.9",
|
|
22
26
|
"@types/mocha": "^9.0.0",
|
|
23
27
|
"chai": "^4",
|
|
24
28
|
"eslint": "^8.56.0",
|
|
@@ -70,7 +74,7 @@
|
|
|
70
74
|
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
|
|
71
75
|
"version": "oclif readme && git add README.md"
|
|
72
76
|
},
|
|
73
|
-
"version": "0.0.1-alpha.
|
|
77
|
+
"version": "0.0.1-alpha.4",
|
|
74
78
|
"bugs": {
|
|
75
79
|
"url": "https://discord.gg/GzZBHcUJ"
|
|
76
80
|
},
|