@openfn/cli 0.0.35 → 0.0.38
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 +70 -21
- package/dist/{chunk-XYZNU5CH.js → chunk-DOKL2XM5.js} +20 -4
- package/dist/index.js +35 -29
- package/dist/process/runner.js +372 -204
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @openfn/cli
|
|
2
2
|
|
|
3
|
-
This package contains a new devtools CLI for running
|
|
3
|
+
This package contains a new devtools CLI for running OpenFn jobs.
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The CLI includes:
|
|
6
6
|
|
|
7
|
-
* A
|
|
8
|
-
* A
|
|
9
|
-
*
|
|
10
|
-
* Auto
|
|
7
|
+
* A secure runtime for executing OpenFn jobs and workflows
|
|
8
|
+
* A compiler for making OpenFn jobs runnable
|
|
9
|
+
* Configurable logging output
|
|
10
|
+
* Auto-installation of language adaptors
|
|
11
11
|
* Support for the adaptors monorepo
|
|
12
12
|
|
|
13
13
|
## Getting Started
|
|
@@ -36,6 +36,22 @@ Get help:
|
|
|
36
36
|
openfn help
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
## Updating
|
|
40
|
+
|
|
41
|
+
You should be able to install a new version straight on top of your current installation:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
npm install -g @openfn/cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If this fails, try uninstalling the current version first:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
npm uninstall -g @openfn/cli
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
And then re-installing.
|
|
54
|
+
|
|
39
55
|
## Migrating from devtools
|
|
40
56
|
|
|
41
57
|
If you're coming to the CLI from the old openfn devtools, here are a couple of key points to be aware of:
|
|
@@ -46,31 +62,37 @@ If you're coming to the CLI from the old openfn devtools, here are a couple of k
|
|
|
46
62
|
|
|
47
63
|
## Basic Usage
|
|
48
64
|
|
|
49
|
-
You're probably here to run jobs (expressions), which the CLI makes easy:
|
|
65
|
+
You're probably here to run jobs (expressions) or workflows, which the CLI makes easy:
|
|
66
|
+
|
|
50
67
|
|
|
51
68
|
```
|
|
69
|
+
openfn path/to/workflow.json
|
|
52
70
|
openfn path/to/job.js -ia adaptor-name
|
|
53
71
|
```
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
If running a single job, you MUST specify which adaptor to use.
|
|
74
|
+
|
|
75
|
+
Pass the `-i` flag to auto-install any required adaptors (it's safe to do this redundantly, although the run will be a little slower).
|
|
56
76
|
|
|
57
|
-
When the
|
|
77
|
+
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.
|
|
58
78
|
|
|
59
|
-
|
|
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
|
+
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.
|
|
60
82
|
|
|
61
83
|
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`.
|
|
62
84
|
|
|
63
|
-
If you have the adaptors monorepo set up on your machine, you can also run adaptors straight from
|
|
85
|
+
If you have the adaptors monorepo set up on your machine, you can also run adaptors straight from the local build. Pass the `-m <path>` flag to load from the monorepo. You can also set the monorepo location by setting the `OPENFN_ADAPTORS_REPO` env var to a valid path. After that just include `-m` to load from the monorepo. Remember that adaptors will be loaded from the BUILT package in `dist`, so remember to build an adaptor before running!
|
|
64
86
|
|
|
65
87
|
You can pass `--log info` to get more feedback about what's happening, or `--log debug` for more details than you could ever use.
|
|
66
88
|
|
|
67
89
|
## Advanced Usage
|
|
68
90
|
|
|
69
|
-
The CLI has
|
|
91
|
+
The CLI has a number of commands (the first argument after openfn)
|
|
70
92
|
|
|
71
93
|
* execute - run a job
|
|
72
94
|
* compile - compile a job to a .js file
|
|
73
|
-
*
|
|
95
|
+
* docs - show documentation for an adaptor function
|
|
74
96
|
* repo - manage the repo of installed modules
|
|
75
97
|
* docgen - generate JSON documentation for an adaptor based on its typescript
|
|
76
98
|
|
|
@@ -109,9 +131,42 @@ For a more structured output, you can emit logs as JSON objects with `level`, `n
|
|
|
109
131
|
```
|
|
110
132
|
{ level: 'info', name: 'CLI', message: ['Loaded adaptor'] }
|
|
111
133
|
```
|
|
112
|
-
|
|
113
134
|
Pass `--log-json` to the CLI to do this. You can also set the OPENFN_LOG_JSON env var (and use `--no-log-json` to disable).
|
|
114
135
|
|
|
136
|
+
## Workflows
|
|
137
|
+
|
|
138
|
+
As of v0.0.35 the CLI supports running workflows as well as jobs.
|
|
139
|
+
|
|
140
|
+
A workflow is in execution plan for running several jobs in a sequence. It is defined as a JSON structure.
|
|
141
|
+
|
|
142
|
+
To see an example workflow, run the test command with `openfn test`.
|
|
143
|
+
|
|
144
|
+
A workflow has a structure like this (better documentation is coming soon):
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
{
|
|
148
|
+
"start": "a", // optionally specify the start node (defaults to jobs[0])
|
|
149
|
+
"jobs": [
|
|
150
|
+
{
|
|
151
|
+
"id": "a",
|
|
152
|
+
"expression": "fn((state) => state)", // code or a path
|
|
153
|
+
"adaptor": "@openfn/language-common@1.75", // specifiy the adaptor to use (version optional)
|
|
154
|
+
"data": {}, // optionally pre-populate the data object (this will be overriden by keys in in previous state)
|
|
155
|
+
"configuration": {}, // Use this to pass credentials
|
|
156
|
+
"next": {
|
|
157
|
+
// This object defines which jobs to call next
|
|
158
|
+
// All edges returning true will run
|
|
159
|
+
// If there are no next edges, the workflow will end
|
|
160
|
+
"b": true,
|
|
161
|
+
"c": {
|
|
162
|
+
"condition": "!state.error" // Not that this is an expression, not a function
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
115
170
|
## Compilation
|
|
116
171
|
|
|
117
172
|
The CLI will attempt to compile your job code into normalized Javascript. It will do a number of things to make your code robust and portable:
|
|
@@ -129,12 +184,6 @@ All jobs which work against `@openfn/core` will work in the new CLI and runtime
|
|
|
129
184
|
|
|
130
185
|
If you want to see how the compiler is changing your job, run `openfn compile path/to/job -a <adaptor>` to return the compiled code to stdout. Add `-o path/to/output.js` to save the result to disk.
|
|
131
186
|
|
|
132
|
-
## New Runtime notes
|
|
133
|
-
|
|
134
|
-
The new OpenFn runtime will create a secure sandboxed environemtn which loads a Javascript Module, finds the default export, and execute the functions held within it.
|
|
135
|
-
|
|
136
|
-
So long as your job has an array of functions as its default export, it will run in the new runtime.
|
|
137
|
-
|
|
138
187
|
# Contributing
|
|
139
188
|
|
|
140
189
|
First of all, thanks for helping! You're contributing to a digital public good that will always be free and open source and aimed at serving innovative NGOs, governments, and social impact organizations the world over! You rock. heart
|
|
@@ -170,8 +219,8 @@ The CLI will save and load adaptors from an arbitrary folder on your system.
|
|
|
170
219
|
|
|
171
220
|
You should set the OPENFN_REPO_DIR env var to something sensible.
|
|
172
221
|
|
|
222
|
+
In `~/.bashrc` (or whatever you use), add:
|
|
173
223
|
```
|
|
174
|
-
# In ~/.bashc or whatever
|
|
175
224
|
export OPENFN_REPO_DIR=~/repo/openfn/cli-repo
|
|
176
225
|
```
|
|
177
226
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/util/expand-adaptors.ts
|
|
2
|
-
var
|
|
2
|
+
var expand = (name) => {
|
|
3
3
|
if (typeof name === "string") {
|
|
4
4
|
const [left] = name.split("=");
|
|
5
5
|
if (left.match("/") || left.endsWith(".js")) {
|
|
@@ -8,7 +8,21 @@ var expand_adaptors_default = (names) => names?.map((name) => {
|
|
|
8
8
|
return `@openfn/language-${name}`;
|
|
9
9
|
}
|
|
10
10
|
return name;
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
|
+
var expand_adaptors_default = (opts) => {
|
|
13
|
+
const { adaptors, workflow } = opts;
|
|
14
|
+
if (adaptors) {
|
|
15
|
+
opts.adaptors = adaptors?.map(expand);
|
|
16
|
+
}
|
|
17
|
+
if (workflow) {
|
|
18
|
+
Object.values(workflow.jobs).forEach((job) => {
|
|
19
|
+
if (job.adaptor) {
|
|
20
|
+
job.adaptor = expand(job.adaptor);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return opts;
|
|
25
|
+
};
|
|
12
26
|
|
|
13
27
|
// src/util/logger.ts
|
|
14
28
|
import actualCreateLogger, { printDuration } from "@openfn/logger";
|
|
@@ -24,13 +38,15 @@ var namespaces = {
|
|
|
24
38
|
[JOB]: "JOB"
|
|
25
39
|
};
|
|
26
40
|
var createLogger = (name = "", options) => {
|
|
27
|
-
const logOptions = options.log;
|
|
41
|
+
const logOptions = options.log || {};
|
|
42
|
+
let json = false;
|
|
28
43
|
let level = logOptions[name] || logOptions.default || "default";
|
|
29
44
|
if (options.logJson) {
|
|
30
|
-
|
|
45
|
+
json = true;
|
|
31
46
|
}
|
|
32
47
|
return actualCreateLogger(namespaces[name] || name, {
|
|
33
48
|
level,
|
|
49
|
+
json,
|
|
34
50
|
...logOptions
|
|
35
51
|
});
|
|
36
52
|
};
|
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-DOKL2XM5.js";
|
|
6
6
|
|
|
7
7
|
// src/process/spawn.ts
|
|
8
8
|
import path from "node:path";
|
|
@@ -128,7 +128,7 @@ var adaptors = {
|
|
|
128
128
|
opts2.adaptors = [];
|
|
129
129
|
}
|
|
130
130
|
if (opts2.expandAdaptors) {
|
|
131
|
-
|
|
131
|
+
expand_adaptors_default(opts2);
|
|
132
132
|
}
|
|
133
133
|
delete opts2.adaptor;
|
|
134
134
|
delete opts2.a;
|
|
@@ -193,19 +193,21 @@ var ignoreImports = {
|
|
|
193
193
|
};
|
|
194
194
|
var getBaseDir = (opts2) => {
|
|
195
195
|
const basePath = opts2.path ?? ".";
|
|
196
|
-
if (
|
|
196
|
+
if (/\.(jso?n?)$/.test(basePath)) {
|
|
197
197
|
return path2.dirname(basePath);
|
|
198
198
|
}
|
|
199
199
|
return basePath;
|
|
200
200
|
};
|
|
201
|
-
var
|
|
202
|
-
name: "
|
|
201
|
+
var inputPath = {
|
|
202
|
+
name: "input-path",
|
|
203
203
|
yargs: {
|
|
204
204
|
hidden: true
|
|
205
205
|
},
|
|
206
206
|
ensure: (opts2) => {
|
|
207
207
|
const { path: basePath } = opts2;
|
|
208
|
-
if (basePath?.endsWith(".
|
|
208
|
+
if (basePath?.endsWith(".json")) {
|
|
209
|
+
opts2.workflowPath = basePath;
|
|
210
|
+
} else if (basePath?.endsWith(".js")) {
|
|
209
211
|
opts2.jobPath = basePath;
|
|
210
212
|
} else {
|
|
211
213
|
const base = getBaseDir(opts2);
|
|
@@ -262,6 +264,13 @@ var repoDir = {
|
|
|
262
264
|
default: process.env.OPENFN_REPO_DIR || DEFAULT_REPO_DIR
|
|
263
265
|
}
|
|
264
266
|
};
|
|
267
|
+
var start = {
|
|
268
|
+
name: "start",
|
|
269
|
+
yargs: {
|
|
270
|
+
string: true,
|
|
271
|
+
description: "Specifiy the start node in a workflow"
|
|
272
|
+
}
|
|
273
|
+
};
|
|
265
274
|
var strictOutput = {
|
|
266
275
|
name: "no-strict-output",
|
|
267
276
|
yargs: {
|
|
@@ -325,12 +334,13 @@ var options = [
|
|
|
325
334
|
compile,
|
|
326
335
|
immutable,
|
|
327
336
|
ignoreImports,
|
|
328
|
-
|
|
337
|
+
inputPath,
|
|
329
338
|
logJson,
|
|
330
339
|
outputPath,
|
|
331
340
|
outputStdout,
|
|
332
341
|
repoDir,
|
|
333
342
|
skipAdaptorValidation,
|
|
343
|
+
start,
|
|
334
344
|
statePath,
|
|
335
345
|
stateStdin,
|
|
336
346
|
strictOutput,
|
|
@@ -339,9 +349,9 @@ var options = [
|
|
|
339
349
|
];
|
|
340
350
|
var executeCommand = {
|
|
341
351
|
command: "execute [path]",
|
|
342
|
-
desc: `Run an openfn job. Get more help by running openfn <command> help.
|
|
352
|
+
desc: `Run an openfn job or workflow. Get more help by running openfn <command> help.
|
|
343
353
|
|
|
344
|
-
Execute will run a job/
|
|
354
|
+
Execute will run a job/workflow at the path and write the output state to disk (to ./state.json unless otherwise specified)
|
|
345
355
|
|
|
346
356
|
By default only state.data will be written to the output. Include --no-strict-output to write the entire state object.
|
|
347
357
|
|
|
@@ -349,14 +359,14 @@ Remember to include the adaptor name with -a. Auto install adaptors with the -i
|
|
|
349
359
|
aliases: ["$0"],
|
|
350
360
|
handler: ensure("execute", options),
|
|
351
361
|
builder: (yargs2) => build(options, yargs2).positional("path", {
|
|
352
|
-
describe: "The path to load the job from (a .js file or a dir containing a job.js file)",
|
|
362
|
+
describe: "The path to load the job or workflow from (a .js or .json file or a dir containing a job.js file)",
|
|
353
363
|
demandOption: true
|
|
354
364
|
}).example(
|
|
355
365
|
"openfn foo/job.js",
|
|
356
366
|
"Execute foo/job.js with no adaptor and write the final state to foo/job.json"
|
|
357
367
|
).example(
|
|
358
|
-
"openfn
|
|
359
|
-
"Execute
|
|
368
|
+
"openfn workflow.json -ia common",
|
|
369
|
+
"Execute workflow.json using @openfn/language-commom (with autoinstall enabled)"
|
|
360
370
|
).example(
|
|
361
371
|
"openfn job.js -a common --log info",
|
|
362
372
|
"Execute job.js with common adaptor and info-level logging"
|
|
@@ -372,7 +382,7 @@ var options2 = [
|
|
|
372
382
|
expandAdaptors,
|
|
373
383
|
adaptors,
|
|
374
384
|
ignoreImports,
|
|
375
|
-
|
|
385
|
+
inputPath,
|
|
376
386
|
logJson,
|
|
377
387
|
override(outputStdout, {
|
|
378
388
|
default: true
|
|
@@ -383,32 +393,28 @@ var options2 = [
|
|
|
383
393
|
];
|
|
384
394
|
var compileCommand = {
|
|
385
395
|
command: "compile [path]",
|
|
386
|
-
desc: "Compile an openfn job and print or save the resulting JavaScript.",
|
|
396
|
+
desc: "Compile an openfn job or workflow and print or save the resulting JavaScript.",
|
|
387
397
|
handler: ensure("compile", options2),
|
|
388
398
|
builder: (yargs2) => build(options2, yargs2).positional("path", {
|
|
389
|
-
describe: "The path to load the job from (a .js file or a dir containing a job.js file)",
|
|
399
|
+
describe: "The path to load the job or workflow from (a .js or .json file or a dir containing a job.js file)",
|
|
390
400
|
demandOption: true
|
|
391
401
|
}).example(
|
|
392
|
-
"compile foo/job.js
|
|
393
|
-
"Compiles foo/job.js and prints the result to stdout"
|
|
402
|
+
"compile foo/job.js",
|
|
403
|
+
"Compiles the job at foo/job.js and prints the result to stdout"
|
|
394
404
|
).example(
|
|
395
|
-
"compile foo/
|
|
396
|
-
"Compiles foo/
|
|
405
|
+
"compile foo/workflow.json -o foo/workflow-compiled.json",
|
|
406
|
+
"Compiles the workflow at foo/work.json and prints the result to -o foo/workflow-compiled.json"
|
|
397
407
|
)
|
|
398
408
|
};
|
|
399
409
|
var command_default2 = compileCommand;
|
|
400
410
|
|
|
401
411
|
// src/test/command.ts
|
|
412
|
+
var options3 = [stateStdin];
|
|
402
413
|
var command_default3 = {
|
|
403
414
|
command: "test",
|
|
404
415
|
desc: "Compiles and runs a test job, printing the result to stdout",
|
|
405
|
-
handler: (
|
|
406
|
-
|
|
407
|
-
},
|
|
408
|
-
builder: (yargs2) => yargs2.option("state-stdin", {
|
|
409
|
-
alias: "S",
|
|
410
|
-
description: "Read state from stdin (instead of a file)"
|
|
411
|
-
}).example("test", "run the test script").example("test -S 42", "run the test script with state 42")
|
|
416
|
+
handler: ensure("test", options3),
|
|
417
|
+
builder: (yargs2) => build(options3, yargs2).example("test", "Run the test script")
|
|
412
418
|
};
|
|
413
419
|
|
|
414
420
|
// src/docgen/command.ts
|
|
@@ -435,7 +441,7 @@ var command_default5 = {
|
|
|
435
441
|
};
|
|
436
442
|
|
|
437
443
|
// src/metadata/command.ts
|
|
438
|
-
var
|
|
444
|
+
var options4 = [
|
|
439
445
|
expandAdaptors,
|
|
440
446
|
adaptors,
|
|
441
447
|
force,
|
|
@@ -448,8 +454,8 @@ var options3 = [
|
|
|
448
454
|
var command_default6 = {
|
|
449
455
|
command: "metadata",
|
|
450
456
|
desc: "Generate metadata for an adaptor config",
|
|
451
|
-
handler: ensure("metadata",
|
|
452
|
-
builder: (yargs2) => build(
|
|
457
|
+
handler: ensure("metadata", options4),
|
|
458
|
+
builder: (yargs2) => build(options4, yargs2).example(
|
|
453
459
|
"metadata -a salesforce -s tmp/state.json",
|
|
454
460
|
"Generate salesforce metadata from config in state.json"
|
|
455
461
|
)
|
package/dist/process/runner.js
CHANGED
|
@@ -10,54 +10,13 @@ import {
|
|
|
10
10
|
expand_adaptors_default,
|
|
11
11
|
logger_default,
|
|
12
12
|
printDuration
|
|
13
|
-
} from "../chunk-
|
|
14
|
-
|
|
15
|
-
// src/execute/handler.ts
|
|
16
|
-
import { readFile } from "node:fs/promises";
|
|
17
|
-
|
|
18
|
-
// src/execute/load-state.ts
|
|
19
|
-
import fs from "node:fs/promises";
|
|
20
|
-
var load_state_default = async (opts, log) => {
|
|
21
|
-
const { stateStdin, statePath } = opts;
|
|
22
|
-
log.debug("Load state...");
|
|
23
|
-
if (stateStdin) {
|
|
24
|
-
try {
|
|
25
|
-
const json = JSON.parse(stateStdin);
|
|
26
|
-
log.success("Read state from stdin");
|
|
27
|
-
log.debug("state:", json);
|
|
28
|
-
return json;
|
|
29
|
-
} catch (e) {
|
|
30
|
-
log.error("Failed to load state from stdin");
|
|
31
|
-
log.error(stateStdin);
|
|
32
|
-
log.error(e);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (statePath) {
|
|
37
|
-
try {
|
|
38
|
-
const str = await fs.readFile(statePath, "utf8");
|
|
39
|
-
const json = JSON.parse(str);
|
|
40
|
-
log.success(`Loaded state from ${statePath}`);
|
|
41
|
-
log.debug("state:", json);
|
|
42
|
-
return json;
|
|
43
|
-
} catch (e) {
|
|
44
|
-
log.warn(`Error loading state from ${statePath}`);
|
|
45
|
-
log.warn(e);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
log.info(
|
|
49
|
-
"No state provided - using default state { data: {}, configuration: {}"
|
|
50
|
-
);
|
|
51
|
-
return {
|
|
52
|
-
data: {},
|
|
53
|
-
configuration: {}
|
|
54
|
-
};
|
|
55
|
-
};
|
|
13
|
+
} from "../chunk-DOKL2XM5.js";
|
|
56
14
|
|
|
57
15
|
// src/execute/execute.ts
|
|
58
16
|
import run, { getNameAndVersion } from "@openfn/runtime";
|
|
59
|
-
var execute_default = (
|
|
60
|
-
return run(
|
|
17
|
+
var execute_default = (input, state, opts) => {
|
|
18
|
+
return run(input, state, {
|
|
19
|
+
start: opts.start,
|
|
61
20
|
timeout: opts.timeout,
|
|
62
21
|
immutableState: opts.immutable,
|
|
63
22
|
logger: logger_default(RUNTIME, opts),
|
|
@@ -69,96 +28,41 @@ var execute_default = (code, state, opts) => {
|
|
|
69
28
|
});
|
|
70
29
|
};
|
|
71
30
|
function parseAdaptors(opts) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const [module, path5] = exp.split("=");
|
|
31
|
+
const extractInfo = (specifier) => {
|
|
32
|
+
const [module, path6] = specifier.split("=");
|
|
75
33
|
const { name, version } = getNameAndVersion(module);
|
|
76
|
-
const info = {
|
|
77
|
-
|
|
78
|
-
|
|
34
|
+
const info = {
|
|
35
|
+
name
|
|
36
|
+
};
|
|
37
|
+
if (path6) {
|
|
38
|
+
info.path = path6;
|
|
79
39
|
}
|
|
80
40
|
if (version) {
|
|
81
41
|
info.version = version;
|
|
82
42
|
}
|
|
83
|
-
|
|
84
|
-
return obj;
|
|
85
|
-
}, adaptors);
|
|
86
|
-
return adaptors;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/compile/compile.ts
|
|
90
|
-
import compile, { preloadAdaptorExports } from "@openfn/compiler";
|
|
91
|
-
import { getModulePath } from "@openfn/runtime";
|
|
92
|
-
var compile_default = async (opts, log) => {
|
|
93
|
-
log.debug("Loading job...");
|
|
94
|
-
const compilerOptions = await loadTransformOptions(opts, log);
|
|
95
|
-
const job = compile(opts.jobSource || opts.jobPath, compilerOptions);
|
|
96
|
-
if (opts.jobPath) {
|
|
97
|
-
log.success(`Compiled job from ${opts.jobPath}`);
|
|
98
|
-
} else {
|
|
99
|
-
log.success("Compiled job");
|
|
100
|
-
}
|
|
101
|
-
return job;
|
|
102
|
-
};
|
|
103
|
-
var stripVersionSpecifier = (specifier) => {
|
|
104
|
-
const idx = specifier.lastIndexOf("@");
|
|
105
|
-
if (idx > 0) {
|
|
106
|
-
return specifier.substring(0, idx);
|
|
107
|
-
}
|
|
108
|
-
return specifier;
|
|
109
|
-
};
|
|
110
|
-
var resolveSpecifierPath = async (pattern, repoDir, log) => {
|
|
111
|
-
const [specifier, path5] = pattern.split("=");
|
|
112
|
-
if (path5) {
|
|
113
|
-
log.debug(`Resolved ${specifier} to path: ${path5}`);
|
|
114
|
-
return path5;
|
|
115
|
-
}
|
|
116
|
-
const repoPath = await getModulePath(specifier, repoDir, log);
|
|
117
|
-
if (repoPath) {
|
|
118
|
-
return repoPath;
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
};
|
|
122
|
-
var loadTransformOptions = async (opts, log) => {
|
|
123
|
-
const options = {
|
|
124
|
-
logger: log || logger_default(COMPILER, opts)
|
|
43
|
+
return info;
|
|
125
44
|
};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} catch (e) {
|
|
140
|
-
log.error(`Failed to load adaptor typedefs from path ${path5}`);
|
|
141
|
-
log.error(e);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (!exports || exports.length === 0) {
|
|
145
|
-
log.debug(`No module exports found for ${pattern}`);
|
|
146
|
-
}
|
|
147
|
-
options["add-imports"] = {
|
|
148
|
-
ignore: opts.ignoreImports,
|
|
149
|
-
adaptor: {
|
|
150
|
-
name: stripVersionSpecifier(specifier),
|
|
151
|
-
exports,
|
|
152
|
-
exportAll: true
|
|
45
|
+
const adaptors = {};
|
|
46
|
+
if (opts.adaptors) {
|
|
47
|
+
opts.adaptors.reduce((obj, exp) => {
|
|
48
|
+
const { name, ...maybeVersionAndPath } = extractInfo(exp);
|
|
49
|
+
obj[name] = { ...maybeVersionAndPath };
|
|
50
|
+
return obj;
|
|
51
|
+
}, adaptors);
|
|
52
|
+
}
|
|
53
|
+
if (opts.workflow) {
|
|
54
|
+
Object.values(opts.workflow.jobs).forEach((job) => {
|
|
55
|
+
if (job.adaptor) {
|
|
56
|
+
const { name, ...maybeVersionAndPath } = extractInfo(job.adaptor);
|
|
57
|
+
adaptors[name] = { ...maybeVersionAndPath };
|
|
153
58
|
}
|
|
154
|
-
};
|
|
59
|
+
});
|
|
155
60
|
}
|
|
156
|
-
return
|
|
157
|
-
}
|
|
61
|
+
return adaptors;
|
|
62
|
+
}
|
|
158
63
|
|
|
159
64
|
// src/execute/serialize-output.ts
|
|
160
65
|
import { writeFile } from "node:fs/promises";
|
|
161
|
-
import stringify from "fast-safe-stringify";
|
|
162
66
|
var serializeOutput = async (options, result, logger) => {
|
|
163
67
|
let output = result;
|
|
164
68
|
if (output && (output.configuration || output.data)) {
|
|
@@ -175,7 +79,7 @@ var serializeOutput = async (options, result, logger) => {
|
|
|
175
79
|
if (output === void 0) {
|
|
176
80
|
output = "";
|
|
177
81
|
} else {
|
|
178
|
-
output = stringify(output, void 0, 2);
|
|
82
|
+
output = JSON.stringify(output, void 0, 2);
|
|
179
83
|
}
|
|
180
84
|
if (options.outputStdout) {
|
|
181
85
|
logger.success(`Result: `);
|
|
@@ -188,6 +92,24 @@ var serializeOutput = async (options, result, logger) => {
|
|
|
188
92
|
};
|
|
189
93
|
var serialize_output_default = serializeOutput;
|
|
190
94
|
|
|
95
|
+
// src/execute/get-autoinstall-targets.ts
|
|
96
|
+
var getAutoinstallTargets = (options) => {
|
|
97
|
+
if (options.workflow) {
|
|
98
|
+
const adaptors = {};
|
|
99
|
+
Object.values(options.workflow.jobs).forEach((job) => {
|
|
100
|
+
if (job.adaptor) {
|
|
101
|
+
adaptors[job.adaptor] = true;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return Object.keys(adaptors);
|
|
105
|
+
}
|
|
106
|
+
if (options.adaptors) {
|
|
107
|
+
return options.adaptors?.filter((a) => !/=/.test(a));
|
|
108
|
+
}
|
|
109
|
+
return [];
|
|
110
|
+
};
|
|
111
|
+
var get_autoinstall_targets_default = getAutoinstallTargets;
|
|
112
|
+
|
|
191
113
|
// src/repo/handler.ts
|
|
192
114
|
import { exec } from "node:child_process";
|
|
193
115
|
import treeify from "treeify";
|
|
@@ -199,9 +121,10 @@ var install = async (opts, log = defaultLogger) => {
|
|
|
199
121
|
log.success("Installing packages...");
|
|
200
122
|
log.debug("repoDir is set to:", repoDir);
|
|
201
123
|
if (adaptor) {
|
|
202
|
-
|
|
124
|
+
const expanded = expand_adaptors_default({ adaptors: packages });
|
|
125
|
+
packages = expanded.adaptors;
|
|
203
126
|
}
|
|
204
|
-
await rtInstall(packages, repoDir, log);
|
|
127
|
+
await rtInstall(packages ?? [], repoDir, log);
|
|
205
128
|
const duration = log.timer("install");
|
|
206
129
|
log.success(`Installation complete in ${duration}`);
|
|
207
130
|
}
|
|
@@ -258,12 +181,149 @@ var list = async (options, logger) => {
|
|
|
258
181
|
logger.success("Installed packages:\n\n" + treeify.asTree(output));
|
|
259
182
|
};
|
|
260
183
|
|
|
184
|
+
// src/compile/compile.ts
|
|
185
|
+
import compile, { preloadAdaptorExports } from "@openfn/compiler";
|
|
186
|
+
import { getModulePath } from "@openfn/runtime";
|
|
187
|
+
var compile_default = async (opts, log) => {
|
|
188
|
+
log.debug("Compiling...");
|
|
189
|
+
let job;
|
|
190
|
+
if (opts.workflow) {
|
|
191
|
+
job = compileWorkflow(opts.workflow, opts, log);
|
|
192
|
+
} else {
|
|
193
|
+
const compilerOptions = await loadTransformOptions(opts, log);
|
|
194
|
+
job = compile(opts.job || opts.jobPath, compilerOptions);
|
|
195
|
+
}
|
|
196
|
+
if (opts.jobPath) {
|
|
197
|
+
log.success(`Compiled job from ${opts.jobPath}`);
|
|
198
|
+
} else {
|
|
199
|
+
log.success("Compiled job");
|
|
200
|
+
}
|
|
201
|
+
return job;
|
|
202
|
+
};
|
|
203
|
+
var compileWorkflow = async (workflow, opts, log) => {
|
|
204
|
+
for (const job of workflow.jobs) {
|
|
205
|
+
const jobOpts = {
|
|
206
|
+
...opts
|
|
207
|
+
};
|
|
208
|
+
if (job.adaptor) {
|
|
209
|
+
jobOpts.adaptors = [job.adaptor];
|
|
210
|
+
}
|
|
211
|
+
const compilerOptions = await loadTransformOptions(jobOpts, log);
|
|
212
|
+
if (job.expression) {
|
|
213
|
+
job.expression = compile(job.expression, compilerOptions);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return workflow;
|
|
217
|
+
};
|
|
218
|
+
var stripVersionSpecifier = (specifier) => {
|
|
219
|
+
const idx = specifier.lastIndexOf("@");
|
|
220
|
+
if (idx > 0) {
|
|
221
|
+
return specifier.substring(0, idx);
|
|
222
|
+
}
|
|
223
|
+
return specifier;
|
|
224
|
+
};
|
|
225
|
+
var resolveSpecifierPath = async (pattern, repoDir, log) => {
|
|
226
|
+
const [specifier, path6] = pattern.split("=");
|
|
227
|
+
if (path6) {
|
|
228
|
+
log.debug(`Resolved ${specifier} to path: ${path6}`);
|
|
229
|
+
return path6;
|
|
230
|
+
}
|
|
231
|
+
const repoPath = await getModulePath(specifier, repoDir, log);
|
|
232
|
+
if (repoPath) {
|
|
233
|
+
return repoPath;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
};
|
|
237
|
+
var loadTransformOptions = async (opts, log) => {
|
|
238
|
+
const options = {
|
|
239
|
+
logger: log || logger_default(COMPILER, opts)
|
|
240
|
+
};
|
|
241
|
+
if (opts.adaptors?.length && opts.ignoreImports != true) {
|
|
242
|
+
let exports;
|
|
243
|
+
const [pattern] = opts.adaptors;
|
|
244
|
+
const [specifier] = pattern.split("=");
|
|
245
|
+
log.debug(`Attempting to preload types for ${specifier}`);
|
|
246
|
+
const path6 = await resolveSpecifierPath(pattern, opts.repoDir, log);
|
|
247
|
+
if (path6) {
|
|
248
|
+
try {
|
|
249
|
+
exports = await preloadAdaptorExports(
|
|
250
|
+
path6,
|
|
251
|
+
opts.useAdaptorsMonorepo,
|
|
252
|
+
log
|
|
253
|
+
);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
log.error(`Failed to load adaptor typedefs from path ${path6}`);
|
|
256
|
+
log.error(e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!exports || exports.length === 0) {
|
|
260
|
+
log.debug(`No module exports found for ${pattern}`);
|
|
261
|
+
}
|
|
262
|
+
options["add-imports"] = {
|
|
263
|
+
ignore: opts.ignoreImports,
|
|
264
|
+
adaptor: {
|
|
265
|
+
name: stripVersionSpecifier(specifier),
|
|
266
|
+
exports,
|
|
267
|
+
exportAll: true
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return options;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/util/load-state.ts
|
|
275
|
+
import fs from "node:fs/promises";
|
|
276
|
+
var load_state_default = async (opts, log) => {
|
|
277
|
+
const { stateStdin, statePath } = opts;
|
|
278
|
+
log.debug("Loading state...");
|
|
279
|
+
if (stateStdin) {
|
|
280
|
+
try {
|
|
281
|
+
const json = JSON.parse(stateStdin);
|
|
282
|
+
log.success("Read state from stdin");
|
|
283
|
+
log.debug("state:", json);
|
|
284
|
+
return json;
|
|
285
|
+
} catch (e) {
|
|
286
|
+
log.error("Failed to load state from stdin");
|
|
287
|
+
log.error(stateStdin);
|
|
288
|
+
log.error(e);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (statePath) {
|
|
293
|
+
try {
|
|
294
|
+
const str = await fs.readFile(statePath, "utf8");
|
|
295
|
+
const json = JSON.parse(str);
|
|
296
|
+
log.success(`Loaded state from ${statePath}`);
|
|
297
|
+
log.debug("state:", json);
|
|
298
|
+
return json;
|
|
299
|
+
} catch (e) {
|
|
300
|
+
log.warn(`Error loading state from ${statePath}`);
|
|
301
|
+
log.warn(e);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
log.info(
|
|
305
|
+
"No state provided - using default state { data: {}, configuration: {} }"
|
|
306
|
+
);
|
|
307
|
+
return {
|
|
308
|
+
data: {},
|
|
309
|
+
configuration: {}
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
261
313
|
// src/util/validate-adaptors.ts
|
|
262
314
|
var validateAdaptors = async (options, logger) => {
|
|
263
315
|
if (options.skipAdaptorValidation) {
|
|
264
316
|
return;
|
|
265
317
|
}
|
|
266
|
-
|
|
318
|
+
const hasDeclaredAdaptors = options.adaptors && options.adaptors.length > 0;
|
|
319
|
+
if (options.workflowPath && hasDeclaredAdaptors) {
|
|
320
|
+
logger.error("ERROR: adaptor and workflow provided");
|
|
321
|
+
logger.error(
|
|
322
|
+
"This is probably not what you meant to do. A workflow should declare an adaptor for each job."
|
|
323
|
+
);
|
|
324
|
+
throw new Error("adaptor and workflow provided");
|
|
325
|
+
}
|
|
326
|
+
if (!options.workflowPath && !hasDeclaredAdaptors) {
|
|
267
327
|
logger.warn("WARNING: No adaptor provided!");
|
|
268
328
|
logger.warn(
|
|
269
329
|
"This job will probably fail. Pass an adaptor with the -a flag, eg:"
|
|
@@ -275,22 +335,76 @@ var validateAdaptors = async (options, logger) => {
|
|
|
275
335
|
};
|
|
276
336
|
var validate_adaptors_default = validateAdaptors;
|
|
277
337
|
|
|
278
|
-
// src/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
338
|
+
// src/util/load-input.ts
|
|
339
|
+
import path from "node:path";
|
|
340
|
+
import fs2 from "node:fs/promises";
|
|
341
|
+
import { isPath } from "@openfn/compiler";
|
|
342
|
+
var load_input_default = async (opts, log) => {
|
|
343
|
+
log.debug("Loading input...");
|
|
344
|
+
const { job, workflow, jobPath, workflowPath } = opts;
|
|
345
|
+
if (workflow || workflowPath) {
|
|
346
|
+
return loadWorkflow(opts, log);
|
|
347
|
+
}
|
|
348
|
+
if (job) {
|
|
349
|
+
return job;
|
|
350
|
+
}
|
|
351
|
+
if (jobPath) {
|
|
352
|
+
log.debug(`Loading job from ${jobPath}`);
|
|
353
|
+
opts.job = await fs2.readFile(jobPath, "utf8");
|
|
354
|
+
return opts.job;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
var fetchFile = (rootDir, filePath) => {
|
|
358
|
+
const jobPath = filePath.startsWith("~") ? filePath : path.resolve(rootDir, filePath);
|
|
359
|
+
return fs2.readFile(jobPath, "utf8");
|
|
360
|
+
};
|
|
361
|
+
var loadWorkflow = async (opts, log) => {
|
|
362
|
+
const { workflowPath, workflow } = opts;
|
|
363
|
+
log.debug(`Loading workflow from ${workflowPath}`);
|
|
364
|
+
try {
|
|
365
|
+
let wf;
|
|
366
|
+
let rootDir = opts.baseDir;
|
|
367
|
+
if (workflowPath) {
|
|
368
|
+
const workflowRaw = await fs2.readFile(workflowPath, "utf8");
|
|
369
|
+
wf = JSON.parse(workflowRaw);
|
|
370
|
+
if (!rootDir) {
|
|
371
|
+
rootDir = path.dirname(workflowPath);
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
wf = workflow;
|
|
375
|
+
}
|
|
376
|
+
for (const job of wf.jobs) {
|
|
377
|
+
if (typeof job.expression === "string" && isPath(job.expression)) {
|
|
378
|
+
job.expression = await fetchFile(rootDir, job.expression);
|
|
379
|
+
}
|
|
380
|
+
if (typeof job.configuration === "string" && isPath(job.configuration)) {
|
|
381
|
+
const configString = await fetchFile(rootDir, job.configuration);
|
|
382
|
+
job.configuration = JSON.parse(configString);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
opts.workflow = wf;
|
|
386
|
+
log.debug("Workflow loaded!");
|
|
387
|
+
return opts.workflow;
|
|
388
|
+
} catch (e) {
|
|
389
|
+
log.error(`Error loading workflow from ${workflowPath}`);
|
|
390
|
+
throw e;
|
|
282
391
|
}
|
|
283
|
-
return [];
|
|
284
392
|
};
|
|
393
|
+
|
|
394
|
+
// src/execute/handler.ts
|
|
285
395
|
var executeHandler = async (options, logger) => {
|
|
286
396
|
const start = new Date().getTime();
|
|
287
397
|
await validate_adaptors_default(options, logger);
|
|
398
|
+
let input = await load_input_default(options, logger);
|
|
399
|
+
if (options.workflow) {
|
|
400
|
+
expand_adaptors_default(options);
|
|
401
|
+
}
|
|
288
402
|
const { repoDir, monorepoPath, autoinstall } = options;
|
|
289
403
|
if (autoinstall) {
|
|
290
404
|
if (monorepoPath) {
|
|
291
405
|
logger.warn("Skipping auto-install as monorepo is being used");
|
|
292
406
|
} else {
|
|
293
|
-
const autoInstallTargets =
|
|
407
|
+
const autoInstallTargets = get_autoinstall_targets_default(options);
|
|
294
408
|
if (autoInstallTargets.length) {
|
|
295
409
|
logger.info("Auto-installing language adaptors");
|
|
296
410
|
await install({ packages: autoInstallTargets, repoDir }, logger);
|
|
@@ -298,21 +412,17 @@ var executeHandler = async (options, logger) => {
|
|
|
298
412
|
}
|
|
299
413
|
}
|
|
300
414
|
const state = await load_state_default(options, logger);
|
|
301
|
-
let code = "";
|
|
302
415
|
if (options.compile) {
|
|
303
|
-
|
|
416
|
+
input = await compile_default(options, logger);
|
|
304
417
|
} else {
|
|
305
418
|
logger.info("Skipping compilation as noCompile is set");
|
|
306
|
-
if (options.jobPath) {
|
|
307
|
-
code = await readFile(options.jobPath, "utf8");
|
|
308
|
-
logger.success(`Loaded job from ${options.jobPath} (no compilation)`);
|
|
309
|
-
}
|
|
310
419
|
}
|
|
311
420
|
try {
|
|
312
|
-
const result = await execute_default(
|
|
421
|
+
const result = await execute_default(input, state, options);
|
|
313
422
|
await serialize_output_default(options, result, logger);
|
|
314
423
|
const duration = printDuration(new Date().getTime() - start);
|
|
315
424
|
logger.success(`Done in ${duration}! \u2728`);
|
|
425
|
+
return result;
|
|
316
426
|
} catch (error) {
|
|
317
427
|
logger.error(error);
|
|
318
428
|
const duration = printDuration(new Date().getTime() - start);
|
|
@@ -325,33 +435,65 @@ var handler_default = executeHandler;
|
|
|
325
435
|
// src/compile/handler.ts
|
|
326
436
|
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
327
437
|
var compileHandler = async (options, logger) => {
|
|
328
|
-
|
|
438
|
+
await load_input_default(options, logger);
|
|
439
|
+
let result = await compile_default(options, logger);
|
|
440
|
+
if (options.workflow) {
|
|
441
|
+
result = JSON.stringify(result);
|
|
442
|
+
}
|
|
329
443
|
if (options.outputStdout) {
|
|
330
444
|
logger.success("Compiled code:");
|
|
331
|
-
logger.success("\n" +
|
|
445
|
+
logger.success("\n" + result);
|
|
332
446
|
} else {
|
|
333
|
-
await writeFile2(options.outputPath,
|
|
447
|
+
await writeFile2(options.outputPath, result);
|
|
334
448
|
logger.success(`Compiled to ${options.outputPath}`);
|
|
335
449
|
}
|
|
336
450
|
};
|
|
337
451
|
var handler_default2 = compileHandler;
|
|
338
452
|
|
|
339
453
|
// src/test/handler.ts
|
|
340
|
-
var sillyMessage = "Calculating the answer to life, the universe, and everything...";
|
|
341
454
|
var testHandler = async (options, logger) => {
|
|
342
455
|
logger.log("Running test job...");
|
|
343
456
|
options.compile = true;
|
|
344
|
-
options.
|
|
345
|
-
|
|
457
|
+
options.adaptors = [];
|
|
458
|
+
options.workflow = {
|
|
459
|
+
start: "start",
|
|
460
|
+
jobs: [
|
|
461
|
+
{
|
|
462
|
+
id: "start",
|
|
463
|
+
data: { defaultAnswer: 42 },
|
|
464
|
+
expression: "const fn = () => (state) => { console.log('Starting computer...'); return state; }; fn()",
|
|
465
|
+
next: {
|
|
466
|
+
calculate: "!state.error"
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "calculate",
|
|
471
|
+
expression: "const fn = () => (state) => { console.log('Calculating to life, the universe, and everything..'); return state }; fn()",
|
|
472
|
+
next: {
|
|
473
|
+
result: true
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: "result",
|
|
478
|
+
expression: "const fn = () => (state) => ({ data: { answer: state.data.answer || state.data.defaultAnswer } }); fn()"
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
};
|
|
482
|
+
logger.break();
|
|
483
|
+
logger.info("Workflow object:");
|
|
484
|
+
logger.info(JSON.stringify(options.workflow, null, 2));
|
|
485
|
+
logger.break();
|
|
346
486
|
if (!options.stateStdin) {
|
|
347
|
-
logger.debug(
|
|
348
|
-
|
|
487
|
+
logger.debug(
|
|
488
|
+
"No state provided: pass an object with state.data.answer to provide custom input"
|
|
489
|
+
);
|
|
490
|
+
logger.debug('eg: -S "{ "data": { "answer": 33 } }"');
|
|
349
491
|
}
|
|
350
492
|
const silentLogger = createNullLogger();
|
|
351
493
|
const state = await load_state_default(options, silentLogger);
|
|
352
494
|
const code = await compile_default(options, logger);
|
|
353
495
|
const result = await execute_default(code, state, options);
|
|
354
|
-
logger.success(`Result: ${result}`);
|
|
496
|
+
logger.success(`Result: ${result.data.answer}`);
|
|
355
497
|
return result;
|
|
356
498
|
};
|
|
357
499
|
var handler_default3 = testHandler;
|
|
@@ -359,28 +501,28 @@ var handler_default3 = testHandler;
|
|
|
359
501
|
// src/docgen/handler.ts
|
|
360
502
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
361
503
|
import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
362
|
-
import
|
|
504
|
+
import path2 from "node:path";
|
|
363
505
|
import { describePackage } from "@openfn/describe-package";
|
|
364
506
|
import { getNameAndVersion as getNameAndVersion2 } from "@openfn/runtime";
|
|
365
507
|
var RETRY_DURATION = 500;
|
|
366
508
|
var RETRY_COUNT = 20;
|
|
367
509
|
var TIMEOUT_MS = 1e3 * 60;
|
|
368
510
|
var actualDocGen = (specifier) => describePackage(specifier, {});
|
|
369
|
-
var ensurePath = (filePath) => mkdirSync(
|
|
370
|
-
var generatePlaceholder = (
|
|
371
|
-
writeFileSync(
|
|
511
|
+
var ensurePath = (filePath) => mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
512
|
+
var generatePlaceholder = (path6) => {
|
|
513
|
+
writeFileSync(path6, `{ "loading": true, "timestamp": ${Date.now()}}`);
|
|
372
514
|
};
|
|
373
515
|
var finish = (logger, resultPath) => {
|
|
374
516
|
logger.success("Done! Docs can be found at:\n");
|
|
375
|
-
logger.print(` ${
|
|
517
|
+
logger.print(` ${path2.resolve(resultPath)}`);
|
|
376
518
|
};
|
|
377
|
-
var generateDocs = async (specifier,
|
|
519
|
+
var generateDocs = async (specifier, path6, docgen, logger) => {
|
|
378
520
|
const result = await docgen(specifier);
|
|
379
|
-
await writeFile3(
|
|
380
|
-
finish(logger,
|
|
381
|
-
return
|
|
521
|
+
await writeFile3(path6, JSON.stringify(result, null, 2));
|
|
522
|
+
finish(logger, path6);
|
|
523
|
+
return path6;
|
|
382
524
|
};
|
|
383
|
-
var waitForDocs = async (docs,
|
|
525
|
+
var waitForDocs = async (docs, path6, logger, retryDuration = RETRY_DURATION) => {
|
|
384
526
|
try {
|
|
385
527
|
if (docs.hasOwnProperty("loading")) {
|
|
386
528
|
logger.info("Docs are being loaded by another process. Waiting.");
|
|
@@ -392,19 +534,19 @@ var waitForDocs = async (docs, path5, logger, retryDuration = RETRY_DURATION) =>
|
|
|
392
534
|
clearInterval(i);
|
|
393
535
|
reject(new Error("Timed out waiting for docs to load"));
|
|
394
536
|
}
|
|
395
|
-
const updated = JSON.parse(readFileSync(
|
|
537
|
+
const updated = JSON.parse(readFileSync(path6, "utf8"));
|
|
396
538
|
if (!updated.hasOwnProperty("loading")) {
|
|
397
539
|
logger.info("Docs found!");
|
|
398
540
|
clearInterval(i);
|
|
399
|
-
resolve(
|
|
541
|
+
resolve(path6);
|
|
400
542
|
}
|
|
401
543
|
count++;
|
|
402
544
|
}, retryDuration);
|
|
403
545
|
});
|
|
404
546
|
} else {
|
|
405
|
-
logger.info(`Docs already written to cache at ${
|
|
406
|
-
finish(logger,
|
|
407
|
-
return
|
|
547
|
+
logger.info(`Docs already written to cache at ${path6}`);
|
|
548
|
+
finish(logger, path6);
|
|
549
|
+
return path6;
|
|
408
550
|
}
|
|
409
551
|
} catch (e) {
|
|
410
552
|
logger.error("Existing doc JSON corrupt. Aborting");
|
|
@@ -421,28 +563,28 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
|
|
|
421
563
|
process.exit(9);
|
|
422
564
|
}
|
|
423
565
|
logger.success(`Generating docs for ${specifier}`);
|
|
424
|
-
const
|
|
425
|
-
ensurePath(
|
|
566
|
+
const path6 = `${repoDir}/docs/${specifier}.json`;
|
|
567
|
+
ensurePath(path6);
|
|
426
568
|
const handleError = () => {
|
|
427
569
|
logger.info("Removing placeholder");
|
|
428
|
-
rmSync(
|
|
570
|
+
rmSync(path6);
|
|
429
571
|
};
|
|
430
572
|
try {
|
|
431
|
-
const existing = readFileSync(
|
|
573
|
+
const existing = readFileSync(path6, "utf8");
|
|
432
574
|
const json = JSON.parse(existing);
|
|
433
575
|
if (json && json.timeout && Date.now() - json.timeout >= TIMEOUT_MS) {
|
|
434
576
|
logger.info(`Expired placeholder found. Removing.`);
|
|
435
|
-
rmSync(
|
|
577
|
+
rmSync(path6);
|
|
436
578
|
throw new Error("TIMEOUT");
|
|
437
579
|
}
|
|
438
|
-
return waitForDocs(json,
|
|
580
|
+
return waitForDocs(json, path6, logger, retryDuration);
|
|
439
581
|
} catch (e) {
|
|
440
582
|
if (e.message !== "TIMEOUT") {
|
|
441
|
-
logger.info(`Docs JSON not found at ${
|
|
583
|
+
logger.info(`Docs JSON not found at ${path6}`);
|
|
442
584
|
}
|
|
443
585
|
logger.debug("Generating placeholder");
|
|
444
|
-
generatePlaceholder(
|
|
445
|
-
return generateDocs(specifier,
|
|
586
|
+
generatePlaceholder(path6);
|
|
587
|
+
return generateDocs(specifier, path6, docgen, logger).catch((e2) => {
|
|
446
588
|
logger.error("Error generating documentation");
|
|
447
589
|
logger.error(e2);
|
|
448
590
|
handleError();
|
|
@@ -452,7 +594,7 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
|
|
|
452
594
|
var handler_default4 = docgenHandler;
|
|
453
595
|
|
|
454
596
|
// src/docs/handler.ts
|
|
455
|
-
import { readFile
|
|
597
|
+
import { readFile } from "node:fs/promises";
|
|
456
598
|
import { getNameAndVersion as getNameAndVersion3, getLatestVersion } from "@openfn/runtime";
|
|
457
599
|
var describeFn = (adaptorName, fn) => `## ${fn.name}(${fn.parameters.map(({ name }) => name).join(",")})
|
|
458
600
|
|
|
@@ -481,7 +623,8 @@ ${data.functions.map((fn) => ` ${fn.name}(${fn.parameters.map((p) => p.name).jo
|
|
|
481
623
|
`;
|
|
482
624
|
var docsHandler = async (options, logger) => {
|
|
483
625
|
const { adaptor, operation, repoDir } = options;
|
|
484
|
-
const
|
|
626
|
+
const { adaptors } = expand_adaptors_default({ adaptors: [adaptor] });
|
|
627
|
+
const [adaptorName] = adaptors;
|
|
485
628
|
let { name, version } = getNameAndVersion3(adaptorName);
|
|
486
629
|
if (!version) {
|
|
487
630
|
logger.info("No version number provided, looking for latest...");
|
|
@@ -490,29 +633,38 @@ var docsHandler = async (options, logger) => {
|
|
|
490
633
|
logger.success(`Showing docs for ${adaptorName} v${version}`);
|
|
491
634
|
}
|
|
492
635
|
logger.info("Generating/loading documentation...");
|
|
493
|
-
const
|
|
636
|
+
const path6 = await handler_default4(
|
|
494
637
|
{
|
|
495
638
|
specifier: `${name}@${version}`,
|
|
496
639
|
repoDir
|
|
497
640
|
},
|
|
498
641
|
createNullLogger()
|
|
499
642
|
);
|
|
500
|
-
|
|
501
|
-
|
|
643
|
+
let didError = false;
|
|
644
|
+
if (path6) {
|
|
645
|
+
const source = await readFile(path6, "utf8");
|
|
502
646
|
const data = JSON.parse(source);
|
|
503
647
|
let desc;
|
|
504
648
|
if (operation) {
|
|
505
649
|
const fn = data.functions.find(({ name: name2 }) => name2 === operation);
|
|
506
|
-
|
|
507
|
-
|
|
650
|
+
if (fn) {
|
|
651
|
+
logger.debug("Operation schema:", fn);
|
|
652
|
+
logger.success(`Documentation for ${name}.${operation} v${version}:
|
|
508
653
|
`);
|
|
509
|
-
|
|
654
|
+
desc = describeFn(name, fn);
|
|
655
|
+
} else {
|
|
656
|
+
logger.error(`Failed to find ${operation} in ${name}`);
|
|
657
|
+
}
|
|
510
658
|
} else {
|
|
511
659
|
logger.debug("No operation provided, listing available operations");
|
|
512
660
|
desc = describeLib(name, data);
|
|
513
661
|
}
|
|
514
662
|
logger.print(desc);
|
|
515
|
-
|
|
663
|
+
if (didError) {
|
|
664
|
+
logger.error("Error");
|
|
665
|
+
} else {
|
|
666
|
+
logger.success("Done!");
|
|
667
|
+
}
|
|
516
668
|
} else {
|
|
517
669
|
logger.error("Not found");
|
|
518
670
|
}
|
|
@@ -522,13 +674,29 @@ var handler_default5 = docsHandler;
|
|
|
522
674
|
// src/metadata/cache.ts
|
|
523
675
|
import { createHash } from "node:crypto";
|
|
524
676
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
525
|
-
import
|
|
677
|
+
import path3 from "node:path";
|
|
526
678
|
import { writeFile as writeFile4, mkdir } from "node:fs/promises";
|
|
527
679
|
var getPath = (repoDir, key) => `${repoDir}/meta/${key}.json`;
|
|
528
|
-
var
|
|
680
|
+
var sortKeys = (obj) => {
|
|
681
|
+
const newObj = {};
|
|
682
|
+
Object.keys(obj).sort().forEach((k) => {
|
|
683
|
+
const v = obj[k];
|
|
684
|
+
if (!v || typeof v == "string" || !isNaN(v) || Array.isArray(v)) {
|
|
685
|
+
newObj[k] = v;
|
|
686
|
+
} else {
|
|
687
|
+
newObj[k] = sortKeys(v);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
return newObj;
|
|
691
|
+
};
|
|
692
|
+
var generateKey = (config, adaptor) => {
|
|
693
|
+
const sorted = sortKeys(config);
|
|
694
|
+
const key = `${JSON.stringify(sorted)}-${adaptor}}`;
|
|
695
|
+
return createHash("sha256").update(key).digest("hex");
|
|
696
|
+
};
|
|
529
697
|
var get = (repoPath, key) => {
|
|
530
698
|
try {
|
|
531
|
-
const data = readFileSync2(getPath(repoPath, key));
|
|
699
|
+
const data = readFileSync2(getPath(repoPath, key), "utf8");
|
|
532
700
|
const json = JSON.parse(data);
|
|
533
701
|
return json;
|
|
534
702
|
} catch (e) {
|
|
@@ -537,10 +705,10 @@ var get = (repoPath, key) => {
|
|
|
537
705
|
};
|
|
538
706
|
var set = async (repoPath, key, data) => {
|
|
539
707
|
const fullPath = getPath(repoPath, key);
|
|
540
|
-
await mkdir(
|
|
708
|
+
await mkdir(path3.dirname(fullPath), { recursive: true });
|
|
541
709
|
await writeFile4(fullPath, JSON.stringify(data));
|
|
542
710
|
};
|
|
543
|
-
var cache_default = { get, set, generateKey, getPath };
|
|
711
|
+
var cache_default = { get, set, generateKey, getPath, sortKeys };
|
|
544
712
|
|
|
545
713
|
// src/metadata/handler.ts
|
|
546
714
|
import { getModuleEntryPoint } from "@openfn/runtime";
|
|
@@ -580,8 +748,8 @@ var metadataHandler = async (options, logger) => {
|
|
|
580
748
|
const adaptor = adaptors[0];
|
|
581
749
|
const state = await load_state_default(options, logger);
|
|
582
750
|
logger.success(`Generating metadata`);
|
|
751
|
+
logger.info("config:", state);
|
|
583
752
|
const config = state.configuration;
|
|
584
|
-
logger.info("config:", config);
|
|
585
753
|
if (!config || Object.keys(config).length === 0) {
|
|
586
754
|
logger.error("ERROR: Invalid configuration passed");
|
|
587
755
|
process.exit(1);
|
|
@@ -590,7 +758,7 @@ var metadataHandler = async (options, logger) => {
|
|
|
590
758
|
logger.success("Done!");
|
|
591
759
|
logger.print(cache_default.getPath(repoDir, id));
|
|
592
760
|
};
|
|
593
|
-
const id = cache_default.generateKey(config);
|
|
761
|
+
const id = cache_default.generateKey(config, adaptor);
|
|
594
762
|
if (!options.force) {
|
|
595
763
|
logger.debug("config hash: ", id);
|
|
596
764
|
const cached = await cache_default.get(repoDir, id);
|
|
@@ -621,13 +789,13 @@ var metadataHandler = async (options, logger) => {
|
|
|
621
789
|
var handler_default6 = metadataHandler;
|
|
622
790
|
|
|
623
791
|
// src/util/use-adaptors-repo.ts
|
|
624
|
-
import { readFile as
|
|
625
|
-
import
|
|
792
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
793
|
+
import path4 from "node:path";
|
|
626
794
|
import assert from "node:assert";
|
|
627
|
-
import { getNameAndVersion as
|
|
795
|
+
import { getNameAndVersion as getNameAndVersion5 } from "@openfn/runtime";
|
|
628
796
|
var validateMonoRepo = async (repoPath, log) => {
|
|
629
797
|
try {
|
|
630
|
-
const raw = await
|
|
798
|
+
const raw = await readFile2(`${repoPath}/package.json`, "utf8");
|
|
631
799
|
const pkg = JSON.parse(raw);
|
|
632
800
|
assert(pkg.name === "adaptors");
|
|
633
801
|
} catch (e) {
|
|
@@ -639,14 +807,14 @@ var updatePath = (adaptor, repoPath, log) => {
|
|
|
639
807
|
if (adaptor.match("=")) {
|
|
640
808
|
return adaptor;
|
|
641
809
|
}
|
|
642
|
-
const { name, version } =
|
|
810
|
+
const { name, version } = getNameAndVersion5(adaptor);
|
|
643
811
|
if (version) {
|
|
644
812
|
log.warn(
|
|
645
813
|
`Warning: Ignoring version specifier on ${adaptor} as loading from the adaptors monorepo`
|
|
646
814
|
);
|
|
647
815
|
}
|
|
648
816
|
const shortName = name.replace("@openfn/language-", "");
|
|
649
|
-
const abspath =
|
|
817
|
+
const abspath = path4.resolve(repoPath, "packages", shortName);
|
|
650
818
|
return `${name}=${abspath}`;
|
|
651
819
|
};
|
|
652
820
|
var useAdaptorsRepo = async (adaptors, repoPath, log) => {
|
|
@@ -663,8 +831,8 @@ var use_adaptors_repo_default = useAdaptorsRepo;
|
|
|
663
831
|
|
|
664
832
|
// src/util/print-versions.ts
|
|
665
833
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
666
|
-
import
|
|
667
|
-
import { getNameAndVersion as
|
|
834
|
+
import path5 from "node:path";
|
|
835
|
+
import { getNameAndVersion as getNameAndVersion6 } from "@openfn/runtime";
|
|
668
836
|
import { mainSymbols } from "figures";
|
|
669
837
|
var NODE = "node.js";
|
|
670
838
|
var CLI2 = "cli";
|
|
@@ -673,7 +841,7 @@ var COMPILER2 = "compiler";
|
|
|
673
841
|
var { triangleRightSmall: t } = mainSymbols;
|
|
674
842
|
var loadVersionFromPath = (adaptorPath) => {
|
|
675
843
|
try {
|
|
676
|
-
const pkg = JSON.parse(readFileSync3(
|
|
844
|
+
const pkg = JSON.parse(readFileSync3(path5.resolve(adaptorPath, "package.json"), "utf8"));
|
|
677
845
|
return pkg.version;
|
|
678
846
|
} catch (e) {
|
|
679
847
|
return "unknown";
|
|
@@ -691,9 +859,9 @@ var printVersions = async (logger, options = {}) => {
|
|
|
691
859
|
if (adaptor.match("=")) {
|
|
692
860
|
const [namePart, pathPart] = adaptor.split("=");
|
|
693
861
|
adaptorVersion = loadVersionFromPath(pathPart);
|
|
694
|
-
adaptorName =
|
|
862
|
+
adaptorName = getNameAndVersion6(namePart).name;
|
|
695
863
|
} else {
|
|
696
|
-
const { name, version: version2 } =
|
|
864
|
+
const { name, version: version2 } = getNameAndVersion6(adaptor);
|
|
697
865
|
adaptorName = name;
|
|
698
866
|
adaptorVersion = version2 || "latest";
|
|
699
867
|
}
|
|
@@ -750,7 +918,7 @@ var handlers = {
|
|
|
750
918
|
["repo-list"]: list,
|
|
751
919
|
version: async (opts, logger) => print_versions_default(logger, opts)
|
|
752
920
|
};
|
|
753
|
-
var maybeEnsureOpts = (basePath, options) => /^(execute|compile)$/.test(options.command) ? ensureLogOpts(options) : ensureOpts(basePath, options);
|
|
921
|
+
var maybeEnsureOpts = (basePath, options) => /^(execute|compile|test)$/.test(options.command) ? ensureLogOpts(options) : ensureOpts(basePath, options);
|
|
754
922
|
var parse = async (basePath, options, log) => {
|
|
755
923
|
const opts = maybeEnsureOpts(basePath, options);
|
|
756
924
|
const logger = log || logger_default(CLI, opts);
|
|
@@ -771,9 +939,9 @@ var parse = async (basePath, options, log) => {
|
|
|
771
939
|
logger
|
|
772
940
|
);
|
|
773
941
|
} else if (opts.adaptors && opts.expandAdaptors) {
|
|
774
|
-
|
|
942
|
+
expand_adaptors_default(opts);
|
|
775
943
|
}
|
|
776
|
-
if (
|
|
944
|
+
if (!/^(test|version)$/.test(opts.command) && !opts.repoDir) {
|
|
777
945
|
logger.warn(
|
|
778
946
|
"WARNING: no repo module dir found! Using the default (/tmp/repo)"
|
|
779
947
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.38",
|
|
4
4
|
"description": "CLI devtools for the openfn toolchain.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18",
|
|
@@ -36,15 +36,14 @@
|
|
|
36
36
|
"typescript": "^4.7.4"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"
|
|
39
|
+
"@openfn/compiler": "0.0.31",
|
|
40
|
+
"@openfn/describe-package": "0.0.16",
|
|
41
|
+
"@openfn/logger": "0.0.12",
|
|
42
|
+
"@openfn/runtime": "0.0.23",
|
|
40
43
|
"figures": "^5.0.0",
|
|
41
44
|
"rimraf": "^3.0.2",
|
|
42
45
|
"treeify": "^1.1.0",
|
|
43
|
-
"yargs": "^17.5.1"
|
|
44
|
-
"@openfn/compiler": "0.0.29",
|
|
45
|
-
"@openfn/logger": "0.0.12",
|
|
46
|
-
"@openfn/runtime": "0.0.21",
|
|
47
|
-
"@openfn/describe-package": "0.0.16"
|
|
46
|
+
"yargs": "^17.5.1"
|
|
48
47
|
},
|
|
49
48
|
"files": [
|
|
50
49
|
"dist",
|