@openfn/project 0.8.1 → 0.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.d.ts CHANGED
@@ -2,6 +2,17 @@ import * as l from '@openfn/lexicon';
2
2
  import l__default, { WorkspaceConfig, UUID } from '@openfn/lexicon';
3
3
  import { Provisioner } from '@openfn/lexicon/lightning';
4
4
 
5
+ type SerializedProject = Omit<Partial<l.Project>, 'workflows'> & {
6
+ version: number;
7
+ workflows: SerializedWorkflow[];
8
+ };
9
+ type SerializedWorkflow = {
10
+ id: string;
11
+ name: string;
12
+ steps: l.Step[];
13
+ openfn?: l.ProjectMeta;
14
+ };
15
+
5
16
  type WithMeta<T> = T & {
6
17
  openfn?: l.NodeMeta;
7
18
  };
@@ -89,6 +100,7 @@ declare class Project {
89
100
  config: l__default.WorkspaceConfig;
90
101
  collections: any;
91
102
  credentials: string[];
103
+ static from(type: 'project', data: any, options: never): Promise<Project>;
92
104
  static from(type: 'state', data: Provisioner.Project, meta?: Partial<l__default.ProjectMeta>, config?: fromAppStateConfig): Promise<Project>;
93
105
  static from(type: 'fs', options: FromFsConfig): Promise<Project>;
94
106
  static from(type: 'path', data: string, options?: {
@@ -97,7 +109,7 @@ declare class Project {
97
109
  static merge(source: Project, target: Project, options?: Partial<MergeProjectOptions>): Project;
98
110
  constructor(data: Partial<l__default.Project>, config?: Partial<l__default.WorkspaceConfig>);
99
111
  setConfig(config: Partial<WorkspaceConfig>): void;
100
- serialize(type?: 'json' | 'yaml' | 'fs' | 'state', options?: any): any;
112
+ serialize(type?: 'project' | 'fs' | 'state', options?: any): string | Record<string, string> | Provisioner.Project_v1 | SerializedProject;
101
113
  getWorkflow(idOrName: string): Workflow | undefined;
102
114
  getIdentifier(): string;
103
115
  getUUID(workflow: string | Workflow, stepId: string, otherStep?: string): any;
package/dist/index.js CHANGED
@@ -210,7 +210,7 @@ var Workflow = class {
210
210
  return this.index.uuid[id];
211
211
  }
212
212
  toJSON() {
213
- return this.workflow;
213
+ return clone(this.workflow);
214
214
  }
215
215
  getUUIDMap() {
216
216
  return this.index.uuid;
@@ -235,28 +235,10 @@ var Workflow_default = Workflow;
235
235
  var serialize_exports = {};
236
236
  __export(serialize_exports, {
237
237
  fs: () => to_fs_default,
238
- json: () => to_json_default,
238
+ project: () => to_project_default,
239
239
  state: () => to_app_state_default
240
240
  });
241
241
 
242
- // src/serialize/to-json.ts
243
- function to_json_default(project) {
244
- return {
245
- // There must be a better way to do this?
246
- // Do we just serialize all public fields?
247
- id: project.id,
248
- name: project.name,
249
- description: project.description,
250
- config: project.config,
251
- meta: project.meta,
252
- workflows: project.workflows.map((w) => w.toJSON()),
253
- collections: project.collections,
254
- credentials: project.credentials,
255
- openfn: project.openfn,
256
- options: project.options
257
- };
258
- }
259
-
260
242
  // src/serialize/to-app-state.ts
261
243
  import { pick, omitBy, isNil, sortBy } from "lodash-es";
262
244
  import { randomUUID } from "node:crypto";
@@ -286,8 +268,19 @@ function jsonToYaml(json) {
286
268
  }
287
269
 
288
270
  // src/serialize/to-app-state.ts
271
+ var defaultJobProps = {
272
+ keychain_credential_id: null,
273
+ project_credential_id: null
274
+ };
289
275
  function to_app_state_default(project, options = {}) {
290
- const { uuid, endpoint, env, ...rest } = project.openfn ?? {};
276
+ const {
277
+ uuid,
278
+ endpoint,
279
+ env,
280
+ id,
281
+ fetched_at,
282
+ ...rest
283
+ } = project.openfn ?? {};
291
284
  const state = omitBy(
292
285
  pick(project, ["name", "description", "collections"]),
293
286
  isNil
@@ -341,12 +334,10 @@ var mapWorkflow = (workflow) => {
341
334
  node = omitBy(pick(s, ["name", "adaptor"]), isNil);
342
335
  const { uuid: uuid2, ...otherOpenFnProps } = s.openfn ?? {};
343
336
  node.id = uuid2;
344
- Object.assign(node, otherOpenFnProps);
345
337
  if (s.expression) {
346
338
  node.body = s.expression;
347
339
  }
348
- node.project_credential_id = s.openfn?.project_credential_id ?? null;
349
- node.keychain_credential_id = null;
340
+ Object.assign(node, defaultJobProps, otherOpenFnProps);
350
341
  wfState.jobs.push(node);
351
342
  }
352
343
  Object.keys(s.next ?? {}).forEach((next) => {
@@ -380,6 +371,7 @@ var mapWorkflow = (workflow) => {
380
371
 
381
372
  // src/serialize/to-fs.ts
382
373
  import nodepath from "path";
374
+ import { omit } from "lodash-es";
383
375
 
384
376
  // src/util/config.ts
385
377
  import { readFileSync } from "node:fs";
@@ -506,10 +498,16 @@ var extractWorkflow = (project, workflowId) => {
506
498
  // Not crazy about this - maybe we should do something better? Or do we like the consistency?
507
499
  options: workflow.options,
508
500
  steps: workflow.steps.map((step) => {
509
- const { openfn, expression, ...mapped } = step;
501
+ const { openfn, expression, next, ...mapped } = step;
510
502
  if (expression) {
511
503
  mapped.expression = `./${step.id}.js`;
512
504
  }
505
+ if (next && typeof next === "object") {
506
+ mapped.next = {};
507
+ for (const id in next) {
508
+ mapped.next[id] = omit(next[id], ["openfn"]);
509
+ }
510
+ }
513
511
  return mapped;
514
512
  })
515
513
  };
@@ -544,18 +542,52 @@ var handleOutput = (data, filePath, format) => {
544
542
  return { path: path5, content };
545
543
  };
546
544
 
547
- // src/parse/from-app-state.ts
548
- var from_app_state_default = (state, meta = {}, config = {}) => {
549
- let stateJson;
550
- if (typeof state === "string") {
551
- if (config.format === "yaml") {
552
- stateJson = yamlToJson(state);
545
+ // src/serialize/to-project.ts
546
+ import { omitBy as omitBy2, isNil as isNil3 } from "lodash-es";
547
+ var SERIALIZE_VERSION = 2;
548
+ var to_project_default = (project, options = {}) => {
549
+ const proj = omitBy2(
550
+ {
551
+ id: project.id,
552
+ name: project.name,
553
+ version: SERIALIZE_VERSION,
554
+ // important!
555
+ description: project.description,
556
+ collections: project.collections,
557
+ credentials: project.credentials,
558
+ openfn: project.openfn,
559
+ meta: project.meta,
560
+ options: omitBy2(project.options, isNil3),
561
+ //workflows: project.workflows.map(mapWorkflow) as SerializedWorkflow[],
562
+ workflows: project.workflows.map(
563
+ (w) => w.toJSON()
564
+ )
565
+ },
566
+ isNil3
567
+ );
568
+ const format = options.format ?? proj.config?.formats.project;
569
+ if (format === "json") {
570
+ return proj;
571
+ }
572
+ return jsonToYaml(proj);
573
+ };
574
+
575
+ // src/util/ensure-json.ts
576
+ var ensure_json_default = (obj) => {
577
+ if (typeof obj === "string") {
578
+ const firstChar = obj.trim()[0];
579
+ if (firstChar === "{" || firstChar === "[") {
580
+ return JSON.parse(obj);
553
581
  } else {
554
- stateJson = JSON.parse(state);
582
+ return yamlToJson(obj);
555
583
  }
556
- } else {
557
- stateJson = state;
558
584
  }
585
+ return obj;
586
+ };
587
+
588
+ // src/parse/from-app-state.ts
589
+ var from_app_state_default = (state, meta = {}, config = {}) => {
590
+ let stateJson = ensure_json_default(state);
559
591
  delete config.format;
560
592
  const {
561
593
  id,
@@ -658,24 +690,32 @@ var mapWorkflow2 = (workflow) => {
658
690
  };
659
691
 
660
692
  // src/parse/from-path.ts
661
- import { extname } from "node:path";
662
693
  import { readFile } from "node:fs/promises";
663
- import { omit } from "lodash-es";
664
- var from_path_default = async (path5, config = {}) => {
665
- const ext = extname(path5).toLowerCase();
666
- const source = await readFile(path5, "utf8");
667
- let state;
668
- if (ext === ".json") {
669
- config.format = "json";
670
- state = JSON.parse(source);
671
- } else if (ext.match(/(ya?ml)$/)) {
672
- config.format = "yaml";
673
- state = yamlToJson(source);
694
+
695
+ // src/parse/from-project.ts
696
+ var from_project_default = (data, config) => {
697
+ let rawJson = ensure_json_default(data);
698
+ let json;
699
+ if (rawJson.version) {
700
+ json = from_v2(rawJson);
674
701
  } else {
675
- throw new Error(`Cannot load a project from a ${ext} file`);
702
+ json = from_v1(rawJson);
676
703
  }
677
- const meta = {};
678
- return from_app_state_default(state, meta, omit(config, ["format"]));
704
+ return new Project_default(json, config);
705
+ };
706
+ var from_v1 = (data) => {
707
+ return from_app_state_default(data);
708
+ };
709
+ var from_v2 = (data) => {
710
+ return {
711
+ ...data
712
+ };
713
+ };
714
+
715
+ // src/parse/from-path.ts
716
+ var from_path_default = async (path5, config = {}) => {
717
+ const source = await readFile(path5, "utf8");
718
+ return from_project_default(source, config);
679
719
  };
680
720
 
681
721
  // src/parse/from-fs.ts
@@ -697,6 +737,7 @@ var get_identifier_default = (config = {}) => {
697
737
  };
698
738
 
699
739
  // src/parse/from-fs.ts
740
+ import { omit as omit2 } from "lodash-es";
700
741
  var parseProject = async (options) => {
701
742
  const { root } = options;
702
743
  const { type, content } = findWorkspaceFile(root);
@@ -715,13 +756,13 @@ var parseProject = async (options) => {
715
756
  `${identifier}.${format}`
716
757
  );
717
758
  const stateFile = await fs.readFile(statePath, "utf8");
718
- state = from_app_state_default(stateFile, { format });
759
+ state = from_project_default(stateFile, config);
719
760
  } catch (e) {
720
761
  console.warn(`Failed to find state file for ${identifier}`);
721
762
  }
722
763
  const proj = {
723
764
  name: state?.name,
724
- openfn: context.project,
765
+ openfn: omit2(context.project, ["id"]),
725
766
  config,
726
767
  workflows: []
727
768
  };
@@ -1208,6 +1249,7 @@ var Project = class {
1208
1249
  options;
1209
1250
  // local metadata used by the CLI
1210
1251
  // This stuff is not synced back to lightning
1252
+ // TODO maybe rename cli or local
1211
1253
  meta;
1212
1254
  // this contains meta about the connected openfn project
1213
1255
  openfn;
@@ -1216,17 +1258,23 @@ var Project = class {
1216
1258
  collections;
1217
1259
  credentials;
1218
1260
  static async from(type, data, ...rest) {
1219
- if (type === "state") {
1220
- return from_app_state_default(data, rest[0], rest[1]);
1221
- } else if (type === "fs") {
1222
- return parseProject(data);
1223
- } else if (type === "path") {
1224
- return from_path_default(data, rest[0]);
1261
+ switch (type) {
1262
+ case "project":
1263
+ var [config] = rest;
1264
+ return from_project_default(data, config);
1265
+ case "state":
1266
+ return from_app_state_default(data, rest[0], rest[1]);
1267
+ case "fs":
1268
+ return parseProject(data);
1269
+ case "path":
1270
+ var [config] = rest;
1271
+ return from_path_default(data, config);
1272
+ default:
1273
+ throw new Error(`Didn't recognize type ${type}`);
1225
1274
  }
1226
- throw new Error(`Didn't recognize type ${type}`);
1227
1275
  }
1228
1276
  // Diff two projects
1229
- // /static diff(a: Project, b: Project) {}
1277
+ // static diff(a: Project, b: Project) {}
1230
1278
  // Merge a source project (staging) into the target project (main)
1231
1279
  // Returns a new Project
1232
1280
  // TODO: throw if histories have diverged
@@ -1252,7 +1300,7 @@ var Project = class {
1252
1300
  setConfig(config) {
1253
1301
  this.config = buildConfig(config);
1254
1302
  }
1255
- serialize(type = "json", options) {
1303
+ serialize(type = "project", options) {
1256
1304
  if (type in serialize_exports) {
1257
1305
  return serialize_exports[type](this, options);
1258
1306
  }
@@ -1315,7 +1363,7 @@ function pathExists(fpath, type) {
1315
1363
 
1316
1364
  // src/Workspace.ts
1317
1365
  var Workspace = class {
1318
- // @ts-ignore config not defininitely assigned - it sure is
1366
+ // @ts-ignore config not definitely assigned - it sure is
1319
1367
  config;
1320
1368
  activeProject;
1321
1369
  projects = [];
@@ -1343,14 +1391,9 @@ var Workspace = class {
1343
1391
  const stateFilePath = path3.join(projectsPath, file);
1344
1392
  try {
1345
1393
  const data = fs3.readFileSync(stateFilePath, "utf-8");
1346
- const project = from_app_state_default(
1347
- data,
1348
- {},
1349
- {
1350
- ...this.config,
1351
- format: this.config?.formats.project
1352
- }
1353
- );
1394
+ const project = from_project_default(data, {
1395
+ ...this.config
1396
+ });
1354
1397
  this.projectPaths.set(project.id, stateFilePath);
1355
1398
  return project;
1356
1399
  } catch (e) {
@@ -1398,8 +1441,19 @@ import { randomUUID as randomUUID2 } from "node:crypto";
1398
1441
  import path4 from "node:path";
1399
1442
  import { readFileSync as readFileSync2 } from "node:fs";
1400
1443
  import { grammar } from "ohm-js";
1401
- import { isNil as isNil3, set } from "lodash-es";
1444
+ import { isNil as isNil4, set } from "lodash-es";
1402
1445
  var parser;
1446
+ var expectedNodeProps = [
1447
+ // TODO need to clarify adaptor/adaptors confusion
1448
+ "adaptor",
1449
+ "adaptors",
1450
+ "expression",
1451
+ "condition",
1452
+ "label",
1453
+ "type",
1454
+ "disabled",
1455
+ "name"
1456
+ ];
1403
1457
  var initOperations = (options = {}) => {
1404
1458
  let nodes = {};
1405
1459
  const uuidMap = options.uuidMap ?? {};
@@ -1473,7 +1527,12 @@ var initOperations = (options = {}) => {
1473
1527
  const name = nameNode.sourceString;
1474
1528
  const node = buildNode(name);
1475
1529
  props.buildWorkflow().forEach(([key, value]) => {
1476
- nodes[name][key] = value;
1530
+ if (expectedNodeProps.includes(key)) {
1531
+ nodes[name][key] = value;
1532
+ } else {
1533
+ nodes[name].openfn ??= {};
1534
+ nodes[name].openfn[key] = value;
1535
+ }
1477
1536
  });
1478
1537
  return node;
1479
1538
  },
@@ -1553,7 +1612,7 @@ function generateWorkflow(def, options = {}) {
1553
1612
  if (options.uuidMap && raw.id in options.uuidMap) {
1554
1613
  uuid = options.uuidMap[raw.id];
1555
1614
  }
1556
- if (!isNil3(uuid) && options.openfnUuid) {
1615
+ if (!isNil4(uuid) && options.openfnUuid) {
1557
1616
  raw.openfn ??= {};
1558
1617
  raw.openfn.uuid = uuid;
1559
1618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/project",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "Read, serialize, replicate and sync OpenFn projects",
5
5
  "type": "module",
6
6
  "exports": {
@@ -34,8 +34,8 @@
34
34
  "lodash-es": "^4.17.21",
35
35
  "ohm-js": "^17.2.1",
36
36
  "yaml": "^2.2.2",
37
- "@openfn/lexicon": "^1.2.6",
38
- "@openfn/logger": "1.0.6"
37
+ "@openfn/lexicon": "^1.2.7",
38
+ "@openfn/logger": "1.1.0"
39
39
  },
40
40
  "files": [
41
41
  "dist",