@openfn/cli 1.11.4 → 1.12.0

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
@@ -33,7 +33,7 @@ import yargs from "yargs";
33
33
  import { hideBin } from "yargs/helpers";
34
34
 
35
35
  // src/options.ts
36
- import path2 from "node:path";
36
+ import nodePath from "node:path";
37
37
 
38
38
  // src/constants.ts
39
39
  var DEFAULT_REPO_DIR = "/tmp/openfn/repo";
@@ -171,6 +171,13 @@ var autoinstall = {
171
171
  default: true
172
172
  }
173
173
  };
174
+ var apikey = {
175
+ name: "apikey",
176
+ yargs: {
177
+ alias: ["key", "pat", "token"],
178
+ description: "[beta only] API Key, Personal Access Token (Pat), or other access token"
179
+ }
180
+ };
174
181
  var apolloUrl = {
175
182
  name: "apollo-url",
176
183
  yargs: {
@@ -194,6 +201,14 @@ var apolloUrl = {
194
201
  });
195
202
  }
196
203
  };
204
+ var beta = {
205
+ name: "beta",
206
+ yargs: {
207
+ boolean: true,
208
+ description: "Use the beta alternative of this command: see https://github.com/OpenFn/kit/wiki/Pull-Deploy-Beta",
209
+ default: false
210
+ }
211
+ };
197
212
  var cacheSteps = {
198
213
  name: "cache-steps",
199
214
  yargs: {
@@ -254,6 +269,19 @@ var expandAdaptors = {
254
269
  setDefaultValue(opts2, "expandAdaptors", true);
255
270
  }
256
271
  };
272
+ var endpoint = {
273
+ name: "endpoint",
274
+ yargs: {
275
+ alias: ["lightning"],
276
+ description: "[beta only] URL to Lightning endpoint"
277
+ }
278
+ };
279
+ var env = {
280
+ name: "env",
281
+ yargs: {
282
+ description: "[beta only] Environment name (eg staging, prod, branch)"
283
+ }
284
+ };
257
285
  var force = {
258
286
  name: "force",
259
287
  yargs: {
@@ -285,7 +313,7 @@ var ignoreImports = {
285
313
  var getBaseDir = (opts2) => {
286
314
  const basePath = opts2.path ?? ".";
287
315
  if (/\.(jso?n?)$/.test(basePath)) {
288
- return path2.dirname(basePath);
316
+ return nodePath.dirname(basePath);
289
317
  }
290
318
  return basePath;
291
319
  };
@@ -302,7 +330,7 @@ var inputPath = {
302
330
  opts2.expressionPath = basePath;
303
331
  } else {
304
332
  const base = getBaseDir(opts2);
305
- setDefaultValue(opts2, "expressionPath", path2.join(base, "job.js"));
333
+ setDefaultValue(opts2, "expressionPath", nodePath.join(base, "job.js"));
306
334
  }
307
335
  }
308
336
  };
@@ -351,7 +379,7 @@ var outputPath = {
351
379
  delete opts2.outputPath;
352
380
  } else {
353
381
  const base = getBaseDir(opts2);
354
- setDefaultValue(opts2, "outputPath", path2.join(base, "output.json"));
382
+ setDefaultValue(opts2, "outputPath", nodePath.join(base, "output.json"));
355
383
  }
356
384
  }
357
385
  delete opts2.o;
@@ -365,6 +393,12 @@ var projectPath = {
365
393
  description: "The location of your project.yaml file"
366
394
  }
367
395
  };
396
+ var path2 = {
397
+ name: "path",
398
+ yargs: {
399
+ description: "Path"
400
+ }
401
+ };
368
402
  var repoDir = {
369
403
  name: "repo-dir",
370
404
  yargs: () => ({
@@ -475,6 +509,13 @@ var sanitize = {
475
509
  throw new Error(err);
476
510
  }
477
511
  };
512
+ var workflow = {
513
+ name: "workflow",
514
+ yargs: {
515
+ string: true,
516
+ description: "Name of the workflow to execute"
517
+ }
518
+ };
478
519
 
479
520
  // src/util/command-builders.ts
480
521
  import c from "chalk";
@@ -752,7 +793,8 @@ var options2 = [
752
793
  }),
753
794
  outputPath,
754
795
  repoDir,
755
- useAdaptorsMonorepo
796
+ useAdaptorsMonorepo,
797
+ workflow
756
798
  ];
757
799
  var compileCommand = {
758
800
  command: "compile [path]",
@@ -773,6 +815,7 @@ var command_default3 = compileCommand;
773
815
 
774
816
  // src/deploy/command.ts
775
817
  var options3 = [
818
+ beta,
776
819
  configPath,
777
820
  confirm,
778
821
  describe,
@@ -841,7 +884,7 @@ var options5 = [
841
884
  useAdaptorsMonorepo
842
885
  ];
843
886
  var executeCommand = {
844
- command: "execute [path]",
887
+ command: "execute <path> [workflow]",
845
888
  describe: `Run an openfn expression or workflow. Get more help by running openfn <command> help.
846
889
 
847
890
  Execute will run a expression/workflow at the path and write the output state to disk (to ./state.json unless otherwise specified)
@@ -892,16 +935,25 @@ var command_default8 = {
892
935
 
893
936
  // src/pull/command.ts
894
937
  var options7 = [
895
- statePath,
896
- projectPath,
938
+ apikey,
939
+ beta,
940
+ beta,
897
941
  configPath,
942
+ endpoint,
943
+ env,
898
944
  log,
945
+ override(path2, {
946
+ description: "path to output the project to"
947
+ }),
899
948
  logJson,
900
- snapshots
949
+ projectPath,
950
+ snapshots,
951
+ statePath,
952
+ path2
901
953
  ];
902
954
  var pullCommand = {
903
955
  command: "pull [projectId]",
904
- describe: "Pull a project's state and spec from a Lightning Instance to the local directory",
956
+ describe: `Pull a project's state and spec from a Lightning Instance to the local directory. Pass --beta to use the experimental new pull command. See https://github.com/OpenFn/kit/wiki/Pull-Deploy-Beta for docs`,
905
957
  builder: (yargs2) => build(options7, yargs2).positional("projectId", {
906
958
  describe: "The id of the project that should be pulled shouled be a UUID",
907
959
  demandOption: true
@@ -124,14 +124,14 @@ var callApollo = async (apolloBaseUrl, serviceName, payload, logger) => {
124
124
  });
125
125
  });
126
126
  };
127
- var loadPayload = async (logger, path11) => {
128
- if (!path11) {
127
+ var loadPayload = async (logger, path12) => {
128
+ if (!path12) {
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 (path11.endsWith(".json")) {
134
- const str = await readFile(path11, "utf8");
133
+ if (path12.endsWith(".json")) {
134
+ const str = await readFile(path12, "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, path11] = specifier.split("=");
256
+ const [module, path12] = specifier.split("=");
257
257
  const { name, version } = getNameAndVersion(module);
258
258
  const info = {
259
259
  name
260
260
  };
261
- if (path11) {
262
- info.path = path11;
261
+ if (path12) {
262
+ info.path = path12;
263
263
  }
264
264
  if (version) {
265
265
  info.version = version;
@@ -278,7 +278,8 @@ function parseAdaptors(plan) {
278
278
  }
279
279
 
280
280
  // src/execute/serialize-output.ts
281
- import { writeFile as writeFile2 } from "node:fs/promises";
281
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
282
+ import { dirname } from "node:path";
282
283
  var serializeOutput2 = async (options, result, logger) => {
283
284
  let output = result;
284
285
  if (output && (output.configuration || output.data)) {
@@ -294,6 +295,7 @@ var serializeOutput2 = async (options, result, logger) => {
294
295
  logger.success(`Result: `);
295
296
  logger.always(output);
296
297
  } else if (options.outputPath) {
298
+ await mkdir2(dirname(options.outputPath), { recursive: true });
297
299
  logger.debug(`Writing output to ${options.outputPath}`);
298
300
  await writeFile2(options.outputPath, output);
299
301
  logger.success(`State written to ${options.outputPath}`);
@@ -484,10 +486,10 @@ var stripVersionSpecifier = (specifier) => {
484
486
  return specifier;
485
487
  };
486
488
  var resolveSpecifierPath = async (pattern, repoDir, log) => {
487
- const [specifier, path11] = pattern.split("=");
488
- if (path11) {
489
- log.debug(`Resolved ${specifier} to path: ${path11}`);
490
- return path11;
489
+ const [specifier, path12] = pattern.split("=");
490
+ if (path12) {
491
+ log.debug(`Resolved ${specifier} to path: ${path12}`);
492
+ return path12;
491
493
  }
492
494
  const repoPath = await getModulePath(specifier, repoDir, log);
493
495
  if (repoPath) {
@@ -505,12 +507,12 @@ var loadTransformOptions = async (opts, log) => {
505
507
  let exports;
506
508
  const [specifier] = adaptorInput.split("=");
507
509
  log.debug(`Trying to preload types for ${specifier}`);
508
- const path11 = await resolveSpecifierPath(adaptorInput, opts.repoDir, log);
509
- if (path11) {
510
+ const path12 = await resolveSpecifierPath(adaptorInput, opts.repoDir, log);
511
+ if (path12) {
510
512
  try {
511
- exports = await preloadAdaptorExports(path11, log);
513
+ exports = await preloadAdaptorExports(path12, log);
512
514
  } catch (e) {
513
- log.error(`Failed to load adaptor typedefs from path ${path11}`);
515
+ log.error(`Failed to load adaptor typedefs from path ${path12}`);
514
516
  log.error(e);
515
517
  }
516
518
  }
@@ -625,7 +627,7 @@ var validateAdaptors = async (options, logger) => {
625
627
  if (options.skipAdaptorValidation) {
626
628
  return;
627
629
  }
628
- const isPlan = options.planPath || options.workflowPath;
630
+ const isPlan = options.planPath || options.workflowPath || options.workflow;
629
631
  const hasDeclaredAdaptors = options.adaptors && options.adaptors.length > 0;
630
632
  if (isPlan && hasDeclaredAdaptors) {
631
633
  logger.error("ERROR: adaptor and workflow provided");
@@ -648,8 +650,9 @@ var validate_adaptors_default = validateAdaptors;
648
650
 
649
651
  // src/util/load-plan.ts
650
652
  import fs3 from "node:fs/promises";
651
- import path4 from "node:path";
653
+ import path4, { dirname as dirname2 } from "node:path";
652
654
  import { isPath } from "@openfn/compiler";
655
+ import Project, { yamlToJson } from "@openfn/project";
653
656
 
654
657
  // src/util/expand-adaptors.ts
655
658
  var expand = (name) => {
@@ -730,6 +733,20 @@ var map_adaptors_to_monorepo_default = mapAdaptorsToMonorepo;
730
733
  // src/util/load-plan.ts
731
734
  var loadPlan = async (options, logger) => {
732
735
  const { workflowPath, planPath, expressionPath } = options;
736
+ if (options.path && /ya?ml$/.test(options.path)) {
737
+ const content = await fs3.readFile(path4.resolve(options.path), "utf-8");
738
+ const workflow = yamlToJson(content);
739
+ options.baseDir = dirname2(options.path);
740
+ return loadXPlan({ workflow }, options, logger);
741
+ }
742
+ if (options.path && options.workflow) {
743
+ options.baseDir = options.path;
744
+ return fromProject(options.path, options.workflow, options, logger);
745
+ }
746
+ if (!expressionPath && !workflowPath && !/\.(js|json|yaml)+$/.test(options.path || "") && !options.workflow) {
747
+ const workflow = options.path;
748
+ return fromProject(path4.resolve("."), workflow, options, logger);
749
+ }
733
750
  if (expressionPath) {
734
751
  return loadExpression(options, logger);
735
752
  }
@@ -746,6 +763,16 @@ var loadPlan = async (options, logger) => {
746
763
  }
747
764
  };
748
765
  var load_plan_default = loadPlan;
766
+ var fromProject = async (rootDir, workflowName, options, logger) => {
767
+ logger.debug("Loading Repo from ", path4.resolve(rootDir));
768
+ const project = await Project.from("fs", { root: rootDir });
769
+ logger.debug("Loading workflow ", workflowName);
770
+ const workflow = project.getWorkflow(workflowName);
771
+ if (!workflow) {
772
+ throw new Error(`Workflow "${workflowName}" not found`);
773
+ }
774
+ return loadXPlan({ workflow }, options, logger);
775
+ };
749
776
  var loadJson = async (workflowPath, logger) => {
750
777
  let text;
751
778
  try {
@@ -913,8 +940,8 @@ var loadXPlan = async (plan, options, logger, defaultName = "") => {
913
940
  };
914
941
 
915
942
  // src/util/assert-path.ts
916
- var assert_path_default = (path11) => {
917
- if (!path11) {
943
+ var assert_path_default = (path12) => {
944
+ if (!path12) {
918
945
  console.error("ERROR: no path provided!");
919
946
  console.error("\nUsage:");
920
947
  console.error(" open path/to/job");
@@ -975,7 +1002,8 @@ var assertStepStructure = (step, index) => {
975
1002
  "expression",
976
1003
  "state",
977
1004
  "configuration",
978
- "linker"
1005
+ "linker",
1006
+ "openfn"
979
1007
  ];
980
1008
  for (const key in step) {
981
1009
  if (!allowedKeys.includes(key)) {
@@ -1512,8 +1540,36 @@ import {
1512
1540
  getConfig,
1513
1541
  validateConfig
1514
1542
  } from "@openfn/deploy";
1543
+
1544
+ // src/deploy/beta.ts
1545
+ import Project2 from "@openfn/project";
1546
+ import { deployProject } from "@openfn/deploy";
1547
+ async function handler(options, logger) {
1548
+ const { OPENFN_API_KEY } = process.env;
1549
+ const { endpoint } = options;
1550
+ const config = {
1551
+ apiKey: options.apiKey
1552
+ };
1553
+ if (!options.apiKey && OPENFN_API_KEY) {
1554
+ logger.info("Using OPENFN_API_KEY environment variable");
1555
+ config.apiKey = OPENFN_API_KEY;
1556
+ }
1557
+ const project = await Project2.from("fs", { root: options.path || "." });
1558
+ const state = project.serialize("state", { format: "json" });
1559
+ logger.debug("Converted local project to app state:");
1560
+ logger.debug(JSON.stringify(state, null, 2));
1561
+ config.endpoint = endpoint || project.openfn?.endpoint;
1562
+ logger.info("Sending project to app...");
1563
+ await deployProject(config, state);
1564
+ logger.success("Updated project at", config.endpoint);
1565
+ }
1566
+
1567
+ // src/deploy/handler.ts
1515
1568
  var actualDeploy = deploy;
1516
1569
  async function deployHandler(options, logger, deployFn = actualDeploy) {
1570
+ if (options.beta) {
1571
+ return handler(options, logger);
1572
+ }
1517
1573
  try {
1518
1574
  const config = mergeOverrides(await getConfig(options.configPath), options);
1519
1575
  logger.debug("Deploying with config", JSON.stringify(config, null, 2));
@@ -1570,20 +1626,20 @@ var RETRY_COUNT = 20;
1570
1626
  var TIMEOUT_MS = 1e3 * 60;
1571
1627
  var actualDocGen = (specifier) => describePackage(specifier, {});
1572
1628
  var ensurePath = (filePath) => mkdirSync(path7.dirname(filePath), { recursive: true });
1573
- var generatePlaceholder = (path11) => {
1574
- writeFileSync(path11, `{ "loading": true, "timestamp": ${Date.now()}}`);
1629
+ var generatePlaceholder = (path12) => {
1630
+ writeFileSync(path12, `{ "loading": true, "timestamp": ${Date.now()}}`);
1575
1631
  };
1576
1632
  var finish = (logger, resultPath) => {
1577
1633
  logger.success("Done! Docs can be found at:\n");
1578
1634
  logger.print(` ${path7.resolve(resultPath)}`);
1579
1635
  };
1580
- var generateDocs = async (specifier, path11, docgen, logger) => {
1636
+ var generateDocs = async (specifier, path12, docgen, logger) => {
1581
1637
  const result = await docgen(specifier);
1582
- await writeFile5(path11, JSON.stringify(result, null, 2));
1583
- finish(logger, path11);
1584
- return path11;
1638
+ await writeFile5(path12, JSON.stringify(result, null, 2));
1639
+ finish(logger, path12);
1640
+ return path12;
1585
1641
  };
1586
- var waitForDocs = async (docs, path11, logger, retryDuration = RETRY_DURATION) => {
1642
+ var waitForDocs = async (docs, path12, logger, retryDuration = RETRY_DURATION) => {
1587
1643
  try {
1588
1644
  if (docs.hasOwnProperty("loading")) {
1589
1645
  logger.info("Docs are being loaded by another process. Waiting.");
@@ -1595,19 +1651,19 @@ var waitForDocs = async (docs, path11, logger, retryDuration = RETRY_DURATION) =
1595
1651
  clearInterval(i);
1596
1652
  reject(new Error("Timed out waiting for docs to load"));
1597
1653
  }
1598
- const updated = JSON.parse(readFileSync(path11, "utf8"));
1654
+ const updated = JSON.parse(readFileSync(path12, "utf8"));
1599
1655
  if (!updated.hasOwnProperty("loading")) {
1600
1656
  logger.info("Docs found!");
1601
1657
  clearInterval(i);
1602
- resolve(path11);
1658
+ resolve(path12);
1603
1659
  }
1604
1660
  count++;
1605
1661
  }, retryDuration);
1606
1662
  });
1607
1663
  } else {
1608
- logger.info(`Docs already written to cache at ${path11}`);
1609
- finish(logger, path11);
1610
- return path11;
1664
+ logger.info(`Docs already written to cache at ${path12}`);
1665
+ finish(logger, path12);
1666
+ return path12;
1611
1667
  }
1612
1668
  } catch (e) {
1613
1669
  logger.error("Existing doc JSON corrupt. Aborting");
@@ -1624,28 +1680,28 @@ var docgenHandler = (options, logger, docgen = actualDocGen, retryDuration = RET
1624
1680
  process.exit(9);
1625
1681
  }
1626
1682
  logger.success(`Generating docs for ${specifier}`);
1627
- const path11 = `${repoDir}/docs/${specifier}.json`;
1628
- ensurePath(path11);
1683
+ const path12 = `${repoDir}/docs/${specifier}.json`;
1684
+ ensurePath(path12);
1629
1685
  const handleError2 = () => {
1630
1686
  logger.info("Removing placeholder");
1631
- rmSync(path11);
1687
+ rmSync(path12);
1632
1688
  };
1633
1689
  try {
1634
- const existing = readFileSync(path11, "utf8");
1690
+ const existing = readFileSync(path12, "utf8");
1635
1691
  const json = JSON.parse(existing);
1636
1692
  if (json && json.timeout && Date.now() - json.timeout >= TIMEOUT_MS) {
1637
1693
  logger.info(`Expired placeholder found. Removing.`);
1638
- rmSync(path11);
1694
+ rmSync(path12);
1639
1695
  throw new Error("TIMEOUT");
1640
1696
  }
1641
- return waitForDocs(json, path11, logger, retryDuration);
1697
+ return waitForDocs(json, path12, logger, retryDuration);
1642
1698
  } catch (e) {
1643
1699
  if (e.message !== "TIMEOUT") {
1644
- logger.info(`Docs JSON not found at ${path11}`);
1700
+ logger.info(`Docs JSON not found at ${path12}`);
1645
1701
  }
1646
1702
  logger.debug("Generating placeholder");
1647
- generatePlaceholder(path11);
1648
- return generateDocs(specifier, path11, docgen, logger).catch((e2) => {
1703
+ generatePlaceholder(path12);
1704
+ return generateDocs(specifier, path12, docgen, logger).catch((e2) => {
1649
1705
  logger.error("Error generating documentation");
1650
1706
  logger.error(e2);
1651
1707
  handleError2();
@@ -1696,7 +1752,7 @@ var docsHandler = async (options, logger) => {
1696
1752
  logger.success(`Showing docs for ${adaptorName} v${version}`);
1697
1753
  }
1698
1754
  logger.info("Generating/loading documentation...");
1699
- const path11 = await handler_default7(
1755
+ const path12 = await handler_default7(
1700
1756
  {
1701
1757
  specifier: `${name}@${version}`,
1702
1758
  repoDir
@@ -1705,8 +1761,8 @@ var docsHandler = async (options, logger) => {
1705
1761
  createNullLogger()
1706
1762
  );
1707
1763
  let didError = false;
1708
- if (path11) {
1709
- const source = await readFile4(path11, "utf8");
1764
+ if (path12) {
1765
+ const source = await readFile4(path12, "utf8");
1710
1766
  const data = JSON.parse(source);
1711
1767
  let desc;
1712
1768
  if (operation) {
@@ -1745,7 +1801,7 @@ var handler_default8 = docsHandler;
1745
1801
  import { createHash } from "node:crypto";
1746
1802
  import { readFileSync as readFileSync2 } from "node:fs";
1747
1803
  import path8 from "node:path";
1748
- import { writeFile as writeFile6, mkdir as mkdir2 } from "node:fs/promises";
1804
+ import { writeFile as writeFile6, mkdir as mkdir3 } from "node:fs/promises";
1749
1805
  var getPath = (repoDir, key) => `${repoDir}/meta/${key}.json`;
1750
1806
  var sortKeys = (obj) => {
1751
1807
  const newObj = {};
@@ -1775,7 +1831,7 @@ var get2 = (repoPath, key) => {
1775
1831
  };
1776
1832
  var set2 = async (repoPath, key, data) => {
1777
1833
  const fullPath = getPath(repoPath, key);
1778
- await mkdir2(path8.dirname(fullPath), { recursive: true });
1834
+ await mkdir3(path8.dirname(fullPath), { recursive: true });
1779
1835
  await writeFile6(fullPath, JSON.stringify(data));
1780
1836
  };
1781
1837
  var cache_default = { get: get2, set: set2, generateKey, getPath, sortKeys };
@@ -1863,16 +1919,92 @@ var metadataHandler = async (options, logger) => {
1863
1919
  var handler_default9 = metadataHandler;
1864
1920
 
1865
1921
  // src/pull/handler.ts
1866
- import path9 from "path";
1867
- import fs4 from "node:fs/promises";
1922
+ import path10 from "path";
1923
+ import fs5 from "node:fs/promises";
1868
1924
  import {
1869
1925
  getConfig as getConfig2,
1870
- getProject,
1926
+ getProject as getProject2,
1871
1927
  getSpec,
1872
1928
  getStateFromProjectPayload,
1873
1929
  syncRemoteSpec
1874
1930
  } from "@openfn/deploy";
1931
+
1932
+ // src/pull/beta.ts
1933
+ import { confirm } from "@inquirer/prompts";
1934
+ import path9 from "path";
1935
+ import fs4 from "node:fs/promises";
1936
+ import { getProject } from "@openfn/deploy";
1937
+ import Project3 from "@openfn/project";
1938
+ import { rimraf } from "rimraf";
1939
+ async function handler2(options, logger) {
1940
+ const { OPENFN_API_KEY, OPENFN_ENDPOINT } = process.env;
1941
+ const config = {
1942
+ apiKey: options.apiKey,
1943
+ endpoint: options.endpoint
1944
+ };
1945
+ if (!options.apiKey && OPENFN_API_KEY) {
1946
+ logger.info("Using OPENFN_API_KEY environment variable");
1947
+ config.apiKey = OPENFN_API_KEY;
1948
+ }
1949
+ if (!options.endpoint && OPENFN_ENDPOINT) {
1950
+ logger.info("Using OPENFN_ENDPOINT environment variable");
1951
+ config.endpoint = OPENFN_ENDPOINT;
1952
+ }
1953
+ const { data } = await getProject(config, options.projectId);
1954
+ const name = options.env || "project";
1955
+ const project = Project3.from("state", data, {
1956
+ endpoint: config.endpoint,
1957
+ env: name,
1958
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString()
1959
+ });
1960
+ const outputRoot = path9.resolve(options.path || ".");
1961
+ const projectFileName = project.getIdentifier();
1962
+ await fs4.mkdir(`${outputRoot}/.projects`, { recursive: true });
1963
+ let stateOutputPath = `${outputRoot}/.projects/${projectFileName}`;
1964
+ const workflowsRoot = path9.resolve(
1965
+ outputRoot,
1966
+ project.repo?.workflowRoot ?? "workflows"
1967
+ );
1968
+ if (!await confirm({
1969
+ message: `This will remove all files in ${path9.resolve(
1970
+ workflowsRoot
1971
+ )} and rebuild the workflow. Are you sure you wish to proceed?
1972
+ `,
1973
+ default: true
1974
+ })) {
1975
+ logger.always("Cancelled");
1976
+ return false;
1977
+ }
1978
+ await rimraf(workflowsRoot);
1979
+ const state = project?.serialize("state");
1980
+ if (project.repo?.formats.project === "yaml") {
1981
+ await fs4.writeFile(`${stateOutputPath}.yaml`, state);
1982
+ } else {
1983
+ await fs4.writeFile(
1984
+ `${stateOutputPath}.json`,
1985
+ JSON.stringify(state, null, 2)
1986
+ );
1987
+ }
1988
+ logger.success(`Saved project file to ${stateOutputPath}`);
1989
+ const files = project?.serialize("fs");
1990
+ for (const f in files) {
1991
+ if (files[f]) {
1992
+ await fs4.mkdir(path9.join(outputRoot, path9.dirname(f)), {
1993
+ recursive: true
1994
+ });
1995
+ await fs4.writeFile(path9.join(outputRoot, f), files[f]);
1996
+ } else {
1997
+ console.log("WARNING! No content for file", f);
1998
+ }
1999
+ }
2000
+ logger.success(`Expanded project to ${outputRoot}`);
2001
+ }
2002
+
2003
+ // src/pull/handler.ts
1875
2004
  async function pullHandler(options, logger) {
2005
+ if (options.beta) {
2006
+ return handler2(options, logger);
2007
+ }
1876
2008
  try {
1877
2009
  const config = mergeOverrides2(await getConfig2(options.configPath), options);
1878
2010
  if (process.env["OPENFN_API_KEY"]) {
@@ -1886,7 +2018,7 @@ async function pullHandler(options, logger) {
1886
2018
  logger.always(
1887
2019
  "Downloading existing project state (as JSON) from the server."
1888
2020
  );
1889
- const { data: project } = await getProject(
2021
+ const { data: project } = await getProject2(
1890
2022
  config,
1891
2023
  options.projectId,
1892
2024
  options.snapshots
@@ -1925,7 +2057,7 @@ async function pullHandler(options, logger) {
1925
2057
  process.exitCode = 1;
1926
2058
  process.exit(1);
1927
2059
  }
1928
- const resolvedPath = path9.resolve(config.specPath);
2060
+ const resolvedPath = path10.resolve(config.specPath);
1929
2061
  logger.debug("reading spec from", resolvedPath);
1930
2062
  const updatedSpec = await syncRemoteSpec(
1931
2063
  await res.text(),
@@ -1933,11 +2065,11 @@ async function pullHandler(options, logger) {
1933
2065
  config,
1934
2066
  logger
1935
2067
  );
1936
- await fs4.writeFile(
1937
- path9.resolve(config.statePath),
2068
+ await fs5.writeFile(
2069
+ path10.resolve(config.statePath),
1938
2070
  JSON.stringify(state, null, 2)
1939
2071
  );
1940
- await fs4.writeFile(resolvedPath, updatedSpec);
2072
+ await fs5.writeFile(resolvedPath, updatedSpec);
1941
2073
  const spec = await getSpec(resolvedPath);
1942
2074
  if (spec.errors.length > 0) {
1943
2075
  logger.error("ERROR: invalid spec");
@@ -1968,7 +2100,7 @@ var handler_default10 = pullHandler;
1968
2100
 
1969
2101
  // src/util/print-versions.ts
1970
2102
  import { readFileSync as readFileSync3 } from "node:fs";
1971
- import path10 from "node:path";
2103
+ import path11 from "node:path";
1972
2104
  import url from "node:url";
1973
2105
  import { getNameAndVersion as getNameAndVersion5 } from "@openfn/runtime";
1974
2106
  import { mainSymbols } from "figures";
@@ -1980,7 +2112,7 @@ var { triangleRightSmall: t } = mainSymbols;
1980
2112
  var loadVersionFromPath = (adaptorPath) => {
1981
2113
  try {
1982
2114
  const pkg = JSON.parse(
1983
- readFileSync3(path10.resolve(adaptorPath, "package.json"), "utf8")
2115
+ readFileSync3(path11.resolve(adaptorPath, "package.json"), "utf8")
1984
2116
  );
1985
2117
  return pkg.version;
1986
2118
  } catch (e) {
@@ -2015,8 +2147,8 @@ var printVersions = async (logger, options = {}, includeComponents = false) => {
2015
2147
  ...[NODE, CLI2, RUNTIME2, COMPILER2, longestAdaptorName].map((s) => s.length)
2016
2148
  );
2017
2149
  const prefix = (str) => ` ${t} ${str.padEnd(longest + 4, " ")}`;
2018
- const dirname = path10.dirname(url.fileURLToPath(import.meta.url));
2019
- const pkg = JSON.parse(readFileSync3(`${dirname}/../../package.json`, "utf8"));
2150
+ const dirname3 = path11.dirname(url.fileURLToPath(import.meta.url));
2151
+ const pkg = JSON.parse(readFileSync3(`${dirname3}/../../package.json`, "utf8"));
2020
2152
  const { version, dependencies } = pkg;
2021
2153
  const compilerVersion = dependencies["@openfn/compiler"];
2022
2154
  const runtimeVersion = dependencies["@openfn/runtime"];
@@ -2099,13 +2231,13 @@ var parse = async (options, log) => {
2099
2231
  logger
2100
2232
  );
2101
2233
  }
2102
- const handler = handlers[options.command];
2103
- if (!handler) {
2234
+ const handler3 = handlers[options.command];
2235
+ if (!handler3) {
2104
2236
  logger.error(`Unrecognised command: ${options.command}`);
2105
2237
  process.exit(1);
2106
2238
  }
2107
2239
  try {
2108
- return await handler(options, logger);
2240
+ return await handler3(options, logger);
2109
2241
  } catch (e) {
2110
2242
  if (!process.exitCode) {
2111
2243
  process.exitCode = e.exitCode || 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/cli",
3
- "version": "1.11.4",
3
+ "version": "1.12.0",
4
4
  "description": "CLI devtools for the openfn toolchain.",
5
5
  "engines": {
6
6
  "node": ">=18",
@@ -42,17 +42,18 @@
42
42
  "@inquirer/prompts": "^1.1.4",
43
43
  "chalk": "^5.1.2",
44
44
  "figures": "^5.0.0",
45
- "rimraf": "^3.0.2",
45
+ "rimraf": "^6.0.1",
46
46
  "treeify": "^1.1.0",
47
47
  "undici": "^7.1.0",
48
48
  "ws": "^8.18.0",
49
49
  "yargs": "^17.7.2",
50
- "@openfn/lexicon": "^1.2.0",
51
- "@openfn/compiler": "1.0.2",
52
- "@openfn/deploy": "0.11.1",
50
+ "@openfn/compiler": "1.0.4",
53
51
  "@openfn/describe-package": "0.1.4",
54
- "@openfn/runtime": "1.6.4",
55
- "@openfn/logger": "1.0.5"
52
+ "@openfn/deploy": "0.11.1",
53
+ "@openfn/lexicon": "^1.2.2",
54
+ "@openfn/project": "^0.1.0",
55
+ "@openfn/logger": "1.0.5",
56
+ "@openfn/runtime": "1.6.4"
56
57
  },
57
58
  "files": [
58
59
  "dist",