@openfn/cli 0.0.35 → 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
@@ -1,13 +1,13 @@
1
1
  # @openfn/cli
2
2
 
3
- This package contains a new devtools CLI for running openfn jobs.
3
+ This package contains a new devtools CLI for running OpenFn jobs.
4
4
 
5
- The new CLI includes:
5
+ The CLI includes:
6
6
 
7
- * A new runtime for executing openfn jobs
8
- * A new compiler for making openfn jobs runnable
9
- * Improved, customisable logging output
10
- * Auto installation of language adaptors
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,41 +36,60 @@ 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:
42
58
 
43
59
  * The CLI has a shorter, sleeker syntax, so your command should be much shorter
44
60
  * The CLI will automatically install adaptors for you (with full version control)
45
- * 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.
46
61
 
47
62
  ## Basic Usage
48
63
 
49
- You're probably here to run jobs (expressions), which the CLI makes easy:
64
+ You're probably here to run jobs (expressions) or workflows, which the CLI makes easy:
65
+
50
66
 
51
67
  ```
68
+ openfn path/to/workflow.json
52
69
  openfn path/to/job.js -ia adaptor-name
53
70
  ```
54
71
 
55
- You MUST specify which adaptor to use. Pass the `-i` flag to auto-install that adaptor (it's safe to do this redundantly).
72
+ If running a single job, you MUST specify which adaptor to use.
73
+
74
+ 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
75
 
57
- When the job is finished, the CLI will write the `data` property of your 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. To write the entire state object (not just `data`), pass `--no-strict-output`.
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.
58
77
 
59
- The CLI can auto-install language adaptors to its own privately maintained repo, just include the `-i` flag in the command and your adaptors will be forever fully managed. 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.
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.
60
79
 
61
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`.
62
81
 
63
- If you have the adaptors monorepo set up on your machine, you can also run adaptors straight from source. 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!
82
+ 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
83
 
65
84
  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
85
 
67
86
  ## Advanced Usage
68
87
 
69
- The CLI has actually has a number of commands (the first argument after openfn)
88
+ The CLI has a number of commands (the first argument after openfn)
70
89
 
71
90
  * execute - run a job
72
91
  * compile - compile a job to a .js file
73
- * doc - show documentation for an adaptor function
92
+ * docs - show documentation for an adaptor function
74
93
  * repo - manage the repo of installed modules
75
94
  * docgen - generate JSON documentation for an adaptor based on its typescript
76
95
 
@@ -109,9 +128,42 @@ For a more structured output, you can emit logs as JSON objects with `level`, `n
109
128
  ```
110
129
  { level: 'info', name: 'CLI', message: ['Loaded adaptor'] }
111
130
  ```
112
-
113
131
  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
132
 
133
+ ## Workflows
134
+
135
+ As of v0.0.35 the CLI supports running workflows as well as jobs.
136
+
137
+ A workflow is in execution plan for running several jobs in a sequence. It is defined as a JSON structure.
138
+
139
+ To see an example workflow, run the test command with `openfn test`.
140
+
141
+ A workflow has a structure like this (better documentation is coming soon):
142
+
143
+ ```
144
+ {
145
+ "start": "a", // optionally specify the start node (defaults to jobs[0])
146
+ "jobs": [
147
+ {
148
+ "id": "a",
149
+ "expression": "fn((state) => state)", // code or a path
150
+ "adaptor": "@openfn/language-common@1.75", // specifiy the adaptor to use (version optional)
151
+ "data": {}, // optionally pre-populate the data object (this will be overriden by keys in in previous state)
152
+ "configuration": {}, // Use this to pass credentials
153
+ "next": {
154
+ // This object defines which jobs to call next
155
+ // All edges returning true will run
156
+ // If there are no next edges, the workflow will end
157
+ "b": true,
158
+ "c": {
159
+ "condition": "!state.error" // Not that this is an expression, not a function
160
+ }
161
+ }
162
+ },
163
+ ]
164
+ }
165
+ ```
166
+
115
167
  ## Compilation
116
168
 
117
169
  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 +181,6 @@ All jobs which work against `@openfn/core` will work in the new CLI and runtime
129
181
 
130
182
  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
183
 
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
184
  # Contributing
139
185
 
140
186
  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 +216,8 @@ The CLI will save and load adaptors from an arbitrary folder on your system.
170
216
 
171
217
  You should set the OPENFN_REPO_DIR env var to something sensible.
172
218
 
219
+ In `~/.bashrc` (or whatever you use), add:
173
220
  ```
174
- # In ~/.bashc or whatever
175
221
  export OPENFN_REPO_DIR=~/repo/openfn/cli-repo
176
222
  ```
177
223
 
@@ -1,5 +1,5 @@
1
1
  // src/util/expand-adaptors.ts
2
- var expand_adaptors_default = (names) => names?.map((name) => {
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
- logOptions.json = true;
45
+ json = true;
31
46
  }
32
47
  return actualCreateLogger(namespaces[name] || name, {
33
48
  level,
49
+ json,
34
50
  ...logOptions
35
51
  });
36
52
  };
@@ -107,7 +123,6 @@ function ensureOpts(basePath = ".", opts) {
107
123
  skipAdaptorValidation: opts.skipAdaptorValidation ?? false,
108
124
  specifier: opts.specifier,
109
125
  stateStdin: opts.stateStdin,
110
- strictOutput: opts.strictOutput ?? true,
111
126
  timeout: opts.timeout
112
127
  };
113
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-XYZNU5CH.js";
5
+ } from "./chunk-LV5XDERP.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
- opts2.adaptors = expand_adaptors_default(opts2.adaptors);
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 (basePath.endsWith(".js")) {
196
+ if (/\.(jso?n?)$/.test(basePath)) {
197
197
  return path2.dirname(basePath);
198
198
  }
199
199
  return basePath;
200
200
  };
201
- var jobPath = {
202
- name: "job-path",
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(".js")) {
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,14 +264,38 @@ 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: {
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,
268
292
  boolean: true,
269
- 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."
270
294
  },
271
295
  ensure: (opts2) => {
272
- setDefaultValue(opts2, "strictOutput", true);
296
+ if (!opts2.hasOwnProperty("strictOutput")) {
297
+ setDefaultValue(opts2, "strict", false);
298
+ }
273
299
  }
274
300
  };
275
301
  var skipAdaptorValidation = {
@@ -325,38 +351,40 @@ var options = [
325
351
  compile,
326
352
  immutable,
327
353
  ignoreImports,
328
- jobPath,
354
+ inputPath,
329
355
  logJson,
330
356
  outputPath,
331
357
  outputStdout,
332
358
  repoDir,
333
359
  skipAdaptorValidation,
360
+ start,
334
361
  statePath,
335
362
  stateStdin,
363
+ strict,
336
364
  strictOutput,
337
365
  timeout,
338
366
  useAdaptorsMonorepo
339
367
  ];
340
368
  var executeCommand = {
341
369
  command: "execute [path]",
342
- desc: `Run an openfn job. Get more help by running openfn <command> help.
370
+ desc: `Run an openfn job or workflow. Get more help by running openfn <command> help.
343
371
 
344
- Execute will run a job/expression and write the output state to disk (to ./state.json unless otherwise specified)
372
+ Execute will run a job/workflow at the path and write the output state to disk (to ./state.json unless otherwise specified)
345
373
 
346
- 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.
347
375
 
348
376
  Remember to include the adaptor name with -a. Auto install adaptors with the -i flag.`,
349
377
  aliases: ["$0"],
350
378
  handler: ensure("execute", options),
351
379
  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)",
380
+ describe: "The path to load the job or workflow from (a .js or .json file or a dir containing a job.js file)",
353
381
  demandOption: true
354
382
  }).example(
355
383
  "openfn foo/job.js",
356
384
  "Execute foo/job.js with no adaptor and write the final state to foo/job.json"
357
385
  ).example(
358
- "openfn job.js -ia common",
359
- "Execute job.js using @openfn/language-commom , with autoinstall enabled)"
386
+ "openfn workflow.json -ia common",
387
+ "Execute workflow.json using @openfn/language-commom (with autoinstall enabled)"
360
388
  ).example(
361
389
  "openfn job.js -a common --log info",
362
390
  "Execute job.js with common adaptor and info-level logging"
@@ -372,7 +400,7 @@ var options2 = [
372
400
  expandAdaptors,
373
401
  adaptors,
374
402
  ignoreImports,
375
- jobPath,
403
+ inputPath,
376
404
  logJson,
377
405
  override(outputStdout, {
378
406
  default: true
@@ -383,32 +411,28 @@ var options2 = [
383
411
  ];
384
412
  var compileCommand = {
385
413
  command: "compile [path]",
386
- desc: "Compile an openfn job and print or save the resulting JavaScript.",
414
+ desc: "Compile an openfn job or workflow and print or save the resulting JavaScript.",
387
415
  handler: ensure("compile", options2),
388
416
  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)",
417
+ describe: "The path to load the job or workflow from (a .js or .json file or a dir containing a job.js file)",
390
418
  demandOption: true
391
419
  }).example(
392
- "compile foo/job.js -O",
393
- "Compiles foo/job.js and prints the result to stdout"
420
+ "compile foo/job.js",
421
+ "Compiles the job at foo/job.js and prints the result to stdout"
394
422
  ).example(
395
- "compile foo/job.js -o foo/job-compiled.js",
396
- "Compiles foo/job.js and saves the result to foo/job-compiled.js"
423
+ "compile foo/workflow.json -o foo/workflow-compiled.json",
424
+ "Compiles the workflow at foo/work.json and prints the result to -o foo/workflow-compiled.json"
397
425
  )
398
426
  };
399
427
  var command_default2 = compileCommand;
400
428
 
401
429
  // src/test/command.ts
430
+ var options3 = [stateStdin];
402
431
  var command_default3 = {
403
432
  command: "test",
404
433
  desc: "Compiles and runs a test job, printing the result to stdout",
405
- handler: (argv) => {
406
- argv.command = "test";
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")
434
+ handler: ensure("test", options3),
435
+ builder: (yargs2) => build(options3, yargs2).example("test", "Run the test script")
412
436
  };
413
437
 
414
438
  // src/docgen/command.ts
@@ -435,7 +459,7 @@ var command_default5 = {
435
459
  };
436
460
 
437
461
  // src/metadata/command.ts
438
- var options3 = [
462
+ var options4 = [
439
463
  expandAdaptors,
440
464
  adaptors,
441
465
  force,
@@ -448,8 +472,8 @@ var options3 = [
448
472
  var command_default6 = {
449
473
  command: "metadata",
450
474
  desc: "Generate metadata for an adaptor config",
451
- handler: ensure("metadata", options3),
452
- builder: (yargs2) => build(options3, yargs2).example(
475
+ handler: ensure("metadata", options4),
476
+ builder: (yargs2) => build(options4, yargs2).example(
453
477
  "metadata -a salesforce -s tmp/state.json",
454
478
  "Generate salesforce metadata from config in state.json"
455
479
  )