@openfn/cli 0.0.38 → 0.0.39

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 CHANGED
@@ -58,7 +58,6 @@ If you're coming to the CLI from the old openfn devtools, here are a couple of k
58
58
 
59
59
  * The CLI has a shorter, sleeker syntax, so your command should be much shorter
60
60
  * The CLI will automatically install adaptors for you (with full version control)
61
- * By default, the CLI will only write state.data to output. This is to encourage better state management. Pass `--no-strict-output` to save the entire state object.
62
61
 
63
62
  ## Basic Usage
64
63
 
@@ -76,8 +75,6 @@ Pass the `-i` flag to auto-install any required adaptors (it's safe to do this r
76
75
 
77
76
  When the finished, the CLI will write the resulting state to disk. By default the CLI will create an `output.json` next to the job file. You can pass a path to output by passing `-o path/to/output.json` and state by adding `-s path/to/state.json`. You can use `-S` and `-O` to pass state through stdin and return the output through stdout.
78
77
 
79
- Note that the CLI will only include the `state.data` key in the output. To write the entire state object (not just `data`), pass `--no-strict-output`.
80
-
81
78
  The CLI maintains a repo for auto-installed adaptors. Run `openfn repo list` to see where the repo is, and what's in it. Set the `OPENFN_REPO_DIR` env var to specify the repo folder. When autoinstalling, the CLI will check to see if a matching version is found in the repo. `openfn repo clean` will remove all adaptors from the repo. The repo also includes any documentation and metadata built with the CLI.
82
79
 
83
80
  You can specify adaptors with a shorthand (`http`) or use the full package name (`@openfn/language-http`). You can add a specific version like `http@2.0.0`. You can pass a path to a locally installed adaptor like `http=/repo/openfn/adaptors/my-http-build`.
@@ -123,7 +123,6 @@ function ensureOpts(basePath = ".", opts) {
123
123
  skipAdaptorValidation: opts.skipAdaptorValidation ?? false,
124
124
  specifier: opts.specifier,
125
125
  stateStdin: opts.stateStdin,
126
- strictOutput: opts.strictOutput ?? true,
127
126
  timeout: opts.timeout
128
127
  };
129
128
  const set = (key, value) => {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  DEFAULT_REPO_DIR,
4
4
  expand_adaptors_default
5
- } from "./chunk-DOKL2XM5.js";
5
+ } from "./chunk-LV5XDERP.js";
6
6
 
7
7
  // src/process/spawn.ts
8
8
  import path from "node:path";
@@ -274,11 +274,28 @@ var start = {
274
274
  var strictOutput = {
275
275
  name: "no-strict-output",
276
276
  yargs: {
277
+ deprecated: true,
278
+ hidden: true,
279
+ boolean: true
280
+ },
281
+ ensure: (opts2) => {
282
+ if (!opts2.hasOwnProperty("strict")) {
283
+ opts2.strict = opts2.strictOutput;
284
+ }
285
+ delete opts2.strictOutput;
286
+ }
287
+ };
288
+ var strict = {
289
+ name: "no-strict",
290
+ yargs: {
291
+ default: false,
277
292
  boolean: true,
278
- description: "Allow properties other than data to be returned in the output"
293
+ description: "Strict state handling, meaning only state.data is returned from a job."
279
294
  },
280
295
  ensure: (opts2) => {
281
- setDefaultValue(opts2, "strictOutput", true);
296
+ if (!opts2.hasOwnProperty("strictOutput")) {
297
+ setDefaultValue(opts2, "strict", false);
298
+ }
282
299
  }
283
300
  };
284
301
  var skipAdaptorValidation = {
@@ -343,6 +360,7 @@ var options = [
343
360
  start,
344
361
  statePath,
345
362
  stateStdin,
363
+ strict,
346
364
  strictOutput,
347
365
  timeout,
348
366
  useAdaptorsMonorepo
@@ -353,7 +371,7 @@ var executeCommand = {
353
371
 
354
372
  Execute will run a job/workflow at the path and write the output state to disk (to ./state.json unless otherwise specified)
355
373
 
356
- By default only state.data will be written to the output. Include --no-strict-output to write the entire state object.
374
+ By default only state.data will be returned fron a job. Include --no-strict to write the entire state object.
357
375
 
358
376
  Remember to include the adaptor name with -a. Auto install adaptors with the -i flag.`,
359
377
  aliases: ["$0"],
@@ -10,22 +10,51 @@ import {
10
10
  expand_adaptors_default,
11
11
  logger_default,
12
12
  printDuration
13
- } from "../chunk-DOKL2XM5.js";
13
+ } from "../chunk-LV5XDERP.js";
14
14
 
15
15
  // src/execute/execute.ts
16
16
  import run, { getNameAndVersion } from "@openfn/runtime";
17
- var execute_default = (input, state, opts) => {
18
- return run(input, state, {
19
- start: opts.start,
20
- timeout: opts.timeout,
21
- immutableState: opts.immutable,
22
- logger: logger_default(RUNTIME, opts),
23
- jobLogger: logger_default(JOB, opts),
24
- linker: {
25
- repo: opts.repoDir,
26
- modules: parseAdaptors(opts)
27
- }
28
- });
17
+
18
+ // src/util/abort.ts
19
+ var AbortError = class extends Error {
20
+ constructor(reason) {
21
+ super(reason);
22
+ this.handled = true;
23
+ }
24
+ };
25
+ var abort_default = (logger, reason, error, help) => {
26
+ const e = new AbortError(reason);
27
+ logger.error(reason);
28
+ if (error) {
29
+ logger.error(error.message);
30
+ }
31
+ if (help) {
32
+ logger.always(help);
33
+ }
34
+ logger.break();
35
+ logger.error("Critical error: aborting command");
36
+ throw e;
37
+ };
38
+
39
+ // src/execute/execute.ts
40
+ var execute_default = async (input, state, opts, logger) => {
41
+ try {
42
+ const result = await run(input, state, {
43
+ strict: opts.strict,
44
+ start: opts.start,
45
+ timeout: opts.timeout,
46
+ immutableState: opts.immutable,
47
+ logger: logger_default(RUNTIME, opts),
48
+ jobLogger: logger_default(JOB, opts),
49
+ linker: {
50
+ repo: opts.repoDir,
51
+ modules: parseAdaptors(opts)
52
+ }
53
+ });
54
+ return result;
55
+ } catch (e) {
56
+ abort_default(logger, "Invalid workflow", e);
57
+ }
29
58
  };
30
59
  function parseAdaptors(opts) {
31
60
  const extractInfo = (specifier) => {
@@ -66,14 +95,14 @@ import { writeFile } from "node:fs/promises";
66
95
  var serializeOutput = async (options, result, logger) => {
67
96
  let output = result;
68
97
  if (output && (output.configuration || output.data)) {
69
- const { data, configuration, ...rest } = result;
70
- if (options.strictOutput !== false) {
71
- output = { data };
98
+ if (options.strict) {
99
+ output = { data: output.data };
100
+ if (result.errors) {
101
+ output.errors = result.errors;
102
+ }
72
103
  } else {
73
- output = {
74
- data,
75
- ...rest
76
- };
104
+ const { configuration, ...rest } = result;
105
+ output = rest;
77
106
  }
78
107
  }
79
108
  if (output === void 0) {
@@ -83,10 +112,11 @@ var serializeOutput = async (options, result, logger) => {
83
112
  }
84
113
  if (options.outputStdout) {
85
114
  logger.success(`Result: `);
86
- logger.success(output);
115
+ logger.always(output);
87
116
  } else if (options.outputPath) {
88
- logger.success(`Writing output to ${options.outputPath}`);
117
+ logger.debug(`Writing output to ${options.outputPath}`);
89
118
  await writeFile(options.outputPath, output);
119
+ logger.success(`State written to ${options.outputPath}`);
90
120
  }
91
121
  return output;
92
122
  };
@@ -190,16 +220,28 @@ var compile_default = async (opts, log) => {
190
220
  if (opts.workflow) {
191
221
  job = compileWorkflow(opts.workflow, opts, log);
192
222
  } else {
193
- const compilerOptions = await loadTransformOptions(opts, log);
194
- job = compile(opts.job || opts.jobPath, compilerOptions);
223
+ job = await compileJob(opts.job || opts.jobPath, opts, log);
195
224
  }
196
225
  if (opts.jobPath) {
197
- log.success(`Compiled job from ${opts.jobPath}`);
226
+ log.success(`Compiled from ${opts.jobPath}`);
198
227
  } else {
199
- log.success("Compiled job");
228
+ log.success("Compilation complete");
200
229
  }
201
230
  return job;
202
231
  };
232
+ var compileJob = async (job, opts, log, jobName) => {
233
+ try {
234
+ const compilerOptions = await loadTransformOptions(opts, log);
235
+ return compile(job, compilerOptions);
236
+ } catch (e) {
237
+ abort_default(
238
+ log,
239
+ `Failed to compile job ${jobName ?? ""}`.trim(),
240
+ e,
241
+ "Check the syntax of the job expression:\n\n" + job
242
+ );
243
+ }
244
+ };
203
245
  var compileWorkflow = async (workflow, opts, log) => {
204
246
  for (const job of workflow.jobs) {
205
247
  const jobOpts = {
@@ -208,9 +250,13 @@ var compileWorkflow = async (workflow, opts, log) => {
208
250
  if (job.adaptor) {
209
251
  jobOpts.adaptors = [job.adaptor];
210
252
  }
211
- const compilerOptions = await loadTransformOptions(jobOpts, log);
212
253
  if (job.expression) {
213
- job.expression = compile(job.expression, compilerOptions);
254
+ job.expression = await compileJob(
255
+ job.expression,
256
+ jobOpts,
257
+ log,
258
+ job.id
259
+ );
214
260
  }
215
261
  }
216
262
  return workflow;
@@ -340,7 +386,6 @@ import path from "node:path";
340
386
  import fs2 from "node:fs/promises";
341
387
  import { isPath } from "@openfn/compiler";
342
388
  var load_input_default = async (opts, log) => {
343
- log.debug("Loading input...");
344
389
  const { job, workflow, jobPath, workflowPath } = opts;
345
390
  if (workflow || workflowPath) {
346
391
  return loadWorkflow(opts, log);
@@ -349,36 +394,94 @@ var load_input_default = async (opts, log) => {
349
394
  return job;
350
395
  }
351
396
  if (jobPath) {
352
- log.debug(`Loading job from ${jobPath}`);
353
- opts.job = await fs2.readFile(jobPath, "utf8");
354
- return opts.job;
397
+ try {
398
+ log.debug(`Loading job from ${jobPath}`);
399
+ opts.job = await fs2.readFile(jobPath, "utf8");
400
+ return opts.job;
401
+ } catch (e) {
402
+ abort_default(
403
+ log,
404
+ "Job not found",
405
+ void 0,
406
+ `Failed to load the job from ${jobPath}`
407
+ );
408
+ }
355
409
  }
356
410
  };
357
- var fetchFile = (rootDir, filePath) => {
358
- const jobPath = filePath.startsWith("~") ? filePath : path.resolve(rootDir, filePath);
359
- return fs2.readFile(jobPath, "utf8");
360
- };
361
411
  var loadWorkflow = async (opts, log) => {
362
412
  const { workflowPath, workflow } = opts;
413
+ const readWorkflow = async () => {
414
+ try {
415
+ const text = await fs2.readFile(workflowPath, "utf8");
416
+ return text;
417
+ } catch (e) {
418
+ abort_default(
419
+ log,
420
+ "Workflow not found",
421
+ void 0,
422
+ `Failed to load a workflow from ${workflowPath}`
423
+ );
424
+ }
425
+ };
426
+ const parseWorkflow = (contents) => {
427
+ try {
428
+ return JSON.parse(contents);
429
+ } catch (e) {
430
+ abort_default(
431
+ log,
432
+ "Invalid JSON in workflow",
433
+ e,
434
+ `Check the syntax of the JSON at ${workflowPath}`
435
+ );
436
+ }
437
+ };
438
+ const fetchWorkflowFile = async (jobId, rootDir = "", filePath) => {
439
+ try {
440
+ const fullPath = filePath.startsWith("~") ? filePath : path.resolve(rootDir, filePath);
441
+ const result = await fs2.readFile(fullPath, "utf8");
442
+ return result;
443
+ } catch (e) {
444
+ abort_default(
445
+ log,
446
+ `File not found for job ${jobId}: ${filePath}`,
447
+ void 0,
448
+ `This workflow references a file which cannot be found at ${filePath}
449
+
450
+ Paths inside the workflow are relative to the workflow.json`
451
+ );
452
+ }
453
+ };
363
454
  log.debug(`Loading workflow from ${workflowPath}`);
364
455
  try {
365
456
  let wf;
366
457
  let rootDir = opts.baseDir;
367
458
  if (workflowPath) {
368
- const workflowRaw = await fs2.readFile(workflowPath, "utf8");
369
- wf = JSON.parse(workflowRaw);
459
+ let workflowRaw = await readWorkflow();
460
+ wf = parseWorkflow(workflowRaw);
370
461
  if (!rootDir) {
371
462
  rootDir = path.dirname(workflowPath);
372
463
  }
373
464
  } else {
374
465
  wf = workflow;
375
466
  }
467
+ let idx = 0;
376
468
  for (const job of wf.jobs) {
377
- if (typeof job.expression === "string" && isPath(job.expression)) {
378
- job.expression = await fetchFile(rootDir, job.expression);
469
+ idx += 1;
470
+ const expressionStr = typeof job.expression === "string" && job.expression?.trim();
471
+ const configurationStr = typeof job.configuration === "string" && job.configuration?.trim();
472
+ if (expressionStr && isPath(expressionStr)) {
473
+ job.expression = await fetchWorkflowFile(
474
+ job.id || `${idx}`,
475
+ rootDir,
476
+ expressionStr
477
+ );
379
478
  }
380
- if (typeof job.configuration === "string" && isPath(job.configuration)) {
381
- const configString = await fetchFile(rootDir, job.configuration);
479
+ if (configurationStr && isPath(configurationStr)) {
480
+ const configString = await fetchWorkflowFile(
481
+ job.id || `${idx}`,
482
+ rootDir,
483
+ configurationStr
484
+ );
382
485
  job.configuration = JSON.parse(configString);
383
486
  }
384
487
  }
@@ -418,15 +521,23 @@ var executeHandler = async (options, logger) => {
418
521
  logger.info("Skipping compilation as noCompile is set");
419
522
  }
420
523
  try {
421
- const result = await execute_default(input, state, options);
524
+ const result = await execute_default(input, state, options, logger);
422
525
  await serialize_output_default(options, result, logger);
423
526
  const duration = printDuration(new Date().getTime() - start);
424
- logger.success(`Done in ${duration}! \u2728`);
527
+ if (result.errors) {
528
+ logger.warn(
529
+ `Errors reported in ${Object.keys(result.errors).length} jobs`
530
+ );
531
+ }
532
+ logger.success(`Finished in ${duration}${result.errors ? "" : " \u2728"}`);
425
533
  return result;
426
- } catch (error) {
427
- logger.error(error);
534
+ } catch (err) {
535
+ if (!err.handled) {
536
+ logger.error("Unexpected error in execution");
537
+ logger.error(err);
538
+ }
428
539
  const duration = printDuration(new Date().getTime() - start);
429
- logger.error(`Took ${duration}.`);
540
+ logger.always(`Workflow failed in ${duration}.`);
430
541
  process.exitCode = 1;
431
542
  }
432
543
  };
@@ -964,9 +1075,12 @@ var parse = async (basePath, options, log) => {
964
1075
  if (!process.exitCode) {
965
1076
  process.exitCode = e.exitCode || 1;
966
1077
  }
967
- logger.break();
968
- logger.error("Command failed!");
969
- logger.error(e);
1078
+ if (e.handled) {
1079
+ } else {
1080
+ logger.break();
1081
+ logger.error("Command failed!");
1082
+ logger.error(e);
1083
+ }
970
1084
  }
971
1085
  };
972
1086
  var commands_default = parse;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/cli",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "CLI devtools for the openfn toolchain.",
5
5
  "engines": {
6
6
  "node": ">=18",
@@ -36,10 +36,10 @@
36
36
  "typescript": "^4.7.4"
37
37
  },
38
38
  "dependencies": {
39
- "@openfn/compiler": "0.0.31",
39
+ "@openfn/compiler": "0.0.32",
40
40
  "@openfn/describe-package": "0.0.16",
41
- "@openfn/logger": "0.0.12",
42
- "@openfn/runtime": "0.0.23",
41
+ "@openfn/logger": "0.0.13",
42
+ "@openfn/runtime": "0.0.24",
43
43
  "figures": "^5.0.0",
44
44
  "rimraf": "^3.0.2",
45
45
  "treeify": "^1.1.0",