@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 +0 -3
- package/dist/{chunk-DOKL2XM5.js → chunk-LV5XDERP.js} +0 -1
- package/dist/index.js +22 -4
- package/dist/process/runner.js +164 -50
- package/package.json +4 -4
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-
|
|
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: "
|
|
293
|
+
description: "Strict state handling, meaning only state.data is returned from a job."
|
|
279
294
|
},
|
|
280
295
|
ensure: (opts2) => {
|
|
281
|
-
|
|
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
|
|
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"],
|
package/dist/process/runner.js
CHANGED
|
@@ -10,22 +10,51 @@ import {
|
|
|
10
10
|
expand_adaptors_default,
|
|
11
11
|
logger_default,
|
|
12
12
|
printDuration
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-LV5XDERP.js";
|
|
14
14
|
|
|
15
15
|
// src/execute/execute.ts
|
|
16
16
|
import run, { getNameAndVersion } from "@openfn/runtime";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
if (options.strict) {
|
|
99
|
+
output = { data: output.data };
|
|
100
|
+
if (result.errors) {
|
|
101
|
+
output.errors = result.errors;
|
|
102
|
+
}
|
|
72
103
|
} else {
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
|
115
|
+
logger.always(output);
|
|
87
116
|
} else if (options.outputPath) {
|
|
88
|
-
logger.
|
|
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
|
-
|
|
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
|
|
226
|
+
log.success(`Compiled from ${opts.jobPath}`);
|
|
198
227
|
} else {
|
|
199
|
-
log.success("
|
|
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 =
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
369
|
-
wf =
|
|
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
|
-
|
|
378
|
-
|
|
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 (
|
|
381
|
-
const configString = await
|
|
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
|
-
|
|
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 (
|
|
427
|
-
|
|
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.
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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.
|
|
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.
|
|
39
|
+
"@openfn/compiler": "0.0.32",
|
|
40
40
|
"@openfn/describe-package": "0.0.16",
|
|
41
|
-
"@openfn/logger": "0.0.
|
|
42
|
-
"@openfn/runtime": "0.0.
|
|
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",
|