@openfn/cli 1.8.12 → 1.9.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
@@ -135,10 +135,10 @@ var ensureLogOpts = (opts2) => {
135
135
  var ensure_log_opts_default = ensureLogOpts;
136
136
 
137
137
  // src/options.ts
138
- var setDefaultValue = (opts2, key, value) => {
139
- const v = opts2[key];
138
+ var setDefaultValue = (opts2, key2, value2) => {
139
+ const v = opts2[key2];
140
140
  if (isNaN(v) && !v) {
141
- opts2[key] = value;
141
+ opts2[key2] = value2;
142
142
  }
143
143
  };
144
144
  var adaptors = {
@@ -370,7 +370,18 @@ var repoDir = {
370
370
  yargs: () => ({
371
371
  description: "Provide a path to the repo root dir",
372
372
  default: process.env.OPENFN_REPO_DIR || DEFAULT_REPO_DIR
373
- })
373
+ }),
374
+ ensure: (opts2) => {
375
+ if (opts2.repoDir === DEFAULT_REPO_DIR) {
376
+ console.warn(
377
+ "WARNING: no repo module dir found! Using the default (/tmp/repo)"
378
+ );
379
+ console.warn(
380
+ "You should set OPENFN_REPO_DIR or pass --repoDir=some/path in to the CLI"
381
+ );
382
+ console.log();
383
+ }
384
+ }
374
385
  };
375
386
  var start = {
376
387
  name: "start",
@@ -534,6 +545,200 @@ var command_default = {
534
545
  )
535
546
  };
536
547
 
548
+ // src/collections/command.ts
549
+ var desc2 = `Read and write from the OpenFn Collections API`;
550
+ var command_default2 = {
551
+ command: "collections <subcommand>",
552
+ describe: desc2,
553
+ builder: (yargs2) => yargs2.command(get).command(set).command(remove).example(
554
+ "$0 collections get my-collection 2024* -o /tmp/output.json",
555
+ 'Get all keys from my-collection starting with the string "2024" and output the results to file'
556
+ ).example(
557
+ "$0 collections set my-collection my-key path/to/value.json",
558
+ "Set a single key in my-collection to the contents of value.json"
559
+ ).example(
560
+ "$0 collections set my-collection --items path/to/items.json",
561
+ "Set multiple key/value pairs from items.json to my-collection"
562
+ ).example(
563
+ "$0 collections remove my-collection my-key",
564
+ "Remove a single key from my-collection"
565
+ )
566
+ };
567
+ var collectionName = {
568
+ name: "collection-name",
569
+ yargs: {
570
+ alias: ["name"],
571
+ description: "Name of the collection to fetch from",
572
+ demand: true
573
+ }
574
+ };
575
+ var key = {
576
+ name: "key",
577
+ yargs: {
578
+ description: "Key or key pattern to retrieve",
579
+ type: "string",
580
+ demand: true
581
+ },
582
+ ensure: (opts2) => {
583
+ if (opts2.key && typeof opts2.key !== "string") {
584
+ opts2.key = `${opts2.key}`;
585
+ }
586
+ }
587
+ };
588
+ var token = {
589
+ name: "pat",
590
+ yargs: {
591
+ alias: ["token"],
592
+ description: "Lightning Personal Access Token (PAT)"
593
+ }
594
+ };
595
+ var lightningUrl = {
596
+ name: "lightning",
597
+ yargs: {
598
+ description: "URL to OpenFn server. Defaults to OPENFN_ENDPOINT or https://app.openfn.org"
599
+ }
600
+ };
601
+ var pageSize = {
602
+ name: "page-size",
603
+ yargs: {
604
+ description: "Number of items to fetch per page",
605
+ type: "number"
606
+ }
607
+ };
608
+ var limit = {
609
+ name: "limit",
610
+ yargs: {
611
+ description: "Maximum number of items to download",
612
+ type: "number"
613
+ }
614
+ };
615
+ var pretty = {
616
+ name: "pretty",
617
+ yargs: {
618
+ description: "Prettify serialized output",
619
+ type: "boolean"
620
+ }
621
+ };
622
+ var createdBefore = {
623
+ name: "created-before",
624
+ yargs: {
625
+ description: "Matches keys created before the start of the created data"
626
+ }
627
+ };
628
+ var createdAfter = {
629
+ name: "created-after",
630
+ yargs: {
631
+ description: "Matches keys created after the end of the created data"
632
+ }
633
+ };
634
+ var updatedBefore = {
635
+ name: "updated-before",
636
+ yargs: {
637
+ description: "Matches keys updated before the start of the created data"
638
+ }
639
+ };
640
+ var updatedAfter = {
641
+ name: "updated-after",
642
+ yargs: {
643
+ description: "Matches keys updated after the end of the created data"
644
+ }
645
+ };
646
+ var getOptions = [
647
+ collectionName,
648
+ key,
649
+ token,
650
+ lightningUrl,
651
+ pageSize,
652
+ limit,
653
+ pretty,
654
+ createdBefore,
655
+ createdAfter,
656
+ updatedAfter,
657
+ updatedBefore,
658
+ override(log, {
659
+ default: "info"
660
+ }),
661
+ logJson,
662
+ {
663
+ ...outputPath,
664
+ // disable default output path behaviour
665
+ ensure: () => {
666
+ }
667
+ }
668
+ ];
669
+ var get = {
670
+ command: "get <name> <key>",
671
+ describe: "Get values from a collection",
672
+ handler: ensure("collections-get", getOptions),
673
+ builder: (yargs2) => build(getOptions, yargs2)
674
+ };
675
+ var dryRun = {
676
+ name: "dry-run",
677
+ yargs: {
678
+ description: "[Alpha] Do not delete keys and instead return the keys that would be deleted",
679
+ type: "boolean"
680
+ }
681
+ };
682
+ var removeOptions = [
683
+ collectionName,
684
+ key,
685
+ token,
686
+ lightningUrl,
687
+ dryRun,
688
+ createdBefore,
689
+ createdAfter,
690
+ updatedAfter,
691
+ updatedBefore,
692
+ override(log, {
693
+ default: "info"
694
+ }),
695
+ logJson
696
+ ];
697
+ var remove = {
698
+ command: "remove <name> <key>",
699
+ describe: "Remove values from a collection",
700
+ handler: ensure("collections-remove", removeOptions),
701
+ builder: (yargs2) => build(removeOptions, yargs2)
702
+ };
703
+ var value = {
704
+ name: "value",
705
+ yargs: {
706
+ description: "Path to the value to upsert"
707
+ }
708
+ };
709
+ var items = {
710
+ name: "items",
711
+ yargs: {
712
+ description: "Path to a batch of items to upsert. Must contain a JSON object where each key is an item key, and each value is an uploaded value"
713
+ }
714
+ };
715
+ var setOptions = [
716
+ collectionName,
717
+ override(key, {
718
+ demand: false
719
+ }),
720
+ token,
721
+ lightningUrl,
722
+ value,
723
+ items,
724
+ override(log, {
725
+ default: "info"
726
+ }),
727
+ logJson
728
+ ];
729
+ var set = {
730
+ command: "set <name> [key] [value] [--items]",
731
+ describe: "Uploads values to a collection. Must set key & value OR --items.",
732
+ handler: ensure("collections-set", setOptions),
733
+ builder: (yargs2) => build(setOptions, yargs2).example(
734
+ "collections set my-collection cities-mapping ./citymap.json",
735
+ "Upload the data in ./citymap.json to the cities-mapping key"
736
+ ).example(
737
+ "collections set my-collection --items ./items.json",
738
+ "Upsert the object in ./items.json as a batch of items (key/value pairs)"
739
+ )
740
+ };
741
+
537
742
  // src/compile/command.ts
538
743
  var options2 = [
539
744
  expandAdaptors,
@@ -564,7 +769,7 @@ var compileCommand = {
564
769
  "Compiles the workflow at foo/work.json and prints the result to -o foo/workflow-compiled.json"
565
770
  )
566
771
  };
567
- var command_default2 = compileCommand;
772
+ var command_default3 = compileCommand;
568
773
 
569
774
  // src/deploy/command.ts
570
775
  var options3 = [
@@ -582,7 +787,7 @@ var deployCommand = {
582
787
  handler: ensure("deploy", options3),
583
788
  builder: (yargs2) => build(options3, yargs2)
584
789
  };
585
- var command_default3 = deployCommand;
790
+ var command_default4 = deployCommand;
586
791
 
587
792
  // src/docgen/command.ts
588
793
  var docgenCommand = {
@@ -595,7 +800,7 @@ var docgenCommand = {
595
800
  },
596
801
  builder: (yargs2) => yargs2.example("docgen @openfn/language-common@1.7.5", "")
597
802
  };
598
- var command_default4 = docgenCommand;
803
+ var command_default5 = docgenCommand;
599
804
 
600
805
  // src/docs/command.ts
601
806
  var options4 = [log, logJson, repoDir];
@@ -608,7 +813,7 @@ var docsCommand = {
608
813
  "Print help for the common fn operation"
609
814
  )
610
815
  };
611
- var command_default5 = docsCommand;
816
+ var command_default6 = docsCommand;
612
817
 
613
818
  // src/execute/command.ts
614
819
  var options5 = [
@@ -661,7 +866,7 @@ Remember to include the adaptor name with -a. Auto install adaptors with the -i
661
866
  "Compile the expression at job.js with the http adaptor and print the code to stdout"
662
867
  )
663
868
  };
664
- var command_default6 = executeCommand;
869
+ var command_default7 = executeCommand;
665
870
 
666
871
  // src/metadata/command.ts
667
872
  var options6 = [
@@ -675,7 +880,7 @@ var options6 = [
675
880
  stateStdin,
676
881
  useAdaptorsMonorepo
677
882
  ];
678
- var command_default7 = {
883
+ var command_default8 = {
679
884
  command: "metadata",
680
885
  describe: "Generate metadata for an adaptor config",
681
886
  handler: ensure("metadata", options6),
@@ -706,7 +911,7 @@ var pullCommand = {
706
911
  ),
707
912
  handler: ensure("pull", options7)
708
913
  };
709
- var command_default8 = pullCommand;
914
+ var command_default9 = pullCommand;
710
915
 
711
916
  // src/repo/command.ts
712
917
  var repo = {
@@ -768,7 +973,7 @@ var list = {
768
973
 
769
974
  // src/test/command.ts
770
975
  var options8 = [stateStdin, log, logJson];
771
- var command_default9 = {
976
+ var command_default10 = {
772
977
  command: "test",
773
978
  desc: "Compiles and runs a test job, printing the result to stdout",
774
979
  handler: ensure("test", options8),
@@ -777,7 +982,7 @@ var command_default9 = {
777
982
 
778
983
  // src/cli.ts
779
984
  var y = yargs(hideBin(process.argv));
780
- 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({
985
+ var cmd = y.command(command_default7).command(command_default3).command(command_default2).command(command_default4).command(install).command(repo).command(command_default10).command(command_default6).command(command_default).command(command_default8).command(command_default5).command(command_default9).command({
781
986
  command: "version",
782
987
  describe: "Show the currently installed version of the CLI, compiler and runtime.",
783
988
  handler: (argv) => {
@@ -1,2 +1,2 @@
1
1
 
2
- export { }
2
+ export { }
@@ -124,14 +124,14 @@ var callApollo = async (apolloBaseUrl, serviceName, payload, logger) => {
124
124
  });
125
125
  });
126
126
  };
127
- var loadPayload = async (logger, path9) => {
128
- if (!path9) {
127
+ var loadPayload = async (logger, path11) => {
128
+ if (!path11) {
129
129
  logger.warn("No JSON payload provided");
130
130
  logger.warn("Most apollo services require JSON to be uploaded");
131
131
  return {};
132
132
  }
133
- if (path9.endsWith(".json")) {
134
- const str = await readFile(path9, "utf8");
133
+ if (path11.endsWith(".json")) {
134
+ const str = await readFile(path11, "utf8");
135
135
  const json = JSON.parse(str);
136
136
  logger.debug("Loaded JSON payload");
137
137
  return json;
@@ -253,13 +253,13 @@ var execute_default = async (plan, input, opts, logger) => {
253
253
  };
254
254
  function parseAdaptors(plan) {
255
255
  const extractInfo = (specifier) => {
256
- const [module, path9] = specifier.split("=");
256
+ const [module, path11] = specifier.split("=");
257
257
  const { name, version } = getNameAndVersion(module);
258
258
  const info = {
259
259
  name
260
260
  };
261
- if (path9) {
262
- info.path = path9;
261
+ if (path11) {
262
+ info.path = path11;
263
263
  }
264
264
  if (version) {
265
265
  info.version = version;
@@ -413,6 +413,19 @@ var abort_default = (logger, reason, error, help) => {
413
413
  process.exitCode = 1;
414
414
  throw e;
415
415
  };
416
+ var DeferredAbort = class extends Error {
417
+ constructor(reason, help) {
418
+ super("DeferredAbortError");
419
+ this.reason = reason;
420
+ this.help = help ?? "";
421
+ }
422
+ abort = true;
423
+ reason = "";
424
+ help = "";
425
+ };
426
+ var throwAbortableError = (message, help) => {
427
+ throw new DeferredAbort(message, help);
428
+ };
416
429
 
417
430
  // src/compile/compile.ts
418
431
  var compile_default = async (planOrPath, opts, log) => {
@@ -465,10 +478,10 @@ var stripVersionSpecifier = (specifier) => {
465
478
  return specifier;
466
479
  };
467
480
  var resolveSpecifierPath = async (pattern, repoDir, log) => {
468
- const [specifier, path9] = pattern.split("=");
469
- if (path9) {
470
- log.debug(`Resolved ${specifier} to path: ${path9}`);
471
- return path9;
481
+ const [specifier, path11] = pattern.split("=");
482
+ if (path11) {
483
+ log.debug(`Resolved ${specifier} to path: ${path11}`);
484
+ return path11;
472
485
  }
473
486
  const repoPath = await getModulePath(specifier, repoDir, log);
474
487
  if (repoPath) {
@@ -486,12 +499,12 @@ var loadTransformOptions = async (opts, log) => {
486
499
  let exports;
487
500
  const [specifier] = adaptorInput.split("=");
488
501
  log.debug(`Trying to preload types for ${specifier}`);
489
- const path9 = await resolveSpecifierPath(adaptorInput, opts.repoDir, log);
490
- if (path9) {
502
+ const path11 = await resolveSpecifierPath(adaptorInput, opts.repoDir, log);
503
+ if (path11) {
491
504
  try {
492
- exports = await preloadAdaptorExports(path9, log);
505
+ exports = await preloadAdaptorExports(path11, log);
493
506
  } catch (e) {
494
- log.error(`Failed to load adaptor typedefs from path ${path9}`);
507
+ log.error(`Failed to load adaptor typedefs from path ${path11}`);
495
508
  log.error(e);
496
509
  }
497
510
  }
@@ -520,7 +533,7 @@ var getUpstreamStepId = (plan, stepId) => {
520
533
  if (typeof step.next === "string") {
521
534
  return step.next === stepId;
522
535
  }
523
- return stepId in step.next;
536
+ return stepId in step.next || null;
524
537
  }
525
538
  });
526
539
  if (upstreamStep) {
@@ -894,8 +907,8 @@ var loadXPlan = async (plan, options, logger, defaultName = "") => {
894
907
  };
895
908
 
896
909
  // src/util/assert-path.ts
897
- var assert_path_default = (path9) => {
898
- if (!path9) {
910
+ var assert_path_default = (path11) => {
911
+ if (!path11) {
899
912
  console.error("ERROR: no path provided!");
900
913
  console.error("\nUsage:");
901
914
  console.error(" open path/to/job");
@@ -1114,6 +1127,285 @@ var compileHandler = async (options, logger) => {
1114
1127
  };
1115
1128
  var handler_default3 = compileHandler;
1116
1129
 
1130
+ // src/collections/handler.ts
1131
+ import path6 from "node:path";
1132
+ import { readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
1133
+
1134
+ // src/collections/request.ts
1135
+ import path5 from "node:path";
1136
+ import { request } from "undici";
1137
+ var DEFAULT_PAGE_SIZE = 1e3;
1138
+ var request_default = async (method, options, logger) => {
1139
+ const base = options.lightning || process.env.OPENFN_ENDPOINT || "https://app.openfn.org";
1140
+ const url2 = path5.join(base, "/collections", options.collectionName);
1141
+ logger.debug("Calling Collections server at ", url2);
1142
+ const headers = {
1143
+ Authorization: `Bearer ${options.token}`
1144
+ };
1145
+ const query = Object.assign(
1146
+ {
1147
+ key: options.key,
1148
+ limit: options.pageSize || DEFAULT_PAGE_SIZE
1149
+ },
1150
+ options.query
1151
+ );
1152
+ const args = {
1153
+ headers,
1154
+ method,
1155
+ query
1156
+ };
1157
+ if (options.data) {
1158
+ args.body = JSON.stringify(options.data);
1159
+ headers["content-type"] = "application/json";
1160
+ }
1161
+ let result = {};
1162
+ let cursor;
1163
+ let count = 0;
1164
+ let limit = Infinity;
1165
+ do {
1166
+ if (cursor) {
1167
+ query.cursor = cursor;
1168
+ }
1169
+ if (options.limit) {
1170
+ limit = options.limit;
1171
+ query.limit = Math.min(
1172
+ options.pageSize || DEFAULT_PAGE_SIZE,
1173
+ options.limit - count
1174
+ );
1175
+ }
1176
+ try {
1177
+ const response = await request(url2, args);
1178
+ if (response.statusCode >= 400) {
1179
+ return handleError(logger, response);
1180
+ }
1181
+ const responseData = await response.body.json();
1182
+ if (responseData.items) {
1183
+ count += responseData.items.length;
1184
+ logger.debug(
1185
+ "Received",
1186
+ response.statusCode,
1187
+ `- ${responseData.items.length} values`
1188
+ );
1189
+ for (const item of responseData?.items) {
1190
+ try {
1191
+ result[item.key] = JSON.parse(item.value);
1192
+ } catch (e) {
1193
+ result[item.key] = item.value;
1194
+ }
1195
+ }
1196
+ cursor = responseData.cursor;
1197
+ } else {
1198
+ logger.debug(
1199
+ "Received",
1200
+ response.statusCode,
1201
+ `- ${JSON.stringify(responseData)}`
1202
+ );
1203
+ result = responseData;
1204
+ }
1205
+ } catch (e) {
1206
+ logger.error(e);
1207
+ throwAbortableError(
1208
+ `CONNECTION_REFUSED: error connecting to server at ${base}`,
1209
+ "Check you have passed the correct URL to --lightning or OPENFN_ENDPOINT"
1210
+ );
1211
+ }
1212
+ } while (cursor && count < limit);
1213
+ return result;
1214
+ };
1215
+ async function handleError(logger, response) {
1216
+ logger.error("Error from server", response.statusCode);
1217
+ let message;
1218
+ let fix;
1219
+ switch (response.statusCode) {
1220
+ case 404:
1221
+ message = `404: collection not found`;
1222
+ fix = `Ensure the Collection has been created on the admin page`;
1223
+ break;
1224
+ default:
1225
+ message = `Error from server: ${response.statusCode}`;
1226
+ }
1227
+ let contentType = response.headers?.["content-type"] ?? "";
1228
+ if (contentType.startsWith("application/json")) {
1229
+ try {
1230
+ const body = await response.body.json();
1231
+ logger.error(body);
1232
+ } catch (e) {
1233
+ }
1234
+ } else {
1235
+ try {
1236
+ const text = await response.body.text();
1237
+ logger.error(text);
1238
+ } catch (e) {
1239
+ }
1240
+ }
1241
+ throwAbortableError(message, fix);
1242
+ }
1243
+
1244
+ // src/collections/handler.ts
1245
+ var ensureToken = (opts, logger) => {
1246
+ if (!("token" in opts)) {
1247
+ if (process.env.OPENFN_PAT) {
1248
+ const token = process.env.OPENFN_PAT;
1249
+ logger.info(
1250
+ `Using access token ending in ${token?.substring(
1251
+ token.length - 10
1252
+ )} from env (OPENFN_PAT)`
1253
+ );
1254
+ opts.token = token;
1255
+ } else {
1256
+ logger.error("No access token detected!");
1257
+ logger.error(
1258
+ "Ensure you pass a Personal Access Token (PAT) with --token $MY_TOKEN or set the OPENFN_PAT env var"
1259
+ );
1260
+ logger.error(
1261
+ "You can get a PAT from OpenFn, see https://docs.openfn.org/documentation/api-tokens"
1262
+ );
1263
+ throw new Error("NO_PAT");
1264
+ }
1265
+ }
1266
+ };
1267
+ var buildQuery = (options) => {
1268
+ const map = {
1269
+ createdBefore: "created_before",
1270
+ createdAfter: "created_after",
1271
+ updatedBefore: "updated_before",
1272
+ updatedAfter: "updated_after"
1273
+ };
1274
+ const query = {};
1275
+ Object.keys(map).forEach((key) => {
1276
+ if (options[key]) {
1277
+ query[map[key]] = options[key];
1278
+ }
1279
+ });
1280
+ return query;
1281
+ };
1282
+ var get = async (options, logger) => {
1283
+ ensureToken(options, logger);
1284
+ const multiMode = options.key.includes("*");
1285
+ if (multiMode) {
1286
+ logger.info(
1287
+ `Fetching multiple items from collection "${options.collectionName}" with pattern ${options.key}`
1288
+ );
1289
+ } else {
1290
+ logger.info(
1291
+ `Fetching "${options.key}" from collection "${options.collectionName}"`
1292
+ );
1293
+ }
1294
+ let result = await request_default(
1295
+ "GET",
1296
+ {
1297
+ lightning: options.lightning,
1298
+ token: options.token,
1299
+ pageSize: options.pageSize,
1300
+ limit: options.limit,
1301
+ key: options.key,
1302
+ collectionName: options.collectionName,
1303
+ query: buildQuery(options)
1304
+ },
1305
+ logger
1306
+ );
1307
+ if (multiMode) {
1308
+ logger.success(`Fetched ${Object.keys(result).length} items!`);
1309
+ } else {
1310
+ result = Object.values(result)[0];
1311
+ logger.success(`Fetched ${options.key}`);
1312
+ }
1313
+ if (options.outputPath) {
1314
+ const content = JSON.stringify(
1315
+ result,
1316
+ null,
1317
+ options.pretty ? 2 : void 0
1318
+ );
1319
+ await writeFile4(options.outputPath, content);
1320
+ logger.always(`Wrote items to ${options.outputPath}`);
1321
+ } else {
1322
+ logger.print(result);
1323
+ }
1324
+ };
1325
+ var set = async (options, logger) => {
1326
+ if (options.key && options.items) {
1327
+ throwAbortableError(
1328
+ "ARGUMENT_ERROR: arguments for key and items were provided",
1329
+ "If upserting multiple items with --items, do not pass a key"
1330
+ );
1331
+ }
1332
+ ensureToken(options, logger);
1333
+ logger.info(`Upserting items to collection "${options.collectionName}"`);
1334
+ const items = [];
1335
+ if (options.items) {
1336
+ const resolvedPath = path6.resolve(options.items);
1337
+ logger.debug("Loading items from ", resolvedPath);
1338
+ const data = await readFile3(resolvedPath, "utf8");
1339
+ const obj = JSON.parse(data);
1340
+ Object.entries(obj).forEach(([key, value]) => {
1341
+ items.push({ key, value: JSON.stringify(value) });
1342
+ });
1343
+ logger.info(`Upserting ${items.length} items`);
1344
+ } else if (options.key && options.value) {
1345
+ const resolvedPath = path6.resolve(options.value);
1346
+ logger.debug("Loading value from ", resolvedPath);
1347
+ const data = await readFile3(path6.resolve(options.value), "utf8");
1348
+ const value = JSON.stringify(JSON.parse(data));
1349
+ items.push({ key: options.key, value });
1350
+ logger.info(`Upserting data to "${options.key}"`);
1351
+ } else {
1352
+ throw new Error("INVALID_ARGUMENTS");
1353
+ }
1354
+ const result = await request_default(
1355
+ "POST",
1356
+ {
1357
+ lightning: options.lightning,
1358
+ token: options.token,
1359
+ key: options.key,
1360
+ collectionName: options.collectionName,
1361
+ data: { items }
1362
+ },
1363
+ logger
1364
+ );
1365
+ logger.success(`Upserted ${result.upserted} items!`);
1366
+ };
1367
+ var remove = async (options, logger) => {
1368
+ ensureToken(options, logger);
1369
+ logger.info(
1370
+ `Removing "${options.key}" from collection "${options.collectionName}"`
1371
+ );
1372
+ if (options.dryRun) {
1373
+ logger.info("--dry-run passed: fetching affected items");
1374
+ let result = await request_default(
1375
+ "GET",
1376
+ {
1377
+ lightning: options.lightning,
1378
+ token: options.token,
1379
+ key: options.key,
1380
+ collectionName: options.collectionName
1381
+ },
1382
+ logger
1383
+ );
1384
+ const keys = Object.keys(result);
1385
+ logger.info("Keys to be removed:");
1386
+ logger.print(keys);
1387
+ logger.always("Aborting request - keys have not been removed");
1388
+ } else {
1389
+ let result = await request_default(
1390
+ "DELETE",
1391
+ {
1392
+ lightning: options.lightning,
1393
+ token: options.token,
1394
+ key: options.key,
1395
+ collectionName: options.collectionName,
1396
+ query: buildQuery(options)
1397
+ },
1398
+ logger
1399
+ );
1400
+ logger.success(`Removed ${result.deleted} items`);
1401
+ }
1402
+ };
1403
+ var handler_default4 = {
1404
+ get,
1405
+ set,
1406
+ remove
1407
+ };
1408
+
1117
1409
  // src/test/handler.ts
1118
1410
  var testHandler = async (options, logger) => {
1119
1411
  logger.log("Running test workflow...");
@@ -1169,7 +1461,7 @@ var testHandler = async (options, logger) => {
1169
1461
  logger.success(`Result: ${result.data.answer}`);
1170
1462
  return result;
1171
1463
  };
1172
- var handler_default4 = testHandler;
1464
+ var handler_default5 = testHandler;
1173
1465
 
1174
1466
  // src/deploy/handler.ts
1175
1467
  import {
@@ -1223,33 +1515,33 @@ function mergeOverrides(config, options) {
1223
1515
  function pickFirst(...args) {
1224
1516
  return args.find((arg) => arg !== void 0 && arg !== null);
1225
1517
  }
1226
- var handler_default5 = deployHandler;
1518
+ var handler_default6 = deployHandler;
1227
1519
 
1228
1520
  // src/docgen/handler.ts
1229
- import { writeFile as writeFile4 } from "node:fs/promises";
1521
+ import { writeFile as writeFile5 } from "node:fs/promises";
1230
1522
  import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
1231
- import path5 from "node:path";
1523
+ import path7 from "node:path";
1232
1524
  import { describePackage } from "@openfn/describe-package";
1233
1525
  import { getNameAndVersion as getNameAndVersion3 } from "@openfn/runtime";
1234
1526
  var RETRY_DURATION = 500;
1235
1527
  var RETRY_COUNT = 20;
1236
1528
  var TIMEOUT_MS = 1e3 * 60;
1237
1529
  var actualDocGen = (specifier) => describePackage(specifier, {});
1238
- var ensurePath = (filePath) => mkdirSync(path5.dirname(filePath), { recursive: true });
1239
- var generatePlaceholder = (path9) => {
1240
- writeFileSync(path9, `{ "loading": true, "timestamp": ${Date.now()}}`);
1530
+ var ensurePath = (filePath) => mkdirSync(path7.dirname(filePath), { recursive: true });
1531
+ var generatePlaceholder = (path11) => {
1532
+ writeFileSync(path11, `{ "loading": true, "timestamp": ${Date.now()}}`);
1241
1533
  };
1242
1534
  var finish = (logger, resultPath) => {
1243
1535
  logger.success("Done! Docs can be found at:\n");
1244
- logger.print(` ${path5.resolve(resultPath)}`);
1536
+ logger.print(` ${path7.resolve(resultPath)}`);
1245
1537
  };
1246
- var generateDocs = async (specifier, path9, docgen, logger) => {
1538
+ var generateDocs = async (specifier, path11, docgen, logger) => {
1247
1539
  const result = await docgen(specifier);
1248
- await writeFile4(path9, JSON.stringify(result, null, 2));
1249
- finish(logger, path9);
1250
- return path9;
1540
+ await writeFile5(path11, JSON.stringify(result, null, 2));
1541
+ finish(logger, path11);
1542
+ return path11;
1251
1543
  };
1252
- var waitForDocs = async (docs, path9, logger, retryDuration = RETRY_DURATION) => {
1544
+ var waitForDocs = async (docs, path11, logger, retryDuration = RETRY_DURATION) => {
1253
1545
  try {
1254
1546
  if (docs.hasOwnProperty("loading")) {
1255
1547
  logger.info("Docs are being loaded by another process. Waiting.");
@@ -1261,19 +1553,19 @@ var waitForDocs = async (docs, path9, logger, retryDuration = RETRY_DURATION) =>
1261
1553
  clearInterval(i);
1262
1554
  reject(new Error("Timed out waiting for docs to load"));
1263
1555
  }
1264
- const updated = JSON.parse(readFileSync(path9, "utf8"));
1556
+ const updated = JSON.parse(readFileSync(path11, "utf8"));
1265
1557
  if (!updated.hasOwnProperty("loading")) {
1266
1558
  logger.info("Docs found!");
1267
1559
  clearInterval(i);
1268
- resolve(path9);
1560
+ resolve(path11);
1269
1561
  }
1270
1562
  count++;
1271
1563
  }, retryDuration);
1272
1564
  });
1273
1565
  } else {
1274
- logger.info(`Docs already written to cache at ${path9}`);
1275
- finish(logger, path9);
1276
- return path9;
1566
+ logger.info(`Docs already written to cache at ${path11}`);
1567
+ finish(logger, path11);
1568
+ return path11;
1277
1569
  }
1278
1570
  } catch (e) {
1279
1571
  logger.error("Existing doc JSON corrupt. Aborting");
@@ -1290,38 +1582,38 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
1290
1582
  process.exit(9);
1291
1583
  }
1292
1584
  logger.success(`Generating docs for ${specifier}`);
1293
- const path9 = `${repoDir}/docs/${specifier}.json`;
1294
- ensurePath(path9);
1295
- const handleError = () => {
1585
+ const path11 = `${repoDir}/docs/${specifier}.json`;
1586
+ ensurePath(path11);
1587
+ const handleError2 = () => {
1296
1588
  logger.info("Removing placeholder");
1297
- rmSync(path9);
1589
+ rmSync(path11);
1298
1590
  };
1299
1591
  try {
1300
- const existing = readFileSync(path9, "utf8");
1592
+ const existing = readFileSync(path11, "utf8");
1301
1593
  const json = JSON.parse(existing);
1302
1594
  if (json && json.timeout && Date.now() - json.timeout >= TIMEOUT_MS) {
1303
1595
  logger.info(`Expired placeholder found. Removing.`);
1304
- rmSync(path9);
1596
+ rmSync(path11);
1305
1597
  throw new Error("TIMEOUT");
1306
1598
  }
1307
- return waitForDocs(json, path9, logger, retryDuration);
1599
+ return waitForDocs(json, path11, logger, retryDuration);
1308
1600
  } catch (e) {
1309
1601
  if (e.message !== "TIMEOUT") {
1310
- logger.info(`Docs JSON not found at ${path9}`);
1602
+ logger.info(`Docs JSON not found at ${path11}`);
1311
1603
  }
1312
1604
  logger.debug("Generating placeholder");
1313
- generatePlaceholder(path9);
1314
- return generateDocs(specifier, path9, docgen, logger).catch((e2) => {
1605
+ generatePlaceholder(path11);
1606
+ return generateDocs(specifier, path11, docgen, logger).catch((e2) => {
1315
1607
  logger.error("Error generating documentation");
1316
1608
  logger.error(e2);
1317
- handleError();
1609
+ handleError2();
1318
1610
  });
1319
1611
  }
1320
1612
  };
1321
- var handler_default6 = docgenHandler;
1613
+ var handler_default7 = docgenHandler;
1322
1614
 
1323
1615
  // src/docs/handler.ts
1324
- import { readFile as readFile3 } from "node:fs/promises";
1616
+ import { readFile as readFile4 } from "node:fs/promises";
1325
1617
  import c from "chalk";
1326
1618
  import { getNameAndVersion as getNameAndVersion4, getLatestVersion } from "@openfn/runtime";
1327
1619
  var describeFn = (adaptorName, fn) => [
@@ -1362,7 +1654,7 @@ var docsHandler = async (options, logger) => {
1362
1654
  logger.success(`Showing docs for ${adaptorName} v${version}`);
1363
1655
  }
1364
1656
  logger.info("Generating/loading documentation...");
1365
- const path9 = await handler_default6(
1657
+ const path11 = await handler_default7(
1366
1658
  {
1367
1659
  specifier: `${name}@${version}`,
1368
1660
  repoDir
@@ -1371,8 +1663,8 @@ var docsHandler = async (options, logger) => {
1371
1663
  createNullLogger()
1372
1664
  );
1373
1665
  let didError = false;
1374
- if (path9) {
1375
- const source = await readFile3(path9, "utf8");
1666
+ if (path11) {
1667
+ const source = await readFile4(path11, "utf8");
1376
1668
  const data = JSON.parse(source);
1377
1669
  let desc;
1378
1670
  if (operation) {
@@ -1405,13 +1697,13 @@ var docsHandler = async (options, logger) => {
1405
1697
  logger.error("Not found");
1406
1698
  }
1407
1699
  };
1408
- var handler_default7 = docsHandler;
1700
+ var handler_default8 = docsHandler;
1409
1701
 
1410
1702
  // src/metadata/cache.ts
1411
1703
  import { createHash } from "node:crypto";
1412
1704
  import { readFileSync as readFileSync2 } from "node:fs";
1413
- import path6 from "node:path";
1414
- import { writeFile as writeFile5, mkdir as mkdir2 } from "node:fs/promises";
1705
+ import path8 from "node:path";
1706
+ import { writeFile as writeFile6, mkdir as mkdir2 } from "node:fs/promises";
1415
1707
  var getPath = (repoDir, key) => `${repoDir}/meta/${key}.json`;
1416
1708
  var sortKeys = (obj) => {
1417
1709
  const newObj = {};
@@ -1430,7 +1722,7 @@ var generateKey = (config, adaptor) => {
1430
1722
  const key = `${JSON.stringify(sorted)}-${adaptor}}`;
1431
1723
  return createHash("sha256").update(key).digest("hex");
1432
1724
  };
1433
- var get = (repoPath, key) => {
1725
+ var get2 = (repoPath, key) => {
1434
1726
  try {
1435
1727
  const data = readFileSync2(getPath(repoPath, key), "utf8");
1436
1728
  const json = JSON.parse(data);
@@ -1439,12 +1731,12 @@ var get = (repoPath, key) => {
1439
1731
  return null;
1440
1732
  }
1441
1733
  };
1442
- var set = async (repoPath, key, data) => {
1734
+ var set2 = async (repoPath, key, data) => {
1443
1735
  const fullPath = getPath(repoPath, key);
1444
- await mkdir2(path6.dirname(fullPath), { recursive: true });
1445
- await writeFile5(fullPath, JSON.stringify(data));
1736
+ await mkdir2(path8.dirname(fullPath), { recursive: true });
1737
+ await writeFile6(fullPath, JSON.stringify(data));
1446
1738
  };
1447
- var cache_default = { get, set, generateKey, getPath, sortKeys };
1739
+ var cache_default = { get: get2, set: set2, generateKey, getPath, sortKeys };
1448
1740
 
1449
1741
  // src/metadata/handler.ts
1450
1742
  import { getModuleEntryPoint } from "@openfn/runtime";
@@ -1526,10 +1818,10 @@ var metadataHandler = async (options, logger) => {
1526
1818
  process.exit(1);
1527
1819
  }
1528
1820
  };
1529
- var handler_default8 = metadataHandler;
1821
+ var handler_default9 = metadataHandler;
1530
1822
 
1531
1823
  // src/pull/handler.ts
1532
- import path7 from "path";
1824
+ import path9 from "path";
1533
1825
  import fs4 from "node:fs/promises";
1534
1826
  import {
1535
1827
  getConfig as getConfig2,
@@ -1591,7 +1883,7 @@ async function pullHandler(options, logger) {
1591
1883
  process.exitCode = 1;
1592
1884
  process.exit(1);
1593
1885
  }
1594
- const resolvedPath = path7.resolve(config.specPath);
1886
+ const resolvedPath = path9.resolve(config.specPath);
1595
1887
  logger.debug("reading spec from", resolvedPath);
1596
1888
  const updatedSpec = await syncRemoteSpec(
1597
1889
  await res.text(),
@@ -1600,7 +1892,7 @@ async function pullHandler(options, logger) {
1600
1892
  logger
1601
1893
  );
1602
1894
  await fs4.writeFile(
1603
- path7.resolve(config.statePath),
1895
+ path9.resolve(config.statePath),
1604
1896
  JSON.stringify(state, null, 2)
1605
1897
  );
1606
1898
  await fs4.writeFile(resolvedPath, updatedSpec);
@@ -1630,11 +1922,11 @@ function mergeOverrides2(config, options) {
1630
1922
  function pickFirst2(...args) {
1631
1923
  return args.find((arg) => arg !== void 0 && arg !== null);
1632
1924
  }
1633
- var handler_default9 = pullHandler;
1925
+ var handler_default10 = pullHandler;
1634
1926
 
1635
1927
  // src/util/print-versions.ts
1636
1928
  import { readFileSync as readFileSync3 } from "node:fs";
1637
- import path8 from "node:path";
1929
+ import path10 from "node:path";
1638
1930
  import url from "node:url";
1639
1931
  import { getNameAndVersion as getNameAndVersion5 } from "@openfn/runtime";
1640
1932
  import { mainSymbols } from "figures";
@@ -1646,7 +1938,7 @@ var { triangleRightSmall: t } = mainSymbols;
1646
1938
  var loadVersionFromPath = (adaptorPath) => {
1647
1939
  try {
1648
1940
  const pkg = JSON.parse(
1649
- readFileSync3(path8.resolve(adaptorPath, "package.json"), "utf8")
1941
+ readFileSync3(path10.resolve(adaptorPath, "package.json"), "utf8")
1650
1942
  );
1651
1943
  return pkg.version;
1652
1944
  } catch (e) {
@@ -1681,7 +1973,7 @@ var printVersions = async (logger, options = {}, includeComponents = false) => {
1681
1973
  ...[NODE, CLI2, RUNTIME2, COMPILER2, longestAdaptorName].map((s) => s.length)
1682
1974
  );
1683
1975
  const prefix = (str) => ` ${t} ${str.padEnd(longest + 4, " ")}`;
1684
- const dirname = path8.dirname(url.fileURLToPath(import.meta.url));
1976
+ const dirname = path10.dirname(url.fileURLToPath(import.meta.url));
1685
1977
  const pkg = JSON.parse(readFileSync3(`${dirname}/../../package.json`, "utf8"));
1686
1978
  const { version, dependencies } = pkg;
1687
1979
  const compilerVersion = dependencies["@openfn/compiler"];
@@ -1728,12 +2020,15 @@ var handlers = {
1728
2020
  apollo: handler_default,
1729
2021
  execute: handler_default2,
1730
2022
  compile: handler_default3,
1731
- test: handler_default4,
1732
- deploy: handler_default5,
1733
- docgen: handler_default6,
1734
- docs: handler_default7,
1735
- metadata: handler_default8,
1736
- pull: handler_default9,
2023
+ test: handler_default5,
2024
+ deploy: handler_default6,
2025
+ docgen: handler_default7,
2026
+ docs: handler_default8,
2027
+ metadata: handler_default9,
2028
+ pull: handler_default10,
2029
+ ["collections-get"]: handler_default4.get,
2030
+ ["collections-set"]: handler_default4.set,
2031
+ ["collections-remove"]: handler_default4.remove,
1737
2032
  ["repo-clean"]: clean,
1738
2033
  ["repo-install"]: install,
1739
2034
  ["repo-pwd"]: pwd,
@@ -1762,14 +2057,6 @@ var parse = async (options, log) => {
1762
2057
  logger
1763
2058
  );
1764
2059
  }
1765
- if (!/^(pull|deploy|test|version|apollo)$/.test(options.command) && !options.repoDir) {
1766
- logger.warn(
1767
- "WARNING: no repo module dir found! Using the default (/tmp/repo)"
1768
- );
1769
- logger.warn(
1770
- "You should set OPENFN_REPO_DIR or pass --repoDir=some/path in to the CLI"
1771
- );
1772
- }
1773
2060
  const handler = handlers[options.command];
1774
2061
  if (!handler) {
1775
2062
  logger.error(`Unrecognised command: ${options.command}`);
@@ -1782,6 +2069,11 @@ var parse = async (options, log) => {
1782
2069
  process.exitCode = e.exitCode || 1;
1783
2070
  }
1784
2071
  if (e.handled) {
2072
+ } else if (e.abort) {
2073
+ try {
2074
+ abort_default(logger, e.reason, e.error, e.help);
2075
+ } catch (e2) {
2076
+ }
1785
2077
  } else {
1786
2078
  logger.break();
1787
2079
  logger.error("Command failed!");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/cli",
3
- "version": "1.8.12",
3
+ "version": "1.9.1",
4
4
  "description": "CLI devtools for the openfn toolchain.",
5
5
  "engines": {
6
6
  "node": ">=18",
@@ -24,6 +24,7 @@
24
24
  "author": "Open Function Group <admin@openfn.org>",
25
25
  "license": "ISC",
26
26
  "devDependencies": {
27
+ "@openfn/language-collections": "^0.6.2",
27
28
  "@openfn/language-common": "2.0.0-rc3",
28
29
  "@types/mock-fs": "^4.13.1",
29
30
  "@types/node": "^18.15.13",
@@ -32,8 +33,7 @@
32
33
  "@types/ws": "^8.5.10",
33
34
  "@types/yargs": "^17.0.24",
34
35
  "ava": "5.3.1",
35
- "mock-fs": "^5.1.4",
36
- "ts-node": "^10.9.1",
36
+ "mock-fs": "^5.4.1",
37
37
  "tslib": "^2.4.0",
38
38
  "tsup": "^7.2.0",
39
39
  "typescript": "^5.1.6",
@@ -45,13 +45,14 @@
45
45
  "figures": "^5.0.0",
46
46
  "rimraf": "^3.0.2",
47
47
  "treeify": "^1.1.0",
48
+ "undici": "^7.1.0",
48
49
  "ws": "^8.18.0",
49
50
  "yargs": "^17.7.2",
50
- "@openfn/compiler": "0.4.2",
51
- "@openfn/deploy": "0.8.0",
52
- "@openfn/describe-package": "0.1.3",
53
- "@openfn/logger": "1.0.2",
54
- "@openfn/runtime": "1.5.3"
51
+ "@openfn/deploy": "0.8.1",
52
+ "@openfn/describe-package": "0.1.4",
53
+ "@openfn/runtime": "1.5.4",
54
+ "@openfn/logger": "1.0.3",
55
+ "@openfn/compiler": "0.4.3"
55
56
  },
56
57
  "files": [
57
58
  "dist",