@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.
@@ -10,184 +10,136 @@ import {
10
10
  expand_adaptors_default,
11
11
  logger_default,
12
12
  printDuration
13
- } from "../chunk-XYZNU5CH.js";
13
+ } from "../chunk-LV5XDERP.js";
14
14
 
15
- // src/execute/handler.ts
16
- import { readFile } from "node:fs/promises";
15
+ // src/execute/execute.ts
16
+ import run, { getNameAndVersion } from "@openfn/runtime";
17
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
- }
18
+ // src/util/abort.ts
19
+ var AbortError = class extends Error {
20
+ constructor(reason) {
21
+ super(reason);
22
+ this.handled = true;
35
23
  }
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
- }
24
+ };
25
+ var abort_default = (logger, reason, error, help) => {
26
+ const e = new AbortError(reason);
27
+ logger.error(reason);
28
+ if (error) {
29
+ logger.error(error.message);
47
30
  }
48
- log.info(
49
- "No state provided - using default state { data: {}, configuration: {}"
50
- );
51
- return {
52
- data: {},
53
- configuration: {}
54
- };
31
+ if (help) {
32
+ logger.always(help);
33
+ }
34
+ logger.break();
35
+ logger.error("Critical error: aborting command");
36
+ throw e;
55
37
  };
56
38
 
57
39
  // src/execute/execute.ts
58
- import run, { getNameAndVersion } from "@openfn/runtime";
59
- var execute_default = (code, state, opts) => {
60
- return run(code, state, {
61
- timeout: opts.timeout,
62
- immutableState: opts.immutable,
63
- logger: logger_default(RUNTIME, opts),
64
- jobLogger: logger_default(JOB, opts),
65
- linker: {
66
- repo: opts.repoDir,
67
- modules: parseAdaptors(opts)
68
- }
69
- });
40
+ var execute_default = async (input, state, opts, logger) => {
41
+ try {
42
+ const result = await run(input, state, {
43
+ strict: opts.strict,
44
+ start: opts.start,
45
+ timeout: opts.timeout,
46
+ immutableState: opts.immutable,
47
+ logger: logger_default(RUNTIME, opts),
48
+ jobLogger: logger_default(JOB, opts),
49
+ linker: {
50
+ repo: opts.repoDir,
51
+ modules: parseAdaptors(opts)
52
+ }
53
+ });
54
+ return result;
55
+ } catch (e) {
56
+ abort_default(logger, "Invalid workflow", e);
57
+ }
70
58
  };
71
59
  function parseAdaptors(opts) {
72
- const adaptors = {};
73
- opts.adaptors.reduce((obj, exp) => {
74
- const [module, path5] = exp.split("=");
60
+ const extractInfo = (specifier) => {
61
+ const [module, path6] = specifier.split("=");
75
62
  const { name, version } = getNameAndVersion(module);
76
- const info = {};
77
- if (path5) {
78
- info.path = path5;
63
+ const info = {
64
+ name
65
+ };
66
+ if (path6) {
67
+ info.path = path6;
79
68
  }
80
69
  if (version) {
81
70
  info.version = version;
82
71
  }
83
- obj[name] = info;
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)
72
+ return info;
125
73
  };
126
- if (opts.adaptors?.length && opts.ignoreImports != true) {
127
- let exports;
128
- const [pattern] = opts.adaptors;
129
- const [specifier] = pattern.split("=");
130
- log.debug(`Attempting to preload types for ${specifier}`);
131
- const path5 = await resolveSpecifierPath(pattern, opts.repoDir, log);
132
- if (path5) {
133
- try {
134
- exports = await preloadAdaptorExports(
135
- path5,
136
- opts.useAdaptorsMonorepo,
137
- log
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
74
+ const adaptors = {};
75
+ if (opts.adaptors) {
76
+ opts.adaptors.reduce((obj, exp) => {
77
+ const { name, ...maybeVersionAndPath } = extractInfo(exp);
78
+ obj[name] = { ...maybeVersionAndPath };
79
+ return obj;
80
+ }, adaptors);
81
+ }
82
+ if (opts.workflow) {
83
+ Object.values(opts.workflow.jobs).forEach((job) => {
84
+ if (job.adaptor) {
85
+ const { name, ...maybeVersionAndPath } = extractInfo(job.adaptor);
86
+ adaptors[name] = { ...maybeVersionAndPath };
153
87
  }
154
- };
88
+ });
155
89
  }
156
- return options;
157
- };
90
+ return adaptors;
91
+ }
158
92
 
159
93
  // src/execute/serialize-output.ts
160
94
  import { writeFile } from "node:fs/promises";
161
- import stringify from "fast-safe-stringify";
162
95
  var serializeOutput = async (options, result, logger) => {
163
96
  let output = result;
164
97
  if (output && (output.configuration || output.data)) {
165
- const { data, configuration, ...rest } = result;
166
- if (options.strictOutput !== false) {
167
- output = { data };
98
+ if (options.strict) {
99
+ output = { data: output.data };
100
+ if (result.errors) {
101
+ output.errors = result.errors;
102
+ }
168
103
  } else {
169
- output = {
170
- data,
171
- ...rest
172
- };
104
+ const { configuration, ...rest } = result;
105
+ output = rest;
173
106
  }
174
107
  }
175
108
  if (output === void 0) {
176
109
  output = "";
177
110
  } else {
178
- output = stringify(output, void 0, 2);
111
+ output = JSON.stringify(output, void 0, 2);
179
112
  }
180
113
  if (options.outputStdout) {
181
114
  logger.success(`Result: `);
182
- logger.success(output);
115
+ logger.always(output);
183
116
  } else if (options.outputPath) {
184
- logger.success(`Writing output to ${options.outputPath}`);
117
+ logger.debug(`Writing output to ${options.outputPath}`);
185
118
  await writeFile(options.outputPath, output);
119
+ logger.success(`State written to ${options.outputPath}`);
186
120
  }
187
121
  return output;
188
122
  };
189
123
  var serialize_output_default = serializeOutput;
190
124
 
125
+ // src/execute/get-autoinstall-targets.ts
126
+ var getAutoinstallTargets = (options) => {
127
+ if (options.workflow) {
128
+ const adaptors = {};
129
+ Object.values(options.workflow.jobs).forEach((job) => {
130
+ if (job.adaptor) {
131
+ adaptors[job.adaptor] = true;
132
+ }
133
+ });
134
+ return Object.keys(adaptors);
135
+ }
136
+ if (options.adaptors) {
137
+ return options.adaptors?.filter((a) => !/=/.test(a));
138
+ }
139
+ return [];
140
+ };
141
+ var get_autoinstall_targets_default = getAutoinstallTargets;
142
+
191
143
  // src/repo/handler.ts
192
144
  import { exec } from "node:child_process";
193
145
  import treeify from "treeify";
@@ -199,9 +151,10 @@ var install = async (opts, log = defaultLogger) => {
199
151
  log.success("Installing packages...");
200
152
  log.debug("repoDir is set to:", repoDir);
201
153
  if (adaptor) {
202
- packages = expand_adaptors_default(packages);
154
+ const expanded = expand_adaptors_default({ adaptors: packages });
155
+ packages = expanded.adaptors;
203
156
  }
204
- await rtInstall(packages, repoDir, log);
157
+ await rtInstall(packages ?? [], repoDir, log);
205
158
  const duration = log.timer("install");
206
159
  log.success(`Installation complete in ${duration}`);
207
160
  }
@@ -258,12 +211,165 @@ var list = async (options, logger) => {
258
211
  logger.success("Installed packages:\n\n" + treeify.asTree(output));
259
212
  };
260
213
 
214
+ // src/compile/compile.ts
215
+ import compile, { preloadAdaptorExports } from "@openfn/compiler";
216
+ import { getModulePath } from "@openfn/runtime";
217
+ var compile_default = async (opts, log) => {
218
+ log.debug("Compiling...");
219
+ let job;
220
+ if (opts.workflow) {
221
+ job = compileWorkflow(opts.workflow, opts, log);
222
+ } else {
223
+ job = await compileJob(opts.job || opts.jobPath, opts, log);
224
+ }
225
+ if (opts.jobPath) {
226
+ log.success(`Compiled from ${opts.jobPath}`);
227
+ } else {
228
+ log.success("Compilation complete");
229
+ }
230
+ return job;
231
+ };
232
+ var compileJob = async (job, opts, log, jobName) => {
233
+ try {
234
+ const compilerOptions = await loadTransformOptions(opts, log);
235
+ return compile(job, compilerOptions);
236
+ } catch (e) {
237
+ abort_default(
238
+ log,
239
+ `Failed to compile job ${jobName ?? ""}`.trim(),
240
+ e,
241
+ "Check the syntax of the job expression:\n\n" + job
242
+ );
243
+ }
244
+ };
245
+ var compileWorkflow = async (workflow, opts, log) => {
246
+ for (const job of workflow.jobs) {
247
+ const jobOpts = {
248
+ ...opts
249
+ };
250
+ if (job.adaptor) {
251
+ jobOpts.adaptors = [job.adaptor];
252
+ }
253
+ if (job.expression) {
254
+ job.expression = await compileJob(
255
+ job.expression,
256
+ jobOpts,
257
+ log,
258
+ job.id
259
+ );
260
+ }
261
+ }
262
+ return workflow;
263
+ };
264
+ var stripVersionSpecifier = (specifier) => {
265
+ const idx = specifier.lastIndexOf("@");
266
+ if (idx > 0) {
267
+ return specifier.substring(0, idx);
268
+ }
269
+ return specifier;
270
+ };
271
+ var resolveSpecifierPath = async (pattern, repoDir, log) => {
272
+ const [specifier, path6] = pattern.split("=");
273
+ if (path6) {
274
+ log.debug(`Resolved ${specifier} to path: ${path6}`);
275
+ return path6;
276
+ }
277
+ const repoPath = await getModulePath(specifier, repoDir, log);
278
+ if (repoPath) {
279
+ return repoPath;
280
+ }
281
+ return null;
282
+ };
283
+ var loadTransformOptions = async (opts, log) => {
284
+ const options = {
285
+ logger: log || logger_default(COMPILER, opts)
286
+ };
287
+ if (opts.adaptors?.length && opts.ignoreImports != true) {
288
+ let exports;
289
+ const [pattern] = opts.adaptors;
290
+ const [specifier] = pattern.split("=");
291
+ log.debug(`Attempting to preload types for ${specifier}`);
292
+ const path6 = await resolveSpecifierPath(pattern, opts.repoDir, log);
293
+ if (path6) {
294
+ try {
295
+ exports = await preloadAdaptorExports(
296
+ path6,
297
+ opts.useAdaptorsMonorepo,
298
+ log
299
+ );
300
+ } catch (e) {
301
+ log.error(`Failed to load adaptor typedefs from path ${path6}`);
302
+ log.error(e);
303
+ }
304
+ }
305
+ if (!exports || exports.length === 0) {
306
+ log.debug(`No module exports found for ${pattern}`);
307
+ }
308
+ options["add-imports"] = {
309
+ ignore: opts.ignoreImports,
310
+ adaptor: {
311
+ name: stripVersionSpecifier(specifier),
312
+ exports,
313
+ exportAll: true
314
+ }
315
+ };
316
+ }
317
+ return options;
318
+ };
319
+
320
+ // src/util/load-state.ts
321
+ import fs from "node:fs/promises";
322
+ var load_state_default = async (opts, log) => {
323
+ const { stateStdin, statePath } = opts;
324
+ log.debug("Loading state...");
325
+ if (stateStdin) {
326
+ try {
327
+ const json = JSON.parse(stateStdin);
328
+ log.success("Read state from stdin");
329
+ log.debug("state:", json);
330
+ return json;
331
+ } catch (e) {
332
+ log.error("Failed to load state from stdin");
333
+ log.error(stateStdin);
334
+ log.error(e);
335
+ process.exit(1);
336
+ }
337
+ }
338
+ if (statePath) {
339
+ try {
340
+ const str = await fs.readFile(statePath, "utf8");
341
+ const json = JSON.parse(str);
342
+ log.success(`Loaded state from ${statePath}`);
343
+ log.debug("state:", json);
344
+ return json;
345
+ } catch (e) {
346
+ log.warn(`Error loading state from ${statePath}`);
347
+ log.warn(e);
348
+ }
349
+ }
350
+ log.info(
351
+ "No state provided - using default state { data: {}, configuration: {} }"
352
+ );
353
+ return {
354
+ data: {},
355
+ configuration: {}
356
+ };
357
+ };
358
+
261
359
  // src/util/validate-adaptors.ts
262
360
  var validateAdaptors = async (options, logger) => {
263
361
  if (options.skipAdaptorValidation) {
264
362
  return;
265
363
  }
266
- if (!options.adaptors || options.adaptors.length === 0) {
364
+ const hasDeclaredAdaptors = options.adaptors && options.adaptors.length > 0;
365
+ if (options.workflowPath && hasDeclaredAdaptors) {
366
+ logger.error("ERROR: adaptor and workflow provided");
367
+ logger.error(
368
+ "This is probably not what you meant to do. A workflow should declare an adaptor for each job."
369
+ );
370
+ throw new Error("adaptor and workflow provided");
371
+ }
372
+ if (!options.workflowPath && !hasDeclaredAdaptors) {
267
373
  logger.warn("WARNING: No adaptor provided!");
268
374
  logger.warn(
269
375
  "This job will probably fail. Pass an adaptor with the -a flag, eg:"
@@ -275,22 +381,133 @@ var validateAdaptors = async (options, logger) => {
275
381
  };
276
382
  var validate_adaptors_default = validateAdaptors;
277
383
 
278
- // src/execute/handler.ts
279
- var getAutoinstallTargets = (options) => {
280
- if (options.adaptors) {
281
- return options.adaptors?.filter((a) => !/=/.test(a));
384
+ // src/util/load-input.ts
385
+ import path from "node:path";
386
+ import fs2 from "node:fs/promises";
387
+ import { isPath } from "@openfn/compiler";
388
+ var load_input_default = async (opts, log) => {
389
+ const { job, workflow, jobPath, workflowPath } = opts;
390
+ if (workflow || workflowPath) {
391
+ return loadWorkflow(opts, log);
392
+ }
393
+ if (job) {
394
+ return job;
395
+ }
396
+ if (jobPath) {
397
+ try {
398
+ log.debug(`Loading job from ${jobPath}`);
399
+ opts.job = await fs2.readFile(jobPath, "utf8");
400
+ return opts.job;
401
+ } catch (e) {
402
+ abort_default(
403
+ log,
404
+ "Job not found",
405
+ void 0,
406
+ `Failed to load the job from ${jobPath}`
407
+ );
408
+ }
409
+ }
410
+ };
411
+ var loadWorkflow = async (opts, log) => {
412
+ const { workflowPath, workflow } = opts;
413
+ const readWorkflow = async () => {
414
+ try {
415
+ const text = await fs2.readFile(workflowPath, "utf8");
416
+ return text;
417
+ } catch (e) {
418
+ abort_default(
419
+ log,
420
+ "Workflow not found",
421
+ void 0,
422
+ `Failed to load a workflow from ${workflowPath}`
423
+ );
424
+ }
425
+ };
426
+ const parseWorkflow = (contents) => {
427
+ try {
428
+ return JSON.parse(contents);
429
+ } catch (e) {
430
+ abort_default(
431
+ log,
432
+ "Invalid JSON in workflow",
433
+ e,
434
+ `Check the syntax of the JSON at ${workflowPath}`
435
+ );
436
+ }
437
+ };
438
+ const fetchWorkflowFile = async (jobId, rootDir = "", filePath) => {
439
+ try {
440
+ const fullPath = filePath.startsWith("~") ? filePath : path.resolve(rootDir, filePath);
441
+ const result = await fs2.readFile(fullPath, "utf8");
442
+ return result;
443
+ } catch (e) {
444
+ abort_default(
445
+ log,
446
+ `File not found for job ${jobId}: ${filePath}`,
447
+ void 0,
448
+ `This workflow references a file which cannot be found at ${filePath}
449
+
450
+ Paths inside the workflow are relative to the workflow.json`
451
+ );
452
+ }
453
+ };
454
+ log.debug(`Loading workflow from ${workflowPath}`);
455
+ try {
456
+ let wf;
457
+ let rootDir = opts.baseDir;
458
+ if (workflowPath) {
459
+ let workflowRaw = await readWorkflow();
460
+ wf = parseWorkflow(workflowRaw);
461
+ if (!rootDir) {
462
+ rootDir = path.dirname(workflowPath);
463
+ }
464
+ } else {
465
+ wf = workflow;
466
+ }
467
+ let idx = 0;
468
+ for (const job of wf.jobs) {
469
+ idx += 1;
470
+ const expressionStr = typeof job.expression === "string" && job.expression?.trim();
471
+ const configurationStr = typeof job.configuration === "string" && job.configuration?.trim();
472
+ if (expressionStr && isPath(expressionStr)) {
473
+ job.expression = await fetchWorkflowFile(
474
+ job.id || `${idx}`,
475
+ rootDir,
476
+ expressionStr
477
+ );
478
+ }
479
+ if (configurationStr && isPath(configurationStr)) {
480
+ const configString = await fetchWorkflowFile(
481
+ job.id || `${idx}`,
482
+ rootDir,
483
+ configurationStr
484
+ );
485
+ job.configuration = JSON.parse(configString);
486
+ }
487
+ }
488
+ opts.workflow = wf;
489
+ log.debug("Workflow loaded!");
490
+ return opts.workflow;
491
+ } catch (e) {
492
+ log.error(`Error loading workflow from ${workflowPath}`);
493
+ throw e;
282
494
  }
283
- return [];
284
495
  };
496
+
497
+ // src/execute/handler.ts
285
498
  var executeHandler = async (options, logger) => {
286
499
  const start = new Date().getTime();
287
500
  await validate_adaptors_default(options, logger);
501
+ let input = await load_input_default(options, logger);
502
+ if (options.workflow) {
503
+ expand_adaptors_default(options);
504
+ }
288
505
  const { repoDir, monorepoPath, autoinstall } = options;
289
506
  if (autoinstall) {
290
507
  if (monorepoPath) {
291
508
  logger.warn("Skipping auto-install as monorepo is being used");
292
509
  } else {
293
- const autoInstallTargets = getAutoinstallTargets(options);
510
+ const autoInstallTargets = get_autoinstall_targets_default(options);
294
511
  if (autoInstallTargets.length) {
295
512
  logger.info("Auto-installing language adaptors");
296
513
  await install({ packages: autoInstallTargets, repoDir }, logger);
@@ -298,25 +515,29 @@ var executeHandler = async (options, logger) => {
298
515
  }
299
516
  }
300
517
  const state = await load_state_default(options, logger);
301
- let code = "";
302
518
  if (options.compile) {
303
- code = await compile_default(options, logger);
519
+ input = await compile_default(options, logger);
304
520
  } else {
305
521
  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
522
  }
311
523
  try {
312
- const result = await execute_default(code, state, options);
524
+ const result = await execute_default(input, state, options, logger);
313
525
  await serialize_output_default(options, result, logger);
314
526
  const duration = printDuration(new Date().getTime() - start);
315
- logger.success(`Done in ${duration}! \u2728`);
316
- } catch (error) {
317
- logger.error(error);
527
+ if (result.errors) {
528
+ logger.warn(
529
+ `Errors reported in ${Object.keys(result.errors).length} jobs`
530
+ );
531
+ }
532
+ logger.success(`Finished in ${duration}${result.errors ? "" : " \u2728"}`);
533
+ return result;
534
+ } catch (err) {
535
+ if (!err.handled) {
536
+ logger.error("Unexpected error in execution");
537
+ logger.error(err);
538
+ }
318
539
  const duration = printDuration(new Date().getTime() - start);
319
- logger.error(`Took ${duration}.`);
540
+ logger.always(`Workflow failed in ${duration}.`);
320
541
  process.exitCode = 1;
321
542
  }
322
543
  };
@@ -325,33 +546,65 @@ var handler_default = executeHandler;
325
546
  // src/compile/handler.ts
326
547
  import { writeFile as writeFile2 } from "node:fs/promises";
327
548
  var compileHandler = async (options, logger) => {
328
- const code = await compile_default(options, logger);
549
+ await load_input_default(options, logger);
550
+ let result = await compile_default(options, logger);
551
+ if (options.workflow) {
552
+ result = JSON.stringify(result);
553
+ }
329
554
  if (options.outputStdout) {
330
555
  logger.success("Compiled code:");
331
- logger.success("\n" + code);
556
+ logger.success("\n" + result);
332
557
  } else {
333
- await writeFile2(options.outputPath, code);
558
+ await writeFile2(options.outputPath, result);
334
559
  logger.success(`Compiled to ${options.outputPath}`);
335
560
  }
336
561
  };
337
562
  var handler_default2 = compileHandler;
338
563
 
339
564
  // src/test/handler.ts
340
- var sillyMessage = "Calculating the answer to life, the universe, and everything...";
341
565
  var testHandler = async (options, logger) => {
342
566
  logger.log("Running test job...");
343
567
  options.compile = true;
344
- options.jobSource = `const fn = () => state => { console.log('${sillyMessage}'); return state * 2; } ; fn()`;
345
- delete options.jobPath;
568
+ options.adaptors = [];
569
+ options.workflow = {
570
+ start: "start",
571
+ jobs: [
572
+ {
573
+ id: "start",
574
+ data: { defaultAnswer: 42 },
575
+ expression: "const fn = () => (state) => { console.log('Starting computer...'); return state; }; fn()",
576
+ next: {
577
+ calculate: "!state.error"
578
+ }
579
+ },
580
+ {
581
+ id: "calculate",
582
+ expression: "const fn = () => (state) => { console.log('Calculating to life, the universe, and everything..'); return state }; fn()",
583
+ next: {
584
+ result: true
585
+ }
586
+ },
587
+ {
588
+ id: "result",
589
+ expression: "const fn = () => (state) => ({ data: { answer: state.data.answer || state.data.defaultAnswer } }); fn()"
590
+ }
591
+ ]
592
+ };
593
+ logger.break();
594
+ logger.info("Workflow object:");
595
+ logger.info(JSON.stringify(options.workflow, null, 2));
596
+ logger.break();
346
597
  if (!options.stateStdin) {
347
- logger.debug("No state provided: try -S <number> to provide some state");
348
- options.stateStdin = "21";
598
+ logger.debug(
599
+ "No state provided: pass an object with state.data.answer to provide custom input"
600
+ );
601
+ logger.debug('eg: -S "{ "data": { "answer": 33 } }"');
349
602
  }
350
603
  const silentLogger = createNullLogger();
351
604
  const state = await load_state_default(options, silentLogger);
352
605
  const code = await compile_default(options, logger);
353
606
  const result = await execute_default(code, state, options);
354
- logger.success(`Result: ${result}`);
607
+ logger.success(`Result: ${result.data.answer}`);
355
608
  return result;
356
609
  };
357
610
  var handler_default3 = testHandler;
@@ -359,28 +612,28 @@ var handler_default3 = testHandler;
359
612
  // src/docgen/handler.ts
360
613
  import { writeFile as writeFile3 } from "node:fs/promises";
361
614
  import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
362
- import path from "node:path";
615
+ import path2 from "node:path";
363
616
  import { describePackage } from "@openfn/describe-package";
364
617
  import { getNameAndVersion as getNameAndVersion2 } from "@openfn/runtime";
365
618
  var RETRY_DURATION = 500;
366
619
  var RETRY_COUNT = 20;
367
620
  var TIMEOUT_MS = 1e3 * 60;
368
621
  var actualDocGen = (specifier) => describePackage(specifier, {});
369
- var ensurePath = (filePath) => mkdirSync(path.dirname(filePath), { recursive: true });
370
- var generatePlaceholder = (path5) => {
371
- writeFileSync(path5, `{ "loading": true, "timestamp": ${Date.now()}}`);
622
+ var ensurePath = (filePath) => mkdirSync(path2.dirname(filePath), { recursive: true });
623
+ var generatePlaceholder = (path6) => {
624
+ writeFileSync(path6, `{ "loading": true, "timestamp": ${Date.now()}}`);
372
625
  };
373
626
  var finish = (logger, resultPath) => {
374
627
  logger.success("Done! Docs can be found at:\n");
375
- logger.print(` ${path.resolve(resultPath)}`);
628
+ logger.print(` ${path2.resolve(resultPath)}`);
376
629
  };
377
- var generateDocs = async (specifier, path5, docgen, logger) => {
630
+ var generateDocs = async (specifier, path6, docgen, logger) => {
378
631
  const result = await docgen(specifier);
379
- await writeFile3(path5, JSON.stringify(result, null, 2));
380
- finish(logger, path5);
381
- return path5;
632
+ await writeFile3(path6, JSON.stringify(result, null, 2));
633
+ finish(logger, path6);
634
+ return path6;
382
635
  };
383
- var waitForDocs = async (docs, path5, logger, retryDuration = RETRY_DURATION) => {
636
+ var waitForDocs = async (docs, path6, logger, retryDuration = RETRY_DURATION) => {
384
637
  try {
385
638
  if (docs.hasOwnProperty("loading")) {
386
639
  logger.info("Docs are being loaded by another process. Waiting.");
@@ -392,19 +645,19 @@ var waitForDocs = async (docs, path5, logger, retryDuration = RETRY_DURATION) =>
392
645
  clearInterval(i);
393
646
  reject(new Error("Timed out waiting for docs to load"));
394
647
  }
395
- const updated = JSON.parse(readFileSync(path5, "utf8"));
648
+ const updated = JSON.parse(readFileSync(path6, "utf8"));
396
649
  if (!updated.hasOwnProperty("loading")) {
397
650
  logger.info("Docs found!");
398
651
  clearInterval(i);
399
- resolve(path5);
652
+ resolve(path6);
400
653
  }
401
654
  count++;
402
655
  }, retryDuration);
403
656
  });
404
657
  } else {
405
- logger.info(`Docs already written to cache at ${path5}`);
406
- finish(logger, path5);
407
- return path5;
658
+ logger.info(`Docs already written to cache at ${path6}`);
659
+ finish(logger, path6);
660
+ return path6;
408
661
  }
409
662
  } catch (e) {
410
663
  logger.error("Existing doc JSON corrupt. Aborting");
@@ -421,28 +674,28 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
421
674
  process.exit(9);
422
675
  }
423
676
  logger.success(`Generating docs for ${specifier}`);
424
- const path5 = `${repoDir}/docs/${specifier}.json`;
425
- ensurePath(path5);
677
+ const path6 = `${repoDir}/docs/${specifier}.json`;
678
+ ensurePath(path6);
426
679
  const handleError = () => {
427
680
  logger.info("Removing placeholder");
428
- rmSync(path5);
681
+ rmSync(path6);
429
682
  };
430
683
  try {
431
- const existing = readFileSync(path5, "utf8");
684
+ const existing = readFileSync(path6, "utf8");
432
685
  const json = JSON.parse(existing);
433
686
  if (json && json.timeout && Date.now() - json.timeout >= TIMEOUT_MS) {
434
687
  logger.info(`Expired placeholder found. Removing.`);
435
- rmSync(path5);
688
+ rmSync(path6);
436
689
  throw new Error("TIMEOUT");
437
690
  }
438
- return waitForDocs(json, path5, logger, retryDuration);
691
+ return waitForDocs(json, path6, logger, retryDuration);
439
692
  } catch (e) {
440
693
  if (e.message !== "TIMEOUT") {
441
- logger.info(`Docs JSON not found at ${path5}`);
694
+ logger.info(`Docs JSON not found at ${path6}`);
442
695
  }
443
696
  logger.debug("Generating placeholder");
444
- generatePlaceholder(path5);
445
- return generateDocs(specifier, path5, docgen, logger).catch((e2) => {
697
+ generatePlaceholder(path6);
698
+ return generateDocs(specifier, path6, docgen, logger).catch((e2) => {
446
699
  logger.error("Error generating documentation");
447
700
  logger.error(e2);
448
701
  handleError();
@@ -452,7 +705,7 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
452
705
  var handler_default4 = docgenHandler;
453
706
 
454
707
  // src/docs/handler.ts
455
- import { readFile as readFile2 } from "node:fs/promises";
708
+ import { readFile } from "node:fs/promises";
456
709
  import { getNameAndVersion as getNameAndVersion3, getLatestVersion } from "@openfn/runtime";
457
710
  var describeFn = (adaptorName, fn) => `## ${fn.name}(${fn.parameters.map(({ name }) => name).join(",")})
458
711
 
@@ -481,7 +734,8 @@ ${data.functions.map((fn) => ` ${fn.name}(${fn.parameters.map((p) => p.name).jo
481
734
  `;
482
735
  var docsHandler = async (options, logger) => {
483
736
  const { adaptor, operation, repoDir } = options;
484
- const [adaptorName] = expand_adaptors_default([adaptor], logger);
737
+ const { adaptors } = expand_adaptors_default({ adaptors: [adaptor] });
738
+ const [adaptorName] = adaptors;
485
739
  let { name, version } = getNameAndVersion3(adaptorName);
486
740
  if (!version) {
487
741
  logger.info("No version number provided, looking for latest...");
@@ -490,29 +744,38 @@ var docsHandler = async (options, logger) => {
490
744
  logger.success(`Showing docs for ${adaptorName} v${version}`);
491
745
  }
492
746
  logger.info("Generating/loading documentation...");
493
- const path5 = await handler_default4(
747
+ const path6 = await handler_default4(
494
748
  {
495
749
  specifier: `${name}@${version}`,
496
750
  repoDir
497
751
  },
498
752
  createNullLogger()
499
753
  );
500
- if (path5) {
501
- const source = await readFile2(path5, "utf8");
754
+ let didError = false;
755
+ if (path6) {
756
+ const source = await readFile(path6, "utf8");
502
757
  const data = JSON.parse(source);
503
758
  let desc;
504
759
  if (operation) {
505
760
  const fn = data.functions.find(({ name: name2 }) => name2 === operation);
506
- logger.debug("Operation schema:", fn);
507
- logger.success(`Documentation for ${name}.${operation} v${version}:
761
+ if (fn) {
762
+ logger.debug("Operation schema:", fn);
763
+ logger.success(`Documentation for ${name}.${operation} v${version}:
508
764
  `);
509
- desc = describeFn(name, fn);
765
+ desc = describeFn(name, fn);
766
+ } else {
767
+ logger.error(`Failed to find ${operation} in ${name}`);
768
+ }
510
769
  } else {
511
770
  logger.debug("No operation provided, listing available operations");
512
771
  desc = describeLib(name, data);
513
772
  }
514
773
  logger.print(desc);
515
- logger.success("Done!");
774
+ if (didError) {
775
+ logger.error("Error");
776
+ } else {
777
+ logger.success("Done!");
778
+ }
516
779
  } else {
517
780
  logger.error("Not found");
518
781
  }
@@ -522,13 +785,29 @@ var handler_default5 = docsHandler;
522
785
  // src/metadata/cache.ts
523
786
  import { createHash } from "node:crypto";
524
787
  import { readFileSync as readFileSync2 } from "node:fs";
525
- import path2 from "node:path";
788
+ import path3 from "node:path";
526
789
  import { writeFile as writeFile4, mkdir } from "node:fs/promises";
527
790
  var getPath = (repoDir, key) => `${repoDir}/meta/${key}.json`;
528
- var generateKey = (config) => createHash("sha256").update(JSON.stringify(config)).digest("hex");
791
+ var sortKeys = (obj) => {
792
+ const newObj = {};
793
+ Object.keys(obj).sort().forEach((k) => {
794
+ const v = obj[k];
795
+ if (!v || typeof v == "string" || !isNaN(v) || Array.isArray(v)) {
796
+ newObj[k] = v;
797
+ } else {
798
+ newObj[k] = sortKeys(v);
799
+ }
800
+ });
801
+ return newObj;
802
+ };
803
+ var generateKey = (config, adaptor) => {
804
+ const sorted = sortKeys(config);
805
+ const key = `${JSON.stringify(sorted)}-${adaptor}}`;
806
+ return createHash("sha256").update(key).digest("hex");
807
+ };
529
808
  var get = (repoPath, key) => {
530
809
  try {
531
- const data = readFileSync2(getPath(repoPath, key));
810
+ const data = readFileSync2(getPath(repoPath, key), "utf8");
532
811
  const json = JSON.parse(data);
533
812
  return json;
534
813
  } catch (e) {
@@ -537,10 +816,10 @@ var get = (repoPath, key) => {
537
816
  };
538
817
  var set = async (repoPath, key, data) => {
539
818
  const fullPath = getPath(repoPath, key);
540
- await mkdir(path2.dirname(fullPath), { recursive: true });
819
+ await mkdir(path3.dirname(fullPath), { recursive: true });
541
820
  await writeFile4(fullPath, JSON.stringify(data));
542
821
  };
543
- var cache_default = { get, set, generateKey, getPath };
822
+ var cache_default = { get, set, generateKey, getPath, sortKeys };
544
823
 
545
824
  // src/metadata/handler.ts
546
825
  import { getModuleEntryPoint } from "@openfn/runtime";
@@ -580,8 +859,8 @@ var metadataHandler = async (options, logger) => {
580
859
  const adaptor = adaptors[0];
581
860
  const state = await load_state_default(options, logger);
582
861
  logger.success(`Generating metadata`);
862
+ logger.info("config:", state);
583
863
  const config = state.configuration;
584
- logger.info("config:", config);
585
864
  if (!config || Object.keys(config).length === 0) {
586
865
  logger.error("ERROR: Invalid configuration passed");
587
866
  process.exit(1);
@@ -590,7 +869,7 @@ var metadataHandler = async (options, logger) => {
590
869
  logger.success("Done!");
591
870
  logger.print(cache_default.getPath(repoDir, id));
592
871
  };
593
- const id = cache_default.generateKey(config);
872
+ const id = cache_default.generateKey(config, adaptor);
594
873
  if (!options.force) {
595
874
  logger.debug("config hash: ", id);
596
875
  const cached = await cache_default.get(repoDir, id);
@@ -621,13 +900,13 @@ var metadataHandler = async (options, logger) => {
621
900
  var handler_default6 = metadataHandler;
622
901
 
623
902
  // src/util/use-adaptors-repo.ts
624
- import { readFile as readFile3 } from "node:fs/promises";
625
- import path3 from "node:path";
903
+ import { readFile as readFile2 } from "node:fs/promises";
904
+ import path4 from "node:path";
626
905
  import assert from "node:assert";
627
- import { getNameAndVersion as getNameAndVersion4 } from "@openfn/runtime";
906
+ import { getNameAndVersion as getNameAndVersion5 } from "@openfn/runtime";
628
907
  var validateMonoRepo = async (repoPath, log) => {
629
908
  try {
630
- const raw = await readFile3(`${repoPath}/package.json`, "utf8");
909
+ const raw = await readFile2(`${repoPath}/package.json`, "utf8");
631
910
  const pkg = JSON.parse(raw);
632
911
  assert(pkg.name === "adaptors");
633
912
  } catch (e) {
@@ -639,14 +918,14 @@ var updatePath = (adaptor, repoPath, log) => {
639
918
  if (adaptor.match("=")) {
640
919
  return adaptor;
641
920
  }
642
- const { name, version } = getNameAndVersion4(adaptor);
921
+ const { name, version } = getNameAndVersion5(adaptor);
643
922
  if (version) {
644
923
  log.warn(
645
924
  `Warning: Ignoring version specifier on ${adaptor} as loading from the adaptors monorepo`
646
925
  );
647
926
  }
648
927
  const shortName = name.replace("@openfn/language-", "");
649
- const abspath = path3.resolve(repoPath, "packages", shortName);
928
+ const abspath = path4.resolve(repoPath, "packages", shortName);
650
929
  return `${name}=${abspath}`;
651
930
  };
652
931
  var useAdaptorsRepo = async (adaptors, repoPath, log) => {
@@ -663,8 +942,8 @@ var use_adaptors_repo_default = useAdaptorsRepo;
663
942
 
664
943
  // src/util/print-versions.ts
665
944
  import { readFileSync as readFileSync3 } from "node:fs";
666
- import path4 from "node:path";
667
- import { getNameAndVersion as getNameAndVersion5 } from "@openfn/runtime";
945
+ import path5 from "node:path";
946
+ import { getNameAndVersion as getNameAndVersion6 } from "@openfn/runtime";
668
947
  import { mainSymbols } from "figures";
669
948
  var NODE = "node.js";
670
949
  var CLI2 = "cli";
@@ -673,7 +952,7 @@ var COMPILER2 = "compiler";
673
952
  var { triangleRightSmall: t } = mainSymbols;
674
953
  var loadVersionFromPath = (adaptorPath) => {
675
954
  try {
676
- const pkg = JSON.parse(readFileSync3(path4.resolve(adaptorPath, "package.json"), "utf8"));
955
+ const pkg = JSON.parse(readFileSync3(path5.resolve(adaptorPath, "package.json"), "utf8"));
677
956
  return pkg.version;
678
957
  } catch (e) {
679
958
  return "unknown";
@@ -691,9 +970,9 @@ var printVersions = async (logger, options = {}) => {
691
970
  if (adaptor.match("=")) {
692
971
  const [namePart, pathPart] = adaptor.split("=");
693
972
  adaptorVersion = loadVersionFromPath(pathPart);
694
- adaptorName = getNameAndVersion5(namePart).name;
973
+ adaptorName = getNameAndVersion6(namePart).name;
695
974
  } else {
696
- const { name, version: version2 } = getNameAndVersion5(adaptor);
975
+ const { name, version: version2 } = getNameAndVersion6(adaptor);
697
976
  adaptorName = name;
698
977
  adaptorVersion = version2 || "latest";
699
978
  }
@@ -750,7 +1029,7 @@ var handlers = {
750
1029
  ["repo-list"]: list,
751
1030
  version: async (opts, logger) => print_versions_default(logger, opts)
752
1031
  };
753
- var maybeEnsureOpts = (basePath, options) => /^(execute|compile)$/.test(options.command) ? ensureLogOpts(options) : ensureOpts(basePath, options);
1032
+ var maybeEnsureOpts = (basePath, options) => /^(execute|compile|test)$/.test(options.command) ? ensureLogOpts(options) : ensureOpts(basePath, options);
754
1033
  var parse = async (basePath, options, log) => {
755
1034
  const opts = maybeEnsureOpts(basePath, options);
756
1035
  const logger = log || logger_default(CLI, opts);
@@ -771,9 +1050,9 @@ var parse = async (basePath, options, log) => {
771
1050
  logger
772
1051
  );
773
1052
  } else if (opts.adaptors && opts.expandAdaptors) {
774
- opts.adaptors = expand_adaptors_default(opts.adaptors);
1053
+ expand_adaptors_default(opts);
775
1054
  }
776
- if (/^(test|version)$/.test(opts.command) && !opts.repoDir) {
1055
+ if (!/^(test|version)$/.test(opts.command) && !opts.repoDir) {
777
1056
  logger.warn(
778
1057
  "WARNING: no repo module dir found! Using the default (/tmp/repo)"
779
1058
  );
@@ -796,9 +1075,12 @@ var parse = async (basePath, options, log) => {
796
1075
  if (!process.exitCode) {
797
1076
  process.exitCode = e.exitCode || 1;
798
1077
  }
799
- logger.break();
800
- logger.error("Command failed!");
801
- logger.error(e);
1078
+ if (e.handled) {
1079
+ } else {
1080
+ logger.break();
1081
+ logger.error("Command failed!");
1082
+ logger.error(e);
1083
+ }
802
1084
  }
803
1085
  };
804
1086
  var commands_default = parse;