@openfn/project 0.9.1 → 0.9.3

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
@@ -1,18 +1,8 @@
1
1
  import * as l from '@openfn/lexicon';
2
2
  import l__default, { WorkspaceConfig, UUID } from '@openfn/lexicon';
3
+ import { Logger } from '@openfn/logger';
3
4
  import { Provisioner } from '@openfn/lexicon/lightning';
4
5
 
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
-
16
6
  type WithMeta<T> = T & {
17
7
  openfn?: l.NodeMeta;
18
8
  };
@@ -53,6 +43,17 @@ type FromFsConfig = {
53
43
  root: string;
54
44
  };
55
45
 
46
+ type SerializedProject = Omit<Partial<l.Project>, 'workflows'> & {
47
+ version: number;
48
+ workflows: SerializedWorkflow[];
49
+ };
50
+ type SerializedWorkflow = {
51
+ id: string;
52
+ name: string;
53
+ steps: WithMeta<l.Step[]>;
54
+ openfn?: l.ProjectMeta;
55
+ };
56
+
56
57
  type MergeProjectOptions = {
57
58
  workflowMappings: Record<string, string>;
58
59
  removeUnmapped: boolean;
@@ -65,7 +66,8 @@ declare class Workspace {
65
66
  private projects;
66
67
  private projectPaths;
67
68
  private isValid;
68
- constructor(workspacePath: string);
69
+ private logger;
70
+ constructor(workspacePath: string, logger?: Logger);
69
71
  loadProject(): void;
70
72
  list(): Project[];
71
73
  /** Get a project by its id or UUID */
@@ -109,7 +111,9 @@ declare class Project {
109
111
  static merge(source: Project, target: Project, options?: Partial<MergeProjectOptions>): Project;
110
112
  constructor(data: Partial<l__default.Project>, config?: Partial<l__default.WorkspaceConfig>);
111
113
  setConfig(config: Partial<WorkspaceConfig>): void;
112
- serialize(type?: 'project' | 'fs' | 'state', options?: any): string | Record<string, string> | Provisioner.Project_v1 | SerializedProject;
114
+ serialize(type: 'project', options?: any): SerializedProject | string;
115
+ serialize(type: 'state', options?: any): Provisioner.Project | string;
116
+ serialize(type: 'fs', options?: any): Record<string, string>;
113
117
  getWorkflow(idOrName: string): Workflow | undefined;
114
118
  getIdentifier(): string;
115
119
  getUUID(workflow: string | Workflow, stepId: string, otherStep?: string): any;
@@ -117,6 +121,7 @@ declare class Project {
117
121
  * Returns a map of ids:uuids for everything in the project
118
122
  */
119
123
  getUUIDMap(): UUIDMap;
124
+ canMergeInto(target: Project): boolean;
120
125
  }
121
126
 
122
127
  declare function yamlToJson(y: string): any;
package/dist/index.js CHANGED
@@ -105,7 +105,7 @@ var Workflow = class {
105
105
  };
106
106
  this.workflow = clone(workflow);
107
107
  this.workflow.history = workflow.history?.length ? workflow.history : [];
108
- const { id, name, openfn, steps, ...options } = workflow;
108
+ const { id, name, openfn, steps, history, ...options } = workflow;
109
109
  if (!(id || name)) {
110
110
  throw new Error("A Workflow MUST have a name or id");
111
111
  }
@@ -151,7 +151,7 @@ var Workflow = class {
151
151
  Object.assign(item, props);
152
152
  return this;
153
153
  }
154
- // Get properties on any step or edge by id
154
+ // Get properties on any step or edge by id or uuid
155
155
  get(id) {
156
156
  const item = this.index.edges[id] || this.index.steps[id];
157
157
  if (!item) {
@@ -555,13 +555,32 @@ var to_project_default = (project, options = {}) => {
555
555
  description: project.description,
556
556
  collections: project.collections,
557
557
  credentials: project.credentials,
558
- openfn: project.openfn,
558
+ openfn: omitBy2(project.openfn, isNil3),
559
559
  meta: project.meta,
560
560
  options: omitBy2(project.options, isNil3),
561
- //workflows: project.workflows.map(mapWorkflow) as SerializedWorkflow[],
562
- workflows: project.workflows.map(
563
- (w) => w.toJSON()
564
- )
561
+ workflows: project.workflows.map((w) => {
562
+ const obj = w.toJSON();
563
+ if (obj.openfn) {
564
+ obj.openfn = omitBy2(obj.openfn, isNil3);
565
+ }
566
+ if (obj.steps) {
567
+ obj.steps = obj.steps.sort((a, b) => {
568
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
569
+ });
570
+ obj.steps.forEach((s) => {
571
+ s.openfn = omitBy2(s.openfn, isNil3);
572
+ if (s.next && typeof s.next !== "string") {
573
+ for (const id in s.next) {
574
+ const edge = s.next[id];
575
+ if (edge.openfn) {
576
+ edge.openfn = omitBy2(edge.openfn, isNil3);
577
+ }
578
+ }
579
+ }
580
+ });
581
+ }
582
+ return obj;
583
+ })
565
584
  },
566
585
  isNil3
567
586
  );
@@ -636,10 +655,11 @@ var mapTriggerEdgeCondition = (edge) => {
636
655
  return e;
637
656
  };
638
657
  var mapWorkflow2 = (workflow) => {
639
- const { jobs, edges, triggers, name, ...remoteProps } = workflow;
658
+ const { jobs, edges, triggers, name, version_history, ...remoteProps } = workflow;
640
659
  const mapped = {
641
660
  name: workflow.name,
642
661
  steps: [],
662
+ history: workflow.version_history ?? [],
643
663
  openfn: renameKeys(remoteProps, { id: "uuid" })
644
664
  };
645
665
  if (workflow.name) {
@@ -777,12 +797,12 @@ var parseProject = async (options) => {
777
797
  try {
778
798
  const wf = fileType === "yaml" ? yamlToJson(candidate) : JSON.parse(candidate);
779
799
  if (wf.id && Array.isArray(wf.steps)) {
780
- const wfState = (state && state.getWorkflow(wf.id)) ?? {};
781
- wf.openfn = {
782
- uuid: wfState.openfn?.uuid ?? null
783
- // TODO do we need to transfer more stuff? Options maybe?
784
- };
800
+ const wfState = state?.getWorkflow(wf.id);
801
+ wf.openfn = Object.assign({}, wfState?.openfn, {
802
+ uuid: wfState?.openfn?.uuid ?? null
803
+ });
785
804
  for (const step of wf.steps) {
805
+ const stateStep = wfState?.get(step.id);
786
806
  if (step.expression && step.expression.endsWith(".js")) {
787
807
  const dir = path2.dirname(filePath);
788
808
  const exprPath = path2.join(dir, step.expression);
@@ -793,15 +813,14 @@ var parseProject = async (options) => {
793
813
  console.error(`Error loading expression from ${exprPath}`);
794
814
  }
795
815
  }
796
- const uuid = state?.getUUID(wf.id, step.id) ?? null;
797
- step.openfn = { uuid };
816
+ step.openfn = Object.assign({}, stateStep?.openfn);
798
817
  for (const target in step.next || {}) {
799
818
  if (typeof step.next[target] === "boolean") {
800
819
  const bool = step.next[target];
801
820
  step.next[target] = { condition: bool };
802
821
  }
803
- const uuid2 = state?.getUUID(wf.id, step.id, target) ?? null;
804
- step.next[target].openfn = { uuid: uuid2 };
822
+ const uuid = state?.getUUID(wf.id, step.id, target) ?? null;
823
+ step.next[target].openfn = { uuid };
805
824
  }
806
825
  }
807
826
  proj.workflows.push(wf);
@@ -1339,10 +1358,25 @@ var Project = class {
1339
1358
  }
1340
1359
  return result;
1341
1360
  }
1361
+ canMergeInto(target) {
1362
+ const potentialConflicts = {};
1363
+ for (const sourceWorkflow of this.workflows) {
1364
+ const targetId = sourceWorkflow.id;
1365
+ const targetWorkflow = target.getWorkflow(targetId);
1366
+ if (targetWorkflow && !sourceWorkflow.canMergeInto(targetWorkflow)) {
1367
+ potentialConflicts[sourceWorkflow.id] = targetWorkflow?.id;
1368
+ }
1369
+ }
1370
+ if (Object.keys(potentialConflicts).length) {
1371
+ return false;
1372
+ }
1373
+ return true;
1374
+ }
1342
1375
  };
1343
1376
  var Project_default = Project;
1344
1377
 
1345
1378
  // src/Workspace.ts
1379
+ import createLogger from "@openfn/logger";
1346
1380
  import path3 from "node:path";
1347
1381
  import fs3 from "node:fs";
1348
1382
 
@@ -1369,20 +1403,23 @@ var Workspace = class {
1369
1403
  projects = [];
1370
1404
  projectPaths = /* @__PURE__ */ new Map();
1371
1405
  isValid = false;
1372
- constructor(workspacePath) {
1373
- let context;
1406
+ logger;
1407
+ constructor(workspacePath, logger) {
1408
+ this.logger = logger ?? createLogger("Workspace", { level: "info" });
1409
+ let context = { workspace: void 0, project: void 0 };
1374
1410
  try {
1375
1411
  const { type, content } = findWorkspaceFile(workspacePath);
1376
1412
  context = loadWorkspaceFile(content, type);
1377
1413
  this.isValid = true;
1378
1414
  } catch (e) {
1379
- console.error(e);
1380
- return;
1415
+ this.logger.warn(
1416
+ `Could not find openfn.yaml at ${workspacePath}. Using default values.`
1417
+ );
1381
1418
  }
1382
1419
  this.config = buildConfig(context.workspace);
1383
1420
  this.activeProject = context.project;
1384
1421
  const projectsPath = path3.join(workspacePath, this.config.dirs.projects);
1385
- if (this.isValid && pathExists(projectsPath, "directory")) {
1422
+ if (pathExists(projectsPath, "directory")) {
1386
1423
  const ext = `.${this.config.formats.project}`;
1387
1424
  const stateFiles = fs3.readdirSync(projectsPath).filter(
1388
1425
  (fileName) => path3.extname(fileName) === ext && path3.parse(fileName).name !== "openfn"
@@ -1401,6 +1438,10 @@ var Workspace = class {
1401
1438
  console.warn(e);
1402
1439
  }
1403
1440
  }).filter((s) => s);
1441
+ } else {
1442
+ this.logger.warn(
1443
+ `No projects found: directory at ${projectsPath} does not exist`
1444
+ );
1404
1445
  }
1405
1446
  }
1406
1447
  // TODO
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/project",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
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.7",
38
- "@openfn/logger": "1.1.0"
37
+ "@openfn/lexicon": "^1.3.0",
38
+ "@openfn/logger": "1.1.1"
39
39
  },
40
40
  "files": [
41
41
  "dist",