@openfn/project 0.4.0 → 0.5.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 +81 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +161 -72
- package/package.json +3 -1
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
## openfn/project
|
|
2
|
+
|
|
3
|
+
A package to track, parse and serialize OpenFn project definitions.
|
|
4
|
+
|
|
5
|
+
A PROJECT is defined as a set of connected workflows with a single billing account, like a project in the app.
|
|
6
|
+
|
|
7
|
+
A single Project can be Checked Out to disk at a time, meaning its source workflows and expressions will be expanded nicely onto the file system.
|
|
8
|
+
|
|
9
|
+
A Workspace is a set of related Projects , including a Project and its associated Sandboxes, or a Project deployed to apps in multiple web domains
|
|
10
|
+
|
|
11
|
+
### Serializing and Parsing
|
|
12
|
+
|
|
13
|
+
The main idea of Projects is that a Project represents a set of OpenFn workflows defined in any format and present a standard JS-friendly interface to manipulate and reason about them.
|
|
14
|
+
|
|
15
|
+
The from/to serializers are designed to support the following formats:
|
|
16
|
+
|
|
17
|
+
- Projects expanded to the file system (through CLI or hand-written)
|
|
18
|
+
- v1 JSON state files generated by `openfn pull`
|
|
19
|
+
- v2 Project files (basically v1 state with some extra props)
|
|
20
|
+
|
|
21
|
+
Serializers and parsers also support JSON and YAML formats interchangeably.
|
|
22
|
+
|
|
23
|
+
### Project & Workflow Generation
|
|
24
|
+
|
|
25
|
+
`project` exports utility functions to generate Projects and Workflows from a simple syntax. This is useful for testing.
|
|
26
|
+
|
|
27
|
+
Really it's a Workflow generator, but you can have it wrapped in a Project if you like.
|
|
28
|
+
|
|
29
|
+
Use it like this:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
import { generateProject, generateWorkflow } from '@openfn/project'
|
|
33
|
+
import type { Project, Workflow } from '@openfn/project'
|
|
34
|
+
|
|
35
|
+
const proj: Project = generateProject('my-project', ['a-b b-c'])
|
|
36
|
+
const wfL Workflow = generateWorkflow('a-b b-c')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Project generation uses a simple string language to represent a workflow structure:
|
|
40
|
+
|
|
41
|
+
Define nodes in pairs seperated by a dash (no whitespace)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
a-b # parent-child
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For multiple children, define multiple pairs:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
a-b
|
|
51
|
+
a-c
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You can set properties on the workflow itself - probably the name, with `@attributes`
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
@name my-cool-workflow
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can also set properties on a node by putting comma seperated key-value pairs in brackets
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
a(adaptor=http)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can use quotes to include spaces and brackets in a property value - great for expressions:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
a(expression="fn(s => s)")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can comment inside the string with `#`, which is a basic single-line comment
|
|
73
|
+
|
|
74
|
+
Reference:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
# Comments behind hashes
|
|
78
|
+
@attribute-name attribute-value
|
|
79
|
+
parent(propName=propValue,x=y)-child
|
|
80
|
+
a-b # can comment here to
|
|
81
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -97,10 +97,12 @@ declare class Project {
|
|
|
97
97
|
declare class Workspace {
|
|
98
98
|
private config?;
|
|
99
99
|
private projects;
|
|
100
|
+
private projectPaths;
|
|
100
101
|
private isValid;
|
|
101
102
|
constructor(workspacePath: string);
|
|
102
103
|
list(): Project[];
|
|
103
104
|
get(id: string): Project | undefined;
|
|
105
|
+
getProjectPath(id: string): string | undefined;
|
|
104
106
|
getActiveProject(): Project | undefined;
|
|
105
107
|
getConfig(): OpenfnConfig | undefined;
|
|
106
108
|
get activeProjectId(): string | undefined;
|
|
@@ -114,12 +116,13 @@ type GenerateWorkflowOptions = {
|
|
|
114
116
|
name: string;
|
|
115
117
|
uuidSeed: number;
|
|
116
118
|
openfnUuid: boolean;
|
|
119
|
+
printErrors: boolean;
|
|
117
120
|
};
|
|
118
121
|
/**
|
|
119
122
|
* Generate a Workflow from a simple text based representation
|
|
120
|
-
*
|
|
121
|
-
* eg, ['a-b', 'b-c']
|
|
123
|
+
* eg, `a-b b-c a-c`
|
|
122
124
|
*/
|
|
123
|
-
declare function generateWorkflow(def: string
|
|
125
|
+
declare function generateWorkflow(def: string, options?: Partial<GenerateWorkflowOptions>): Workflow;
|
|
126
|
+
declare function generateProject(name: string, workflowDefs: string[], options: Partial<GenerateWorkflowOptions>): Project;
|
|
124
127
|
|
|
125
|
-
export { Workspace, Project as default, generateWorkflow
|
|
128
|
+
export { Workspace, Project as default, generateProject, generateWorkflow, jsonToYaml, yamlToJson };
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,11 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/util/slugify.ts
|
|
8
|
+
function slugify(text) {
|
|
9
|
+
return text?.replace(/\W/g, " ").trim().replace(/\s+/g, "-").toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
// src/Workflow.ts
|
|
8
13
|
var clone = (obj) => JSON.parse(JSON.stringify(obj));
|
|
9
14
|
var Workflow = class {
|
|
@@ -26,8 +31,8 @@ var Workflow = class {
|
|
|
26
31
|
};
|
|
27
32
|
this.workflow = clone(workflow);
|
|
28
33
|
const { id, name, openfn, steps, ...options } = workflow;
|
|
29
|
-
this.id = id;
|
|
30
|
-
this.name = name;
|
|
34
|
+
this.id = id ?? slugify(name);
|
|
35
|
+
this.name = name ?? id;
|
|
31
36
|
this.openfn = openfn;
|
|
32
37
|
this.options = options;
|
|
33
38
|
this.#buildIndex();
|
|
@@ -262,16 +267,16 @@ import nodepath from "path";
|
|
|
262
267
|
var stringify = (json) => JSON.stringify(json, null, 2);
|
|
263
268
|
function to_fs_default(project) {
|
|
264
269
|
const files = {};
|
|
265
|
-
const { path:
|
|
266
|
-
files[
|
|
270
|
+
const { path: path4, content } = extractRepoConfig(project);
|
|
271
|
+
files[path4] = content;
|
|
267
272
|
for (const wf of project.workflows) {
|
|
268
|
-
const { path:
|
|
269
|
-
files[
|
|
273
|
+
const { path: path5, content: content2 } = extractWorkflow(project, wf.id);
|
|
274
|
+
files[path5] = content2;
|
|
270
275
|
for (const s of wf.steps) {
|
|
271
276
|
const result = extractStep(project, wf.id, s.id);
|
|
272
277
|
if (result) {
|
|
273
|
-
const { path:
|
|
274
|
-
files[
|
|
278
|
+
const { path: path6, content: content3 } = result;
|
|
279
|
+
files[path6] = content3;
|
|
275
280
|
}
|
|
276
281
|
}
|
|
277
282
|
}
|
|
@@ -284,7 +289,7 @@ var extractWorkflow = (project, workflowId2) => {
|
|
|
284
289
|
throw new Error(`workflow not found: ${workflowId2}`);
|
|
285
290
|
}
|
|
286
291
|
const root = project.repo?.workflowRoot ?? "workflows/";
|
|
287
|
-
const
|
|
292
|
+
const path4 = nodepath.join(root, workflow.id, workflow.id);
|
|
288
293
|
const wf = {
|
|
289
294
|
id: workflow.id,
|
|
290
295
|
name: workflow.name,
|
|
@@ -299,7 +304,7 @@ var extractWorkflow = (project, workflowId2) => {
|
|
|
299
304
|
return mapped;
|
|
300
305
|
})
|
|
301
306
|
};
|
|
302
|
-
return handleOutput(wf,
|
|
307
|
+
return handleOutput(wf, path4, format);
|
|
303
308
|
};
|
|
304
309
|
var extractStep = (project, workflowId2, stepId) => {
|
|
305
310
|
const workflow = project.getWorkflow(workflowId2);
|
|
@@ -312,9 +317,9 @@ var extractStep = (project, workflowId2, stepId) => {
|
|
|
312
317
|
}
|
|
313
318
|
if (step.expression) {
|
|
314
319
|
const root = project.config?.workflowRoot ?? "workflows/";
|
|
315
|
-
const
|
|
320
|
+
const path4 = nodepath.join(root, `${workflow.id}/${step.id}.js`);
|
|
316
321
|
const content = step.expression;
|
|
317
|
-
return { path:
|
|
322
|
+
return { path: path4, content };
|
|
318
323
|
}
|
|
319
324
|
};
|
|
320
325
|
var extractRepoConfig = (project) => {
|
|
@@ -327,7 +332,7 @@ var extractRepoConfig = (project) => {
|
|
|
327
332
|
return handleOutput(config, "openfn", format);
|
|
328
333
|
};
|
|
329
334
|
var handleOutput = (data, filePath, format) => {
|
|
330
|
-
const
|
|
335
|
+
const path4 = `${filePath}.${format}`;
|
|
331
336
|
let content;
|
|
332
337
|
if (format === "json") {
|
|
333
338
|
content = stringify(data, null, 2);
|
|
@@ -336,11 +341,11 @@ var handleOutput = (data, filePath, format) => {
|
|
|
336
341
|
} else {
|
|
337
342
|
throw new Error(`Unrecognised format: ${format}`);
|
|
338
343
|
}
|
|
339
|
-
return { path:
|
|
344
|
+
return { path: path4, content };
|
|
340
345
|
};
|
|
341
346
|
|
|
342
347
|
// src/parse/from-app-state.ts
|
|
343
|
-
function
|
|
348
|
+
function slugify2(text) {
|
|
344
349
|
return text.replace(/\W/g, " ").trim().replace(/\s+/g, "-").toLowerCase();
|
|
345
350
|
}
|
|
346
351
|
var from_app_state_default = (state, config) => {
|
|
@@ -401,7 +406,7 @@ var mapTriggerEdgeCondition = (edge) => {
|
|
|
401
406
|
var mapWorkflow2 = (workflow) => {
|
|
402
407
|
const { jobs, edges, triggers, name, ...remoteProps } = workflow;
|
|
403
408
|
const mapped = {
|
|
404
|
-
id:
|
|
409
|
+
id: slugify2(workflow.name),
|
|
405
410
|
name: workflow.name,
|
|
406
411
|
steps: [],
|
|
407
412
|
openfn: renameKeys(remoteProps, { id: "uuid" })
|
|
@@ -420,7 +425,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
420
425
|
if (!target) {
|
|
421
426
|
throw new Error(`Failed to find ${edge.target_job_id}`);
|
|
422
427
|
}
|
|
423
|
-
obj[
|
|
428
|
+
obj[slugify2(target.name)] = mapTriggerEdgeCondition(edge);
|
|
424
429
|
return obj;
|
|
425
430
|
}, {})
|
|
426
431
|
});
|
|
@@ -431,7 +436,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
431
436
|
);
|
|
432
437
|
const { body: expression, name: name2, adaptor, ...remoteProps2 } = step;
|
|
433
438
|
const s = {
|
|
434
|
-
id:
|
|
439
|
+
id: slugify2(name2),
|
|
435
440
|
name: name2,
|
|
436
441
|
expression,
|
|
437
442
|
adaptor,
|
|
@@ -440,7 +445,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
440
445
|
if (outboundEdges.length) {
|
|
441
446
|
s.next = outboundEdges.reduce((next, edge) => {
|
|
442
447
|
const target = jobs.find((j) => j.id === edge.target_job_id);
|
|
443
|
-
next[
|
|
448
|
+
next[slugify2(target.name)] = mapTriggerEdgeCondition(edge);
|
|
444
449
|
return next;
|
|
445
450
|
}, {});
|
|
446
451
|
}
|
|
@@ -1048,6 +1053,7 @@ var Project = class {
|
|
|
1048
1053
|
return getUuidForStep(this, workflow, stepId);
|
|
1049
1054
|
}
|
|
1050
1055
|
};
|
|
1056
|
+
var Project_default = Project;
|
|
1051
1057
|
|
|
1052
1058
|
// src/util/path-exists.ts
|
|
1053
1059
|
import fs2 from "fs";
|
|
@@ -1073,6 +1079,7 @@ var PROJECT_EXTENSIONS = [".yaml", ".yml"];
|
|
|
1073
1079
|
var Workspace = class {
|
|
1074
1080
|
config;
|
|
1075
1081
|
projects = [];
|
|
1082
|
+
projectPaths = /* @__PURE__ */ new Map();
|
|
1076
1083
|
isValid = false;
|
|
1077
1084
|
constructor(workspacePath) {
|
|
1078
1085
|
const projectsPath = path2.join(workspacePath, PROJECTS_DIRECTORY);
|
|
@@ -1087,8 +1094,11 @@ var Workspace = class {
|
|
|
1087
1094
|
(fileName) => PROJECT_EXTENSIONS.includes(path2.extname(fileName))
|
|
1088
1095
|
);
|
|
1089
1096
|
this.projects = stateFiles.map((file) => {
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1097
|
+
const stateFilePath = path2.join(projectsPath, file);
|
|
1098
|
+
const data = fs3.readFileSync(stateFilePath, "utf-8");
|
|
1099
|
+
const project = from_app_state_default(data, { format: "yaml" });
|
|
1100
|
+
this.projectPaths.set(project.name, stateFilePath);
|
|
1101
|
+
return project;
|
|
1092
1102
|
});
|
|
1093
1103
|
}
|
|
1094
1104
|
}
|
|
@@ -1098,6 +1108,9 @@ var Workspace = class {
|
|
|
1098
1108
|
get(id) {
|
|
1099
1109
|
return this.projects.find((p) => p.name === id);
|
|
1100
1110
|
}
|
|
1111
|
+
getProjectPath(id) {
|
|
1112
|
+
return this.projectPaths.get(id);
|
|
1113
|
+
}
|
|
1101
1114
|
getActiveProject() {
|
|
1102
1115
|
return this.projects.find((p) => p.name === this.config?.name);
|
|
1103
1116
|
}
|
|
@@ -1112,67 +1125,142 @@ var Workspace = class {
|
|
|
1112
1125
|
}
|
|
1113
1126
|
};
|
|
1114
1127
|
|
|
1115
|
-
// src/gen/
|
|
1128
|
+
// src/gen/generator.ts
|
|
1116
1129
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
nodes[from].next[to] = edge(`${from}-${to}`);
|
|
1130
|
+
import path3 from "node:path";
|
|
1131
|
+
import { readFileSync } from "node:fs";
|
|
1132
|
+
import { grammar } from "ohm-js";
|
|
1133
|
+
var parser;
|
|
1134
|
+
var initOperations = (options = {}) => {
|
|
1135
|
+
let nodes = {};
|
|
1136
|
+
const uuid = () => {
|
|
1137
|
+
return options.uuidSeed ? options.uuidSeed++ : randomUUID2();
|
|
1138
|
+
};
|
|
1139
|
+
const buildNode = (name) => {
|
|
1140
|
+
if (!nodes[name]) {
|
|
1141
|
+
nodes[name] = {
|
|
1142
|
+
name,
|
|
1143
|
+
id: slugify(name),
|
|
1144
|
+
openfn: {
|
|
1145
|
+
uuid: uuid()
|
|
1134
1146
|
}
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
return nodes[name];
|
|
1150
|
+
};
|
|
1151
|
+
const operations = {
|
|
1152
|
+
Workflow(attrs, pair) {
|
|
1153
|
+
pair.children.forEach((child) => child.buildWorkflow());
|
|
1154
|
+
const steps = Object.values(nodes);
|
|
1155
|
+
const attributes = attrs.children.map((c) => c.buildWorkflow()).reduce((obj, next) => {
|
|
1156
|
+
const [key, value] = next;
|
|
1157
|
+
obj[key] = value;
|
|
1158
|
+
return obj;
|
|
1159
|
+
}, {});
|
|
1160
|
+
return { ...attributes, steps };
|
|
1161
|
+
},
|
|
1162
|
+
comment(_a, _b) {
|
|
1163
|
+
return null;
|
|
1164
|
+
},
|
|
1165
|
+
attribute(_, name, _space, value) {
|
|
1166
|
+
return [name.sourceString, value.sourceString];
|
|
1167
|
+
},
|
|
1168
|
+
Pair(parent, edge, child) {
|
|
1169
|
+
const n1 = parent.buildWorkflow();
|
|
1170
|
+
const n2 = child.buildWorkflow();
|
|
1171
|
+
const e = edge.buildWorkflow();
|
|
1172
|
+
n1.next ??= {};
|
|
1173
|
+
n1.next[n2.name] = e;
|
|
1174
|
+
return [n1, n2];
|
|
1175
|
+
},
|
|
1176
|
+
// node could just be a node name, or a node with props
|
|
1177
|
+
// different results have different requirements
|
|
1178
|
+
// Not sure the best way to handle this, but this seems to work
|
|
1179
|
+
node(node) {
|
|
1180
|
+
if (node._node.ruleName === "node_name") {
|
|
1181
|
+
return buildNode(node.sourceString);
|
|
1135
1182
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1183
|
+
return node.buildWorkflow();
|
|
1184
|
+
},
|
|
1185
|
+
nodeWithProps(nameNode, props) {
|
|
1186
|
+
const name = nameNode.sourceString;
|
|
1187
|
+
const node = buildNode(name);
|
|
1188
|
+
props.buildWorkflow().forEach(([key, value]) => {
|
|
1189
|
+
nodes[name][key] = value;
|
|
1190
|
+
});
|
|
1191
|
+
return node;
|
|
1192
|
+
},
|
|
1193
|
+
node_name(n) {
|
|
1194
|
+
return n.sourceString;
|
|
1195
|
+
},
|
|
1196
|
+
props(_lbr, props, _rbr) {
|
|
1197
|
+
return props.asIteration().children.map((c) => c.buildWorkflow());
|
|
1198
|
+
},
|
|
1199
|
+
prop(key, _op, value) {
|
|
1200
|
+
if (value._iter) {
|
|
1201
|
+
console.log(">>>> ITER");
|
|
1140
1202
|
}
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1203
|
+
return [key.sourceString, value.buildWorkflow()];
|
|
1204
|
+
},
|
|
1205
|
+
// Bit flaky - we need this to handle quoted props
|
|
1206
|
+
_iter(...items) {
|
|
1207
|
+
return items.map((i) => i.buildWorkflow()).join("");
|
|
1208
|
+
},
|
|
1209
|
+
alnum(a) {
|
|
1210
|
+
return a.sourceString;
|
|
1211
|
+
},
|
|
1212
|
+
quotedProp(_left, value, _right) {
|
|
1213
|
+
return value.sourceString;
|
|
1214
|
+
},
|
|
1215
|
+
edge(_) {
|
|
1216
|
+
return {
|
|
1217
|
+
openfn: {
|
|
1218
|
+
uuid: uuid()
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1145
1221
|
}
|
|
1146
|
-
}
|
|
1222
|
+
};
|
|
1223
|
+
return operations;
|
|
1224
|
+
};
|
|
1225
|
+
var createParser = () => {
|
|
1226
|
+
const contents = readFileSync(path3.resolve("src/gen/workflow.ohm"), "utf-8");
|
|
1227
|
+
const parser2 = grammar(contents);
|
|
1147
1228
|
return {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1229
|
+
parse(str, options) {
|
|
1230
|
+
const { printErrors = true } = options;
|
|
1231
|
+
const semantics = parser2.createSemantics();
|
|
1232
|
+
semantics.addOperation("buildWorkflow", initOperations(options));
|
|
1233
|
+
const result = parser2.match(str);
|
|
1234
|
+
if (!result.succeeded()) {
|
|
1235
|
+
if (printErrors) {
|
|
1236
|
+
console.error(result.shortMessage);
|
|
1237
|
+
console.error(result.message);
|
|
1238
|
+
}
|
|
1239
|
+
throw new Error("Parsing failed!" + result.shortMessage);
|
|
1240
|
+
}
|
|
1241
|
+
const adaptor = semantics(result);
|
|
1242
|
+
return adaptor.buildWorkflow();
|
|
1243
|
+
}
|
|
1152
1244
|
};
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
openfn: { uuid: uuid(id) }
|
|
1158
|
-
};
|
|
1245
|
+
};
|
|
1246
|
+
function generateWorkflow(def, options = {}) {
|
|
1247
|
+
if (!parser) {
|
|
1248
|
+
parser = createParser();
|
|
1159
1249
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1250
|
+
const wf = new Workflow_default(parser.parse(def, options));
|
|
1251
|
+
if (options.openfnUuid) {
|
|
1252
|
+
wf.openfn = {
|
|
1253
|
+
uuid: randomUUID2()
|
|
1164
1254
|
};
|
|
1165
1255
|
}
|
|
1166
|
-
|
|
1167
|
-
const muuid = !isNaN(uuidSeed) ? ++uuidSeed : randomUUID2();
|
|
1168
|
-
ids.set(id, muuid);
|
|
1169
|
-
return muuid;
|
|
1170
|
-
}
|
|
1256
|
+
return wf;
|
|
1171
1257
|
}
|
|
1172
|
-
function
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1258
|
+
function generateProject(name, workflowDefs, options) {
|
|
1259
|
+
const workflows = workflowDefs.map((w) => generateWorkflow(w, options));
|
|
1260
|
+
return new Project_default({
|
|
1261
|
+
name,
|
|
1262
|
+
workflows
|
|
1263
|
+
});
|
|
1176
1264
|
}
|
|
1177
1265
|
|
|
1178
1266
|
// src/index.ts
|
|
@@ -1180,7 +1268,8 @@ var src_default = Project;
|
|
|
1180
1268
|
export {
|
|
1181
1269
|
Workspace,
|
|
1182
1270
|
src_default as default,
|
|
1183
|
-
|
|
1271
|
+
generateProject,
|
|
1272
|
+
generateWorkflow,
|
|
1184
1273
|
jsonToYaml,
|
|
1185
1274
|
yamlToJson
|
|
1186
1275
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/project",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Read, serialize, replicate and sync OpenFn projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"glob": "^11.0.2",
|
|
31
|
+
"lodash": "^4.17.21",
|
|
31
32
|
"lodash-es": "^4.17.21",
|
|
33
|
+
"ohm-js": "^17.2.1",
|
|
32
34
|
"yaml": "^2.2.2",
|
|
33
35
|
"@openfn/lexicon": "^1.2.3",
|
|
34
36
|
"@openfn/logger": "1.0.6"
|