@openfn/cli 0.0.17 → 0.0.20

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/dist/index.js CHANGED
@@ -12,13 +12,15 @@ function spawn_default(basePath, opts2) {
12
12
  ];
13
13
  const dirname = path.dirname(url.fileURLToPath(import.meta.url));
14
14
  const child = fork(`${dirname}/process/runner.js`, [], { execArgv });
15
- child.on("message", ({ done }) => {
15
+ child.on("message", ({ done, init }) => {
16
+ if (init) {
17
+ child.send({ init: true, basePath, opts: opts2 });
18
+ }
16
19
  if (done) {
17
20
  child.kill();
18
21
  process.exit(0);
19
22
  }
20
23
  });
21
- child.send({ init: true, basePath, opts: opts2 });
22
24
  }
23
25
 
24
26
  // src/cli.ts
@@ -81,7 +83,7 @@ var list = {
81
83
  // src/execute/command.ts
82
84
  var executeCommand = {
83
85
  command: "execute [path]",
84
- desc: "Run an openfn job",
86
+ desc: `Run an openfn job. Get more help by running openfn <command> help`,
85
87
  aliases: ["$0"],
86
88
  handler: (argv) => {
87
89
  argv.command = "execute";
@@ -103,6 +105,9 @@ var executeCommand = {
103
105
  }).option("no-compile", {
104
106
  boolean: true,
105
107
  description: "Skip compilation"
108
+ }).option("no-strict-output", {
109
+ boolean: true,
110
+ description: "Allow properties other than data to be returned in the output."
106
111
  }).example(
107
112
  "openfn foo/job.js",
108
113
  "Reads foo/job.js, looks for state and output in foo"
@@ -124,7 +129,7 @@ var applyExecuteOptions = (yargs2) => yargs2.positional("path", {
124
129
  }).option("output-stdout", {
125
130
  alias: "O",
126
131
  boolean: true,
127
- description: "Print output to stdout (intead of a file)"
132
+ description: "Print output to stdout (instead of a file)"
128
133
  }).option("adaptors", {
129
134
  alias: ["a", "adaptor"],
130
135
  description: "A language adaptor to use for the job. Short-form names are allowed. Can include an explicit path to a local adaptor build.",
@@ -167,12 +172,38 @@ var command_default3 = {
167
172
  }).example("test", "run the test script").example("test -S 42", "run the test script with state 42")
168
173
  };
169
174
 
175
+ // src/docgen/command.ts
176
+ var docgenCommand = {
177
+ command: "docgen <specifier>",
178
+ desc: "Generate documentation into the repo. Specifier must include a version number.",
179
+ handler: (argv) => {
180
+ argv.command = "docgen";
181
+ },
182
+ builder: (yargs2) => {
183
+ return yargs2.example("docgen @openfn/language-common@1.7.5", "");
184
+ }
185
+ };
186
+ var command_default4 = docgenCommand;
187
+
188
+ // src/docs/command.ts
189
+ var command_default5 = {
190
+ command: "docs [adaptor] [operation]",
191
+ desc: "Print help for an adaptor function. You can use short-hand for adaptor names (ie, common instead of @openfn/language-common)",
192
+ handler: (argv) => {
193
+ argv.command = "docs";
194
+ },
195
+ builder: (yargs2) => yargs2.example("docs common fn", "Print help for the common fn operation")
196
+ };
197
+
170
198
  // src/cli.ts
171
- var cmd = yargs(hideBin(process.argv)).command(command_default).command(command_default2).command(install).command(repo).command(command_default3).option("log", {
199
+ var cmd = yargs(hideBin(process.argv)).command(command_default).command(command_default2).command(install).command(repo).command(command_default3).command(command_default5).command(command_default4).option("log", {
172
200
  alias: ["l"],
173
201
  description: "Set the default log level to none, default, info or debug",
174
202
  array: true
175
- }).alias("v", "version").help();
203
+ }).example("openfn execute help", "Show documentation for the execute command").example(
204
+ "openfn docs @openfn/language-common each",
205
+ "Get more help on the common.each command"
206
+ ).alias("v", "version").help();
176
207
 
177
208
  // src/index.ts
178
209
  var opts = cmd.parse();
@@ -80,8 +80,11 @@ function ensureOpts(basePath = ".", opts) {
80
80
  noCompile: Boolean(opts.noCompile),
81
81
  expand: opts.expand !== false,
82
82
  outputStdout: Boolean(opts.outputStdout),
83
+ operation: opts.operation,
83
84
  packages: opts.packages,
84
85
  stateStdin: opts.stateStdin,
86
+ specifier: opts.specifier,
87
+ strictOutput: opts.strictOutput ?? true,
85
88
  immutable: opts.immutable || false
86
89
  };
87
90
  const set = (key, value) => {
@@ -105,9 +108,6 @@ function ensureOpts(basePath = ".", opts) {
105
108
  return newOpts;
106
109
  }
107
110
 
108
- // src/execute/handler.ts
109
- import { writeFile } from "node:fs/promises";
110
-
111
111
  // src/execute/load-state.ts
112
112
  import fs from "node:fs/promises";
113
113
  var load_state_default = async (opts, log) => {
@@ -158,11 +158,11 @@ var execute_default = (code, state, opts) => {
158
158
  function parseAdaptors(opts) {
159
159
  const adaptors = {};
160
160
  opts.adaptors.reduce((obj, exp) => {
161
- const [module, path2] = exp.split("=");
161
+ const [module, path3] = exp.split("=");
162
162
  const { name, version } = getNameAndVersion(module);
163
163
  const info = {};
164
- if (path2) {
165
- info.path = path2;
164
+ if (path3) {
165
+ info.path = path3;
166
166
  }
167
167
  if (version) {
168
168
  info.version = version;
@@ -204,10 +204,10 @@ var stripVersionSpecifier = (specifier) => {
204
204
  return specifier;
205
205
  };
206
206
  var resolveSpecifierPath = async (pattern, repoDir, log) => {
207
- const [specifier, path2] = pattern.split("=");
208
- if (path2) {
209
- log.debug(`Resolved ${specifier} to path: ${path2}`);
210
- return path2;
207
+ const [specifier, path3] = pattern.split("=");
208
+ if (path3) {
209
+ log.debug(`Resolved ${specifier} to path: ${path3}`);
210
+ return path3;
211
211
  }
212
212
  const repoPath = await getModulePath(specifier, repoDir, log);
213
213
  if (repoPath) {
@@ -225,15 +225,15 @@ var loadTransformOptions = async (opts, log) => {
225
225
  const [pattern] = opts.adaptors;
226
226
  const [specifier] = pattern.split("=");
227
227
  log.debug(`Attempting to preload typedefs for ${specifier}`);
228
- const path2 = await resolveSpecifierPath(pattern, opts.repoDir, log);
229
- if (path2) {
228
+ const path3 = await resolveSpecifierPath(pattern, opts.repoDir, log);
229
+ if (path3) {
230
230
  try {
231
- exports = await preloadAdaptorExports(path2);
231
+ exports = await preloadAdaptorExports(path3);
232
232
  if (exports) {
233
233
  log.info(`Loaded typedefs for ${specifier}`);
234
234
  }
235
235
  } catch (e) {
236
- log.error(`Failed to load adaptor typedefs from path ${path2}`);
236
+ log.error(`Failed to load adaptor typedefs from path ${path3}`);
237
237
  log.error(e);
238
238
  }
239
239
  }
@@ -251,6 +251,38 @@ var loadTransformOptions = async (opts, log) => {
251
251
  return options;
252
252
  };
253
253
 
254
+ // src/execute/serialize-output.ts
255
+ import { writeFile } from "node:fs/promises";
256
+ import stringify from "fast-safe-stringify";
257
+ var serializeOutput = async (options, result, logger) => {
258
+ let output = result;
259
+ if (output && (output.configuration || output.data)) {
260
+ const { data, configuration, ...rest } = result;
261
+ if (options.strictOutput !== false) {
262
+ output = { data };
263
+ } else {
264
+ output = {
265
+ data,
266
+ ...rest
267
+ };
268
+ }
269
+ }
270
+ if (output === void 0) {
271
+ output = "";
272
+ } else {
273
+ output = stringify(output, void 0, 2);
274
+ }
275
+ if (options.outputStdout) {
276
+ logger.success(`Result: `);
277
+ logger.success(output);
278
+ } else if (options.outputPath) {
279
+ logger.success(`Writing output to ${options.outputPath}`);
280
+ await writeFile(options.outputPath, output);
281
+ }
282
+ return output;
283
+ };
284
+ var serialize_output_default = serializeOutput;
285
+
254
286
  // src/repo/handler.ts
255
287
  import { exec } from "node:child_process";
256
288
  import treeify from "treeify";
@@ -269,18 +301,18 @@ var expand_adaptors_default = (names, log = nullLogger) => names == null ? void
269
301
 
270
302
  // src/repo/handler.ts
271
303
  var install = async (opts, log = defaultLogger) => {
272
- log.timer("install");
273
304
  let { packages, adaptor, repoDir } = opts;
274
- log.success("Installing packages...");
275
305
  if (packages) {
306
+ log.timer("install");
307
+ log.success("Installing packages...");
276
308
  log.debug("repoDir is set to:", repoDir);
277
309
  if (adaptor) {
278
310
  packages = expand_adaptors_default(packages, log);
279
311
  }
280
312
  await rtInstall(packages, repoDir, log);
313
+ const duration = log.timer("install");
314
+ log.success(`Installation complete in ${duration}`);
281
315
  }
282
- const duration = log.timer("install");
283
- log.success(`Installation complete in ${duration}`);
284
316
  };
285
317
  var clean = async (options, logger) => {
286
318
  if (options.repoDir) {
@@ -335,23 +367,25 @@ var list = async (options, logger) => {
335
367
  };
336
368
 
337
369
  // src/execute/handler.ts
370
+ var getAutoinstallTargets = (options) => {
371
+ var _a;
372
+ if (options.autoinstall && options.adaptors) {
373
+ return (_a = options.adaptors) == null ? void 0 : _a.filter((a) => !/=/.test(a));
374
+ }
375
+ return [];
376
+ };
338
377
  var executeHandler = async (options, logger) => {
339
378
  const start = new Date().getTime();
340
- if (options.autoinstall) {
379
+ const autoInstallTargets = getAutoinstallTargets(options);
380
+ if (autoInstallTargets.length) {
341
381
  const { repoDir } = options;
342
382
  logger.info("Auto-installing language adaptors");
343
- await install({ packages: options.adaptors, repoDir }, logger);
383
+ await install({ packages: autoInstallTargets, repoDir }, logger);
344
384
  }
345
385
  const state = await load_state_default(options, logger);
346
386
  const code = await compile_default(options, logger);
347
387
  const result = await execute_default(code, state, options);
348
- if (options.outputStdout) {
349
- logger.success(`Result: `);
350
- logger.success(result);
351
- } else {
352
- logger.success(`Writing output to ${options.outputPath}`);
353
- await writeFile(options.outputPath, JSON.stringify(result, null, 4));
354
- }
388
+ await serialize_output_default(options, result, logger);
355
389
  const duration = printDuration(new Date().getTime() - start);
356
390
  logger.success(`Done in ${duration}! \u2728`);
357
391
  };
@@ -392,7 +426,165 @@ var testHandler = async (options, logger) => {
392
426
  };
393
427
  var handler_default3 = testHandler;
394
428
 
429
+ // src/docgen/handler.ts
430
+ import { writeFile as writeFile3 } from "node:fs/promises";
431
+ import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
432
+ import path2 from "node:path";
433
+ import { describePackage } from "@openfn/describe-package";
434
+ import { getNameAndVersion as getNameAndVersion2 } from "@openfn/runtime";
435
+ var RETRY_DURATION = 500;
436
+ var RETRY_COUNT = 20;
437
+ var TIMEOUT_MS = 1e3 * 60;
438
+ var actualDocGen = (specifier) => describePackage(specifier, {});
439
+ var ensurePath = (filePath) => mkdirSync(path2.dirname(filePath), { recursive: true });
440
+ var generatePlaceholder = (path3) => {
441
+ writeFileSync(path3, `{ "loading": true, "timestamp": ${Date.now()}}`);
442
+ };
443
+ var finish = (logger, resultPath) => {
444
+ logger.success("Done! Docs can be found at:\n");
445
+ logger.print(` ${path2.resolve(resultPath)}`);
446
+ };
447
+ var generateDocs = async (specifier, path3, docgen, logger) => {
448
+ const result = await docgen(specifier);
449
+ await writeFile3(path3, JSON.stringify(result, null, 2));
450
+ finish(logger, path3);
451
+ return path3;
452
+ };
453
+ var waitForDocs = async (docs, path3, logger, retryDuration = RETRY_DURATION) => {
454
+ try {
455
+ if (docs.hasOwnProperty("loading")) {
456
+ logger.info("Docs are being loaded by another process. Waiting.");
457
+ return new Promise((resolve, reject) => {
458
+ let count = 0;
459
+ let i = setInterval(() => {
460
+ logger.info("Waiting..");
461
+ if (count > RETRY_COUNT) {
462
+ clearInterval(i);
463
+ reject(new Error("Timed out waiting for docs to load"));
464
+ }
465
+ const updated = JSON.parse(readFileSync(path3, "utf8"));
466
+ if (!updated.hasOwnProperty("loading")) {
467
+ logger.info("Docs found!");
468
+ clearInterval(i);
469
+ resolve(path3);
470
+ }
471
+ count++;
472
+ }, retryDuration);
473
+ });
474
+ } else {
475
+ logger.info(`Docs already written to cache at ${path3}`);
476
+ finish(logger, path3);
477
+ return path3;
478
+ }
479
+ } catch (e) {
480
+ logger.error("Existing doc JSON corrupt. Aborting");
481
+ throw e;
482
+ }
483
+ };
484
+ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RETRY_DURATION) => {
485
+ const { specifier, repoDir } = options;
486
+ const { version } = getNameAndVersion2(specifier);
487
+ if (!version) {
488
+ logger.error("Error: No version number detected");
489
+ logger.error("eg, @openfn/language-common@1.7.5");
490
+ logger.error("Aborting");
491
+ process.exit(9);
492
+ }
493
+ logger.success(`Generating docs for ${specifier}`);
494
+ const path3 = `${repoDir}/docs/${specifier}.json`;
495
+ ensurePath(path3);
496
+ const handleError = () => {
497
+ logger.info("Removing placeholder");
498
+ rmSync(path3);
499
+ };
500
+ try {
501
+ const existing = readFileSync(path3, "utf8");
502
+ const json = JSON.parse(existing);
503
+ if (json && json.timeout && Date.now() - json.timeout >= TIMEOUT_MS) {
504
+ logger.info(`Expired placeholder found. Removing.`);
505
+ rmSync(path3);
506
+ throw new Error("TIMEOUT");
507
+ }
508
+ return waitForDocs(json, path3, logger, retryDuration);
509
+ } catch (e) {
510
+ if (e.message !== "TIMEOUT") {
511
+ logger.info(`Docs JSON not found at ${path3}`);
512
+ }
513
+ logger.debug("Generating placeholder");
514
+ generatePlaceholder(path3);
515
+ return generateDocs(specifier, path3, docgen, logger).catch((e2) => {
516
+ logger.error("Error generating documentation");
517
+ logger.error(e2);
518
+ handleError();
519
+ });
520
+ }
521
+ };
522
+ var handler_default4 = docgenHandler;
523
+
524
+ // src/docs/handler.ts
525
+ import { readFile } from "node:fs/promises";
526
+ import { getNameAndVersion as getNameAndVersion3, getLatestVersion } from "@openfn/runtime";
527
+ var describe = (adaptorName, fn) => `## ${fn.name}(${fn.parameters.map(({ name }) => name).join(",")})
528
+
529
+ ${fn.description}
530
+
531
+ ### Usage Examples
532
+
533
+ ${fn.examples.length ? fn.examples.map((eg) => eg).join("\n\n") : "None"}
534
+
535
+ ### API Reference
536
+
537
+ https://docs.openfn.org/adaptors/packages/${adaptorName.replace(
538
+ "@openfn/language-",
539
+ ""
540
+ )}-docs#${fn.name}
541
+ `;
542
+ var docsHandler = async (options, logger) => {
543
+ const { adaptor, operation, repoDir } = options;
544
+ const [adaptorName] = expand_adaptors_default([adaptor], logger);
545
+ let { name, version } = getNameAndVersion3(adaptorName);
546
+ if (!version) {
547
+ logger.info("No version number provided, looking for latest...");
548
+ version = await getLatestVersion(version);
549
+ logger.info("Found ", version);
550
+ logger.success(`Showing docs for ${adaptorName} v${version}`);
551
+ }
552
+ logger.info("Generating/loading documentation...");
553
+ const path3 = await handler_default4(
554
+ {
555
+ specifier: `${name}@${version}`,
556
+ repoDir
557
+ },
558
+ createNullLogger()
559
+ );
560
+ if (path3) {
561
+ const source = await readFile(path3, "utf8");
562
+ const data = JSON.parse(source);
563
+ const fn = data.functions.find(({ name: name2 }) => name2 === operation);
564
+ logger.debug("Operation schema:", fn);
565
+ logger.success(`Documentation for ${name}.${operation} v${version}:
566
+ `);
567
+ const desc = describe(name, fn);
568
+ logger.print(desc);
569
+ logger.success("Done!");
570
+ } else {
571
+ logger.error("Not found");
572
+ }
573
+ };
574
+ var handler_default5 = docsHandler;
575
+
395
576
  // src/commands.ts
577
+ var handlers = {
578
+ execute: handler_default,
579
+ compile: handler_default2,
580
+ test: handler_default3,
581
+ docgen: handler_default4,
582
+ docs: handler_default5,
583
+ ["repo-clean"]: clean,
584
+ ["repo-install"]: install,
585
+ ["repo-pwd"]: pwd,
586
+ ["repo-list"]: list
587
+ };
396
588
  var parse = async (basePath, options, log) => {
397
589
  const opts = ensureOpts(basePath, options);
398
590
  const logger = log || logger_default(CLI, opts);
@@ -407,31 +599,13 @@ var parse = async (basePath, options, log) => {
407
599
  "You should set OPENFN_REPO_DIR or pass --repoDir=some/path in to the CLI"
408
600
  );
409
601
  }
410
- let handler = () => null;
411
- switch (options.command) {
412
- case "repo-install":
413
- handler = install;
414
- break;
415
- case "repo-clean":
416
- handler = clean;
417
- break;
418
- case "repo-pwd":
419
- handler = pwd;
420
- break;
421
- case "repo-list":
422
- handler = list;
423
- break;
424
- case "compile":
425
- assertPath(basePath);
426
- handler = handler_default2;
427
- break;
428
- case "test":
429
- handler = handler_default3;
430
- break;
431
- case "execute":
432
- default:
433
- assertPath(basePath);
434
- handler = handler_default;
602
+ const handler = options.command ? handlers[options.command] : handler_default;
603
+ if (!opts.command || /^(compile|execute)$/.test(opts.command)) {
604
+ assertPath(basePath);
605
+ }
606
+ if (!handler) {
607
+ logger.error(`Unrecognise command: ${options.command}`);
608
+ process.exit(1);
435
609
  }
436
610
  return handler(opts, logger);
437
611
  };
@@ -455,3 +629,6 @@ process.on("message", ({ init, basePath, opts }) => {
455
629
  });
456
630
  }
457
631
  });
632
+ process.send({
633
+ init: true
634
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.20",
4
4
  "description": "CLI devtools for the openfn toolchain.",
5
5
  "engines": {
6
6
  "node": ">=16",
@@ -30,15 +30,17 @@
30
30
  "@types/yargs": "^17.0.12",
31
31
  "ava": "5.1.0",
32
32
  "mock-fs": "^5.1.4",
33
- "ts-node": "^10.8.1",
33
+ "ts-node": "^10.9.1",
34
34
  "tslib": "^2.4.0",
35
35
  "tsup": "^6.2.3",
36
36
  "typescript": "^4.7.4"
37
37
  },
38
38
  "dependencies": {
39
- "@openfn/compiler": "^0.0.16",
40
- "@openfn/logger": "^0.0.6",
41
- "@openfn/runtime": "^0.0.11",
39
+ "@openfn/compiler": "^0.0.19",
40
+ "@openfn/describe-package": "^0.0.11",
41
+ "@openfn/logger": "^0.0.8",
42
+ "@openfn/runtime": "^0.0.13",
43
+ "fast-safe-stringify": "^2.1.1",
42
44
  "rimraf": "^3.0.2",
43
45
  "treeify": "^1.1.0",
44
46
  "yargs": "^17.5.1"