@openfn/project 0.12.0 → 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 +42 -4
- package/dist/index.js +355 -229
- 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,7 +69,7 @@ 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
74
|
canMergeInto(target: Workflow): boolean;
|
|
62
75
|
}
|
|
@@ -75,6 +88,7 @@ type FromFsConfig = {
|
|
|
75
88
|
root: string;
|
|
76
89
|
config?: Partial<l.WorkspaceConfig>;
|
|
77
90
|
logger?: Logger;
|
|
91
|
+
alias?: string;
|
|
78
92
|
};
|
|
79
93
|
|
|
80
94
|
type SerializedProject = Omit<Partial<l.Project>, 'workflows'> & {
|
|
@@ -94,7 +108,16 @@ type MergeProjectOptions = {
|
|
|
94
108
|
workflowMappings: Record<string, string>;
|
|
95
109
|
removeUnmapped: boolean;
|
|
96
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
|
+
*/
|
|
97
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;
|
|
98
121
|
};
|
|
99
122
|
|
|
100
123
|
declare class Workspace {
|
|
@@ -108,6 +131,8 @@ declare class Workspace {
|
|
|
108
131
|
constructor(workspacePath: string, logger?: Logger, validate?: boolean);
|
|
109
132
|
loadProject(): void;
|
|
110
133
|
list(): Project[];
|
|
134
|
+
get projectsPath(): string;
|
|
135
|
+
get workflowsPath(): string;
|
|
111
136
|
/** Get a project by its alias, id or UUID. Can also include a UUID */
|
|
112
137
|
get(nameyThing: string): Project | null;
|
|
113
138
|
getProjectPath(id: string): string | undefined;
|
|
@@ -130,6 +155,7 @@ type UUIDMap = {
|
|
|
130
155
|
type CLIMeta = {
|
|
131
156
|
version?: number;
|
|
132
157
|
alias?: string;
|
|
158
|
+
forked_from?: Record<string, string>;
|
|
133
159
|
};
|
|
134
160
|
declare class Project {
|
|
135
161
|
/** Human readable project name. This corresponds to the label in Lightning */
|
|
@@ -160,6 +186,7 @@ declare class Project {
|
|
|
160
186
|
constructor(data?: Partial<l__default.Project>, meta?: Partial<l__default.WorkspaceConfig> & CLIMeta);
|
|
161
187
|
/** Local alias for the project. Comes from the file name. Not shared with Lightning. */
|
|
162
188
|
get alias(): string;
|
|
189
|
+
set alias(value: string);
|
|
163
190
|
get uuid(): string | undefined;
|
|
164
191
|
get host(): string | undefined;
|
|
165
192
|
setConfig(config: Partial<WorkspaceConfig>): void;
|
|
@@ -174,8 +201,17 @@ declare class Project {
|
|
|
174
201
|
* Returns a map of ids:uuids for everything in the project
|
|
175
202
|
*/
|
|
176
203
|
getUUIDMap(): UUIDMap;
|
|
177
|
-
diff(project: Project): WorkflowDiff[];
|
|
204
|
+
diff(project: Project, workflows?: string[]): WorkflowDiff[];
|
|
178
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;
|
|
179
215
|
}
|
|
180
216
|
|
|
181
217
|
declare function yamlToJson(y: string): any;
|
|
@@ -187,6 +223,8 @@ type GenerateWorkflowOptions = {
|
|
|
187
223
|
printErrors: boolean;
|
|
188
224
|
uuidMap?: Record<string, string>;
|
|
189
225
|
openfnUuid: boolean;
|
|
226
|
+
/** If true, will set up a version hash in the history array */
|
|
227
|
+
history: boolean;
|
|
190
228
|
};
|
|
191
229
|
type GenerateProjectOptions = GenerateWorkflowOptions & {
|
|
192
230
|
uuidMap: Array<Record<string, string>>;
|
|
@@ -199,4 +237,4 @@ type GenerateProjectOptions = GenerateWorkflowOptions & {
|
|
|
199
237
|
declare function generateWorkflow(def: string, options?: Partial<GenerateWorkflowOptions>): Workflow;
|
|
200
238
|
declare function generateProject(name: string, workflowDefs: string[], options?: Partial<GenerateProjectOptions>): Project;
|
|
201
239
|
|
|
202
|
-
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,8 +432,8 @@ 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);
|
|
@@ -254,155 +456,6 @@ __export(serialize_exports, {
|
|
|
254
456
|
state: () => to_app_state_default
|
|
255
457
|
});
|
|
256
458
|
|
|
257
|
-
// src/serialize/to-app-state.ts
|
|
258
|
-
import { pick, omitBy, isNil, sortBy } from "lodash-es";
|
|
259
|
-
import { randomUUID } from "node:crypto";
|
|
260
|
-
|
|
261
|
-
// src/util/rename-keys.ts
|
|
262
|
-
function renameKeys(props = {}, keyMap) {
|
|
263
|
-
return Object.fromEntries(
|
|
264
|
-
Object.entries(props).map(([key, value]) => [
|
|
265
|
-
keyMap[key] ? keyMap[key] : key,
|
|
266
|
-
value
|
|
267
|
-
])
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// src/util/yaml.ts
|
|
272
|
-
import yaml from "yaml";
|
|
273
|
-
function yamlToJson(y) {
|
|
274
|
-
const doc = yaml.parseDocument(y);
|
|
275
|
-
return doc.toJS();
|
|
276
|
-
}
|
|
277
|
-
function jsonToYaml(json) {
|
|
278
|
-
if (typeof json === "string") {
|
|
279
|
-
json = JSON.parse(json);
|
|
280
|
-
}
|
|
281
|
-
const doc = new yaml.Document(json);
|
|
282
|
-
return yaml.stringify(doc, null, 2);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// src/serialize/to-app-state.ts
|
|
286
|
-
var defaultJobProps = {
|
|
287
|
-
// TODO why does the provisioner throw if these keys are not set?
|
|
288
|
-
// Ok, 90% of jobs will have a credenial, but it's still optional right?
|
|
289
|
-
keychain_credential_id: null,
|
|
290
|
-
project_credential_id: null
|
|
291
|
-
};
|
|
292
|
-
function to_app_state_default(project, options = {}) {
|
|
293
|
-
const {
|
|
294
|
-
uuid,
|
|
295
|
-
endpoint,
|
|
296
|
-
env,
|
|
297
|
-
id,
|
|
298
|
-
fetched_at,
|
|
299
|
-
...rest
|
|
300
|
-
} = project.openfn ?? {};
|
|
301
|
-
const state = omitBy(
|
|
302
|
-
pick(project, ["name", "description", "collections"]),
|
|
303
|
-
isNil
|
|
304
|
-
);
|
|
305
|
-
state.id = uuid;
|
|
306
|
-
Object.assign(state, rest, project.options);
|
|
307
|
-
state.project_credentials = project.credentials ?? [];
|
|
308
|
-
state.workflows = project.workflows.map(mapWorkflow).reduce((obj, wf) => {
|
|
309
|
-
obj[slugify(wf.name ?? wf.id)] = wf;
|
|
310
|
-
return obj;
|
|
311
|
-
}, {});
|
|
312
|
-
const shouldReturnYaml = options.format === "yaml" || !options.format && project.config.formats.project === "yaml";
|
|
313
|
-
if (shouldReturnYaml) {
|
|
314
|
-
return jsonToYaml(state);
|
|
315
|
-
}
|
|
316
|
-
return state;
|
|
317
|
-
}
|
|
318
|
-
var mapWorkflow = (workflow) => {
|
|
319
|
-
if (workflow instanceof Workflow_default) {
|
|
320
|
-
workflow = workflow.toJSON();
|
|
321
|
-
}
|
|
322
|
-
const { uuid, ...originalOpenfnProps } = workflow.openfn ?? {};
|
|
323
|
-
const wfState = {
|
|
324
|
-
...originalOpenfnProps,
|
|
325
|
-
id: workflow.openfn?.uuid ?? randomUUID(),
|
|
326
|
-
jobs: {},
|
|
327
|
-
triggers: {},
|
|
328
|
-
edges: {},
|
|
329
|
-
lock_version: workflow.openfn?.lock_version ?? null
|
|
330
|
-
// TODO needs testing
|
|
331
|
-
};
|
|
332
|
-
if (workflow.name) {
|
|
333
|
-
wfState.name = workflow.name;
|
|
334
|
-
}
|
|
335
|
-
const lookup = workflow.steps.reduce((obj, next) => {
|
|
336
|
-
if (!next.openfn?.uuid) {
|
|
337
|
-
next.openfn ??= {};
|
|
338
|
-
next.openfn.uuid = randomUUID();
|
|
339
|
-
}
|
|
340
|
-
obj[next.id] = next.openfn.uuid;
|
|
341
|
-
return obj;
|
|
342
|
-
}, {});
|
|
343
|
-
sortBy(workflow.steps, "name").forEach((s) => {
|
|
344
|
-
let isTrigger = false;
|
|
345
|
-
let node;
|
|
346
|
-
if (s.type && !s.expression) {
|
|
347
|
-
isTrigger = true;
|
|
348
|
-
node = {
|
|
349
|
-
type: s.type,
|
|
350
|
-
...renameKeys(s.openfn, { uuid: "id" })
|
|
351
|
-
};
|
|
352
|
-
wfState.triggers[node.type] = node;
|
|
353
|
-
} else {
|
|
354
|
-
node = omitBy(pick(s, ["name", "adaptor"]), isNil);
|
|
355
|
-
const { uuid: uuid2, ...otherOpenFnProps } = s.openfn ?? {};
|
|
356
|
-
node.id = uuid2;
|
|
357
|
-
if (s.expression) {
|
|
358
|
-
node.body = s.expression;
|
|
359
|
-
}
|
|
360
|
-
if (typeof s.configuration === "string" && !s.configuration.endsWith(".json")) {
|
|
361
|
-
otherOpenFnProps.project_credential_id = s.configuration;
|
|
362
|
-
}
|
|
363
|
-
Object.assign(node, defaultJobProps, otherOpenFnProps);
|
|
364
|
-
wfState.jobs[s.id ?? slugify(s.name)] = node;
|
|
365
|
-
}
|
|
366
|
-
Object.keys(s.next ?? {}).forEach((next) => {
|
|
367
|
-
const rules = s.next[next];
|
|
368
|
-
const { uuid: uuid2, ...otherOpenFnProps } = rules.openfn ?? {};
|
|
369
|
-
const e = {
|
|
370
|
-
id: uuid2 ?? randomUUID(),
|
|
371
|
-
target_job_id: lookup[next],
|
|
372
|
-
enabled: !rules.disabled,
|
|
373
|
-
source_trigger_id: null
|
|
374
|
-
// lightning complains if this isn't set, even if its falsy :(
|
|
375
|
-
};
|
|
376
|
-
Object.assign(e, otherOpenFnProps);
|
|
377
|
-
if (isTrigger) {
|
|
378
|
-
e.source_trigger_id = node.id;
|
|
379
|
-
} else {
|
|
380
|
-
e.source_job_id = node.id;
|
|
381
|
-
}
|
|
382
|
-
if (rules.condition) {
|
|
383
|
-
if (typeof rules.condition === "boolean") {
|
|
384
|
-
e.condition_type = rules.condition ? "always" : "never";
|
|
385
|
-
} else if (rules.condition.match(
|
|
386
|
-
/^(always|never|on_job_success|on_job_failure)$/
|
|
387
|
-
)) {
|
|
388
|
-
e.condition_type = rules.condition;
|
|
389
|
-
} else {
|
|
390
|
-
e.condition_type = "js_expression";
|
|
391
|
-
e.condition_expression = rules.condition;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
wfState.edges[`${s.id}->${next}`] = e;
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
wfState.edges = Object.keys(wfState.edges).sort(
|
|
398
|
-
(a, b) => `${wfState.edges[a].id}`.localeCompare("" + wfState.edges[b].id)
|
|
399
|
-
).reduce((obj, key) => {
|
|
400
|
-
obj[key] = wfState.edges[key];
|
|
401
|
-
return obj;
|
|
402
|
-
}, {});
|
|
403
|
-
return wfState;
|
|
404
|
-
};
|
|
405
|
-
|
|
406
459
|
// src/serialize/to-fs.ts
|
|
407
460
|
import nodepath from "path";
|
|
408
461
|
import { omit } from "lodash-es";
|
|
@@ -432,6 +485,9 @@ var extractConfig = (source, format) => {
|
|
|
432
485
|
if (source.name) {
|
|
433
486
|
project.name = source.name;
|
|
434
487
|
}
|
|
488
|
+
if (source.cli.forked_from && Object.keys(source.cli.forked_from).length) {
|
|
489
|
+
project.forked_from = source.cli.forked_from;
|
|
490
|
+
}
|
|
435
491
|
const workspace = {
|
|
436
492
|
...source.config
|
|
437
493
|
};
|
|
@@ -587,10 +643,13 @@ import { omitBy as omitBy3, isNil as isNil4 } from "lodash-es";
|
|
|
587
643
|
// src/util/omit-nil.ts
|
|
588
644
|
import { omitBy as omitBy2, isNil as isNil3 } from "lodash-es";
|
|
589
645
|
var omitNil = (obj, key) => {
|
|
590
|
-
if (obj[key]) {
|
|
646
|
+
if (key && obj[key]) {
|
|
591
647
|
obj[key] = omitBy2(obj[key], isNil3);
|
|
648
|
+
} else {
|
|
649
|
+
return omitBy2(obj, isNil3);
|
|
592
650
|
}
|
|
593
651
|
};
|
|
652
|
+
var omit_nil_default = omitNil;
|
|
594
653
|
var tidyOpenfn = (obj) => omitNil(obj, "openfn");
|
|
595
654
|
|
|
596
655
|
// src/serialize/to-project.ts
|
|
@@ -707,7 +766,7 @@ var mapEdge = (edge) => {
|
|
|
707
766
|
e.condition = edge.condition_type;
|
|
708
767
|
}
|
|
709
768
|
if (edge.condition_label) {
|
|
710
|
-
e.
|
|
769
|
+
e.label = edge.condition_label;
|
|
711
770
|
}
|
|
712
771
|
if (edge.id) {
|
|
713
772
|
e.openfn = {
|
|
@@ -728,7 +787,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
728
787
|
mapped.id = slugify(workflow.name);
|
|
729
788
|
}
|
|
730
789
|
Object.values(workflow.triggers).forEach((trigger) => {
|
|
731
|
-
const { type, ...otherProps } = trigger;
|
|
790
|
+
const { type, enabled, ...otherProps } = trigger;
|
|
732
791
|
if (!mapped.start) {
|
|
733
792
|
mapped.start = type;
|
|
734
793
|
}
|
|
@@ -738,6 +797,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
738
797
|
mapped.steps.push({
|
|
739
798
|
id: type,
|
|
740
799
|
type,
|
|
800
|
+
enabled,
|
|
741
801
|
openfn: renameKeys(otherProps, { id: "uuid" }),
|
|
742
802
|
next: connectedEdges.reduce((obj, edge) => {
|
|
743
803
|
const target = Object.values(jobs).find(
|
|
@@ -829,16 +889,19 @@ import path3 from "node:path";
|
|
|
829
889
|
import { glob } from "glob";
|
|
830
890
|
import { omit as omit2 } from "lodash-es";
|
|
831
891
|
var parseProject = async (options) => {
|
|
832
|
-
const { root, logger } = options;
|
|
892
|
+
const { root, logger, alias } = options;
|
|
833
893
|
const { type, content } = findWorkspaceFile(root);
|
|
834
894
|
const context = loadWorkspaceFile(content, type);
|
|
835
895
|
const config = buildConfig(options.config ?? context.workspace);
|
|
836
896
|
const proj = {
|
|
837
897
|
id: context.project?.id,
|
|
838
898
|
name: context.project?.name,
|
|
839
|
-
openfn: omit2(context.project, ["id"]),
|
|
899
|
+
openfn: omit2(context.project, ["id", "forked_from"]),
|
|
840
900
|
config,
|
|
841
|
-
workflows: []
|
|
901
|
+
workflows: [],
|
|
902
|
+
cli: omit_nil_default({
|
|
903
|
+
forked_from: context.project.forked_from
|
|
904
|
+
})
|
|
842
905
|
};
|
|
843
906
|
const workflowDir = config.workflowRoot ?? config.dirs?.workflows ?? "workflows";
|
|
844
907
|
const fileType = config.formats?.workflow ?? "yaml";
|
|
@@ -888,7 +951,10 @@ var parseProject = async (options) => {
|
|
|
888
951
|
continue;
|
|
889
952
|
}
|
|
890
953
|
}
|
|
891
|
-
return new Project(proj,
|
|
954
|
+
return new Project(proj, {
|
|
955
|
+
alias,
|
|
956
|
+
...context.workspace
|
|
957
|
+
});
|
|
892
958
|
};
|
|
893
959
|
|
|
894
960
|
// src/util/uuid.ts
|
|
@@ -934,7 +1000,7 @@ function baseMerge(target, source, sourceKeys, assigns = {}) {
|
|
|
934
1000
|
return assign(target, { ...pickedSource, ...assigns });
|
|
935
1001
|
}
|
|
936
1002
|
|
|
937
|
-
// src/merge/merge-
|
|
1003
|
+
// src/merge/merge-workflow.ts
|
|
938
1004
|
var clone2 = (obj) => JSON.parse(JSON.stringify(obj));
|
|
939
1005
|
function mergeWorkflows(source, target, mappings) {
|
|
940
1006
|
const targetNodes = {};
|
|
@@ -979,6 +1045,7 @@ function mergeWorkflows(source, target, mappings) {
|
|
|
979
1045
|
return {
|
|
980
1046
|
...target,
|
|
981
1047
|
...newSource,
|
|
1048
|
+
history: source.history ?? target.history,
|
|
982
1049
|
openfn: {
|
|
983
1050
|
...target.openfn,
|
|
984
1051
|
...source.openfn,
|
|
@@ -1239,6 +1306,26 @@ function getDuplicates(arr) {
|
|
|
1239
1306
|
return Array.from(duplicates);
|
|
1240
1307
|
}
|
|
1241
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
|
+
|
|
1242
1329
|
// src/merge/merge-project.ts
|
|
1243
1330
|
var SANDBOX_MERGE = "sandbox";
|
|
1244
1331
|
var UnsafeMergeError = class extends Error {
|
|
@@ -1247,35 +1334,34 @@ var defaultOptions = {
|
|
|
1247
1334
|
workflowMappings: {},
|
|
1248
1335
|
removeUnmapped: false,
|
|
1249
1336
|
force: true,
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
* If mode is replace, all properties on the source will override the target (including UUIDs, name)
|
|
1253
|
-
*/
|
|
1254
|
-
mode: SANDBOX_MERGE
|
|
1337
|
+
mode: SANDBOX_MERGE,
|
|
1338
|
+
onlyUpdated: false
|
|
1255
1339
|
};
|
|
1256
1340
|
function merge(source, target, opts) {
|
|
1257
1341
|
const options = defaultsDeep(
|
|
1258
1342
|
opts,
|
|
1259
1343
|
defaultOptions
|
|
1260
1344
|
);
|
|
1261
|
-
const dupTargetMappings = getDuplicates(
|
|
1262
|
-
Object.values(options.workflowMappings ?? {})
|
|
1263
|
-
);
|
|
1264
|
-
if (dupTargetMappings.length) {
|
|
1265
|
-
throw new Error(
|
|
1266
|
-
`The following target workflows have multiple source workflows merging into them: ${dupTargetMappings.join(
|
|
1267
|
-
", "
|
|
1268
|
-
)}`
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
1345
|
const finalWorkflows = [];
|
|
1272
1346
|
const usedTargetIds = /* @__PURE__ */ new Set();
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
+
}
|
|
1279
1365
|
const potentialConflicts = {};
|
|
1280
1366
|
for (const sourceWorkflow of sourceWorkflows) {
|
|
1281
1367
|
const targetId = options.workflowMappings?.[sourceWorkflow.id] ?? sourceWorkflow.id;
|
|
@@ -1327,6 +1413,7 @@ Pass --force to force the merge anyway`
|
|
|
1327
1413
|
...source.options
|
|
1328
1414
|
},
|
|
1329
1415
|
name: source.name ?? target.name,
|
|
1416
|
+
alias: source.alias ?? target.alias,
|
|
1330
1417
|
description: source.description ?? target.description,
|
|
1331
1418
|
credentials: source.credentials ?? target.credentials,
|
|
1332
1419
|
collections: source.collections ?? target.collections
|
|
@@ -1337,9 +1424,12 @@ Pass --force to force the merge anyway`
|
|
|
1337
1424
|
}
|
|
1338
1425
|
|
|
1339
1426
|
// src/util/project-diff.ts
|
|
1340
|
-
function diff(a, b) {
|
|
1427
|
+
function diff(a, b, workflows) {
|
|
1341
1428
|
const diffs = [];
|
|
1342
1429
|
for (const workflowA of a.workflows) {
|
|
1430
|
+
if (workflows?.length && !workflows.includes(workflowA.id)) {
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1343
1433
|
const workflowB = b.getWorkflow(workflowA.id);
|
|
1344
1434
|
if (!workflowB) {
|
|
1345
1435
|
diffs.push({ id: workflowA.id, type: "removed" });
|
|
@@ -1348,6 +1438,9 @@ function diff(a, b) {
|
|
|
1348
1438
|
}
|
|
1349
1439
|
}
|
|
1350
1440
|
for (const workflowB of b.workflows) {
|
|
1441
|
+
if (workflows?.length && !workflows.includes(workflowB.id)) {
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1351
1444
|
if (!a.getWorkflow(workflowB.id)) {
|
|
1352
1445
|
diffs.push({ id: workflowB.id, type: "added" });
|
|
1353
1446
|
}
|
|
@@ -1357,7 +1450,7 @@ function diff(a, b) {
|
|
|
1357
1450
|
|
|
1358
1451
|
// src/Project.ts
|
|
1359
1452
|
var maybeCreateWorkflow = (wf) => wf instanceof Workflow_default ? wf : new Workflow_default(wf);
|
|
1360
|
-
var Project = class {
|
|
1453
|
+
var Project = class _Project {
|
|
1361
1454
|
// what schema version is this?
|
|
1362
1455
|
// And how are we tracking this?
|
|
1363
1456
|
// version;
|
|
@@ -1431,6 +1524,10 @@ var Project = class {
|
|
|
1431
1524
|
get alias() {
|
|
1432
1525
|
return this.cli.alias ?? "main";
|
|
1433
1526
|
}
|
|
1527
|
+
set alias(value) {
|
|
1528
|
+
this.cli ??= {};
|
|
1529
|
+
this.cli.alias = value;
|
|
1530
|
+
}
|
|
1434
1531
|
get uuid() {
|
|
1435
1532
|
return this.openfn?.uuid ? `${this.openfn.uuid}` : void 0;
|
|
1436
1533
|
}
|
|
@@ -1486,8 +1583,8 @@ var Project = class {
|
|
|
1486
1583
|
return result;
|
|
1487
1584
|
}
|
|
1488
1585
|
// Compare this project with another and return a list of workflow changes
|
|
1489
|
-
diff(project) {
|
|
1490
|
-
return diff(this, project);
|
|
1586
|
+
diff(project, workflows = []) {
|
|
1587
|
+
return diff(this, project, workflows);
|
|
1491
1588
|
}
|
|
1492
1589
|
canMergeInto(target) {
|
|
1493
1590
|
const potentialConflicts = {};
|
|
@@ -1503,6 +1600,16 @@ var Project = class {
|
|
|
1503
1600
|
}
|
|
1504
1601
|
return true;
|
|
1505
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
|
+
}
|
|
1506
1613
|
};
|
|
1507
1614
|
var Project_default = Project;
|
|
1508
1615
|
|
|
@@ -1623,6 +1730,12 @@ var Workspace = class {
|
|
|
1623
1730
|
list() {
|
|
1624
1731
|
return this.projects;
|
|
1625
1732
|
}
|
|
1733
|
+
get projectsPath() {
|
|
1734
|
+
return path4.join(this.root, this.config.dirs.projects);
|
|
1735
|
+
}
|
|
1736
|
+
get workflowsPath() {
|
|
1737
|
+
return path4.join(this.root, this.config.dirs.workflows);
|
|
1738
|
+
}
|
|
1626
1739
|
/** Get a project by its alias, id or UUID. Can also include a UUID */
|
|
1627
1740
|
get(nameyThing) {
|
|
1628
1741
|
return match_project_default(nameyThing, this.projects);
|
|
@@ -1683,12 +1796,18 @@ var initOperations = (options = {}) => {
|
|
|
1683
1796
|
if (!nodes[name]) {
|
|
1684
1797
|
const id = slugify(name);
|
|
1685
1798
|
nodes[name] = {
|
|
1686
|
-
|
|
1687
|
-
id,
|
|
1688
|
-
openfn: {
|
|
1689
|
-
uuid: uuid(id)
|
|
1690
|
-
}
|
|
1799
|
+
id
|
|
1691
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
|
+
}
|
|
1692
1811
|
}
|
|
1693
1812
|
return nodes[name];
|
|
1694
1813
|
};
|
|
@@ -1725,9 +1844,11 @@ var initOperations = (options = {}) => {
|
|
|
1725
1844
|
const n1 = parent.buildWorkflow();
|
|
1726
1845
|
const n2 = child.buildWorkflow();
|
|
1727
1846
|
const e = edge.buildWorkflow();
|
|
1728
|
-
|
|
1847
|
+
if (options.openfnUuid !== false) {
|
|
1848
|
+
e.openfn.uuid = uuid(`${n1.id}-${n2.id}`);
|
|
1849
|
+
}
|
|
1729
1850
|
n1.next ??= {};
|
|
1730
|
-
n1.next[n2.name] = e;
|
|
1851
|
+
n1.next[n2.id ?? slugify(n2.name)] = e;
|
|
1731
1852
|
return [n1, n2];
|
|
1732
1853
|
},
|
|
1733
1854
|
// node could just be a node name, or a node with props
|
|
@@ -1833,6 +1954,9 @@ function generateWorkflow(def, options = {}) {
|
|
|
1833
1954
|
raw.openfn.uuid = uuid;
|
|
1834
1955
|
}
|
|
1835
1956
|
const wf = new Workflow_default(raw);
|
|
1957
|
+
if (options.history) {
|
|
1958
|
+
wf.pushHistory(wf.getVersionHash());
|
|
1959
|
+
}
|
|
1836
1960
|
return wf;
|
|
1837
1961
|
}
|
|
1838
1962
|
function generateProject(name, workflowDefs, options = {}) {
|
|
@@ -1858,7 +1982,9 @@ export {
|
|
|
1858
1982
|
src_default as default,
|
|
1859
1983
|
diff,
|
|
1860
1984
|
generateProject,
|
|
1985
|
+
generateHash as generateVersionHash,
|
|
1861
1986
|
generateWorkflow,
|
|
1862
1987
|
jsonToYaml,
|
|
1988
|
+
match as versionsEqual,
|
|
1863
1989
|
yamlToJson
|
|
1864
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": [
|