@openfn/cli 1.2.5 → 1.3.1

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
@@ -171,6 +171,29 @@ var autoinstall = {
171
171
  default: true
172
172
  }
173
173
  };
174
+ var apolloUrl = {
175
+ name: "apollo-url",
176
+ yargs: {
177
+ alias: ["url"],
178
+ description: "Auto-install the language adaptor(s)"
179
+ },
180
+ // map local, staging, prod to apollo-url
181
+ ensure: (opts2) => {
182
+ let didLoadShortcut = false;
183
+ ["local", "staging", "prod", "production"].forEach((shortcut) => {
184
+ if (shortcut in opts2) {
185
+ if (didLoadShortcut) {
186
+ throw new Error(
187
+ "Invalid apollo URL - please only enter one of local, staging or prod"
188
+ );
189
+ }
190
+ opts2.apolloUrl = shortcut;
191
+ delete opts2[shortcut];
192
+ didLoadShortcut = true;
193
+ }
194
+ });
195
+ }
196
+ };
174
197
  var cacheSteps = {
175
198
  name: "cache-steps",
176
199
  yargs: {
@@ -319,7 +342,7 @@ var outputPath = {
319
342
  description: "Path to the output file"
320
343
  },
321
344
  ensure: (opts2) => {
322
- if (opts2.command == "compile") {
345
+ if (/^(compile|apollo)$/.test(opts2.command)) {
323
346
  if (opts2.outputPath) {
324
347
  delete opts2.outputStdout;
325
348
  }
@@ -476,8 +499,36 @@ var override = (command, yargs2) => {
476
499
  };
477
500
  };
478
501
 
479
- // src/compile/command.ts
502
+ // src/apollo/util.ts
503
+ var STAGING_URL = "http://34.95.82.109";
504
+
505
+ // src/apollo/command.ts
480
506
  var options = [
507
+ apolloUrl,
508
+ stateStdin,
509
+ log,
510
+ logJson,
511
+ outputPath,
512
+ override(outputStdout, {
513
+ default: true
514
+ })
515
+ ];
516
+ var desc = `Call services on the openfn apollo server. Set the local server location with OPENFN_APOLLO_SERVER. The staging server is set to ${STAGING_URL}`;
517
+ var command_default = {
518
+ command: "apollo <service> [payload]",
519
+ desc,
520
+ handler: ensure("apollo", options),
521
+ builder: (yargs2) => build(options, yargs2).example(
522
+ "apollo echo path/to/json",
523
+ "Call the echo service, which returns json back"
524
+ ).example(
525
+ "apollo adaptor_gen cat-facts.example.json --local",
526
+ "Generate an adaptor template against a local server"
527
+ )
528
+ };
529
+
530
+ // src/compile/command.ts
531
+ var options2 = [
481
532
  expandAdaptors,
482
533
  adaptors,
483
534
  ignoreImports,
@@ -494,8 +545,8 @@ var options = [
494
545
  var compileCommand = {
495
546
  command: "compile [path]",
496
547
  describe: "Compile an openfn job or workflow and print or save the resulting JavaScript.",
497
- handler: ensure("compile", options),
498
- builder: (yargs2) => build(options, yargs2).positional("path", {
548
+ handler: ensure("compile", options2),
549
+ builder: (yargs2) => build(options2, yargs2).positional("path", {
499
550
  describe: "The path to load the job or workflow from (a .js or .json file or a dir containing a job.js file)",
500
551
  demandOption: true
501
552
  }).example(
@@ -506,10 +557,10 @@ var compileCommand = {
506
557
  "Compiles the workflow at foo/work.json and prints the result to -o foo/workflow-compiled.json"
507
558
  )
508
559
  };
509
- var command_default = compileCommand;
560
+ var command_default2 = compileCommand;
510
561
 
511
562
  // src/deploy/command.ts
512
- var options2 = [
563
+ var options3 = [
513
564
  configPath,
514
565
  confirm,
515
566
  describe,
@@ -521,10 +572,10 @@ var options2 = [
521
572
  var deployCommand = {
522
573
  command: "deploy",
523
574
  describe: "Deploy a project's config to a remote Lightning instance",
524
- handler: ensure("deploy", options2),
525
- builder: (yargs2) => build(options2, yargs2)
575
+ handler: ensure("deploy", options3),
576
+ builder: (yargs2) => build(options3, yargs2)
526
577
  };
527
- var command_default2 = deployCommand;
578
+ var command_default3 = deployCommand;
528
579
 
529
580
  // src/docgen/command.ts
530
581
  var docgenCommand = {
@@ -537,23 +588,23 @@ var docgenCommand = {
537
588
  },
538
589
  builder: (yargs2) => yargs2.example("docgen @openfn/language-common@1.7.5", "")
539
590
  };
540
- var command_default3 = docgenCommand;
591
+ var command_default4 = docgenCommand;
541
592
 
542
593
  // src/docs/command.ts
543
- var options3 = [log, logJson, repoDir];
594
+ var options4 = [log, logJson, repoDir];
544
595
  var docsCommand = {
545
596
  command: "docs <adaptor> [operation]",
546
597
  describe: "Print help for an adaptor function. You can use short-hand for adaptor names (ie, common instead of @openfn/language-common)",
547
- handler: ensure("docs", options3),
548
- builder: (yargs2) => build(options3, yargs2).example(
598
+ handler: ensure("docs", options4),
599
+ builder: (yargs2) => build(options4, yargs2).example(
549
600
  "docs common fn",
550
601
  "Print help for the common fn operation"
551
602
  )
552
603
  };
553
- var command_default4 = docsCommand;
604
+ var command_default5 = docsCommand;
554
605
 
555
606
  // src/execute/command.ts
556
- var options4 = [
607
+ var options5 = [
557
608
  expandAdaptors,
558
609
  adaptors,
559
610
  autoinstall,
@@ -585,8 +636,8 @@ Execute will run a expression/workflow at the path and write the output state to
585
636
 
586
637
  Remember to include the adaptor name with -a. Auto install adaptors with the -i flag.`,
587
638
  aliases: ["$0"],
588
- handler: ensure("execute", options4),
589
- builder: (yargs2) => build(options4, yargs2).positional("path", {
639
+ handler: ensure("execute", options5),
640
+ builder: (yargs2) => build(options5, yargs2).positional("path", {
590
641
  describe: "The path to load the job or workflow from (a .js or .json file or a dir containing a job.js file)",
591
642
  demandOption: true
592
643
  }).example(
@@ -603,10 +654,10 @@ Remember to include the adaptor name with -a. Auto install adaptors with the -i
603
654
  "Compile the expression at job.js with the http adaptor and print the code to stdout"
604
655
  )
605
656
  };
606
- var command_default5 = executeCommand;
657
+ var command_default6 = executeCommand;
607
658
 
608
659
  // src/metadata/command.ts
609
- var options5 = [
660
+ var options6 = [
610
661
  expandAdaptors,
611
662
  adaptors,
612
663
  force,
@@ -617,31 +668,31 @@ var options5 = [
617
668
  stateStdin,
618
669
  useAdaptorsMonorepo
619
670
  ];
620
- var command_default6 = {
671
+ var command_default7 = {
621
672
  command: "metadata",
622
673
  describe: "Generate metadata for an adaptor config",
623
- handler: ensure("metadata", options5),
624
- builder: (yargs2) => build(options5, yargs2).example(
674
+ handler: ensure("metadata", options6),
675
+ builder: (yargs2) => build(options6, yargs2).example(
625
676
  "metadata -a salesforce -s tmp/state.json",
626
677
  "Generate salesforce metadata from config in state.json"
627
678
  )
628
679
  };
629
680
 
630
681
  // src/pull/command.ts
631
- var options6 = [statePath, projectPath, configPath, log, logJson];
682
+ var options7 = [statePath, projectPath, configPath, log, logJson];
632
683
  var pullCommand = {
633
684
  command: "pull [projectId]",
634
685
  describe: "Pull a project's state and spec from a Lightning Instance to the local directory",
635
- builder: (yargs2) => build(options6, yargs2).positional("projectId", {
686
+ builder: (yargs2) => build(options7, yargs2).positional("projectId", {
636
687
  describe: "The id of the project that should be pulled shouled be a UUID",
637
688
  demandOption: true
638
689
  }).example(
639
690
  "pull 57862287-23e6-4650-8d79-e1dd88b24b1c",
640
691
  "Pull an updated copy of a the above spec and state from a Lightning Instance"
641
692
  ),
642
- handler: ensure("pull", options6)
693
+ handler: ensure("pull", options7)
643
694
  };
644
- var command_default7 = pullCommand;
695
+ var command_default8 = pullCommand;
645
696
 
646
697
  // src/repo/command.ts
647
698
  var repo = {
@@ -702,17 +753,17 @@ var list = {
702
753
  };
703
754
 
704
755
  // src/test/command.ts
705
- var options7 = [stateStdin, log, logJson];
706
- var command_default8 = {
756
+ var options8 = [stateStdin, log, logJson];
757
+ var command_default9 = {
707
758
  command: "test",
708
759
  desc: "Compiles and runs a test job, printing the result to stdout",
709
- handler: ensure("test", options7),
710
- builder: (yargs2) => build(options7, yargs2).example("test", "Run the test script")
760
+ handler: ensure("test", options8),
761
+ builder: (yargs2) => build(options8, yargs2).example("test", "Run the test script")
711
762
  };
712
763
 
713
764
  // src/cli.ts
714
765
  var y = yargs(hideBin(process.argv));
715
- var cmd = y.command(command_default5).command(command_default).command(command_default2).command(install).command(repo).command(command_default8).command(command_default4).command(command_default6).command(command_default3).command(command_default7).command({
766
+ var cmd = y.command(command_default6).command(command_default2).command(command_default3).command(install).command(repo).command(command_default9).command(command_default5).command(command_default).command(command_default7).command(command_default4).command(command_default8).command({
716
767
  command: "version",
717
768
  describe: "Show the currently installed version of the CLI, compiler and runtime.",
718
769
  handler: (argv) => {
@@ -1,3 +1,144 @@
1
+ // src/apollo/handler.ts
2
+ import { WebSocket } from "ws";
3
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import createLogger from "@openfn/logger";
6
+
7
+ // src/apollo/util.ts
8
+ var PRODUCTION_URL = "https://apollo.openfn.org";
9
+ var STAGING_URL = "http://34.95.82.109";
10
+ var LOCAL_URL = "http://localhost:3000";
11
+ var urlMap = {
12
+ ["prod"]: PRODUCTION_URL,
13
+ ["production"]: PRODUCTION_URL,
14
+ ["staging"]: STAGING_URL,
15
+ ["local"]: LOCAL_URL
16
+ };
17
+ var DEFAULT_ENV = "staging";
18
+ var getURL = (options) => {
19
+ if (options.apolloUrl) {
20
+ if (options.apolloUrl in urlMap) {
21
+ return urlMap[options.apolloUrl];
22
+ }
23
+ if (options.apolloUrl.startsWith("http")) {
24
+ return options.apolloUrl;
25
+ }
26
+ throw new Error(`Unrecognised apollo URL`);
27
+ }
28
+ const userDefault = process.env.OPENFN_APOLLO_DEFAULT_ENV || DEFAULT_ENV;
29
+ if (!/^(staging|prod|production|local|http*)$/.test(userDefault)) {
30
+ throw new Error(`Unrecognised apollo URL loaded from env: ${userDefault}`);
31
+ }
32
+ return urlMap[userDefault || "staging"];
33
+ };
34
+ var outputFiles = (files, logger) => {
35
+ logger.print();
36
+ for (const k in files) {
37
+ const line = new Array(k.length + 1).fill("-").join("");
38
+ logger.print(line);
39
+ logger.print(`${k}`);
40
+ logger.print(line);
41
+ logger.print();
42
+ logger.print(files[k]);
43
+ logger.print();
44
+ logger.print();
45
+ }
46
+ };
47
+
48
+ // src/apollo/handler.ts
49
+ var apolloHandler = async (options, logger) => {
50
+ logger.always(`Calling Apollo service: ${options.service}`);
51
+ const json = await loadPayload(logger, options.payload);
52
+ const url2 = getURL(options);
53
+ logger.success(`Using apollo server at`, url2);
54
+ const result = await callApollo(url2, options.service, json, logger);
55
+ if (result) {
56
+ await serializeOutput(options, result, logger);
57
+ } else {
58
+ logger.warn("No output returned from Apollo");
59
+ }
60
+ logger.success("Done!");
61
+ };
62
+ var write = async (basePath, filePath, content, logger) => {
63
+ const ext = path.extname(basePath);
64
+ let dir;
65
+ if (ext) {
66
+ dir = path.dirname(path.resolve(basePath));
67
+ } else {
68
+ dir = basePath;
69
+ }
70
+ await mkdir(dir, { recursive: true });
71
+ console.log(content);
72
+ const dest = path.resolve(basePath, filePath);
73
+ await writeFile(dest, content);
74
+ logger.success(`Wrote content to ${dest}`);
75
+ };
76
+ var serializeOutput = async (options, result, logger) => {
77
+ if (options.outputPath) {
78
+ if (result.files && !options.outputPath.endsWith(".json")) {
79
+ for (const p in result.files) {
80
+ await write(options.outputPath, p, result.files[p], logger);
81
+ }
82
+ } else {
83
+ await write(
84
+ options.outputPath,
85
+ "",
86
+ JSON.stringify(result, null, 2),
87
+ logger
88
+ );
89
+ }
90
+ return;
91
+ }
92
+ logger.success("Result:");
93
+ if (result.files) {
94
+ outputFiles(result.files, logger);
95
+ } else {
96
+ logger.always(JSON.stringify(result, void 0, 2));
97
+ }
98
+ };
99
+ var callApollo = async (apolloBaseUrl, serviceName, payload, logger) => {
100
+ return new Promise((resolve) => {
101
+ const apolloLogger = createLogger("APO", { level: "debug" });
102
+ const url2 = `${apolloBaseUrl.replace(
103
+ /^http/,
104
+ "ws"
105
+ )}/services/${serviceName}`;
106
+ logger.always("Calling apollo: ", url2);
107
+ const socket = new WebSocket(url2);
108
+ socket.addEventListener("message", ({ data }) => {
109
+ const evt = JSON.parse(data);
110
+ if (evt.event === "complete") {
111
+ logger.debug("Apollo responded with: ", evt.data);
112
+ resolve(evt.data);
113
+ } else if (evt.event === "log") {
114
+ apolloLogger.info(evt.data);
115
+ }
116
+ });
117
+ socket.addEventListener("open", () => {
118
+ socket.send(
119
+ JSON.stringify({
120
+ event: "start",
121
+ data: payload
122
+ })
123
+ );
124
+ });
125
+ });
126
+ };
127
+ var loadPayload = async (logger, path9) => {
128
+ if (!path9) {
129
+ logger.warn("No JSON payload provided");
130
+ logger.warn("Most apollo services require JSON to be uploaded");
131
+ return {};
132
+ }
133
+ if (path9.endsWith(".json")) {
134
+ const str = await readFile(path9, "utf8");
135
+ const json = JSON.parse(str);
136
+ logger.debug("Loaded JSON payload");
137
+ return json;
138
+ }
139
+ };
140
+ var handler_default = apolloHandler;
141
+
1
142
  // src/execute/execute.ts
2
143
  import run, { NOTIFY_JOB_COMPLETE, getNameAndVersion } from "@openfn/runtime";
3
144
 
@@ -14,7 +155,7 @@ var namespaces = {
14
155
  [COMPILER]: "CMP",
15
156
  [JOB]: "JOB"
16
157
  };
17
- var createLogger = (name = "", options) => {
158
+ var createLogger2 = (name = "", options) => {
18
159
  const logOptions = options.log || {};
19
160
  let json = false;
20
161
  let level = logOptions[name] || logOptions.default || "default";
@@ -28,25 +169,25 @@ var createLogger = (name = "", options) => {
28
169
  ...logOptions
29
170
  });
30
171
  };
31
- var logger_default = createLogger;
32
- var createNullLogger = () => createLogger(void 0, { log: { default: "none" } });
172
+ var logger_default = createLogger2;
173
+ var createNullLogger = () => createLogger2(void 0, { log: { default: "none" } });
33
174
 
34
175
  // src/util/cache.ts
35
176
  import fs from "node:fs";
36
- import path from "node:path";
177
+ import path2 from "node:path";
37
178
  import { rmdir } from "node:fs/promises";
38
179
  var getCachePath = async (plan, options, stepId) => {
39
180
  const { baseDir } = options;
40
181
  const { name } = plan.workflow;
41
182
  const basePath = `${baseDir}/.cli-cache/${name}`;
42
183
  if (stepId) {
43
- return path.resolve(`${basePath}/${stepId.replace(/ /, "-")}.json`);
184
+ return path2.resolve(`${basePath}/${stepId.replace(/ /, "-")}.json`);
44
185
  }
45
- return path.resolve(basePath);
186
+ return path2.resolve(basePath);
46
187
  };
47
188
  var ensureGitIgnore = (options) => {
48
189
  if (!options._hasGitIgnore) {
49
- const ignorePath = path.resolve(
190
+ const ignorePath = path2.resolve(
50
191
  options.baseDir,
51
192
  ".cli-cache",
52
193
  ".gitignore"
@@ -62,7 +203,7 @@ var ensureGitIgnore = (options) => {
62
203
  var saveToCache = async (plan, stepId, output, options, logger) => {
63
204
  if (options.cacheSteps) {
64
205
  const cachePath = await getCachePath(plan, options, stepId);
65
- fs.mkdirSync(path.dirname(cachePath), { recursive: true });
206
+ fs.mkdirSync(path2.dirname(cachePath), { recursive: true });
66
207
  ensureGitIgnore(options);
67
208
  logger.info(`Writing ${stepId} output to ${cachePath}`);
68
209
  fs.writeFileSync(cachePath, JSON.stringify(output));
@@ -112,13 +253,13 @@ var execute_default = async (plan, input, opts, logger) => {
112
253
  };
113
254
  function parseAdaptors(plan) {
114
255
  const extractInfo = (specifier) => {
115
- const [module, path8] = specifier.split("=");
256
+ const [module, path9] = specifier.split("=");
116
257
  const { name, version } = getNameAndVersion(module);
117
258
  const info = {
118
259
  name
119
260
  };
120
- if (path8) {
121
- info.path = path8;
261
+ if (path9) {
262
+ info.path = path9;
122
263
  }
123
264
  if (version) {
124
265
  info.version = version;
@@ -137,8 +278,8 @@ function parseAdaptors(plan) {
137
278
  }
138
279
 
139
280
  // src/execute/serialize-output.ts
140
- import { writeFile } from "node:fs/promises";
141
- var serializeOutput = async (options, result, logger) => {
281
+ import { writeFile as writeFile2 } from "node:fs/promises";
282
+ var serializeOutput2 = async (options, result, logger) => {
142
283
  let output = result;
143
284
  if (output && (output.configuration || output.data)) {
144
285
  const { configuration, ...rest } = result;
@@ -154,12 +295,12 @@ var serializeOutput = async (options, result, logger) => {
154
295
  logger.always(output);
155
296
  } else if (options.outputPath) {
156
297
  logger.debug(`Writing output to ${options.outputPath}`);
157
- await writeFile(options.outputPath, output);
298
+ await writeFile2(options.outputPath, output);
158
299
  logger.success(`State written to ${options.outputPath}`);
159
300
  }
160
301
  return output;
161
302
  };
162
- var serialize_output_default = serializeOutput;
303
+ var serialize_output_default = serializeOutput2;
163
304
 
164
305
  // src/execute/get-autoinstall-targets.ts
165
306
  var getAutoinstallTargets = (plan) => {
@@ -324,10 +465,10 @@ var stripVersionSpecifier = (specifier) => {
324
465
  return specifier;
325
466
  };
326
467
  var resolveSpecifierPath = async (pattern, repoDir, log) => {
327
- const [specifier, path8] = pattern.split("=");
328
- if (path8) {
329
- log.debug(`Resolved ${specifier} to path: ${path8}`);
330
- return path8;
468
+ const [specifier, path9] = pattern.split("=");
469
+ if (path9) {
470
+ log.debug(`Resolved ${specifier} to path: ${path9}`);
471
+ return path9;
331
472
  }
332
473
  const repoPath = await getModulePath(specifier, repoDir, log);
333
474
  if (repoPath) {
@@ -344,16 +485,16 @@ var loadTransformOptions = async (opts, log) => {
344
485
  const [pattern] = opts.adaptors;
345
486
  const [specifier] = pattern.split("=");
346
487
  log.debug(`Trying to preload types for ${specifier}`);
347
- const path8 = await resolveSpecifierPath(pattern, opts.repoDir, log);
348
- if (path8) {
488
+ const path9 = await resolveSpecifierPath(pattern, opts.repoDir, log);
489
+ if (path9) {
349
490
  try {
350
491
  exports = await preloadAdaptorExports(
351
- path8,
492
+ path9,
352
493
  opts.useAdaptorsMonorepo,
353
494
  log
354
495
  );
355
496
  } catch (e) {
356
- log.error(`Failed to load adaptor typedefs from path ${path8}`);
497
+ log.error(`Failed to load adaptor typedefs from path ${path9}`);
357
498
  log.error(e);
358
499
  }
359
500
  }
@@ -489,7 +630,7 @@ var validate_adaptors_default = validateAdaptors;
489
630
 
490
631
  // src/util/load-plan.ts
491
632
  import fs3 from "node:fs/promises";
492
- import path3 from "node:path";
633
+ import path4 from "node:path";
493
634
  import { isPath } from "@openfn/compiler";
494
635
 
495
636
  // src/util/expand-adaptors.ts
@@ -518,13 +659,13 @@ var expand_adaptors_default = (input) => {
518
659
  };
519
660
 
520
661
  // src/util/map-adaptors-to-monorepo.ts
521
- import { readFile } from "node:fs/promises";
522
- import path2 from "node:path";
662
+ import { readFile as readFile2 } from "node:fs/promises";
663
+ import path3 from "node:path";
523
664
  import assert from "node:assert";
524
665
  import { getNameAndVersion as getNameAndVersion2 } from "@openfn/runtime";
525
666
  var validateMonoRepo = async (repoPath, log) => {
526
667
  try {
527
- const raw = await readFile(`${repoPath}/package.json`, "utf8");
668
+ const raw = await readFile2(`${repoPath}/package.json`, "utf8");
528
669
  const pkg = JSON.parse(raw);
529
670
  assert(pkg.name === "adaptors");
530
671
  } catch (e) {
@@ -543,7 +684,7 @@ var updatePath = (adaptor, repoPath, log) => {
543
684
  );
544
685
  }
545
686
  const shortName = name.replace("@openfn/language-", "");
546
- const abspath = path2.resolve(repoPath, "packages", shortName);
687
+ const abspath = path3.resolve(repoPath, "packages", shortName);
547
688
  log.info(`Mapped adaptor ${name} to monorepo: ${abspath}`);
548
689
  return `${name}=${abspath}`;
549
690
  };
@@ -574,10 +715,10 @@ var loadPlan = async (options, logger) => {
574
715
  }
575
716
  const jsonPath = planPath || workflowPath;
576
717
  if (!options.baseDir) {
577
- options.baseDir = path3.dirname(jsonPath);
718
+ options.baseDir = path4.dirname(jsonPath);
578
719
  }
579
720
  const json = await loadJson(jsonPath, logger);
580
- const defaultName = path3.parse(jsonPath).name;
721
+ const defaultName = path4.parse(jsonPath).name;
581
722
  if (json.workflow) {
582
723
  return loadXPlan(json, options, logger, defaultName);
583
724
  } else {
@@ -623,7 +764,7 @@ var loadExpression = async (options, logger) => {
623
764
  logger.debug(`Loading expression from ${expressionPath}`);
624
765
  try {
625
766
  const expression = await fs3.readFile(expressionPath, "utf8");
626
- const name = path3.parse(expressionPath).name;
767
+ const name = path4.parse(expressionPath).name;
627
768
  const step = { expression };
628
769
  if (options.adaptors) {
629
770
  const [adaptor] = options.adaptors;
@@ -670,7 +811,7 @@ var loadOldWorkflow = async (workflow, options, logger, defaultName = "") => {
670
811
  };
671
812
  var fetchFile = async (jobId, rootDir = "", filePath, log) => {
672
813
  try {
673
- const fullPath = filePath.startsWith("~") ? filePath : path3.resolve(rootDir, filePath);
814
+ const fullPath = filePath.startsWith("~") ? filePath : path4.resolve(rootDir, filePath);
674
815
  const result = await fs3.readFile(fullPath, "utf8");
675
816
  log.debug("Loaded file", fullPath);
676
817
  return result;
@@ -733,8 +874,8 @@ var loadXPlan = async (plan, options, logger, defaultName = "") => {
733
874
  };
734
875
 
735
876
  // src/util/assert-path.ts
736
- var assert_path_default = (path8) => {
737
- if (!path8) {
877
+ var assert_path_default = (path9) => {
878
+ if (!path9) {
738
879
  console.error("ERROR: no path provided!");
739
880
  console.error("\nUsage:");
740
881
  console.error(" open path/to/job");
@@ -768,6 +909,56 @@ var fuzzy_match_step_default = (plan, stepPattern) => {
768
909
  }
769
910
  };
770
911
 
912
+ // src/util/validate-plan.ts
913
+ var assertWorkflowStructure = (plan, logger) => {
914
+ const { workflow, options } = plan;
915
+ if (!workflow || typeof workflow !== "object") {
916
+ throw new Error(`Missing or invalid "workflow" key in execution plan`);
917
+ }
918
+ if (!Array.isArray(workflow.steps)) {
919
+ throw new Error("The workflow.steps key must be an array");
920
+ }
921
+ if (workflow.steps.length === 0) {
922
+ logger.warn("The workflow.steps array is empty");
923
+ }
924
+ workflow.steps.forEach((step, index) => {
925
+ assertStepStructure(step, index);
926
+ });
927
+ assertOptionsStructure(options, logger);
928
+ };
929
+ var assertStepStructure = (step, index) => {
930
+ const allowedKeys = [
931
+ "id",
932
+ "name",
933
+ "next",
934
+ "previous",
935
+ "adaptor",
936
+ "expression",
937
+ "state",
938
+ "configuration",
939
+ "linker"
940
+ ];
941
+ for (const key in step) {
942
+ if (!allowedKeys.includes(key)) {
943
+ throw new Error(`Invalid key "${key}" in step ${step.id || index}`);
944
+ }
945
+ }
946
+ if ("adaptor" in step && !("expression" in step)) {
947
+ throw new Error(
948
+ `Step ${step.id ?? index} with an adaptor must also have an expression`
949
+ );
950
+ }
951
+ };
952
+ var assertOptionsStructure = (options = {}, logger) => {
953
+ const allowedKeys = ["timeout", "stepTimeout", "start", "end", "sanitize"];
954
+ for (const key in options) {
955
+ if (!allowedKeys.includes(key)) {
956
+ logger.warn(`Unrecognized option "${key}" in options object`);
957
+ }
958
+ }
959
+ };
960
+ var validate_plan_default = assertWorkflowStructure;
961
+
771
962
  // src/execute/handler.ts
772
963
  var matchStep = (plan, stepPattern, stepName, logger) => {
773
964
  try {
@@ -793,6 +984,7 @@ var executeHandler = async (options, logger) => {
793
984
  assert_path_default(options.path);
794
985
  await validate_adaptors_default(options, logger);
795
986
  let plan = await load_plan_default(options, logger);
987
+ validate_plan_default(plan, logger);
796
988
  if (options.cacheSteps) {
797
989
  await clearCache(plan, options, logger);
798
990
  }
@@ -876,10 +1068,10 @@ var executeHandler = async (options, logger) => {
876
1068
  process.exitCode = 1;
877
1069
  }
878
1070
  };
879
- var handler_default = executeHandler;
1071
+ var handler_default2 = executeHandler;
880
1072
 
881
1073
  // src/compile/handler.ts
882
- import { writeFile as writeFile2 } from "node:fs/promises";
1074
+ import { writeFile as writeFile3 } from "node:fs/promises";
883
1075
  var compileHandler = async (options, logger) => {
884
1076
  assert_path_default(options.path);
885
1077
  let result;
@@ -893,11 +1085,11 @@ var compileHandler = async (options, logger) => {
893
1085
  if (options.outputStdout) {
894
1086
  logger.success("Result:\n\n" + result);
895
1087
  } else {
896
- await writeFile2(options.outputPath, result);
1088
+ await writeFile3(options.outputPath, result);
897
1089
  logger.success(`Compiled to ${options.outputPath}`);
898
1090
  }
899
1091
  };
900
- var handler_default2 = compileHandler;
1092
+ var handler_default3 = compileHandler;
901
1093
 
902
1094
  // src/test/handler.ts
903
1095
  var testHandler = async (options, logger) => {
@@ -945,11 +1137,16 @@ var testHandler = async (options, logger) => {
945
1137
  }
946
1138
  const state = await load_state_default(plan, opts, createNullLogger());
947
1139
  const compiledPlan = await compile_default(plan, opts, logger);
948
- const result = await execute_default(compiledPlan, state, opts, logger);
1140
+ const result = await execute_default(
1141
+ compiledPlan,
1142
+ state,
1143
+ opts,
1144
+ logger
1145
+ );
949
1146
  logger.success(`Result: ${result.data.answer}`);
950
1147
  return result;
951
1148
  };
952
- var handler_default3 = testHandler;
1149
+ var handler_default4 = testHandler;
953
1150
 
954
1151
  // src/deploy/handler.ts
955
1152
  import {
@@ -1002,33 +1199,33 @@ function mergeOverrides(config, options) {
1002
1199
  function pickFirst(...args) {
1003
1200
  return args.find((arg) => arg !== void 0 && arg !== null);
1004
1201
  }
1005
- var handler_default4 = deployHandler;
1202
+ var handler_default5 = deployHandler;
1006
1203
 
1007
1204
  // src/docgen/handler.ts
1008
- import { writeFile as writeFile3 } from "node:fs/promises";
1205
+ import { writeFile as writeFile4 } from "node:fs/promises";
1009
1206
  import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
1010
- import path4 from "node:path";
1207
+ import path5 from "node:path";
1011
1208
  import { describePackage } from "@openfn/describe-package";
1012
1209
  import { getNameAndVersion as getNameAndVersion3 } from "@openfn/runtime";
1013
1210
  var RETRY_DURATION = 500;
1014
1211
  var RETRY_COUNT = 20;
1015
1212
  var TIMEOUT_MS = 1e3 * 60;
1016
1213
  var actualDocGen = (specifier) => describePackage(specifier, {});
1017
- var ensurePath = (filePath) => mkdirSync(path4.dirname(filePath), { recursive: true });
1018
- var generatePlaceholder = (path8) => {
1019
- writeFileSync(path8, `{ "loading": true, "timestamp": ${Date.now()}}`);
1214
+ var ensurePath = (filePath) => mkdirSync(path5.dirname(filePath), { recursive: true });
1215
+ var generatePlaceholder = (path9) => {
1216
+ writeFileSync(path9, `{ "loading": true, "timestamp": ${Date.now()}}`);
1020
1217
  };
1021
1218
  var finish = (logger, resultPath) => {
1022
1219
  logger.success("Done! Docs can be found at:\n");
1023
- logger.print(` ${path4.resolve(resultPath)}`);
1220
+ logger.print(` ${path5.resolve(resultPath)}`);
1024
1221
  };
1025
- var generateDocs = async (specifier, path8, docgen, logger) => {
1222
+ var generateDocs = async (specifier, path9, docgen, logger) => {
1026
1223
  const result = await docgen(specifier);
1027
- await writeFile3(path8, JSON.stringify(result, null, 2));
1028
- finish(logger, path8);
1029
- return path8;
1224
+ await writeFile4(path9, JSON.stringify(result, null, 2));
1225
+ finish(logger, path9);
1226
+ return path9;
1030
1227
  };
1031
- var waitForDocs = async (docs, path8, logger, retryDuration = RETRY_DURATION) => {
1228
+ var waitForDocs = async (docs, path9, logger, retryDuration = RETRY_DURATION) => {
1032
1229
  try {
1033
1230
  if (docs.hasOwnProperty("loading")) {
1034
1231
  logger.info("Docs are being loaded by another process. Waiting.");
@@ -1040,19 +1237,19 @@ var waitForDocs = async (docs, path8, logger, retryDuration = RETRY_DURATION) =>
1040
1237
  clearInterval(i);
1041
1238
  reject(new Error("Timed out waiting for docs to load"));
1042
1239
  }
1043
- const updated = JSON.parse(readFileSync(path8, "utf8"));
1240
+ const updated = JSON.parse(readFileSync(path9, "utf8"));
1044
1241
  if (!updated.hasOwnProperty("loading")) {
1045
1242
  logger.info("Docs found!");
1046
1243
  clearInterval(i);
1047
- resolve(path8);
1244
+ resolve(path9);
1048
1245
  }
1049
1246
  count++;
1050
1247
  }, retryDuration);
1051
1248
  });
1052
1249
  } else {
1053
- logger.info(`Docs already written to cache at ${path8}`);
1054
- finish(logger, path8);
1055
- return path8;
1250
+ logger.info(`Docs already written to cache at ${path9}`);
1251
+ finish(logger, path9);
1252
+ return path9;
1056
1253
  }
1057
1254
  } catch (e) {
1058
1255
  logger.error("Existing doc JSON corrupt. Aborting");
@@ -1069,42 +1266,44 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
1069
1266
  process.exit(9);
1070
1267
  }
1071
1268
  logger.success(`Generating docs for ${specifier}`);
1072
- const path8 = `${repoDir}/docs/${specifier}.json`;
1073
- ensurePath(path8);
1269
+ const path9 = `${repoDir}/docs/${specifier}.json`;
1270
+ ensurePath(path9);
1074
1271
  const handleError = () => {
1075
1272
  logger.info("Removing placeholder");
1076
- rmSync(path8);
1273
+ rmSync(path9);
1077
1274
  };
1078
1275
  try {
1079
- const existing = readFileSync(path8, "utf8");
1276
+ const existing = readFileSync(path9, "utf8");
1080
1277
  const json = JSON.parse(existing);
1081
1278
  if (json && json.timeout && Date.now() - json.timeout >= TIMEOUT_MS) {
1082
1279
  logger.info(`Expired placeholder found. Removing.`);
1083
- rmSync(path8);
1280
+ rmSync(path9);
1084
1281
  throw new Error("TIMEOUT");
1085
1282
  }
1086
- return waitForDocs(json, path8, logger, retryDuration);
1283
+ return waitForDocs(json, path9, logger, retryDuration);
1087
1284
  } catch (e) {
1088
1285
  if (e.message !== "TIMEOUT") {
1089
- logger.info(`Docs JSON not found at ${path8}`);
1286
+ logger.info(`Docs JSON not found at ${path9}`);
1090
1287
  }
1091
1288
  logger.debug("Generating placeholder");
1092
- generatePlaceholder(path8);
1093
- return generateDocs(specifier, path8, docgen, logger).catch((e2) => {
1289
+ generatePlaceholder(path9);
1290
+ return generateDocs(specifier, path9, docgen, logger).catch((e2) => {
1094
1291
  logger.error("Error generating documentation");
1095
1292
  logger.error(e2);
1096
1293
  handleError();
1097
1294
  });
1098
1295
  }
1099
1296
  };
1100
- var handler_default5 = docgenHandler;
1297
+ var handler_default6 = docgenHandler;
1101
1298
 
1102
1299
  // src/docs/handler.ts
1103
- import { readFile as readFile2 } from "node:fs/promises";
1300
+ import { readFile as readFile3 } from "node:fs/promises";
1104
1301
  import c from "chalk";
1105
1302
  import { getNameAndVersion as getNameAndVersion4, getLatestVersion } from "@openfn/runtime";
1106
1303
  var describeFn = (adaptorName, fn) => [
1107
- c.green(`## ${fn.name}(${fn.parameters.map(({ name }) => name).join(",")})`),
1304
+ c.green(
1305
+ `## ${fn.name}(${fn.parameters.map(({ name }) => name).join(",")})`
1306
+ ),
1108
1307
  `${fn.description}`,
1109
1308
  c.green("### Usage Examples"),
1110
1309
  fn.examples.length ? fn.examples.map(({ code, caption }) => {
@@ -1123,7 +1322,9 @@ ${code}`;
1123
1322
  ].join("\n\n");
1124
1323
  var describeLib = (adaptorName, data) => c.green(`## ${adaptorName} ${data.version}`) + `
1125
1324
 
1126
- ${data.functions.map((fn) => ` ${c.yellow(fn.name)} (${fn.parameters.map((p) => p.name).join(", ")})`).sort().join("\n")}
1325
+ ${data.functions.map(
1326
+ (fn) => ` ${c.yellow(fn.name)} (${fn.parameters.map((p) => p.name).join(", ")})`
1327
+ ).sort().join("\n")}
1127
1328
  `;
1128
1329
  var docsHandler = async (options, logger) => {
1129
1330
  const { adaptor, operation, repoDir } = options;
@@ -1137,7 +1338,7 @@ var docsHandler = async (options, logger) => {
1137
1338
  logger.success(`Showing docs for ${adaptorName} v${version}`);
1138
1339
  }
1139
1340
  logger.info("Generating/loading documentation...");
1140
- const path8 = await handler_default5(
1341
+ const path9 = await handler_default6(
1141
1342
  {
1142
1343
  specifier: `${name}@${version}`,
1143
1344
  repoDir
@@ -1146,8 +1347,8 @@ var docsHandler = async (options, logger) => {
1146
1347
  createNullLogger()
1147
1348
  );
1148
1349
  let didError = false;
1149
- if (path8) {
1150
- const source = await readFile2(path8, "utf8");
1350
+ if (path9) {
1351
+ const source = await readFile3(path9, "utf8");
1151
1352
  const data = JSON.parse(source);
1152
1353
  let desc;
1153
1354
  if (operation) {
@@ -1180,13 +1381,13 @@ var docsHandler = async (options, logger) => {
1180
1381
  logger.error("Not found");
1181
1382
  }
1182
1383
  };
1183
- var handler_default6 = docsHandler;
1384
+ var handler_default7 = docsHandler;
1184
1385
 
1185
1386
  // src/metadata/cache.ts
1186
1387
  import { createHash } from "node:crypto";
1187
1388
  import { readFileSync as readFileSync2 } from "node:fs";
1188
- import path5 from "node:path";
1189
- import { writeFile as writeFile4, mkdir } from "node:fs/promises";
1389
+ import path6 from "node:path";
1390
+ import { writeFile as writeFile5, mkdir as mkdir2 } from "node:fs/promises";
1190
1391
  var getPath = (repoDir, key) => `${repoDir}/meta/${key}.json`;
1191
1392
  var sortKeys = (obj) => {
1192
1393
  const newObj = {};
@@ -1216,8 +1417,8 @@ var get = (repoPath, key) => {
1216
1417
  };
1217
1418
  var set = async (repoPath, key, data) => {
1218
1419
  const fullPath = getPath(repoPath, key);
1219
- await mkdir(path5.dirname(fullPath), { recursive: true });
1220
- await writeFile4(fullPath, JSON.stringify(data));
1420
+ await mkdir2(path6.dirname(fullPath), { recursive: true });
1421
+ await writeFile5(fullPath, JSON.stringify(data));
1221
1422
  };
1222
1423
  var cache_default = { get, set, generateKey, getPath, sortKeys };
1223
1424
 
@@ -1301,10 +1502,10 @@ var metadataHandler = async (options, logger) => {
1301
1502
  process.exit(1);
1302
1503
  }
1303
1504
  };
1304
- var handler_default7 = metadataHandler;
1505
+ var handler_default8 = metadataHandler;
1305
1506
 
1306
1507
  // src/pull/handler.ts
1307
- import path6 from "path";
1508
+ import path7 from "path";
1308
1509
  import fs4 from "node:fs/promises";
1309
1510
  import {
1310
1511
  getConfig as getConfig2,
@@ -1316,7 +1517,9 @@ async function pullHandler(options, logger) {
1316
1517
  try {
1317
1518
  assert_path_default(options.projectId);
1318
1519
  const config = mergeOverrides2(await getConfig2(options.configPath), options);
1319
- logger.always("Downloading existing project state (as JSON) from the server.");
1520
+ logger.always(
1521
+ "Downloading existing project state (as JSON) from the server."
1522
+ );
1320
1523
  const { data: project } = await getProject(config, options.projectId);
1321
1524
  if (!project) {
1322
1525
  logger.error("ERROR: Project not found.");
@@ -1328,12 +1531,10 @@ async function pullHandler(options, logger) {
1328
1531
  }
1329
1532
  const state = getStateFromProjectPayload(project);
1330
1533
  await fs4.writeFile(
1331
- path6.resolve(config.statePath),
1534
+ path7.resolve(config.statePath),
1332
1535
  JSON.stringify(state, null, 2)
1333
1536
  );
1334
- logger.always(
1335
- "Downloading the project spec (as YAML) from the server."
1336
- );
1537
+ logger.always("Downloading the project spec (as YAML) from the server.");
1337
1538
  const url2 = new URL(
1338
1539
  `api/provision/yaml?id=${options.projectId}`,
1339
1540
  config.endpoint
@@ -1353,7 +1554,7 @@ async function pullHandler(options, logger) {
1353
1554
  process.exitCode = 1;
1354
1555
  process.exit(1);
1355
1556
  }
1356
- const resolvedPath = path6.resolve(config.specPath);
1557
+ const resolvedPath = path7.resolve(config.specPath);
1357
1558
  logger.debug("reading spec from", resolvedPath);
1358
1559
  await fs4.writeFile(resolvedPath, res.body);
1359
1560
  const spec = await getSpec(resolvedPath);
@@ -1382,11 +1583,11 @@ function mergeOverrides2(config, options) {
1382
1583
  function pickFirst2(...args) {
1383
1584
  return args.find((arg) => arg !== void 0 && arg !== null);
1384
1585
  }
1385
- var handler_default8 = pullHandler;
1586
+ var handler_default9 = pullHandler;
1386
1587
 
1387
1588
  // src/util/print-versions.ts
1388
1589
  import { readFileSync as readFileSync3 } from "node:fs";
1389
- import path7 from "node:path";
1590
+ import path8 from "node:path";
1390
1591
  import url from "node:url";
1391
1592
  import { getNameAndVersion as getNameAndVersion5 } from "@openfn/runtime";
1392
1593
  import { mainSymbols } from "figures";
@@ -1398,7 +1599,7 @@ var { triangleRightSmall: t } = mainSymbols;
1398
1599
  var loadVersionFromPath = (adaptorPath) => {
1399
1600
  try {
1400
1601
  const pkg = JSON.parse(
1401
- readFileSync3(path7.resolve(adaptorPath, "package.json"), "utf8")
1602
+ readFileSync3(path8.resolve(adaptorPath, "package.json"), "utf8")
1402
1603
  );
1403
1604
  return pkg.version;
1404
1605
  } catch (e) {
@@ -1431,7 +1632,7 @@ var printVersions = async (logger, options = {}, includeComponents = false) => {
1431
1632
  ...[NODE, CLI2, RUNTIME2, COMPILER2, adaptorName].map((s) => s.length)
1432
1633
  );
1433
1634
  const prefix = (str) => ` ${t} ${str.padEnd(longest + 4, " ")}`;
1434
- const dirname = path7.dirname(url.fileURLToPath(import.meta.url));
1635
+ const dirname = path8.dirname(url.fileURLToPath(import.meta.url));
1435
1636
  const pkg = JSON.parse(readFileSync3(`${dirname}/../../package.json`, "utf8"));
1436
1637
  const { version, dependencies } = pkg;
1437
1638
  const compilerVersion = dependencies["@openfn/compiler"];
@@ -1471,14 +1672,15 @@ var print_versions_default = printVersions;
1471
1672
 
1472
1673
  // src/commands.ts
1473
1674
  var handlers = {
1474
- execute: handler_default,
1475
- compile: handler_default2,
1476
- test: handler_default3,
1477
- deploy: handler_default4,
1478
- docgen: handler_default5,
1479
- docs: handler_default6,
1480
- metadata: handler_default7,
1481
- pull: handler_default8,
1675
+ apollo: handler_default,
1676
+ execute: handler_default2,
1677
+ compile: handler_default3,
1678
+ test: handler_default4,
1679
+ deploy: handler_default5,
1680
+ docgen: handler_default6,
1681
+ docs: handler_default7,
1682
+ metadata: handler_default8,
1683
+ pull: handler_default9,
1482
1684
  ["repo-clean"]: clean,
1483
1685
  ["repo-install"]: install,
1484
1686
  ["repo-pwd"]: pwd,
@@ -1507,7 +1709,7 @@ var parse = async (options, log) => {
1507
1709
  logger
1508
1710
  );
1509
1711
  }
1510
- if (!/^(pull|deploy|test|version)$/.test(options.command) && !options.repoDir) {
1712
+ if (!/^(pull|deploy|test|version|apollo)$/.test(options.command) && !options.repoDir) {
1511
1713
  logger.warn(
1512
1714
  "WARNING: no repo module dir found! Using the default (/tmp/repo)"
1513
1715
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/cli",
3
- "version": "1.2.5",
3
+ "version": "1.3.1",
4
4
  "description": "CLI devtools for the openfn toolchain.",
5
5
  "engines": {
6
6
  "node": ">=18",
@@ -29,6 +29,7 @@
29
29
  "@types/node": "^18.15.13",
30
30
  "@types/rimraf": "^3.0.2",
31
31
  "@types/treeify": "^1.0.0",
32
+ "@types/ws": "^8.5.10",
32
33
  "@types/yargs": "^17.0.24",
33
34
  "ava": "5.3.1",
34
35
  "mock-fs": "^5.1.4",
@@ -44,12 +45,13 @@
44
45
  "figures": "^5.0.0",
45
46
  "rimraf": "^3.0.2",
46
47
  "treeify": "^1.1.0",
48
+ "ws": "^8.14.1",
47
49
  "yargs": "^17.7.2",
48
50
  "@openfn/compiler": "0.1.2",
51
+ "@openfn/deploy": "0.4.6",
49
52
  "@openfn/describe-package": "0.0.19",
50
53
  "@openfn/logger": "1.0.1",
51
- "@openfn/runtime": "1.1.3",
52
- "@openfn/deploy": "0.4.6"
54
+ "@openfn/runtime": "1.1.3"
53
55
  },
54
56
  "files": [
55
57
  "dist",