@openfn/project 0.12.1 → 0.13.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 +2 -0
- package/dist/index.d.ts +40 -5
- package/dist/index.js +347 -231
- package/dist/workflow.ohm +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,19 @@ type WorkflowDiff = {
|
|
|
29
29
|
* // Shows how staging has diverged from main
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
declare function diff(a: Project, b: Project): WorkflowDiff[];
|
|
32
|
+
declare function diff(a: Project, b: Project, workflows?: string[]): WorkflowDiff[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* Compare two version hashes
|
|
37
|
+
* Ignores the source specifier (if present)
|
|
38
|
+
*/
|
|
39
|
+
declare const match: (a: string, b: string) => boolean;
|
|
40
|
+
type HashOptions = {
|
|
41
|
+
source?: string;
|
|
42
|
+
sha?: boolean;
|
|
43
|
+
};
|
|
44
|
+
declare const generateHash: (workflow: Workflow, { source, sha }?: HashOptions) => string;
|
|
33
45
|
|
|
34
46
|
type WithMeta<T> = T & {
|
|
35
47
|
openfn?: l.NodeMeta;
|
|
@@ -45,6 +57,7 @@ declare class Workflow {
|
|
|
45
57
|
get steps(): WithMeta<l.Job & l.Trigger>[];
|
|
46
58
|
get start(): string | undefined;
|
|
47
59
|
set start(s: string);
|
|
60
|
+
get history(): string[];
|
|
48
61
|
_buildIndex(): void;
|
|
49
62
|
set(id: string, props: Partial<l.Job | l.StepEdge>): this;
|
|
50
63
|
get(id: string): WithMeta<l.Step | l.Trigger | l.StepEdge>;
|
|
@@ -56,9 +69,8 @@ declare class Workflow {
|
|
|
56
69
|
getUUID(id: string): string;
|
|
57
70
|
toJSON(): Object;
|
|
58
71
|
getUUIDMap(): Record<string, string>;
|
|
59
|
-
getVersionHash(): string;
|
|
72
|
+
getVersionHash(options?: HashOptions): string;
|
|
60
73
|
pushHistory(versionHash: string): void;
|
|
61
|
-
get history(): string[];
|
|
62
74
|
canMergeInto(target: Workflow): boolean;
|
|
63
75
|
}
|
|
64
76
|
|
|
@@ -76,6 +88,7 @@ type FromFsConfig = {
|
|
|
76
88
|
root: string;
|
|
77
89
|
config?: Partial<l.WorkspaceConfig>;
|
|
78
90
|
logger?: Logger;
|
|
91
|
+
alias?: string;
|
|
79
92
|
};
|
|
80
93
|
|
|
81
94
|
type SerializedProject = Omit<Partial<l.Project>, 'workflows'> & {
|
|
@@ -95,7 +108,16 @@ type MergeProjectOptions = {
|
|
|
95
108
|
workflowMappings: Record<string, string>;
|
|
96
109
|
removeUnmapped: boolean;
|
|
97
110
|
force: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* If mode is sandbox, basically only content will be merged and all metadata/settings/options/config is ignored
|
|
113
|
+
* If mode is replace, all properties on the source will override the target (including UUIDs, name)
|
|
114
|
+
*/
|
|
98
115
|
mode: typeof SANDBOX_MERGE | typeof REPLACE_MERGE;
|
|
116
|
+
/**
|
|
117
|
+
* If true, only workflows that have changed in the source
|
|
118
|
+
* will be merged.
|
|
119
|
+
*/
|
|
120
|
+
onlyUpdated: boolean;
|
|
99
121
|
};
|
|
100
122
|
|
|
101
123
|
declare class Workspace {
|
|
@@ -133,6 +155,7 @@ type UUIDMap = {
|
|
|
133
155
|
type CLIMeta = {
|
|
134
156
|
version?: number;
|
|
135
157
|
alias?: string;
|
|
158
|
+
forked_from?: Record<string, string>;
|
|
136
159
|
};
|
|
137
160
|
declare class Project {
|
|
138
161
|
/** Human readable project name. This corresponds to the label in Lightning */
|
|
@@ -163,6 +186,7 @@ declare class Project {
|
|
|
163
186
|
constructor(data?: Partial<l__default.Project>, meta?: Partial<l__default.WorkspaceConfig> & CLIMeta);
|
|
164
187
|
/** Local alias for the project. Comes from the file name. Not shared with Lightning. */
|
|
165
188
|
get alias(): string;
|
|
189
|
+
set alias(value: string);
|
|
166
190
|
get uuid(): string | undefined;
|
|
167
191
|
get host(): string | undefined;
|
|
168
192
|
setConfig(config: Partial<WorkspaceConfig>): void;
|
|
@@ -177,8 +201,17 @@ declare class Project {
|
|
|
177
201
|
* Returns a map of ids:uuids for everything in the project
|
|
178
202
|
*/
|
|
179
203
|
getUUIDMap(): UUIDMap;
|
|
180
|
-
diff(project: Project): WorkflowDiff[];
|
|
204
|
+
diff(project: Project, workflows?: string[]): WorkflowDiff[];
|
|
181
205
|
canMergeInto(target: Project): boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Generates the contents of the openfn.yaml file,
|
|
208
|
+
* plus its file path
|
|
209
|
+
*/
|
|
210
|
+
generateConfig(): {
|
|
211
|
+
path: string;
|
|
212
|
+
content: string;
|
|
213
|
+
};
|
|
214
|
+
clone(): Project;
|
|
182
215
|
}
|
|
183
216
|
|
|
184
217
|
declare function yamlToJson(y: string): any;
|
|
@@ -190,6 +223,8 @@ type GenerateWorkflowOptions = {
|
|
|
190
223
|
printErrors: boolean;
|
|
191
224
|
uuidMap?: Record<string, string>;
|
|
192
225
|
openfnUuid: boolean;
|
|
226
|
+
/** If true, will set up a version hash in the history array */
|
|
227
|
+
history: boolean;
|
|
193
228
|
};
|
|
194
229
|
type GenerateProjectOptions = GenerateWorkflowOptions & {
|
|
195
230
|
uuidMap: Array<Record<string, string>>;
|
|
@@ -202,4 +237,4 @@ type GenerateProjectOptions = GenerateWorkflowOptions & {
|
|
|
202
237
|
declare function generateWorkflow(def: string, options?: Partial<GenerateWorkflowOptions>): Workflow;
|
|
203
238
|
declare function generateProject(name: string, workflowDefs: string[], options?: Partial<GenerateProjectOptions>): Project;
|
|
204
239
|
|
|
205
|
-
export { DiffType, WorkflowDiff, Workspace, Project as default, diff, generateProject, generateWorkflow, jsonToYaml, yamlToJson };
|
|
240
|
+
export { DiffType, WorkflowDiff, Workspace, Project as default, diff, generateProject, generateHash as generateVersionHash, generateWorkflow, jsonToYaml, match as versionsEqual, yamlToJson };
|
package/dist/index.js
CHANGED
|
@@ -14,65 +14,261 @@ function slugify(text) {
|
|
|
14
14
|
|
|
15
15
|
// src/util/version.ts
|
|
16
16
|
import crypto from "node:crypto";
|
|
17
|
+
import { get } from "lodash-es";
|
|
18
|
+
|
|
19
|
+
// src/serialize/to-app-state.ts
|
|
20
|
+
import { pick, omitBy, isNil, sortBy } from "lodash-es";
|
|
21
|
+
import { randomUUID } from "node:crypto";
|
|
22
|
+
|
|
23
|
+
// src/util/rename-keys.ts
|
|
24
|
+
function renameKeys(props = {}, keyMap) {
|
|
25
|
+
return Object.fromEntries(
|
|
26
|
+
Object.entries(props).map(([key, value]) => [
|
|
27
|
+
keyMap[key] ? keyMap[key] : key,
|
|
28
|
+
value
|
|
29
|
+
])
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/util/yaml.ts
|
|
34
|
+
import yaml from "yaml";
|
|
35
|
+
function yamlToJson(y) {
|
|
36
|
+
const doc = yaml.parseDocument(y);
|
|
37
|
+
return doc.toJS();
|
|
38
|
+
}
|
|
39
|
+
function jsonToYaml(json) {
|
|
40
|
+
if (typeof json === "string") {
|
|
41
|
+
json = JSON.parse(json);
|
|
42
|
+
}
|
|
43
|
+
const doc = new yaml.Document(json);
|
|
44
|
+
return yaml.stringify(doc, null, 2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/serialize/to-app-state.ts
|
|
48
|
+
var defaultJobProps = {
|
|
49
|
+
// TODO why does the provisioner throw if these keys are not set?
|
|
50
|
+
// Ok, 90% of jobs will have a credenial, but it's still optional right?
|
|
51
|
+
keychain_credential_id: null,
|
|
52
|
+
project_credential_id: null
|
|
53
|
+
};
|
|
54
|
+
function to_app_state_default(project, options = {}) {
|
|
55
|
+
const {
|
|
56
|
+
uuid,
|
|
57
|
+
endpoint,
|
|
58
|
+
env,
|
|
59
|
+
id,
|
|
60
|
+
fetched_at,
|
|
61
|
+
...rest
|
|
62
|
+
} = project.openfn ?? {};
|
|
63
|
+
const state = omitBy(
|
|
64
|
+
pick(project, ["name", "description", "collections"]),
|
|
65
|
+
isNil
|
|
66
|
+
);
|
|
67
|
+
state.id = uuid;
|
|
68
|
+
Object.assign(state, rest, project.options);
|
|
69
|
+
state.project_credentials = project.credentials ?? [];
|
|
70
|
+
state.workflows = project.workflows.map(mapWorkflow).reduce((obj, wf) => {
|
|
71
|
+
obj[slugify(wf.name ?? wf.id)] = wf;
|
|
72
|
+
return obj;
|
|
73
|
+
}, {});
|
|
74
|
+
const shouldReturnYaml = options.format === "yaml" || !options.format && project.config.formats.project === "yaml";
|
|
75
|
+
if (shouldReturnYaml) {
|
|
76
|
+
return jsonToYaml(state);
|
|
77
|
+
}
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
var mapWorkflow = (workflow) => {
|
|
81
|
+
if (workflow instanceof Workflow_default) {
|
|
82
|
+
workflow = workflow.toJSON();
|
|
83
|
+
}
|
|
84
|
+
const { uuid, ...originalOpenfnProps } = workflow.openfn ?? {};
|
|
85
|
+
const wfState = {
|
|
86
|
+
...originalOpenfnProps,
|
|
87
|
+
id: workflow.openfn?.uuid ?? randomUUID(),
|
|
88
|
+
jobs: {},
|
|
89
|
+
triggers: {},
|
|
90
|
+
edges: {},
|
|
91
|
+
lock_version: workflow.openfn?.lock_version ?? null
|
|
92
|
+
// TODO needs testing
|
|
93
|
+
};
|
|
94
|
+
if (workflow.name) {
|
|
95
|
+
wfState.name = workflow.name;
|
|
96
|
+
}
|
|
97
|
+
const lookup = workflow.steps.reduce((obj, next) => {
|
|
98
|
+
if (!next.openfn?.uuid) {
|
|
99
|
+
next.openfn ??= {};
|
|
100
|
+
next.openfn.uuid = randomUUID();
|
|
101
|
+
}
|
|
102
|
+
obj[next.id] = next.openfn.uuid;
|
|
103
|
+
return obj;
|
|
104
|
+
}, {});
|
|
105
|
+
sortBy(workflow.steps, "name").forEach((s) => {
|
|
106
|
+
let isTrigger = false;
|
|
107
|
+
let node;
|
|
108
|
+
if (s.type) {
|
|
109
|
+
isTrigger = true;
|
|
110
|
+
node = {
|
|
111
|
+
type: s.type ?? "webhook",
|
|
112
|
+
// this is mostly for tests
|
|
113
|
+
enabled: s.enabled,
|
|
114
|
+
...renameKeys(s.openfn, { uuid: "id" })
|
|
115
|
+
};
|
|
116
|
+
wfState.triggers[node.type] = node;
|
|
117
|
+
} else {
|
|
118
|
+
node = omitBy(pick(s, ["name", "adaptor"]), isNil);
|
|
119
|
+
const { uuid: uuid2, ...otherOpenFnProps } = s.openfn ?? {};
|
|
120
|
+
node.id = uuid2;
|
|
121
|
+
if (s.expression) {
|
|
122
|
+
node.body = s.expression;
|
|
123
|
+
}
|
|
124
|
+
if (typeof s.configuration === "string" && !s.configuration.endsWith(".json")) {
|
|
125
|
+
otherOpenFnProps.project_credential_id = s.configuration;
|
|
126
|
+
}
|
|
127
|
+
Object.assign(node, defaultJobProps, otherOpenFnProps);
|
|
128
|
+
wfState.jobs[s.id ?? slugify(s.name)] = node;
|
|
129
|
+
}
|
|
130
|
+
Object.keys(s.next ?? {}).forEach((next) => {
|
|
131
|
+
const rules = s.next[next];
|
|
132
|
+
const { uuid: uuid2, ...otherOpenFnProps } = rules.openfn ?? {};
|
|
133
|
+
const e = {
|
|
134
|
+
id: uuid2 ?? randomUUID(),
|
|
135
|
+
target_job_id: lookup[next],
|
|
136
|
+
enabled: !rules.disabled,
|
|
137
|
+
source_trigger_id: null
|
|
138
|
+
// lightning complains if this isn't set, even if its falsy :(
|
|
139
|
+
};
|
|
140
|
+
Object.assign(e, otherOpenFnProps);
|
|
141
|
+
if (isTrigger) {
|
|
142
|
+
e.source_trigger_id = node.id;
|
|
143
|
+
} else {
|
|
144
|
+
e.source_job_id = node.id;
|
|
145
|
+
}
|
|
146
|
+
if (rules.label) {
|
|
147
|
+
e.condition_label = rules.label;
|
|
148
|
+
}
|
|
149
|
+
if (rules.condition) {
|
|
150
|
+
if (typeof rules.condition === "boolean") {
|
|
151
|
+
e.condition_type = rules.condition ? "always" : "never";
|
|
152
|
+
} else if (rules.condition.match(
|
|
153
|
+
/^(always|never|on_job_success|on_job_failure)$/
|
|
154
|
+
)) {
|
|
155
|
+
e.condition_type = rules.condition;
|
|
156
|
+
} else {
|
|
157
|
+
e.condition_type = "js_expression";
|
|
158
|
+
e.condition_expression = rules.condition;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
wfState.edges[`${s.id}->${next}`] = e;
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
wfState.edges = Object.keys(wfState.edges).sort(
|
|
165
|
+
(a, b) => `${wfState.edges[a].id}`.localeCompare("" + wfState.edges[b].id)
|
|
166
|
+
).reduce((obj, key) => {
|
|
167
|
+
obj[key] = wfState.edges[key];
|
|
168
|
+
return obj;
|
|
169
|
+
}, {});
|
|
170
|
+
return wfState;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/util/version.ts
|
|
17
174
|
var SHORT_HASH_LENGTH = 12;
|
|
18
175
|
function isDefined(v) {
|
|
19
176
|
return v !== void 0 && v !== null;
|
|
20
177
|
}
|
|
21
|
-
var
|
|
178
|
+
var parse = (version) => {
|
|
179
|
+
if (version.match(":")) {
|
|
180
|
+
const [source, hash] = version.split(":");
|
|
181
|
+
return { source, hash };
|
|
182
|
+
}
|
|
183
|
+
return { hash: version };
|
|
184
|
+
};
|
|
185
|
+
var match = (a, b) => {
|
|
186
|
+
return parse(a).hash === parse(b).hash;
|
|
187
|
+
};
|
|
188
|
+
var generateHash = (workflow, { source = "cli", sha = true } = {}) => {
|
|
22
189
|
const parts = [];
|
|
23
|
-
const
|
|
190
|
+
const wfState = mapWorkflow(workflow);
|
|
191
|
+
const wfKeys = ["name", "positions"].sort();
|
|
24
192
|
const stepKeys = [
|
|
25
193
|
"name",
|
|
26
|
-
"adaptors",
|
|
27
194
|
"adaptor",
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
// assumes a string credential id
|
|
32
|
-
"expression"
|
|
33
|
-
// TODO need to model trigger types in this, which I think are currently ignored
|
|
195
|
+
"keychain_credential_id",
|
|
196
|
+
"project_credential_id",
|
|
197
|
+
"body"
|
|
34
198
|
].sort();
|
|
199
|
+
const triggerKeys = ["type", "cron_expression", "enabled"].sort();
|
|
35
200
|
const edgeKeys = [
|
|
36
|
-
"
|
|
201
|
+
"name",
|
|
202
|
+
// generated
|
|
37
203
|
"label",
|
|
38
|
-
"
|
|
39
|
-
|
|
204
|
+
"condition_type",
|
|
205
|
+
"condition_label",
|
|
206
|
+
"condition_expression",
|
|
207
|
+
"enabled"
|
|
40
208
|
].sort();
|
|
41
209
|
wfKeys.forEach((key) => {
|
|
42
|
-
|
|
43
|
-
|
|
210
|
+
const value = get(workflow, key);
|
|
211
|
+
if (isDefined(value)) {
|
|
212
|
+
parts.push(serializeValue(value));
|
|
44
213
|
}
|
|
45
214
|
});
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
215
|
+
for (const triggerId in wfState.triggers) {
|
|
216
|
+
const trigger = wfState.triggers[triggerId];
|
|
217
|
+
triggerKeys.forEach((key) => {
|
|
218
|
+
const value = get(trigger, key);
|
|
219
|
+
if (isDefined(value)) {
|
|
220
|
+
parts.push(serializeValue(value));
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const steps = Object.values(wfState.jobs).sort((a, b) => {
|
|
225
|
+
const aName = a.name ?? a.id ?? "";
|
|
226
|
+
const bName = b.name ?? b.id ?? "";
|
|
227
|
+
return aName.toLowerCase().localeCompare(bName.toLowerCase());
|
|
50
228
|
});
|
|
51
229
|
for (const step of steps) {
|
|
52
230
|
stepKeys.forEach((key) => {
|
|
53
|
-
|
|
54
|
-
|
|
231
|
+
const value = get(step, key);
|
|
232
|
+
if (isDefined(value)) {
|
|
233
|
+
parts.push(serializeValue(value));
|
|
55
234
|
}
|
|
56
235
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
236
|
+
}
|
|
237
|
+
const uuidMap = {};
|
|
238
|
+
for (const t in wfState.triggers) {
|
|
239
|
+
const uuid = wfState.triggers[t].id;
|
|
240
|
+
uuidMap[uuid] = wfState.triggers[t];
|
|
241
|
+
wfState.triggers[t].name = wfState.triggers[t].type;
|
|
242
|
+
}
|
|
243
|
+
for (const j in wfState.jobs) {
|
|
244
|
+
const uuid = wfState.jobs[j].id;
|
|
245
|
+
uuidMap[uuid] = wfState.jobs[j];
|
|
246
|
+
}
|
|
247
|
+
const edges = Object.values(wfState.edges).map((edge) => {
|
|
248
|
+
const source2 = uuidMap[edge.source_trigger_id ?? edge.source_job_id];
|
|
249
|
+
const target = uuidMap[edge.target_job_id];
|
|
250
|
+
edge.name = `${source2.name ?? source2.id}-${target.name ?? target.id}`;
|
|
251
|
+
return edge;
|
|
252
|
+
}).sort((a, b) => {
|
|
253
|
+
const aName = a.name ?? "";
|
|
254
|
+
const bName = b.name ?? "";
|
|
255
|
+
return aName.localeCompare(bName);
|
|
256
|
+
});
|
|
257
|
+
for (const edge of edges) {
|
|
258
|
+
edgeKeys.forEach((key) => {
|
|
259
|
+
const value = get(edge, key);
|
|
260
|
+
if (isDefined(value)) {
|
|
261
|
+
parts.push(serializeValue(value));
|
|
70
262
|
}
|
|
71
|
-
}
|
|
263
|
+
});
|
|
72
264
|
}
|
|
73
265
|
const str = parts.join("");
|
|
74
|
-
|
|
75
|
-
|
|
266
|
+
if (sha) {
|
|
267
|
+
const hash = crypto.createHash("sha256").update(str).digest("hex");
|
|
268
|
+
return `${source}:${hash.substring(0, SHORT_HASH_LENGTH)}`;
|
|
269
|
+
} else {
|
|
270
|
+
return `${source}:${str}`;
|
|
271
|
+
}
|
|
76
272
|
};
|
|
77
273
|
function serializeValue(val) {
|
|
78
274
|
if (typeof val === "object") {
|
|
@@ -104,7 +300,7 @@ var Workflow = class {
|
|
|
104
300
|
// uuid to ids
|
|
105
301
|
};
|
|
106
302
|
this.workflow = clone(workflow);
|
|
107
|
-
this.workflow.history = workflow.history
|
|
303
|
+
this.workflow.history = workflow.history ?? [];
|
|
108
304
|
const {
|
|
109
305
|
id,
|
|
110
306
|
name,
|
|
@@ -137,6 +333,9 @@ var Workflow = class {
|
|
|
137
333
|
set start(s) {
|
|
138
334
|
this.workflow.start = s;
|
|
139
335
|
}
|
|
336
|
+
get history() {
|
|
337
|
+
return this.workflow.history ?? [];
|
|
338
|
+
}
|
|
140
339
|
_buildIndex() {
|
|
141
340
|
for (const step of this.workflow.steps) {
|
|
142
341
|
const s = step;
|
|
@@ -168,7 +367,10 @@ var Workflow = class {
|
|
|
168
367
|
}
|
|
169
368
|
// Get properties on any step or edge by id or uuid
|
|
170
369
|
get(id) {
|
|
171
|
-
|
|
370
|
+
if (id in this.index.id) {
|
|
371
|
+
id = this.index.id[id];
|
|
372
|
+
}
|
|
373
|
+
let item = this.index.edges[id] || this.index.steps[id];
|
|
172
374
|
if (!item) {
|
|
173
375
|
throw new Error(`step/edge with id "${id}" does not exist in workflow`);
|
|
174
376
|
}
|
|
@@ -230,15 +432,12 @@ var Workflow = class {
|
|
|
230
432
|
getUUIDMap() {
|
|
231
433
|
return this.index.uuid;
|
|
232
434
|
}
|
|
233
|
-
getVersionHash() {
|
|
234
|
-
return generateHash(this);
|
|
435
|
+
getVersionHash(options) {
|
|
436
|
+
return generateHash(this, options);
|
|
235
437
|
}
|
|
236
438
|
pushHistory(versionHash) {
|
|
237
439
|
this.workflow.history?.push(versionHash);
|
|
238
440
|
}
|
|
239
|
-
get history() {
|
|
240
|
-
return this.workflow.history ?? [];
|
|
241
|
-
}
|
|
242
441
|
// return true if the current workflow can be merged into the target workflow without losing any changes
|
|
243
442
|
canMergeInto(target) {
|
|
244
443
|
const thisHistory = this.workflow.history?.concat(this.getVersionHash()) ?? [];
|
|
@@ -257,155 +456,6 @@ __export(serialize_exports, {
|
|
|
257
456
|
state: () => to_app_state_default
|
|
258
457
|
});
|
|
259
458
|
|
|
260
|
-
// src/serialize/to-app-state.ts
|
|
261
|
-
import { pick, omitBy, isNil, sortBy } from "lodash-es";
|
|
262
|
-
import { randomUUID } from "node:crypto";
|
|
263
|
-
|
|
264
|
-
// src/util/rename-keys.ts
|
|
265
|
-
function renameKeys(props = {}, keyMap) {
|
|
266
|
-
return Object.fromEntries(
|
|
267
|
-
Object.entries(props).map(([key, value]) => [
|
|
268
|
-
keyMap[key] ? keyMap[key] : key,
|
|
269
|
-
value
|
|
270
|
-
])
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// src/util/yaml.ts
|
|
275
|
-
import yaml from "yaml";
|
|
276
|
-
function yamlToJson(y) {
|
|
277
|
-
const doc = yaml.parseDocument(y);
|
|
278
|
-
return doc.toJS();
|
|
279
|
-
}
|
|
280
|
-
function jsonToYaml(json) {
|
|
281
|
-
if (typeof json === "string") {
|
|
282
|
-
json = JSON.parse(json);
|
|
283
|
-
}
|
|
284
|
-
const doc = new yaml.Document(json);
|
|
285
|
-
return yaml.stringify(doc, null, 2);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// src/serialize/to-app-state.ts
|
|
289
|
-
var defaultJobProps = {
|
|
290
|
-
// TODO why does the provisioner throw if these keys are not set?
|
|
291
|
-
// Ok, 90% of jobs will have a credenial, but it's still optional right?
|
|
292
|
-
keychain_credential_id: null,
|
|
293
|
-
project_credential_id: null
|
|
294
|
-
};
|
|
295
|
-
function to_app_state_default(project, options = {}) {
|
|
296
|
-
const {
|
|
297
|
-
uuid,
|
|
298
|
-
endpoint,
|
|
299
|
-
env,
|
|
300
|
-
id,
|
|
301
|
-
fetched_at,
|
|
302
|
-
...rest
|
|
303
|
-
} = project.openfn ?? {};
|
|
304
|
-
const state = omitBy(
|
|
305
|
-
pick(project, ["name", "description", "collections"]),
|
|
306
|
-
isNil
|
|
307
|
-
);
|
|
308
|
-
state.id = uuid;
|
|
309
|
-
Object.assign(state, rest, project.options);
|
|
310
|
-
state.project_credentials = project.credentials ?? [];
|
|
311
|
-
state.workflows = project.workflows.map(mapWorkflow).reduce((obj, wf) => {
|
|
312
|
-
obj[slugify(wf.name ?? wf.id)] = wf;
|
|
313
|
-
return obj;
|
|
314
|
-
}, {});
|
|
315
|
-
const shouldReturnYaml = options.format === "yaml" || !options.format && project.config.formats.project === "yaml";
|
|
316
|
-
if (shouldReturnYaml) {
|
|
317
|
-
return jsonToYaml(state);
|
|
318
|
-
}
|
|
319
|
-
return state;
|
|
320
|
-
}
|
|
321
|
-
var mapWorkflow = (workflow) => {
|
|
322
|
-
if (workflow instanceof Workflow_default) {
|
|
323
|
-
workflow = workflow.toJSON();
|
|
324
|
-
}
|
|
325
|
-
const { uuid, ...originalOpenfnProps } = workflow.openfn ?? {};
|
|
326
|
-
const wfState = {
|
|
327
|
-
...originalOpenfnProps,
|
|
328
|
-
id: workflow.openfn?.uuid ?? randomUUID(),
|
|
329
|
-
jobs: {},
|
|
330
|
-
triggers: {},
|
|
331
|
-
edges: {},
|
|
332
|
-
lock_version: workflow.openfn?.lock_version ?? null
|
|
333
|
-
// TODO needs testing
|
|
334
|
-
};
|
|
335
|
-
if (workflow.name) {
|
|
336
|
-
wfState.name = workflow.name;
|
|
337
|
-
}
|
|
338
|
-
const lookup = workflow.steps.reduce((obj, next) => {
|
|
339
|
-
if (!next.openfn?.uuid) {
|
|
340
|
-
next.openfn ??= {};
|
|
341
|
-
next.openfn.uuid = randomUUID();
|
|
342
|
-
}
|
|
343
|
-
obj[next.id] = next.openfn.uuid;
|
|
344
|
-
return obj;
|
|
345
|
-
}, {});
|
|
346
|
-
sortBy(workflow.steps, "name").forEach((s) => {
|
|
347
|
-
let isTrigger = false;
|
|
348
|
-
let node;
|
|
349
|
-
if (s.type && !s.expression) {
|
|
350
|
-
isTrigger = true;
|
|
351
|
-
node = {
|
|
352
|
-
type: s.type,
|
|
353
|
-
...renameKeys(s.openfn, { uuid: "id" })
|
|
354
|
-
};
|
|
355
|
-
wfState.triggers[node.type] = node;
|
|
356
|
-
} else {
|
|
357
|
-
node = omitBy(pick(s, ["name", "adaptor"]), isNil);
|
|
358
|
-
const { uuid: uuid2, ...otherOpenFnProps } = s.openfn ?? {};
|
|
359
|
-
node.id = uuid2;
|
|
360
|
-
if (s.expression) {
|
|
361
|
-
node.body = s.expression;
|
|
362
|
-
}
|
|
363
|
-
if (typeof s.configuration === "string" && !s.configuration.endsWith(".json")) {
|
|
364
|
-
otherOpenFnProps.project_credential_id = s.configuration;
|
|
365
|
-
}
|
|
366
|
-
Object.assign(node, defaultJobProps, otherOpenFnProps);
|
|
367
|
-
wfState.jobs[s.id ?? slugify(s.name)] = node;
|
|
368
|
-
}
|
|
369
|
-
Object.keys(s.next ?? {}).forEach((next) => {
|
|
370
|
-
const rules = s.next[next];
|
|
371
|
-
const { uuid: uuid2, ...otherOpenFnProps } = rules.openfn ?? {};
|
|
372
|
-
const e = {
|
|
373
|
-
id: uuid2 ?? randomUUID(),
|
|
374
|
-
target_job_id: lookup[next],
|
|
375
|
-
enabled: !rules.disabled,
|
|
376
|
-
source_trigger_id: null
|
|
377
|
-
// lightning complains if this isn't set, even if its falsy :(
|
|
378
|
-
};
|
|
379
|
-
Object.assign(e, otherOpenFnProps);
|
|
380
|
-
if (isTrigger) {
|
|
381
|
-
e.source_trigger_id = node.id;
|
|
382
|
-
} else {
|
|
383
|
-
e.source_job_id = node.id;
|
|
384
|
-
}
|
|
385
|
-
if (rules.condition) {
|
|
386
|
-
if (typeof rules.condition === "boolean") {
|
|
387
|
-
e.condition_type = rules.condition ? "always" : "never";
|
|
388
|
-
} else if (rules.condition.match(
|
|
389
|
-
/^(always|never|on_job_success|on_job_failure)$/
|
|
390
|
-
)) {
|
|
391
|
-
e.condition_type = rules.condition;
|
|
392
|
-
} else {
|
|
393
|
-
e.condition_type = "js_expression";
|
|
394
|
-
e.condition_expression = rules.condition;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
wfState.edges[`${s.id}->${next}`] = e;
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
wfState.edges = Object.keys(wfState.edges).sort(
|
|
401
|
-
(a, b) => `${wfState.edges[a].id}`.localeCompare("" + wfState.edges[b].id)
|
|
402
|
-
).reduce((obj, key) => {
|
|
403
|
-
obj[key] = wfState.edges[key];
|
|
404
|
-
return obj;
|
|
405
|
-
}, {});
|
|
406
|
-
return wfState;
|
|
407
|
-
};
|
|
408
|
-
|
|
409
459
|
// src/serialize/to-fs.ts
|
|
410
460
|
import nodepath from "path";
|
|
411
461
|
import { omit } from "lodash-es";
|
|
@@ -435,6 +485,9 @@ var extractConfig = (source, format) => {
|
|
|
435
485
|
if (source.name) {
|
|
436
486
|
project.name = source.name;
|
|
437
487
|
}
|
|
488
|
+
if (source.cli.forked_from && Object.keys(source.cli.forked_from).length) {
|
|
489
|
+
project.forked_from = source.cli.forked_from;
|
|
490
|
+
}
|
|
438
491
|
const workspace = {
|
|
439
492
|
...source.config
|
|
440
493
|
};
|
|
@@ -590,10 +643,13 @@ import { omitBy as omitBy3, isNil as isNil4 } from "lodash-es";
|
|
|
590
643
|
// src/util/omit-nil.ts
|
|
591
644
|
import { omitBy as omitBy2, isNil as isNil3 } from "lodash-es";
|
|
592
645
|
var omitNil = (obj, key) => {
|
|
593
|
-
if (obj[key]) {
|
|
646
|
+
if (key && obj[key]) {
|
|
594
647
|
obj[key] = omitBy2(obj[key], isNil3);
|
|
648
|
+
} else {
|
|
649
|
+
return omitBy2(obj, isNil3);
|
|
595
650
|
}
|
|
596
651
|
};
|
|
652
|
+
var omit_nil_default = omitNil;
|
|
597
653
|
var tidyOpenfn = (obj) => omitNil(obj, "openfn");
|
|
598
654
|
|
|
599
655
|
// src/serialize/to-project.ts
|
|
@@ -710,7 +766,7 @@ var mapEdge = (edge) => {
|
|
|
710
766
|
e.condition = edge.condition_type;
|
|
711
767
|
}
|
|
712
768
|
if (edge.condition_label) {
|
|
713
|
-
e.
|
|
769
|
+
e.label = edge.condition_label;
|
|
714
770
|
}
|
|
715
771
|
if (edge.id) {
|
|
716
772
|
e.openfn = {
|
|
@@ -731,7 +787,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
731
787
|
mapped.id = slugify(workflow.name);
|
|
732
788
|
}
|
|
733
789
|
Object.values(workflow.triggers).forEach((trigger) => {
|
|
734
|
-
const { type, ...otherProps } = trigger;
|
|
790
|
+
const { type, enabled, ...otherProps } = trigger;
|
|
735
791
|
if (!mapped.start) {
|
|
736
792
|
mapped.start = type;
|
|
737
793
|
}
|
|
@@ -741,6 +797,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
741
797
|
mapped.steps.push({
|
|
742
798
|
id: type,
|
|
743
799
|
type,
|
|
800
|
+
enabled,
|
|
744
801
|
openfn: renameKeys(otherProps, { id: "uuid" }),
|
|
745
802
|
next: connectedEdges.reduce((obj, edge) => {
|
|
746
803
|
const target = Object.values(jobs).find(
|
|
@@ -832,16 +889,19 @@ import path3 from "node:path";
|
|
|
832
889
|
import { glob } from "glob";
|
|
833
890
|
import { omit as omit2 } from "lodash-es";
|
|
834
891
|
var parseProject = async (options) => {
|
|
835
|
-
const { root, logger } = options;
|
|
892
|
+
const { root, logger, alias } = options;
|
|
836
893
|
const { type, content } = findWorkspaceFile(root);
|
|
837
894
|
const context = loadWorkspaceFile(content, type);
|
|
838
895
|
const config = buildConfig(options.config ?? context.workspace);
|
|
839
896
|
const proj = {
|
|
840
897
|
id: context.project?.id,
|
|
841
898
|
name: context.project?.name,
|
|
842
|
-
openfn: omit2(context.project, ["id"]),
|
|
899
|
+
openfn: omit2(context.project, ["id", "forked_from"]),
|
|
843
900
|
config,
|
|
844
|
-
workflows: []
|
|
901
|
+
workflows: [],
|
|
902
|
+
cli: omit_nil_default({
|
|
903
|
+
forked_from: context.project.forked_from
|
|
904
|
+
})
|
|
845
905
|
};
|
|
846
906
|
const workflowDir = config.workflowRoot ?? config.dirs?.workflows ?? "workflows";
|
|
847
907
|
const fileType = config.formats?.workflow ?? "yaml";
|
|
@@ -891,7 +951,10 @@ var parseProject = async (options) => {
|
|
|
891
951
|
continue;
|
|
892
952
|
}
|
|
893
953
|
}
|
|
894
|
-
return new Project(proj,
|
|
954
|
+
return new Project(proj, {
|
|
955
|
+
alias,
|
|
956
|
+
...context.workspace
|
|
957
|
+
});
|
|
895
958
|
};
|
|
896
959
|
|
|
897
960
|
// src/util/uuid.ts
|
|
@@ -1243,6 +1306,26 @@ function getDuplicates(arr) {
|
|
|
1243
1306
|
return Array.from(duplicates);
|
|
1244
1307
|
}
|
|
1245
1308
|
|
|
1309
|
+
// src/util/find-changed-workflows.ts
|
|
1310
|
+
var find_changed_workflows_default = (project) => {
|
|
1311
|
+
const base = project.cli.forked_from ?? project.workflows.reduce((obj, wf) => {
|
|
1312
|
+
if (wf.history.length) {
|
|
1313
|
+
obj[wf.id] = wf.history.at(-1);
|
|
1314
|
+
}
|
|
1315
|
+
return obj;
|
|
1316
|
+
}, {});
|
|
1317
|
+
const changed = [];
|
|
1318
|
+
for (const wf of project.workflows) {
|
|
1319
|
+
if (wf.id in base) {
|
|
1320
|
+
const hash = generateHash(wf);
|
|
1321
|
+
if (hash !== base[wf.id]) {
|
|
1322
|
+
changed.push(wf);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return changed;
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1246
1329
|
// src/merge/merge-project.ts
|
|
1247
1330
|
var SANDBOX_MERGE = "sandbox";
|
|
1248
1331
|
var UnsafeMergeError = class extends Error {
|
|
@@ -1251,35 +1334,34 @@ var defaultOptions = {
|
|
|
1251
1334
|
workflowMappings: {},
|
|
1252
1335
|
removeUnmapped: false,
|
|
1253
1336
|
force: true,
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
* If mode is replace, all properties on the source will override the target (including UUIDs, name)
|
|
1257
|
-
*/
|
|
1258
|
-
mode: SANDBOX_MERGE
|
|
1337
|
+
mode: SANDBOX_MERGE,
|
|
1338
|
+
onlyUpdated: false
|
|
1259
1339
|
};
|
|
1260
1340
|
function merge(source, target, opts) {
|
|
1261
1341
|
const options = defaultsDeep(
|
|
1262
1342
|
opts,
|
|
1263
1343
|
defaultOptions
|
|
1264
1344
|
);
|
|
1265
|
-
const dupTargetMappings = getDuplicates(
|
|
1266
|
-
Object.values(options.workflowMappings ?? {})
|
|
1267
|
-
);
|
|
1268
|
-
if (dupTargetMappings.length) {
|
|
1269
|
-
throw new Error(
|
|
1270
|
-
`The following target workflows have multiple source workflows merging into them: ${dupTargetMappings.join(
|
|
1271
|
-
", "
|
|
1272
|
-
)}`
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
1345
|
const finalWorkflows = [];
|
|
1276
1346
|
const usedTargetIds = /* @__PURE__ */ new Set();
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1347
|
+
let sourceWorkflows = source.workflows;
|
|
1348
|
+
const noMappings = isEmpty(options.workflowMappings);
|
|
1349
|
+
if (options.onlyUpdated) {
|
|
1350
|
+
sourceWorkflows = find_changed_workflows_default(source);
|
|
1351
|
+
}
|
|
1352
|
+
if (!noMappings) {
|
|
1353
|
+
const dupes = getDuplicates(Object.values(options.workflowMappings ?? {}));
|
|
1354
|
+
if (dupes.length) {
|
|
1355
|
+
throw new Error(
|
|
1356
|
+
`The following target workflows have multiple source workflows merging into them: ${dupes.join(
|
|
1357
|
+
", "
|
|
1358
|
+
)}`
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
sourceWorkflows = source.workflows.filter(
|
|
1362
|
+
(w) => !!options.workflowMappings[w.id]
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1283
1365
|
const potentialConflicts = {};
|
|
1284
1366
|
for (const sourceWorkflow of sourceWorkflows) {
|
|
1285
1367
|
const targetId = options.workflowMappings?.[sourceWorkflow.id] ?? sourceWorkflow.id;
|
|
@@ -1331,6 +1413,7 @@ Pass --force to force the merge anyway`
|
|
|
1331
1413
|
...source.options
|
|
1332
1414
|
},
|
|
1333
1415
|
name: source.name ?? target.name,
|
|
1416
|
+
alias: source.alias ?? target.alias,
|
|
1334
1417
|
description: source.description ?? target.description,
|
|
1335
1418
|
credentials: source.credentials ?? target.credentials,
|
|
1336
1419
|
collections: source.collections ?? target.collections
|
|
@@ -1341,9 +1424,12 @@ Pass --force to force the merge anyway`
|
|
|
1341
1424
|
}
|
|
1342
1425
|
|
|
1343
1426
|
// src/util/project-diff.ts
|
|
1344
|
-
function diff(a, b) {
|
|
1427
|
+
function diff(a, b, workflows) {
|
|
1345
1428
|
const diffs = [];
|
|
1346
1429
|
for (const workflowA of a.workflows) {
|
|
1430
|
+
if (workflows?.length && !workflows.includes(workflowA.id)) {
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1347
1433
|
const workflowB = b.getWorkflow(workflowA.id);
|
|
1348
1434
|
if (!workflowB) {
|
|
1349
1435
|
diffs.push({ id: workflowA.id, type: "removed" });
|
|
@@ -1352,6 +1438,9 @@ function diff(a, b) {
|
|
|
1352
1438
|
}
|
|
1353
1439
|
}
|
|
1354
1440
|
for (const workflowB of b.workflows) {
|
|
1441
|
+
if (workflows?.length && !workflows.includes(workflowB.id)) {
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1355
1444
|
if (!a.getWorkflow(workflowB.id)) {
|
|
1356
1445
|
diffs.push({ id: workflowB.id, type: "added" });
|
|
1357
1446
|
}
|
|
@@ -1361,7 +1450,7 @@ function diff(a, b) {
|
|
|
1361
1450
|
|
|
1362
1451
|
// src/Project.ts
|
|
1363
1452
|
var maybeCreateWorkflow = (wf) => wf instanceof Workflow_default ? wf : new Workflow_default(wf);
|
|
1364
|
-
var Project = class {
|
|
1453
|
+
var Project = class _Project {
|
|
1365
1454
|
// what schema version is this?
|
|
1366
1455
|
// And how are we tracking this?
|
|
1367
1456
|
// version;
|
|
@@ -1435,6 +1524,10 @@ var Project = class {
|
|
|
1435
1524
|
get alias() {
|
|
1436
1525
|
return this.cli.alias ?? "main";
|
|
1437
1526
|
}
|
|
1527
|
+
set alias(value) {
|
|
1528
|
+
this.cli ??= {};
|
|
1529
|
+
this.cli.alias = value;
|
|
1530
|
+
}
|
|
1438
1531
|
get uuid() {
|
|
1439
1532
|
return this.openfn?.uuid ? `${this.openfn.uuid}` : void 0;
|
|
1440
1533
|
}
|
|
@@ -1490,8 +1583,8 @@ var Project = class {
|
|
|
1490
1583
|
return result;
|
|
1491
1584
|
}
|
|
1492
1585
|
// Compare this project with another and return a list of workflow changes
|
|
1493
|
-
diff(project) {
|
|
1494
|
-
return diff(this, project);
|
|
1586
|
+
diff(project, workflows = []) {
|
|
1587
|
+
return diff(this, project, workflows);
|
|
1495
1588
|
}
|
|
1496
1589
|
canMergeInto(target) {
|
|
1497
1590
|
const potentialConflicts = {};
|
|
@@ -1507,6 +1600,16 @@ var Project = class {
|
|
|
1507
1600
|
}
|
|
1508
1601
|
return true;
|
|
1509
1602
|
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Generates the contents of the openfn.yaml file,
|
|
1605
|
+
* plus its file path
|
|
1606
|
+
*/
|
|
1607
|
+
generateConfig() {
|
|
1608
|
+
return extractConfig(this);
|
|
1609
|
+
}
|
|
1610
|
+
clone() {
|
|
1611
|
+
return new _Project(this.serialize("project"));
|
|
1612
|
+
}
|
|
1510
1613
|
};
|
|
1511
1614
|
var Project_default = Project;
|
|
1512
1615
|
|
|
@@ -1693,12 +1796,18 @@ var initOperations = (options = {}) => {
|
|
|
1693
1796
|
if (!nodes[name]) {
|
|
1694
1797
|
const id = slugify(name);
|
|
1695
1798
|
nodes[name] = {
|
|
1696
|
-
|
|
1697
|
-
id,
|
|
1698
|
-
openfn: {
|
|
1699
|
-
uuid: uuid(id)
|
|
1700
|
-
}
|
|
1799
|
+
id
|
|
1701
1800
|
};
|
|
1801
|
+
if (/^(cron|webhook)$/.test(name)) {
|
|
1802
|
+
nodes[name].type = name;
|
|
1803
|
+
} else {
|
|
1804
|
+
nodes[name].name = name;
|
|
1805
|
+
}
|
|
1806
|
+
if (options.openfnUuid !== false) {
|
|
1807
|
+
nodes[name].openfn = {
|
|
1808
|
+
uuid: uuid(id)
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1702
1811
|
}
|
|
1703
1812
|
return nodes[name];
|
|
1704
1813
|
};
|
|
@@ -1735,9 +1844,11 @@ var initOperations = (options = {}) => {
|
|
|
1735
1844
|
const n1 = parent.buildWorkflow();
|
|
1736
1845
|
const n2 = child.buildWorkflow();
|
|
1737
1846
|
const e = edge.buildWorkflow();
|
|
1738
|
-
|
|
1847
|
+
if (options.openfnUuid !== false) {
|
|
1848
|
+
e.openfn.uuid = uuid(`${n1.id}-${n2.id}`);
|
|
1849
|
+
}
|
|
1739
1850
|
n1.next ??= {};
|
|
1740
|
-
n1.next[n2.name] = e;
|
|
1851
|
+
n1.next[n2.id ?? slugify(n2.name)] = e;
|
|
1741
1852
|
return [n1, n2];
|
|
1742
1853
|
},
|
|
1743
1854
|
// node could just be a node name, or a node with props
|
|
@@ -1843,6 +1954,9 @@ function generateWorkflow(def, options = {}) {
|
|
|
1843
1954
|
raw.openfn.uuid = uuid;
|
|
1844
1955
|
}
|
|
1845
1956
|
const wf = new Workflow_default(raw);
|
|
1957
|
+
if (options.history) {
|
|
1958
|
+
wf.pushHistory(wf.getVersionHash());
|
|
1959
|
+
}
|
|
1846
1960
|
return wf;
|
|
1847
1961
|
}
|
|
1848
1962
|
function generateProject(name, workflowDefs, options = {}) {
|
|
@@ -1868,7 +1982,9 @@ export {
|
|
|
1868
1982
|
src_default as default,
|
|
1869
1983
|
diff,
|
|
1870
1984
|
generateProject,
|
|
1985
|
+
generateHash as generateVersionHash,
|
|
1871
1986
|
generateWorkflow,
|
|
1872
1987
|
jsonToYaml,
|
|
1988
|
+
match as versionsEqual,
|
|
1873
1989
|
yamlToJson
|
|
1874
1990
|
};
|
package/dist/workflow.ohm
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/project",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Read, serialize, replicate and sync OpenFn projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"lodash-es": "^4.17.21",
|
|
35
35
|
"ohm-js": "^17.2.1",
|
|
36
36
|
"yaml": "^2.2.2",
|
|
37
|
-
"@openfn/lexicon": "^1.4.
|
|
37
|
+
"@openfn/lexicon": "^1.4.1",
|
|
38
38
|
"@openfn/logger": "1.1.1"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|