@devicecloud.dev/dcd 0.0.1-alpha.4 → 0.0.1-alpha.6

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.
@@ -12,9 +12,12 @@ export default class Cloud extends Command {
12
12
  apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
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
+ async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
16
  env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ excludeTags: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
16
18
  flows: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
19
  iOSVersion: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ includeTags: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
18
21
  };
19
22
  run(): Promise<void>;
20
23
  }
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  /* eslint-disable complexity */
4
4
  const core_1 = require("@oclif/core");
5
+ const cli_ux_1 = require("@oclif/core/lib/cli-ux");
6
+ const errors_1 = require("@oclif/core/lib/errors");
5
7
  const archiver = require("archiver");
6
8
  const promises_1 = require("node:fs/promises");
7
9
  const node_stream_1 = require("node:stream");
@@ -21,7 +23,14 @@ const PERMITTED_EXTENSIONS = new Set([
21
23
  'mp4',
22
24
  'js',
23
25
  ]);
24
- const typeSafeFetch = async (baseUrl, path, init) => {
26
+ const typeSafePost = async (baseUrl, path, init) => {
27
+ const res = await fetch(baseUrl + path, { ...init, method: 'POST' });
28
+ if (!res.ok) {
29
+ throw new Error(await res.text());
30
+ }
31
+ return res.json();
32
+ };
33
+ const typeSafeGet = async (baseUrl, path, init) => {
25
34
  const res = await fetch(baseUrl + path, init);
26
35
  if (!res.ok) {
27
36
  throw new Error(await res.text());
@@ -105,28 +114,43 @@ class Cloud extends core_1.Command {
105
114
  }),
106
115
  appFile: core_1.Flags.file({
107
116
  aliases: ['app-file'],
108
- description: 'App binary to run your Flows against',
117
+ description: 'App binary to run your flows against',
118
+ }),
119
+ async: core_1.Flags.boolean({
120
+ default: true,
121
+ description: 'Wait for the results of the run',
109
122
  }),
110
123
  env: core_1.Flags.file({
111
- aliases: ['env'],
112
124
  char: 'e',
113
- description: 'One or more environment variables to inject into your Flows',
125
+ description: 'One or more environment variables to inject into your flows',
126
+ multiple: true,
127
+ }),
128
+ excludeTags: core_1.Flags.string({
129
+ aliases: ['exclude-tags'],
130
+ default: [],
131
+ description: 'Flows which have these tags will be excluded from the run',
114
132
  multiple: true,
133
+ parse: (input) => input.split(','),
115
134
  }),
116
135
  flows: core_1.Flags.string({
117
- aliases: ['flows'],
118
- description: 'The path to the flow file or folder containing your Flows',
136
+ description: 'The path to the flow file or folder containing your flows',
119
137
  }),
120
138
  iOSVersion: core_1.Flags.string({
121
139
  aliases: ['ios-version'],
122
140
  description: 'iOS version to run your flow against',
123
141
  options: ['16.4', '17.2'],
124
142
  }),
143
+ includeTags: core_1.Flags.string({
144
+ aliases: ['include-tags'],
145
+ default: [],
146
+ description: 'Only flows which have these tags will be included in the run',
147
+ multiple: true,
148
+ parse: (input) => input.split(','),
149
+ }),
125
150
  };
126
151
  async run() {
127
152
  const { args, flags } = await this.parse(Cloud);
128
- const { apiKey, apiUrl, appBinaryId, appFile, env, flows, ...rest } = flags;
129
- console.log({ args });
153
+ const { apiKey, apiUrl, appBinaryId, appFile, async, env, excludeTags, flows, includeTags, ...rest } = flags;
130
154
  const { firstFile, secondFile } = args;
131
155
  let finalBinaryId = appBinaryId;
132
156
  const finalAppFile = appFile ?? firstFile;
@@ -136,7 +160,11 @@ class Cloud extends core_1.Command {
136
160
  throw new Error('You cannot provide both an appBinaryId and a binary file');
137
161
  }
138
162
  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)}`);
163
+ // this.log(
164
+ // `you want to run the flow(s) ${flowFile} against the binary with id ${appBinaryId} with the following flags: ${JSON.stringify(
165
+ // flags,
166
+ // )}`,
167
+ // );
140
168
  }
141
169
  else {
142
170
  if (!(flowFile && finalAppFile)) {
@@ -155,14 +183,12 @@ class Cloud extends core_1.Command {
155
183
  const binaryBlob = new Blob([await (0, promises_1.readFile)(finalAppFile)], {
156
184
  type: mimeTypeLookupByExtension[finalAppFile.split('.').pop()],
157
185
  });
158
- console.log(mimeTypeLookupByExtension[finalAppFile.split('.').pop()]);
159
186
  binaryFormData.set('file', binaryBlob, finalAppFile);
160
187
  const options = {
161
188
  body: binaryFormData,
162
189
  headers: { 'x-app-api-key': apiKey },
163
- method: 'POST',
164
190
  };
165
- const { binaryId, message } = await typeSafeFetch(apiUrl, '/uploads/binary', options);
191
+ const { binaryId, message } = await typeSafePost(apiUrl, '/uploads/binary', options);
166
192
  if (!binaryId)
167
193
  throw new Error(message);
168
194
  this.log(message);
@@ -172,7 +198,7 @@ class Cloud extends core_1.Command {
172
198
  let flowFileDirectory = flowFile;
173
199
  if (!flowFile?.endsWith('.yaml') && !flowFile?.endsWith('.yml')) {
174
200
  try {
175
- const executionPlan = await (0, plan_1.plan)(flowFile, [], []);
201
+ const executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat());
176
202
  for (const file of executionPlan.flowsToRun) {
177
203
  testFileNames.push(file);
178
204
  }
@@ -180,7 +206,6 @@ class Cloud extends core_1.Command {
180
206
  // todo: handle continueOnFailure and other sequence properties
181
207
  testFileNames.push(file);
182
208
  }
183
- console.log(executionPlan);
184
209
  }
185
210
  catch (error) {
186
211
  console.error(error);
@@ -216,10 +241,46 @@ class Cloud extends core_1.Command {
216
241
  const options = {
217
242
  body: testFormData,
218
243
  headers: { 'x-app-api-key': apiKey },
219
- method: 'POST',
220
244
  };
221
- const { message } = await typeSafeFetch(apiUrl, '/uploads/flow', options);
222
- console.log(message);
245
+ const { message, results } = await typeSafePost(apiUrl, '/uploads/flow', options);
246
+ if (!results?.length)
247
+ (0, errors_1.error)('No tests created: ' + message);
248
+ (0, cli_ux_1.info)(`\nCreated ${results.length} tests: ${results
249
+ .map((r) => r.test_file_name)
250
+ .join(', ')}\n`);
251
+ (0, cli_ux_1.info)('Run triggered, you can access the results at:');
252
+ (0, cli_ux_1.info)(`https://console.devicecloud.dev/results/${results[0].test_upload_id}/${results[0].id}/\n`);
253
+ if (!async) {
254
+ (0, cli_ux_1.info)('completed');
255
+ (0, cli_ux_1.exit)(0);
256
+ }
257
+ if (async) {
258
+ // poll for the run status every 5 seconds
259
+ core_1.ux.action.start('Waiting for results', 'Initializing', { stdout: true });
260
+ (0, cli_ux_1.info)(' ');
261
+ const intervalId = setInterval(async () => {
262
+ const { results: updatedResults } = await typeSafeGet(apiUrl, `/results/${results[0].test_upload_id}`, {
263
+ headers: { 'x-app-api-key': apiKey },
264
+ });
265
+ if (!updatedResults) {
266
+ clearInterval(intervalId);
267
+ (0, errors_1.error)('No results found');
268
+ }
269
+ core_1.ux.action.status = `\nStatus | Test
270
+ ────────── ─────────── `;
271
+ for (const { status, test_file_name: test } of updatedResults) {
272
+ core_1.ux.action.status += `\n${status.padEnd(10, ' ')} | ${test}`;
273
+ }
274
+ if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
275
+ (0, cli_ux_1.info)('\nRun completed\n');
276
+ (0, cli_ux_1.table)(updatedResults, {
277
+ status: { get: (row) => row.status },
278
+ test: { get: (row) => row.test_file_name },
279
+ }, { printLine: this.log.bind(this) });
280
+ clearInterval(intervalId);
281
+ }
282
+ }, 5000);
283
+ }
223
284
  }
224
285
  }
225
286
  exports.default = Cloud;
package/dist/plan.js CHANGED
@@ -39,7 +39,7 @@ const readConfigFromYamlFileAsJson = (filePath) => {
39
39
  if (yamlText.includes('\n---\n')) {
40
40
  const yamlTexts = yamlText.split('\n---\n');
41
41
  const config = yaml.load(yamlTexts[0]);
42
- if (Object.keys(config).filter((key) => key === 'appId').length > 1) {
42
+ if (Object.keys(config).length > 0) {
43
43
  return config;
44
44
  }
45
45
  }
@@ -70,28 +70,39 @@
70
70
  "aliases": [
71
71
  "app-file"
72
72
  ],
73
- "description": "App binary to run your Flows against",
73
+ "description": "App binary to run your flows against",
74
74
  "name": "appFile",
75
75
  "hasDynamicHelp": false,
76
76
  "multiple": false,
77
77
  "type": "option"
78
78
  },
79
+ "async": {
80
+ "description": "Wait for the results of the run",
81
+ "name": "async",
82
+ "allowNo": false,
83
+ "type": "boolean"
84
+ },
79
85
  "env": {
80
- "aliases": [
81
- "env"
82
- ],
83
86
  "char": "e",
84
- "description": "One or more environment variables to inject into your Flows",
87
+ "description": "One or more environment variables to inject into your flows",
85
88
  "name": "env",
86
89
  "hasDynamicHelp": false,
87
90
  "multiple": true,
88
91
  "type": "option"
89
92
  },
90
- "flows": {
93
+ "excludeTags": {
91
94
  "aliases": [
92
- "flows"
95
+ "exclude-tags"
93
96
  ],
94
- "description": "The path to the flow file or folder containing your Flows",
97
+ "description": "Flows which have these tags will be excluded from the run",
98
+ "name": "excludeTags",
99
+ "default": [],
100
+ "hasDynamicHelp": false,
101
+ "multiple": true,
102
+ "type": "option"
103
+ },
104
+ "flows": {
105
+ "description": "The path to the flow file or folder containing your flows",
95
106
  "name": "flows",
96
107
  "hasDynamicHelp": false,
97
108
  "multiple": false,
@@ -110,6 +121,17 @@
110
121
  "17.2"
111
122
  ],
112
123
  "type": "option"
124
+ },
125
+ "includeTags": {
126
+ "aliases": [
127
+ "include-tags"
128
+ ],
129
+ "description": "Only flows which have these tags will be included in the run",
130
+ "name": "includeTags",
131
+ "default": [],
132
+ "hasDynamicHelp": false,
133
+ "multiple": true,
134
+ "type": "option"
113
135
  }
114
136
  },
115
137
  "hasDynamicHelp": false,
@@ -128,5 +150,5 @@
128
150
  ]
129
151
  }
130
152
  },
131
- "version": "0.0.1-alpha.4"
153
+ "version": "0.0.1-alpha.6"
132
154
  }
package/package.json CHANGED
@@ -74,7 +74,7 @@
74
74
  "test": "mocha --forbid-only \"test/**/*.test.ts\"",
75
75
  "version": "oclif readme && git add README.md"
76
76
  },
77
- "version": "0.0.1-alpha.4",
77
+ "version": "0.0.1-alpha.6",
78
78
  "bugs": {
79
79
  "url": "https://discord.gg/GzZBHcUJ"
80
80
  },