@openfn/cli 1.24.0 → 1.25.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
@@ -368,7 +368,7 @@ var ignoreImports = {
368
368
  }
369
369
  };
370
370
  var getBaseDir = (opts2) => {
371
- const basePath = opts2.path ?? ".";
371
+ const basePath = opts2.path ?? opts2.workspace ?? ".";
372
372
  if (/\.(jso?n?|ya?ml)$/.test(basePath)) {
373
373
  return nodePath.dirname(basePath);
374
374
  }
@@ -923,12 +923,27 @@ var alias = {
923
923
  description: "Environment name (eg staging, prod, branch)"
924
924
  }
925
925
  };
926
+ var clean = {
927
+ name: "clean",
928
+ yargs: {
929
+ description: "Clean the working dir before checking out the new project",
930
+ default: false,
931
+ boolean: true
932
+ }
933
+ };
926
934
  var dryRun2 = {
927
935
  name: "dryRun",
928
936
  yargs: {
929
937
  description: "Runs the command but does not commit any changes to disk or app"
930
938
  }
931
939
  };
940
+ var format = {
941
+ name: "format",
942
+ yargs: {
943
+ hidden: true,
944
+ description: "The format to save the project as - state, yaml or json. Use this to download raw state files."
945
+ }
946
+ };
932
947
  var removeUnmapped = {
933
948
  name: "remove-unmapped",
934
949
  yargs: {
@@ -1138,7 +1153,7 @@ var command_default9 = pullCommand;
1138
1153
  var repo = {
1139
1154
  command: "repo [subcommand]",
1140
1155
  describe: "Run commands on the module repo (install|clean)",
1141
- builder: (yargs2) => yargs2.command(clean).command(install).command(list).example("repo install -a http", "Install @openfn/language-http").example("repo clean", "Remove everything from the repo working dir")
1156
+ builder: (yargs2) => yargs2.command(clean2).command(install).command(list).example("repo install -a http", "Install @openfn/language-http").example("repo clean", "Remove everything from the repo working dir")
1142
1157
  };
1143
1158
  var installOptions = [
1144
1159
  log,
@@ -1177,7 +1192,7 @@ var cleanOptions = [
1177
1192
  }
1178
1193
  }
1179
1194
  ];
1180
- var clean = {
1195
+ var clean2 = {
1181
1196
  command: "clean",
1182
1197
  describe: "Removes all modules from the runtime module repo",
1183
1198
  handler: ensure("repo-clean", cleanOptions),
@@ -1229,14 +1244,19 @@ import Project3, { Workspace as Workspace4 } from "@openfn/project";
1229
1244
 
1230
1245
  // src/projects/checkout.ts
1231
1246
  import Project2, { Workspace as Workspace3 } from "@openfn/project";
1247
+ import { rimraf as rimraf2 } from "rimraf";
1248
+
1249
+ // src/projects/util.ts
1232
1250
  import { rimraf } from "rimraf";
1233
- var options11 = [log, workspace];
1251
+
1252
+ // src/projects/checkout.ts
1253
+ var options11 = [log, workspace, clean];
1234
1254
  var command3 = {
1235
1255
  command: "checkout <project>",
1236
1256
  describe: "Switch to a different OpenFn project in the same workspace",
1237
1257
  handler: ensure("project-checkout", options11),
1238
1258
  builder: (yargs2) => build(options11, yargs2).positional("project", {
1239
- describe: "The id, alias or UUID of the project to chcekout",
1259
+ describe: "The id, alias or UUID of the project to checkout",
1240
1260
  demandOption: true
1241
1261
  })
1242
1262
  };
@@ -1291,7 +1311,8 @@ var options13 = [
1291
1311
  }),
1292
1312
  outputPath2,
1293
1313
  env2,
1294
- workspace
1314
+ workspace,
1315
+ format
1295
1316
  ];
1296
1317
  var command5 = {
1297
1318
  command: "fetch [project]",
@@ -1307,6 +1328,7 @@ var command5 = {
1307
1328
  var fetch_default = command5;
1308
1329
 
1309
1330
  // src/projects/pull.ts
1331
+ import { Workspace as Workspace6 } from "@openfn/project";
1310
1332
  var options14 = [
1311
1333
  alias,
1312
1334
  env2,
@@ -190,22 +190,31 @@ var createNullLogger = () => createLogger2(void 0, { log: { default: "none" } })
190
190
  import fs from "node:fs";
191
191
  import path2 from "node:path";
192
192
  import { rmdir } from "node:fs/promises";
193
- var getCachePath = async (plan, options8, stepId) => {
194
- const { baseDir } = options8;
195
- const { name } = plan.workflow;
196
- const basePath = `${baseDir}/.cli-cache/${name}`;
193
+ var CACHE_DIR = ".cli-cache";
194
+ var getCachePath = (options8, workflowName, stepId) => {
195
+ const { baseDir, cachePath } = options8;
196
+ if (cachePath) {
197
+ if (stepId) {
198
+ return path2.resolve(cachePath, `${stepId.replace(/ /, "-")}.json`);
199
+ }
200
+ return path2.resolve(cachePath);
201
+ }
202
+ const basePath = path2.resolve(
203
+ baseDir ?? process.cwd(),
204
+ `${CACHE_DIR}/${workflowName}`
205
+ );
197
206
  if (stepId) {
198
- return path2.resolve(`${basePath}/${stepId.replace(/ /, "-")}.json`);
207
+ return `${basePath}/${stepId.replace(/ /, "-")}.json`;
199
208
  }
200
- return path2.resolve(basePath);
209
+ return basePath;
201
210
  };
202
- var ensureGitIgnore = (options8) => {
211
+ var ensureGitIgnore = (options8, cachePath) => {
203
212
  if (!options8._hasGitIgnore) {
204
- const ignorePath = path2.resolve(
205
- options8.baseDir,
206
- ".cli-cache",
207
- ".gitignore"
208
- );
213
+ let root = cachePath;
214
+ while (root.length > 1 && !root.endsWith(CACHE_DIR)) {
215
+ root = path2.dirname(root);
216
+ }
217
+ const ignorePath = path2.resolve(root, ".gitignore");
209
218
  try {
210
219
  fs.accessSync(ignorePath);
211
220
  } catch (e) {
@@ -216,15 +225,15 @@ var ensureGitIgnore = (options8) => {
216
225
  };
217
226
  var saveToCache = async (plan, stepId, output, options8, logger) => {
218
227
  if (options8.cacheSteps) {
219
- const cachePath = await getCachePath(plan, options8, stepId);
228
+ const cachePath = await getCachePath(options8, plan.workflow.name, stepId);
220
229
  fs.mkdirSync(path2.dirname(cachePath), { recursive: true });
221
- ensureGitIgnore(options8);
230
+ ensureGitIgnore(options8, path2.dirname(cachePath));
222
231
  logger.info(`Writing ${stepId} output to ${cachePath}`);
223
232
  fs.writeFileSync(cachePath, JSON.stringify(output));
224
233
  }
225
234
  };
226
235
  var clearCache = async (plan, options8, logger) => {
227
- const cacheDir = await getCachePath(plan, options8);
236
+ const cacheDir = await getCachePath(options8, plan.workflow?.name);
228
237
  try {
229
238
  await rmdir(cacheDir, { recursive: true });
230
239
  logger.info(`Cleared cache at ${cacheDir}`);
@@ -685,7 +694,11 @@ var load_state_default = async (plan, opts, log2, start) => {
685
694
  const upstreamStepId = getUpstreamStepId(plan, start);
686
695
  if (upstreamStepId) {
687
696
  log2.debug(`Input step for "${start}" is "${upstreamStepId}"`);
688
- const cachedStatePath = await getCachePath(plan, opts, upstreamStepId);
697
+ const cachedStatePath = await getCachePath(
698
+ opts,
699
+ plan.workflow.name,
700
+ upstreamStepId
701
+ );
689
702
  log2.debug("Loading cached state from", cachedStatePath);
690
703
  try {
691
704
  await fs2.access(cachedStatePath);
@@ -857,6 +870,7 @@ var loadPlan = async (options8, logger) => {
857
870
  };
858
871
  options8.credentials ??= workspace2.getConfig().credentials;
859
872
  options8.collectionsEndpoint ??= proj.openfn?.endpoint;
873
+ options8.cachePath ??= workspace2.workflowsPath + `/${name}/${CACHE_DIR}`;
860
874
  }
861
875
  if (options8.path && /ya?ml$/.test(options8.path)) {
862
876
  const content = await fs3.readFile(path4.resolve(options8.path), "utf-8");
@@ -886,7 +900,10 @@ var loadPlan = async (options8, logger) => {
886
900
  defaultName
887
901
  );
888
902
  } else {
889
- return loadXPlan({ workflow: workflowObj }, options8, logger, defaultName);
903
+ const { id, start, options: o, ...w } = workflowObj;
904
+ const opts = { ...o, start };
905
+ const plan = { id, workflow: w, options: opts };
906
+ return loadXPlan(plan, options8, logger, defaultName);
890
907
  }
891
908
  };
892
909
  var load_plan_default = loadPlan;
@@ -1370,7 +1387,10 @@ var executeHandler = async (options8, logger) => {
1370
1387
  const result = await execute_default(finalPlan, state, options8, logger);
1371
1388
  if (options8.cacheSteps) {
1372
1389
  logger.success(
1373
- "Cached output written to ./cli-cache (see info logs for details)"
1390
+ `Cached output written to ${getCachePath(
1391
+ options8,
1392
+ plan.workflow.name
1393
+ )} (see info logs for details)`
1374
1394
  );
1375
1395
  }
1376
1396
  await serialize_output_default(options8, result, logger);
@@ -1956,12 +1976,27 @@ var alias = {
1956
1976
  description: "Environment name (eg staging, prod, branch)"
1957
1977
  }
1958
1978
  };
1979
+ var clean2 = {
1980
+ name: "clean",
1981
+ yargs: {
1982
+ description: "Clean the working dir before checking out the new project",
1983
+ default: false,
1984
+ boolean: true
1985
+ }
1986
+ };
1959
1987
  var dryRun = {
1960
1988
  name: "dryRun",
1961
1989
  yargs: {
1962
1990
  description: "Runs the command but does not commit any changes to disk or app"
1963
1991
  }
1964
1992
  };
1993
+ var format = {
1994
+ name: "format",
1995
+ yargs: {
1996
+ hidden: true,
1997
+ description: "The format to save the project as - state, yaml or json. Use this to download raw state files."
1998
+ }
1999
+ };
1965
2000
  var removeUnmapped = {
1966
2001
  name: "remove-unmapped",
1967
2002
  yargs: {
@@ -2013,6 +2048,7 @@ var CLIError = class extends Error {
2013
2048
  };
2014
2049
 
2015
2050
  // src/projects/util.ts
2051
+ import { rimraf } from "rimraf";
2016
2052
  var loadAppAuthConfig = (options8, logger) => {
2017
2053
  const { OPENFN_API_KEY, OPENFN_ENDPOINT } = process.env;
2018
2054
  const config2 = {
@@ -2036,22 +2072,22 @@ var ensureExt = (filePath, ext) => {
2036
2072
  return filePath;
2037
2073
  };
2038
2074
  var getSerializePath = (project, workspacePath, outputPath2) => {
2039
- const outputRoot = resolve_path_default(outputPath2 || workspacePath);
2075
+ const outputRoot = resolve_path_default(outputPath2 || workspacePath || ".");
2040
2076
  const projectsDir = project?.config.dirs.projects ?? ".projects";
2041
- return outputPath2 ?? `${outputRoot}/${projectsDir}/${project.qname}`;
2077
+ return outputPath2 ?? `${outputRoot}/${projectsDir}/${project?.qname}`;
2042
2078
  };
2043
2079
  var serialize = async (project, outputPath2, formatOverride, dryRun2 = false) => {
2044
2080
  const root = path9.dirname(outputPath2);
2045
2081
  await mkdir3(root, { recursive: true });
2046
- const format = formatOverride ?? project.config?.formats.project;
2047
- const output = project?.serialize("project", { format });
2082
+ const format2 = formatOverride ?? project.config?.formats.project;
2083
+ const output = format2 === "state" ? project?.serialize("state", { format: "json" }) : project?.serialize("project", { format: format2 });
2048
2084
  const maybeWriteFile = (filePath, output2) => {
2049
2085
  if (!dryRun2) {
2050
2086
  return writeFile5(filePath, output2);
2051
2087
  }
2052
2088
  };
2053
2089
  let finalPath;
2054
- if (format === "yaml") {
2090
+ if (format2 === "yaml") {
2055
2091
  finalPath = ensureExt(outputPath2, "yaml");
2056
2092
  await maybeWriteFile(finalPath, output);
2057
2093
  } else {
@@ -2132,6 +2168,23 @@ var DeployError = class extends Error {
2132
2168
  super(message);
2133
2169
  }
2134
2170
  };
2171
+ async function tidyWorkflowDir(currentProject, incomingProject, dryRun2 = false) {
2172
+ if (!currentProject || !incomingProject) {
2173
+ return [];
2174
+ }
2175
+ const currentFiles = currentProject.serialize("fs");
2176
+ const newFiles = incomingProject.serialize("fs");
2177
+ const toRemove = [];
2178
+ for (const path17 in currentFiles) {
2179
+ if (!newFiles[path17]) {
2180
+ toRemove.push(path17);
2181
+ }
2182
+ }
2183
+ if (!dryRun2) {
2184
+ await rimraf(toRemove);
2185
+ }
2186
+ return toRemove.sort();
2187
+ }
2135
2188
 
2136
2189
  // src/util/command-builders.ts
2137
2190
  import c from "chalk";
@@ -2243,7 +2296,13 @@ Pass --force to override this error and deploy anyway.`);
2243
2296
  logger.success("Nothing to deploy");
2244
2297
  return;
2245
2298
  }
2246
- if (!localProject.canMergeInto(remoteProject)) {
2299
+ const skipVersionTest = localProject.workflows.find((wf) => wf.history.length === 0) || remoteProject.workflows.find((wf) => wf.history.length === 0);
2300
+ if (skipVersionTest) {
2301
+ logger.warn(
2302
+ "Skipping compatibility check as no local version history detected"
2303
+ );
2304
+ logger.warn("Pushing these changes may overrite changes made to the app");
2305
+ } else if (!localProject.canMergeInto(remoteProject)) {
2247
2306
  if (!options8.force) {
2248
2307
  logger.error(`Error: Projects have diverged!
2249
2308
 
@@ -2815,9 +2874,13 @@ import {
2815
2874
  syncRemoteSpec
2816
2875
  } from "@openfn/deploy";
2817
2876
 
2877
+ // src/projects/pull.ts
2878
+ import { Workspace as Workspace4 } from "@openfn/project";
2879
+
2818
2880
  // src/projects/fetch.ts
2819
2881
  import path12 from "node:path";
2820
2882
  import Project2, { Workspace as Workspace2 } from "@openfn/project";
2883
+ import { writeFile as writeFile8 } from "node:fs/promises";
2821
2884
  var options2 = [
2822
2885
  alias,
2823
2886
  apiKey,
@@ -2831,7 +2894,8 @@ var options2 = [
2831
2894
  }),
2832
2895
  outputPath,
2833
2896
  env,
2834
- workspace
2897
+ workspace,
2898
+ format
2835
2899
  ];
2836
2900
  var command2 = {
2837
2901
  command: "fetch [project]",
@@ -2845,28 +2909,68 @@ var command2 = {
2845
2909
  handler: ensure("project-fetch", options2)
2846
2910
  };
2847
2911
  var printProjectName2 = (project) => `${project.qname} (${project.id})`;
2848
- var handler2 = async (options8, logger) => {
2912
+ var fetchV1 = async (options8, logger) => {
2849
2913
  const workspacePath = options8.workspace ?? process.cwd();
2850
2914
  logger.debug("Using workspace at", workspacePath);
2851
2915
  const workspace2 = new Workspace2(workspacePath, logger, false);
2852
- const { outputPath: outputPath2 } = options8;
2853
- const localTargetProject = await resolveOutputProject(
2854
- workspace2,
2855
- options8,
2916
+ const localProject = workspace2.get(options8.project);
2917
+ if (localProject) {
2918
+ logger.debug(
2919
+ `Resolved "${options8.project}" to local project ${printProjectName2(
2920
+ localProject
2921
+ )}`
2922
+ );
2923
+ } else {
2924
+ logger.debug(
2925
+ `Failed to resolve "${options8.project}" to local project. Will send request to app anyway.`
2926
+ );
2927
+ }
2928
+ const config2 = loadAppAuthConfig(options8, logger);
2929
+ const { data } = await fetchProject(
2930
+ options8.endpoint ?? localProject?.openfn?.endpoint,
2931
+ config2.apiKey,
2932
+ localProject?.uuid ?? options8.project,
2856
2933
  logger
2857
2934
  );
2935
+ const finalOutputPath = getSerializePath(
2936
+ localProject,
2937
+ options8.workspace,
2938
+ options8.outputPath
2939
+ );
2940
+ logger.success(`Fetched project file to ${finalOutputPath}`);
2941
+ await writeFile8(finalOutputPath, JSON.stringify(data, null, 2));
2942
+ return data;
2943
+ };
2944
+ var handler2 = async (options8, logger) => {
2945
+ if (options8.format === "state") {
2946
+ return fetchV1(options8, logger);
2947
+ }
2948
+ return fetchV2(options8, logger);
2949
+ };
2950
+ var fetchV2 = async (options8, logger) => {
2951
+ const workspacePath = options8.workspace ?? process.cwd();
2952
+ logger.debug("Using workspace at", workspacePath);
2953
+ const workspace2 = new Workspace2(workspacePath, logger, false);
2954
+ const { outputPath: outputPath2 } = options8;
2858
2955
  const remoteProject = await fetchRemoteProject(workspace2, options8, logger);
2859
- ensureTargetCompatible(options8, remoteProject, localTargetProject);
2956
+ if (!options8.force && options8.format !== "state") {
2957
+ const localTargetProject = await resolveOutputProject(
2958
+ workspace2,
2959
+ options8,
2960
+ logger
2961
+ );
2962
+ ensureTargetCompatible(options8, remoteProject, localTargetProject);
2963
+ }
2860
2964
  const finalOutputPath = getSerializePath(
2861
2965
  remoteProject,
2862
2966
  workspacePath,
2863
2967
  outputPath2
2864
2968
  );
2865
- let format = void 0;
2969
+ let format2 = options8.format;
2866
2970
  if (outputPath2) {
2867
2971
  const ext = path12.extname(outputPath2).substring(1);
2868
2972
  if (ext.length) {
2869
- format = ext;
2973
+ format2 = ext;
2870
2974
  }
2871
2975
  if (options8.alias) {
2872
2976
  logger.warn(
@@ -2874,10 +2978,12 @@ var handler2 = async (options8, logger) => {
2874
2978
  );
2875
2979
  }
2876
2980
  }
2877
- await serialize(remoteProject, finalOutputPath, format);
2878
- logger.success(
2879
- `Fetched project file to ${finalOutputPath}.${format ?? "yaml"}`
2981
+ const finalPathWithExt = await serialize(
2982
+ remoteProject,
2983
+ finalOutputPath,
2984
+ format2
2880
2985
  );
2986
+ logger.success(`Fetched project file to ${finalPathWithExt}`);
2881
2987
  return remoteProject;
2882
2988
  };
2883
2989
  async function resolveOutputProject(workspace2, options8, logger) {
@@ -2999,14 +3105,14 @@ To ignore this error and override the local file, pass --force (-f)
2999
3105
  import Project3, { Workspace as Workspace3 } from "@openfn/project";
3000
3106
  import path13 from "path";
3001
3107
  import fs4 from "fs";
3002
- import { rimraf } from "rimraf";
3003
- var options3 = [log, workspace];
3108
+ import { rimraf as rimraf2 } from "rimraf";
3109
+ var options3 = [log, workspace, clean2];
3004
3110
  var command3 = {
3005
3111
  command: "checkout <project>",
3006
3112
  describe: "Switch to a different OpenFn project in the same workspace",
3007
3113
  handler: ensure("project-checkout", options3),
3008
3114
  builder: (yargs) => build(options3, yargs).positional("project", {
3009
- describe: "The id, alias or UUID of the project to chcekout",
3115
+ describe: "The id, alias or UUID of the project to checkout",
3010
3116
  demandOption: true
3011
3117
  })
3012
3118
  };
@@ -3015,6 +3121,7 @@ var handler3 = async (options8, logger) => {
3015
3121
  const workspacePath = options8.workspace ?? process.cwd();
3016
3122
  const workspace2 = new Workspace3(workspacePath, logger);
3017
3123
  const { project: _, ...config2 } = workspace2.getConfig();
3124
+ const currentProject = workspace2.getActiveProject();
3018
3125
  let switchProject;
3019
3126
  if (/\.(yaml|json)$/.test(projectIdentifier)) {
3020
3127
  const filePath = projectIdentifier.startsWith("/") ? projectIdentifier : path13.join(workspacePath, projectIdentifier);
@@ -3028,7 +3135,11 @@ var handler3 = async (options8, logger) => {
3028
3135
  `Project with id ${projectIdentifier} not found in the workspace`
3029
3136
  );
3030
3137
  }
3031
- await rimraf(path13.join(workspacePath, config2.workflowRoot ?? "workflows"));
3138
+ if (options8.clean) {
3139
+ await rimraf2(workspace2.workflowsPath);
3140
+ } else {
3141
+ await tidyWorkflowDir(currentProject, switchProject);
3142
+ }
3032
3143
  const files = switchProject.serialize("fs");
3033
3144
  for (const f in files) {
3034
3145
  if (files[f]) {
@@ -3071,11 +3182,30 @@ var command4 = {
3071
3182
  handler: ensure("project-pull", options4)
3072
3183
  };
3073
3184
  async function handler4(options8, logger) {
3185
+ ensureProjectId(options8, logger);
3074
3186
  await handler2(options8, logger);
3075
3187
  logger.success(`Downloaded latest project version`);
3076
3188
  await handler3(options8, logger);
3077
3189
  logger.success(`Checked out project locally`);
3078
3190
  }
3191
+ var ensureProjectId = (options8, logger) => {
3192
+ if (!options8.project) {
3193
+ logger?.debug(
3194
+ "No project ID specified: looking up checked out project in Workspace"
3195
+ );
3196
+ const ws = new Workspace4(options8.workspace);
3197
+ if (ws.activeProject) {
3198
+ options8.project = ws.activeProject.uuid;
3199
+ logger?.info(
3200
+ `Project id not provided: will default to ${options8.project}`
3201
+ );
3202
+ } else {
3203
+ throw new Error(
3204
+ "Project not provided: specify a project UUID, id or alias"
3205
+ );
3206
+ }
3207
+ }
3208
+ };
3079
3209
  var pull_default = handler4;
3080
3210
 
3081
3211
  // src/pull/handler.ts
@@ -3190,7 +3320,7 @@ __export(projects_exports, {
3190
3320
  });
3191
3321
 
3192
3322
  // src/projects/list.ts
3193
- import { Workspace as Workspace4 } from "@openfn/project";
3323
+ import { Workspace as Workspace5 } from "@openfn/project";
3194
3324
  var options5 = [log, workspace];
3195
3325
  var command5 = {
3196
3326
  command: "list [project-path]",
@@ -3203,7 +3333,7 @@ var handler5 = async (options8, logger) => {
3203
3333
  logger.info("Searching for projects in workspace at:");
3204
3334
  logger.info(" ", options8.workspace);
3205
3335
  logger.break();
3206
- const workspace2 = new Workspace4(options8.workspace);
3336
+ const workspace2 = new Workspace5(options8.workspace);
3207
3337
  if (!workspace2.valid) {
3208
3338
  throw new Error("No OpenFn projects found");
3209
3339
  }
@@ -3221,7 +3351,7 @@ ${project.workflows.map((w) => " - " + w.id).join("\n")}`;
3221
3351
  }
3222
3352
 
3223
3353
  // src/projects/version.ts
3224
- import { Workspace as Workspace5 } from "@openfn/project";
3354
+ import { Workspace as Workspace6 } from "@openfn/project";
3225
3355
  var options6 = [workflow, workspace, workflowMappings];
3226
3356
  var command6 = {
3227
3357
  command: "version [workflow]",
@@ -3230,7 +3360,7 @@ var command6 = {
3230
3360
  builder: (yargs) => build(options6, yargs)
3231
3361
  };
3232
3362
  var handler6 = async (options8, logger) => {
3233
- const workspace2 = new Workspace5(options8.workspace);
3363
+ const workspace2 = new Workspace6(options8.workspace);
3234
3364
  if (!workspace2.valid) {
3235
3365
  logger.error("Command was run in an invalid openfn workspace");
3236
3366
  return;
@@ -3265,7 +3395,7 @@ ${final}`);
3265
3395
  };
3266
3396
 
3267
3397
  // src/projects/merge.ts
3268
- import Project5, { Workspace as Workspace6 } from "@openfn/project";
3398
+ import Project5, { Workspace as Workspace7 } from "@openfn/project";
3269
3399
  import path15 from "node:path";
3270
3400
  import fs6 from "node:fs/promises";
3271
3401
  var options7 = [
@@ -3301,7 +3431,7 @@ var command7 = {
3301
3431
  };
3302
3432
  var handler7 = async (options8, logger) => {
3303
3433
  const workspacePath = options8.workspace;
3304
- const workspace2 = new Workspace6(workspacePath);
3434
+ const workspace2 = new Workspace7(workspacePath);
3305
3435
  if (!workspace2.valid) {
3306
3436
  logger.error("Command was run in an invalid openfn workspace");
3307
3437
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/cli",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "CLI devtools for the OpenFn toolchain",
5
5
  "engines": {
6
6
  "node": ">=18",
@@ -33,6 +33,7 @@
33
33
  "@types/ws": "^8.18.1",
34
34
  "@types/yargs": "^17.0.33",
35
35
  "ava": "5.3.1",
36
+ "lodash-es": "^4.17.21",
36
37
  "mock-fs": "^5.5.0",
37
38
  "tslib": "^2.8.1",
38
39
  "tsup": "^7.2.0",
@@ -49,13 +50,13 @@
49
50
  "undici": "7.12.0",
50
51
  "ws": "^8.18.3",
51
52
  "yargs": "^17.7.2",
52
- "@openfn/deploy": "0.11.5",
53
- "@openfn/compiler": "1.2.2",
54
- "@openfn/lexicon": "^1.4.0",
55
53
  "@openfn/describe-package": "0.1.5",
56
- "@openfn/project": "^0.12.0",
57
- "@openfn/runtime": "1.8.1",
58
- "@openfn/logger": "1.1.1"
54
+ "@openfn/lexicon": "^1.4.0",
55
+ "@openfn/compiler": "1.2.2",
56
+ "@openfn/deploy": "0.11.5",
57
+ "@openfn/logger": "1.1.1",
58
+ "@openfn/project": "^0.12.1",
59
+ "@openfn/runtime": "1.8.3"
59
60
  },
60
61
  "files": [
61
62
  "dist",