@openfn/project 0.5.0 → 0.6.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/README.md CHANGED
@@ -51,10 +51,10 @@ a-b
51
51
  a-c
52
52
  ```
53
53
 
54
- You can set properties on the workflow itself - probably the name, with `@attributes`
54
+ You can set properties on the workflow itself - probably the id, with `@attributes`
55
55
 
56
56
  ```
57
- @name my-cool-workflow
57
+ @id my-cool-workflow
58
58
  ```
59
59
 
60
60
  You can also set properties on a node by putting comma seperated key-value pairs in brackets
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ declare class Workflow {
15
15
  uuid: {};
16
16
  id: {};
17
17
  };
18
- name: string;
18
+ name?: string;
19
19
  id: string;
20
20
  openfn: OpenfnMeta;
21
21
  constructor(workflow: l.Workflow);
@@ -31,6 +31,8 @@ declare class Workflow {
31
31
  }) | undefined;
32
32
  getUUID(id: any): string;
33
33
  toJSON(): JSON.Object;
34
+ getUUIDMap(): Record<string, string>;
35
+ getVersionHash(): string;
34
36
  }
35
37
 
36
38
  type FromFsConfig = {
@@ -47,6 +49,10 @@ type FileFormats = 'yaml' | 'json';
47
49
  interface OpenfnConfig {
48
50
  name: string;
49
51
  workflowRoot: string;
52
+ dirs: {
53
+ workflows: string;
54
+ projects: string;
55
+ };
50
56
  formats: {
51
57
  openfn: FileFormats;
52
58
  project: FileFormats;
@@ -82,16 +88,25 @@ declare class Project {
82
88
  collections: any;
83
89
  static from(type: 'state', data: any, options: Partial<l.ProjectConfig>): Project;
84
90
  static from(type: 'fs', options: FromFsConfig): Project;
85
- static from(type: 'path', data: any): Project;
91
+ static from(type: 'path', data: string, options?: {
92
+ config?: Partial<OpenfnConfig>;
93
+ }): Project;
86
94
  static diff(a: Project, b: Project): void;
87
95
  static merge(source: Project, target: Project, options: MergeProjectOptions): Project;
88
96
  constructor(data: l.Project, repoConfig?: RepoOptions);
89
97
  serialize(type?: 'json' | 'yaml' | 'fs' | 'state', options?: any): any;
90
98
  getVersionHash(): void;
91
- getWorkflow(id: string): Workflow | undefined;
99
+ getWorkflow(idOrName: string): Workflow | undefined;
92
100
  getIdentifier(): string;
93
101
  compare(proj: Project): void;
94
102
  getUUID(workflow: string | Workflow, stepId: string, otherStep?: string): any;
103
+ /**
104
+ * Returns a map of ids:uuids for everything in the project
105
+ */
106
+ getUUIDMap(options?: {
107
+ workflows: boolean;
108
+ project: false;
109
+ }): {};
95
110
  }
96
111
 
97
112
  declare class Workspace {
@@ -104,7 +119,7 @@ declare class Workspace {
104
119
  get(id: string): Project | undefined;
105
120
  getProjectPath(id: string): string | undefined;
106
121
  getActiveProject(): Project | undefined;
107
- getConfig(): OpenfnConfig | undefined;
122
+ getConfig(): Partial<OpenfnConfig>;
108
123
  get activeProjectId(): string | undefined;
109
124
  get valid(): boolean;
110
125
  }
@@ -115,14 +130,18 @@ declare function jsonToYaml(json: string | JSONObject): string;
115
130
  type GenerateWorkflowOptions = {
116
131
  name: string;
117
132
  uuidSeed: number;
118
- openfnUuid: boolean;
119
133
  printErrors: boolean;
134
+ uuidMap?: Record<string, string>;
135
+ openfnUuid: boolean;
136
+ };
137
+ type GenerateProjectOptions = GenerateWorkflowOptions & {
138
+ uuidMap: Array<Record<string, string>>;
120
139
  };
121
140
  /**
122
141
  * Generate a Workflow from a simple text based representation
123
142
  * eg, `a-b b-c a-c`
124
143
  */
125
144
  declare function generateWorkflow(def: string, options?: Partial<GenerateWorkflowOptions>): Workflow;
126
- declare function generateProject(name: string, workflowDefs: string[], options: Partial<GenerateWorkflowOptions>): Project;
145
+ declare function generateProject(name: string, workflowDefs: string[], options?: Partial<GenerateProjectOptions>): Project;
127
146
 
128
147
  export { Workspace, Project as default, generateProject, generateWorkflow, jsonToYaml, yamlToJson };
package/dist/index.js CHANGED
@@ -9,6 +9,74 @@ function slugify(text) {
9
9
  return text?.replace(/\W/g, " ").trim().replace(/\s+/g, "-").toLowerCase();
10
10
  }
11
11
 
12
+ // src/util/version.ts
13
+ import crypto from "node:crypto";
14
+ var SHORT_HASH_LENGTH = 12;
15
+ function isDefined(v) {
16
+ return v !== void 0 && v !== null;
17
+ }
18
+ var generateHash = (workflow, source = "cli") => {
19
+ const parts = [];
20
+ const wfKeys = ["name", "credentials"].sort();
21
+ const stepKeys = [
22
+ "name",
23
+ "adaptors",
24
+ "adaptor",
25
+ // there's ao adaptor & adaptors key in steps somehow.
26
+ "expression",
27
+ "configuration",
28
+ // assumes a string credential id
29
+ "expression"
30
+ // TODO need to model trigger types in this, which I think are currently ignored
31
+ ].sort();
32
+ const edgeKeys = [
33
+ "condition",
34
+ "label",
35
+ "disabled"
36
+ // This feels more like an option - should be excluded?
37
+ ].sort();
38
+ wfKeys.forEach((key) => {
39
+ if (isDefined(workflow[key])) {
40
+ parts.push(key, serializeValue(workflow[key]));
41
+ }
42
+ });
43
+ const steps = (workflow.steps || []).slice().sort((a, b) => {
44
+ const aName = a.name ?? "";
45
+ const bName = b.name ?? "";
46
+ return aName.localeCompare(bName);
47
+ });
48
+ for (const step of steps) {
49
+ stepKeys.forEach((key) => {
50
+ if (isDefined(step[key])) {
51
+ parts.push(key, serializeValue(step[key]));
52
+ }
53
+ });
54
+ if (step.next && Array.isArray(step.next)) {
55
+ const edges = step.next.slice().sort((a, b) => {
56
+ const aLabel = a.label || "";
57
+ const bLabel = b.label || "";
58
+ return aLabel.localeCompare(bLabel);
59
+ });
60
+ for (const edge of edges) {
61
+ edgeKeys.forEach((key) => {
62
+ if (isDefined(edge[key])) {
63
+ parts.push(key, serializeValue(edge[key]));
64
+ }
65
+ });
66
+ }
67
+ }
68
+ }
69
+ const str = parts.join("");
70
+ const hash = crypto.createHash("sha256").update(str).digest("hex");
71
+ return `${source}:${hash.substring(0, SHORT_HASH_LENGTH)}`;
72
+ };
73
+ function serializeValue(val) {
74
+ if (typeof val === "object") {
75
+ return JSON.stringify(val);
76
+ }
77
+ return String(val);
78
+ }
79
+
12
80
  // src/Workflow.ts
13
81
  var clone = (obj) => JSON.parse(JSON.stringify(obj));
14
82
  var Workflow = class {
@@ -31,8 +99,15 @@ var Workflow = class {
31
99
  };
32
100
  this.workflow = clone(workflow);
33
101
  const { id, name, openfn, steps, ...options } = workflow;
102
+ if (!(id || name)) {
103
+ throw new Error("A Workflow MUST have a name or id");
104
+ }
34
105
  this.id = id ?? slugify(name);
35
- this.name = name ?? id;
106
+ this.name = name;
107
+ this.workflow.id = this.id;
108
+ if (name) {
109
+ this.workflow.name = this.name;
110
+ }
36
111
  this.openfn = openfn;
37
112
  this.options = options;
38
113
  this.#buildIndex();
@@ -128,6 +203,12 @@ var Workflow = class {
128
203
  toJSON() {
129
204
  return this.workflow;
130
205
  }
206
+ getUUIDMap() {
207
+ return this.index.uuid;
208
+ }
209
+ getVersionHash() {
210
+ return generateHash(this);
211
+ }
131
212
  };
132
213
  var Workflow_default = Workflow;
133
214
 
@@ -157,7 +238,7 @@ function to_json_default(project) {
157
238
  }
158
239
 
159
240
  // src/util/rename-keys.ts
160
- function renameKeys(props, keyMap) {
241
+ function renameKeys(props = {}, keyMap) {
161
242
  return Object.fromEntries(
162
243
  Object.entries(props).map(([key, value]) => [
163
244
  keyMap[key] ? keyMap[key] : key,
@@ -183,7 +264,7 @@ function jsonToYaml(json) {
183
264
  // src/serialize/to-app-state.ts
184
265
  import { randomUUID } from "node:crypto";
185
266
  function to_app_state_default(project, options = {}) {
186
- const { uuid: id, endpoint, env, ...rest } = project.openfn;
267
+ const { uuid: id, endpoint, env, ...rest } = project.openfn ?? {};
187
268
  const state = {
188
269
  id,
189
270
  name: project.name,
@@ -204,9 +285,11 @@ var mapWorkflow = (workflow) => {
204
285
  if (workflow instanceof Workflow_default) {
205
286
  workflow = workflow.toJSON();
206
287
  }
288
+ const { uuid, ...originalOpenfnProps } = workflow.openfn ?? {};
207
289
  const wfState = {
290
+ ...originalOpenfnProps,
291
+ id: workflow.openfn?.uuid ?? randomUUID(),
208
292
  name: workflow.name,
209
- ...renameKeys(workflow.openfn, { uuid: "id" }),
210
293
  jobs: [],
211
294
  triggers: [],
212
295
  edges: []
@@ -349,10 +432,12 @@ function slugify2(text) {
349
432
  return text.replace(/\W/g, " ").trim().replace(/\s+/g, "-").toLowerCase();
350
433
  }
351
434
  var from_app_state_default = (state, config) => {
352
- if (config.format === "yaml") {
353
- state = yamlToJson(state);
354
- } else if (typeof state === "string") {
355
- state = JSON.parse(state);
435
+ if (typeof state === "string") {
436
+ if (config?.format === "yaml") {
437
+ state = yamlToJson(state);
438
+ } else {
439
+ state = JSON.parse(state);
440
+ }
356
441
  }
357
442
  const {
358
443
  id,
@@ -367,13 +452,11 @@ var from_app_state_default = (state, config) => {
367
452
  } = state;
368
453
  const proj = {
369
454
  name,
370
- // TODO do we need to slug this or anything?
371
455
  description,
372
456
  collections,
373
457
  credentials,
374
458
  options
375
459
  };
376
- const repoConfig = {};
377
460
  proj.openfn = {
378
461
  uuid: id,
379
462
  endpoint: config.endpoint,
@@ -385,7 +468,7 @@ var from_app_state_default = (state, config) => {
385
468
  fetched_at: config.fetchedAt
386
469
  };
387
470
  proj.workflows = state.workflows.map(mapWorkflow2);
388
- return new Project(proj, repoConfig);
471
+ return new Project(proj, config?.repo);
389
472
  };
390
473
  var mapTriggerEdgeCondition = (edge) => {
391
474
  const e = {
@@ -406,11 +489,13 @@ var mapTriggerEdgeCondition = (edge) => {
406
489
  var mapWorkflow2 = (workflow) => {
407
490
  const { jobs, edges, triggers, name, ...remoteProps } = workflow;
408
491
  const mapped = {
409
- id: slugify2(workflow.name),
410
492
  name: workflow.name,
411
493
  steps: [],
412
494
  openfn: renameKeys(remoteProps, { id: "uuid" })
413
495
  };
496
+ if (workflow.name) {
497
+ mapped.id = slugify2(workflow.name);
498
+ }
414
499
  workflow.triggers.forEach((trigger) => {
415
500
  const { type, ...otherProps } = trigger;
416
501
  const connectedEdges = edges.filter(
@@ -454,6 +539,30 @@ var mapWorkflow2 = (workflow) => {
454
539
  return mapped;
455
540
  };
456
541
 
542
+ // src/parse/from-path.ts
543
+ import { extname } from "node:path";
544
+ import { readFile } from "node:fs/promises";
545
+ var from_path_default = async (path4, options = {}) => {
546
+ const ext = extname(path4).toLowerCase();
547
+ const source = await readFile(path4, "utf8");
548
+ const config = {
549
+ format: null,
550
+ repo: options.repo ?? options.config
551
+ // TMP
552
+ };
553
+ let state;
554
+ if (ext === ".json") {
555
+ config.format = "json";
556
+ state = JSON.parse(source);
557
+ } else if (ext.match(/(ya?ml)$/)) {
558
+ config.format = "yaml";
559
+ state = yamlToJson(source);
560
+ } else {
561
+ throw new Error(`Cannot load a project from a ${ext} file`);
562
+ }
563
+ return from_app_state_default(state, config);
564
+ };
565
+
457
566
  // src/parse/from-fs.ts
458
567
  import fs from "node:fs/promises";
459
568
  import path from "node:path";
@@ -501,8 +610,12 @@ var parseProject = async (options = {}) => {
501
610
  env: config.project?.env
502
611
  });
503
612
  try {
504
- const format = config.formats.project ?? "yaml";
505
- const statePath = path.join(root, ".projects", `${identifier}.${format}`);
613
+ const format = config.formats?.project ?? config.formats?.projects ?? "yaml";
614
+ const statePath = path.join(
615
+ root,
616
+ config.dirs?.projects ?? ".projects",
617
+ `${identifier}.${format}`
618
+ );
506
619
  const stateFile = await fs.readFile(statePath, "utf8");
507
620
  state = from_app_state_default(stateFile, { format });
508
621
  } catch (e) {
@@ -510,7 +623,8 @@ var parseProject = async (options = {}) => {
510
623
  }
511
624
  const { project: openfn, ...repo } = config;
512
625
  proj.openfn = openfn;
513
- const workflowDir = config.workflowRoot ?? "workflows";
626
+ proj.config = repo;
627
+ const workflowDir = config.workflowRoot ?? config.dirs?.workflows ?? "workflows";
514
628
  const fileType = config.formats?.workflow ?? "yaml";
515
629
  const pattern = `${root}/${workflowDir}/*/*.${fileType}`;
516
630
  const candidateWfs = await glob(pattern, {
@@ -527,7 +641,6 @@ var parseProject = async (options = {}) => {
527
641
  uuid: wfState.openfn?.uuid ?? null
528
642
  // TODO do we need to transfer more stuff?
529
643
  };
530
- console.log("Loading workflow at ", filePath);
531
644
  for (const step of wf.steps) {
532
645
  if (step.expression && step.expression.endsWith(".js")) {
533
646
  const dir = path.dirname(filePath);
@@ -565,7 +678,9 @@ var parseProject = async (options = {}) => {
565
678
  var getUuidForStep = (project, workflow, stepId) => {
566
679
  const wf = typeof workflow === "string" ? project.getWorkflow(workflow) : workflow;
567
680
  if (!wf) {
568
- throw new Error(`Workflow "${workflow} not found in project ${project.id}`);
681
+ throw new Error(
682
+ `Workflow "${workflow}" not found in project ${project.id}`
683
+ );
569
684
  }
570
685
  for (const step of wf.steps) {
571
686
  if (step.id === stepId) {
@@ -950,6 +1065,7 @@ function merge(source, target, options) {
950
1065
  // src/Project.ts
951
1066
  var maybeCreateWorkflow = (wf) => wf instanceof Workflow_default ? wf : new Workflow_default(wf);
952
1067
  var setConfigDefaults = (config = {}) => ({
1068
+ ...config,
953
1069
  workflowRoot: config.workflowRoot ?? "workflows",
954
1070
  formats: {
955
1071
  // TODO change these maybe
@@ -976,7 +1092,7 @@ var Project = class {
976
1092
  meta;
977
1093
  // this contains meta about the connected openfn project
978
1094
  openfn;
979
- // repo configuration options
1095
+ // workspace-wide configuration options
980
1096
  // these should be shared across projects
981
1097
  // and saved to an openfn.yaml file
982
1098
  repo;
@@ -986,11 +1102,13 @@ var Project = class {
986
1102
  // collections for the project
987
1103
  // TODO to be well typed
988
1104
  collections;
989
- static from(type, data, options) {
1105
+ static from(type, data, options = {}) {
990
1106
  if (type === "state") {
991
1107
  return from_app_state_default(data, options);
992
1108
  } else if (type === "fs") {
993
1109
  return parseProject(data, options);
1110
+ } else if (type === "path") {
1111
+ return from_path_default(data, options);
994
1112
  }
995
1113
  throw new Error(`Didn't recognize type ${type}`);
996
1114
  }
@@ -1032,8 +1150,8 @@ var Project = class {
1032
1150
  // what else might we need?
1033
1151
  // get workflow by name or id
1034
1152
  // this is fuzzy, but is that wrong?
1035
- getWorkflow(id) {
1036
- return this.workflows.find((wf) => wf.id == id);
1153
+ getWorkflow(idOrName) {
1154
+ return this.workflows.find((wf) => wf.id == idOrName) || this.workflows.find((wf) => wf.name === idOrName);
1037
1155
  }
1038
1156
  // it's the name of the project.yaml file
1039
1157
  // qualified name? Remote name? App name?
@@ -1052,6 +1170,19 @@ var Project = class {
1052
1170
  }
1053
1171
  return getUuidForStep(this, workflow, stepId);
1054
1172
  }
1173
+ /**
1174
+ * Returns a map of ids:uuids for everything in the project
1175
+ */
1176
+ getUUIDMap(options = {}) {
1177
+ const result = {};
1178
+ for (const wf of this.workflows) {
1179
+ result[wf.id] = {
1180
+ self: wf.openfn?.uuid,
1181
+ children: wf.getUUIDMap()
1182
+ };
1183
+ }
1184
+ return result;
1185
+ }
1055
1186
  };
1056
1187
  var Project_default = Project;
1057
1188
 
@@ -1082,16 +1213,19 @@ var Workspace = class {
1082
1213
  projectPaths = /* @__PURE__ */ new Map();
1083
1214
  isValid = false;
1084
1215
  constructor(workspacePath) {
1085
- const projectsPath = path2.join(workspacePath, PROJECTS_DIRECTORY);
1086
1216
  const openfnYamlPath = path2.join(workspacePath, OPENFN_YAML_FILE);
1087
1217
  if (pathExists(openfnYamlPath, "file")) {
1088
1218
  this.isValid = true;
1089
1219
  const data = fs3.readFileSync(openfnYamlPath, "utf-8");
1090
1220
  this.config = yamlToJson(data);
1091
1221
  }
1222
+ const projectsPath = path2.join(
1223
+ workspacePath,
1224
+ this.config?.dirs?.projects ?? PROJECTS_DIRECTORY
1225
+ );
1092
1226
  if (this.isValid && pathExists(projectsPath, "directory")) {
1093
1227
  const stateFiles = fs3.readdirSync(projectsPath).filter(
1094
- (fileName) => PROJECT_EXTENSIONS.includes(path2.extname(fileName))
1228
+ (fileName) => PROJECT_EXTENSIONS.includes(path2.extname(fileName)) && path2.parse(fileName).name !== "openfn"
1095
1229
  );
1096
1230
  this.projects = stateFiles.map((file) => {
1097
1231
  const stateFilePath = path2.join(projectsPath, file);
@@ -1099,7 +1233,7 @@ var Workspace = class {
1099
1233
  const project = from_app_state_default(data, { format: "yaml" });
1100
1234
  this.projectPaths.set(project.name, stateFilePath);
1101
1235
  return project;
1102
- });
1236
+ }).filter((s) => s);
1103
1237
  }
1104
1238
  }
1105
1239
  list() {
@@ -1133,16 +1267,21 @@ import { grammar } from "ohm-js";
1133
1267
  var parser;
1134
1268
  var initOperations = (options = {}) => {
1135
1269
  let nodes = {};
1136
- const uuid = () => {
1270
+ const uuidMap = options.uuidMap ?? {};
1271
+ const uuid = (id) => {
1272
+ if (id in uuidMap) {
1273
+ return uuidMap[id];
1274
+ }
1137
1275
  return options.uuidSeed ? options.uuidSeed++ : randomUUID2();
1138
1276
  };
1139
1277
  const buildNode = (name) => {
1140
1278
  if (!nodes[name]) {
1279
+ const id = slugify(name);
1141
1280
  nodes[name] = {
1142
1281
  name,
1143
- id: slugify(name),
1282
+ id,
1144
1283
  openfn: {
1145
- uuid: uuid()
1284
+ uuid: uuid(id)
1146
1285
  }
1147
1286
  };
1148
1287
  }
@@ -1169,6 +1308,7 @@ var initOperations = (options = {}) => {
1169
1308
  const n1 = parent.buildWorkflow();
1170
1309
  const n2 = child.buildWorkflow();
1171
1310
  const e = edge.buildWorkflow();
1311
+ e.openfn.uuid = uuid(`${n1.id}-${n2.id}`);
1172
1312
  n1.next ??= {};
1173
1313
  n1.next[n2.name] = e;
1174
1314
  return [n1, n2];
@@ -1197,9 +1337,6 @@ var initOperations = (options = {}) => {
1197
1337
  return props.asIteration().children.map((c) => c.buildWorkflow());
1198
1338
  },
1199
1339
  prop(key, _op, value) {
1200
- if (value._iter) {
1201
- console.log(">>>> ITER");
1202
- }
1203
1340
  return [key.sourceString, value.buildWorkflow()];
1204
1341
  },
1205
1342
  // Bit flaky - we need this to handle quoted props
@@ -1214,16 +1351,15 @@ var initOperations = (options = {}) => {
1214
1351
  },
1215
1352
  edge(_) {
1216
1353
  return {
1217
- openfn: {
1218
- uuid: uuid()
1219
- }
1354
+ openfn: {}
1220
1355
  };
1221
1356
  }
1222
1357
  };
1223
1358
  return operations;
1224
1359
  };
1225
1360
  var createParser = () => {
1226
- const contents = readFileSync(path3.resolve("src/gen/workflow.ohm"), "utf-8");
1361
+ const grammarPath = path3.resolve(import.meta.dirname, "workflow.ohm");
1362
+ const contents = readFileSync(grammarPath, "utf-8");
1227
1363
  const parser2 = grammar(contents);
1228
1364
  return {
1229
1365
  parse(str, options) {
@@ -1247,19 +1383,31 @@ function generateWorkflow(def, options = {}) {
1247
1383
  if (!parser) {
1248
1384
  parser = createParser();
1249
1385
  }
1250
- const wf = new Workflow_default(parser.parse(def, options));
1386
+ const raw = parser.parse(def, options);
1387
+ if (!raw.name) {
1388
+ raw.name = "Workflow";
1389
+ }
1390
+ if (!raw.id) {
1391
+ raw.id = "workflow";
1392
+ }
1251
1393
  if (options.openfnUuid) {
1252
- wf.openfn = {
1253
- uuid: randomUUID2()
1254
- };
1394
+ raw.openfn ??= {};
1395
+ raw.openfn.uuid = randomUUID2();
1255
1396
  }
1397
+ const wf = new Workflow_default(raw);
1256
1398
  return wf;
1257
1399
  }
1258
- function generateProject(name, workflowDefs, options) {
1259
- const workflows = workflowDefs.map((w) => generateWorkflow(w, options));
1400
+ function generateProject(name, workflowDefs, options = {}) {
1401
+ const workflows = workflowDefs.map(
1402
+ (w, idx) => generateWorkflow(w, {
1403
+ ...options,
1404
+ uuidMap: options.uuidMap && options.uuidMap[idx]
1405
+ })
1406
+ );
1260
1407
  return new Project_default({
1261
1408
  name,
1262
- workflows
1409
+ workflows,
1410
+ openfn: options.openfnUuid && { uuid: randomUUID2() }
1263
1411
  });
1264
1412
  }
1265
1413
 
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Ohm grammar for interpreting our Workflow syntax
3
+ * https://ohmjs.org/docs/intro
4
+ */
5
+ Workflow {
6
+ Workflow = attribute* Pair*
7
+
8
+ attribute = "@" attr_name space attr_name
9
+
10
+ attr_name = (alnum | "_" | "-")+
11
+
12
+ Pair = node edge node
13
+
14
+ comment = "#" (~lineTerminator any)*
15
+
16
+ space := " " | "\t" | lineTerminator | comment
17
+
18
+ /* lower case is important: it disables whitespace */
19
+ node = nodeWithProps | node_name
20
+
21
+ nodeWithProps = node_name props
22
+
23
+ /* A node name can contain letters, numbers or underscores */
24
+ node_name = (alnum | "_")+
25
+
26
+ props = "(" listOf<prop, ","> ")"
27
+
28
+ prop = alnum+ "=" propValue
29
+
30
+ propValue = alnum+ | quotedProp
31
+
32
+ quotedProp = "\"" (~"\"" any)* "\""
33
+
34
+ edge = "-"
35
+
36
+ lineTerminator = "\n" | "\r"
37
+ }
38
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/project",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Read, serialize, replicate and sync OpenFn projects",
5
5
  "type": "module",
6
6
  "exports": {
@@ -32,7 +32,7 @@
32
32
  "lodash-es": "^4.17.21",
33
33
  "ohm-js": "^17.2.1",
34
34
  "yaml": "^2.2.2",
35
- "@openfn/lexicon": "^1.2.3",
35
+ "@openfn/lexicon": "^1.2.4",
36
36
  "@openfn/logger": "1.0.6"
37
37
  },
38
38
  "files": [
@@ -43,8 +43,7 @@
43
43
  "test": "pnpm ava",
44
44
  "test:watch": "pnpm ava -w",
45
45
  "test:types": "pnpm tsc --noEmit --project tsconfig.json",
46
- "build": "tsup --config ../../tsup.config.js src/index.ts",
47
- "build:watch": "pnpm build --watch",
46
+ "build": "tsup --config ../../tsup.config.js src/index.ts && cp src/gen/workflow.ohm dist/workflow.ohm",
48
47
  "pack": "pnpm pack --pack-destination ../../dist"
49
48
  }
50
49
  }