@openfn/project 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +50 -68
- package/dist/index.js +199 -169
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,133 +1,114 @@
|
|
|
1
|
-
import * as l
|
|
1
|
+
import * as l from '@openfn/lexicon';
|
|
2
|
+
import l__default, { WorkspaceConfig, UUID } from '@openfn/lexicon';
|
|
3
|
+
import { Provisioner } from '@openfn/lexicon/lightning';
|
|
2
4
|
|
|
3
|
-
type OpenfnMeta = {
|
|
4
|
-
uuid?: string;
|
|
5
|
-
};
|
|
6
5
|
type WithMeta<T> = T & {
|
|
7
|
-
openfn?:
|
|
6
|
+
openfn?: l.NodeMeta;
|
|
8
7
|
};
|
|
9
8
|
declare class Workflow {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
index: {
|
|
13
|
-
steps: {};
|
|
14
|
-
edges: {};
|
|
15
|
-
uuid: {};
|
|
16
|
-
id: {};
|
|
17
|
-
};
|
|
9
|
+
workflow: l.Workflow;
|
|
10
|
+
index: any;
|
|
18
11
|
name?: string;
|
|
19
12
|
id: string;
|
|
20
|
-
openfn
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
openfn?: l.WorkflowMeta;
|
|
14
|
+
options: any;
|
|
15
|
+
constructor(workflow: l.Workflow);
|
|
16
|
+
get steps(): WithMeta<l.Job & l.Trigger>[];
|
|
17
|
+
_buildIndex(): void;
|
|
18
|
+
set(id: string, props: Partial<l.Job | l.StepEdge>): this;
|
|
19
|
+
get(id: string): WithMeta<l.Step | l.Trigger | l.StepEdge>;
|
|
20
|
+
meta(id: string): l.WorkflowMeta;
|
|
21
|
+
getEdge(from: string, to: string): WithMeta<l.ConditionalStepEdge>;
|
|
27
22
|
getAllEdges(): Record<string, string[]>;
|
|
28
23
|
getStep(id: string): Workflow["steps"][number];
|
|
29
|
-
getRoot():
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
getUUID(id: any): string;
|
|
33
|
-
toJSON(): JSON.Object;
|
|
24
|
+
getRoot(): WithMeta<l.Job & l.Trigger> | undefined;
|
|
25
|
+
getUUID(id: string): string;
|
|
26
|
+
toJSON(): Object;
|
|
34
27
|
getUUIDMap(): Record<string, string>;
|
|
35
28
|
getVersionHash(): string;
|
|
36
29
|
pushHistory(versionHash: string): void;
|
|
37
30
|
canMergeInto(target: Workflow): boolean;
|
|
38
31
|
}
|
|
39
32
|
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
workflows: string;
|
|
44
|
-
projects: string;
|
|
45
|
-
};
|
|
46
|
-
formats: {
|
|
47
|
-
openfn: FileFormats$1;
|
|
48
|
-
project: FileFormats$1;
|
|
49
|
-
workflow: FileFormats$1;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
33
|
+
type fromAppStateConfig = Partial<l.WorkspaceConfig> & {
|
|
34
|
+
format?: 'yaml' | 'json';
|
|
35
|
+
};
|
|
52
36
|
|
|
53
|
-
type FromPathConfig = {
|
|
54
|
-
|
|
37
|
+
type FromPathConfig = l.WorkspaceConfig & {
|
|
38
|
+
format: 'json' | 'yaml';
|
|
55
39
|
};
|
|
56
40
|
|
|
57
41
|
type FromFsConfig = {
|
|
58
42
|
root: string;
|
|
59
43
|
};
|
|
60
44
|
|
|
61
|
-
type MergeProjectOptions =
|
|
45
|
+
type MergeProjectOptions = {
|
|
62
46
|
workflowMappings: Record<string, string>;
|
|
63
47
|
removeUnmapped: boolean;
|
|
64
48
|
force: boolean;
|
|
65
|
-
}
|
|
49
|
+
};
|
|
66
50
|
|
|
67
51
|
declare class Workspace {
|
|
68
|
-
config
|
|
69
|
-
|
|
52
|
+
config: l.WorkspaceConfig;
|
|
53
|
+
activeProject?: l.ProjectMeta;
|
|
70
54
|
private projects;
|
|
71
55
|
private projectPaths;
|
|
72
56
|
private isValid;
|
|
73
57
|
constructor(workspacePath: string);
|
|
74
58
|
loadProject(): void;
|
|
75
59
|
list(): Project[];
|
|
60
|
+
/** Get a project by its id or UUID */
|
|
76
61
|
get(id: string): Project | undefined;
|
|
77
62
|
getProjectPath(id: string): string | undefined;
|
|
78
63
|
getActiveProject(): Project | undefined;
|
|
79
|
-
getConfig(): Partial<WorkspaceConfig>;
|
|
80
|
-
get activeProjectId():
|
|
64
|
+
getConfig(): Partial<l.WorkspaceConfig>;
|
|
65
|
+
get activeProjectId(): unknown;
|
|
81
66
|
get valid(): boolean;
|
|
82
67
|
}
|
|
83
68
|
|
|
84
|
-
type
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
project: FileFormats;
|
|
69
|
+
type UUIDMap = {
|
|
70
|
+
[workflowId: string]: {
|
|
71
|
+
self?: UUID;
|
|
72
|
+
children: {
|
|
73
|
+
[nodeId: string]: UUID;
|
|
74
|
+
};
|
|
91
75
|
};
|
|
92
76
|
};
|
|
93
77
|
declare class Project {
|
|
94
|
-
/** project name */
|
|
78
|
+
/** Human readable project name. This corresponds to the label in Lightning */
|
|
95
79
|
name?: string;
|
|
80
|
+
/** Project id. Must be url safe. May be derived from the name. NOT a UUID */
|
|
81
|
+
id: string;
|
|
96
82
|
description?: string;
|
|
97
83
|
history: string[];
|
|
98
84
|
workflows: Workflow[];
|
|
99
85
|
options: any;
|
|
100
86
|
meta: any;
|
|
101
|
-
openfn?:
|
|
87
|
+
openfn?: l__default.ProjectMeta;
|
|
102
88
|
workspace?: Workspace;
|
|
103
|
-
config: WorkspaceConfig;
|
|
89
|
+
config: l__default.WorkspaceConfig;
|
|
104
90
|
collections: any;
|
|
105
|
-
|
|
106
|
-
static from(type: '
|
|
91
|
+
credentials: string[];
|
|
92
|
+
static from(type: 'state', data: Provisioner.Project, meta?: Partial<l__default.ProjectMeta>, config?: fromAppStateConfig): Promise<Project>;
|
|
93
|
+
static from(type: 'fs', options: FromFsConfig): Promise<Project>;
|
|
107
94
|
static from(type: 'path', data: string, options?: {
|
|
108
95
|
config?: FromPathConfig;
|
|
109
|
-
}): Project
|
|
110
|
-
static
|
|
111
|
-
|
|
112
|
-
constructor(data: l.Project, repoConfig?: RepoOptions);
|
|
96
|
+
}): Promise<Project>;
|
|
97
|
+
static merge(source: Project, target: Project, options?: Partial<MergeProjectOptions>): Project;
|
|
98
|
+
constructor(data: Partial<l__default.Project>, config?: Partial<l__default.WorkspaceConfig>);
|
|
113
99
|
setConfig(config: Partial<WorkspaceConfig>): void;
|
|
114
100
|
serialize(type?: 'json' | 'yaml' | 'fs' | 'state', options?: any): any;
|
|
115
|
-
getVersionHash(): void;
|
|
116
101
|
getWorkflow(idOrName: string): Workflow | undefined;
|
|
117
102
|
getIdentifier(): string;
|
|
118
|
-
compare(proj: Project): void;
|
|
119
103
|
getUUID(workflow: string | Workflow, stepId: string, otherStep?: string): any;
|
|
120
104
|
/**
|
|
121
105
|
* Returns a map of ids:uuids for everything in the project
|
|
122
106
|
*/
|
|
123
|
-
getUUIDMap(
|
|
124
|
-
workflows: boolean;
|
|
125
|
-
project: false;
|
|
126
|
-
}): {};
|
|
107
|
+
getUUIDMap(): UUIDMap;
|
|
127
108
|
}
|
|
128
109
|
|
|
129
110
|
declare function yamlToJson(y: string): any;
|
|
130
|
-
declare function jsonToYaml(json: string |
|
|
111
|
+
declare function jsonToYaml(json: string | Object): string;
|
|
131
112
|
|
|
132
113
|
type GenerateWorkflowOptions = {
|
|
133
114
|
name: string;
|
|
@@ -138,6 +119,7 @@ type GenerateWorkflowOptions = {
|
|
|
138
119
|
};
|
|
139
120
|
type GenerateProjectOptions = GenerateWorkflowOptions & {
|
|
140
121
|
uuidMap: Array<Record<string, string>>;
|
|
122
|
+
uuid?: string | number;
|
|
141
123
|
};
|
|
142
124
|
/**
|
|
143
125
|
* Generate a Workflow from a simple text based representation
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,12 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/Project.ts
|
|
8
|
+
import { humanId } from "human-id";
|
|
9
|
+
|
|
7
10
|
// src/util/slugify.ts
|
|
8
11
|
function slugify(text) {
|
|
9
|
-
return text?.replace(/\W/g, " ").trim().replace(/\s+/g, "-").toLowerCase();
|
|
12
|
+
return text?.replace(/\W/g, " ").trim().replace(/\s+/g, "-").toLowerCase() ?? "";
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
// src/util/version.ts
|
|
@@ -22,7 +25,7 @@ var generateHash = (workflow, source = "cli") => {
|
|
|
22
25
|
"name",
|
|
23
26
|
"adaptors",
|
|
24
27
|
"adaptor",
|
|
25
|
-
// there's
|
|
28
|
+
// there's both adaptor & adaptors key in steps somehow
|
|
26
29
|
"expression",
|
|
27
30
|
"configuration",
|
|
28
31
|
// assumes a string credential id
|
|
@@ -52,12 +55,13 @@ var generateHash = (workflow, source = "cli") => {
|
|
|
52
55
|
}
|
|
53
56
|
});
|
|
54
57
|
if (step.next && Array.isArray(step.next)) {
|
|
55
|
-
const
|
|
58
|
+
const steps2 = step.next.slice();
|
|
59
|
+
steps2.slice().sort((a, b) => {
|
|
56
60
|
const aLabel = a.label || "";
|
|
57
61
|
const bLabel = b.label || "";
|
|
58
62
|
return aLabel.localeCompare(bLabel);
|
|
59
63
|
});
|
|
60
|
-
for (const edge of
|
|
64
|
+
for (const edge of step.next) {
|
|
61
65
|
edgeKeys.forEach((key) => {
|
|
62
66
|
if (isDefined(edge[key])) {
|
|
63
67
|
parts.push(key, serializeValue(edge[key]));
|
|
@@ -86,6 +90,8 @@ var Workflow = class {
|
|
|
86
90
|
name;
|
|
87
91
|
id;
|
|
88
92
|
openfn;
|
|
93
|
+
options;
|
|
94
|
+
// TODO
|
|
89
95
|
constructor(workflow) {
|
|
90
96
|
this.index = {
|
|
91
97
|
steps: {},
|
|
@@ -111,13 +117,14 @@ var Workflow = class {
|
|
|
111
117
|
}
|
|
112
118
|
this.openfn = openfn;
|
|
113
119
|
this.options = options;
|
|
114
|
-
this
|
|
120
|
+
this._buildIndex();
|
|
115
121
|
}
|
|
116
122
|
get steps() {
|
|
117
123
|
return this.workflow.steps;
|
|
118
124
|
}
|
|
119
|
-
|
|
120
|
-
for (const
|
|
125
|
+
_buildIndex() {
|
|
126
|
+
for (const step of this.workflow.steps) {
|
|
127
|
+
const s = step;
|
|
121
128
|
this.index.steps[s.id] = s;
|
|
122
129
|
this.index.uuid[s.id] = s.openfn?.uuid;
|
|
123
130
|
if (s.openfn?.uuid) {
|
|
@@ -171,7 +178,8 @@ var Workflow = class {
|
|
|
171
178
|
}
|
|
172
179
|
getAllEdges() {
|
|
173
180
|
const edges = {};
|
|
174
|
-
for (const
|
|
181
|
+
for (const s of this.steps) {
|
|
182
|
+
const step = s;
|
|
175
183
|
const next = typeof step.next === "string" ? { [step.next]: true } : step.next || {};
|
|
176
184
|
for (const toNode of Object.keys(next)) {
|
|
177
185
|
if (!Array.isArray(edges[step.id]))
|
|
@@ -215,14 +223,10 @@ var Workflow = class {
|
|
|
215
223
|
}
|
|
216
224
|
// return true if the current workflow can be merged into the target workflow without losing any changes
|
|
217
225
|
canMergeInto(target) {
|
|
218
|
-
const thisHistory = this.workflow.history?.concat(this.getVersionHash());
|
|
219
|
-
const targetHistory = target.workflow.history?.concat(
|
|
220
|
-
target.getVersionHash()
|
|
221
|
-
);
|
|
226
|
+
const thisHistory = this.workflow.history?.concat(this.getVersionHash()) ?? [];
|
|
227
|
+
const targetHistory = target.workflow.history?.concat(target.getVersionHash()) ?? [];
|
|
222
228
|
const targetHead = targetHistory[targetHistory.length - 1];
|
|
223
|
-
|
|
224
|
-
return true;
|
|
225
|
-
return false;
|
|
229
|
+
return thisHistory.indexOf(targetHead) > -1;
|
|
226
230
|
}
|
|
227
231
|
};
|
|
228
232
|
var Workflow_default = Workflow;
|
|
@@ -240,11 +244,12 @@ function to_json_default(project) {
|
|
|
240
244
|
return {
|
|
241
245
|
// There must be a better way to do this?
|
|
242
246
|
// Do we just serialize all public fields?
|
|
247
|
+
id: project.id,
|
|
243
248
|
name: project.name,
|
|
244
249
|
description: project.description,
|
|
245
250
|
config: project.config,
|
|
246
251
|
meta: project.meta,
|
|
247
|
-
workflows: project.workflows,
|
|
252
|
+
workflows: project.workflows.map((w) => w.toJSON()),
|
|
248
253
|
collections: project.collections,
|
|
249
254
|
credentials: project.credentials,
|
|
250
255
|
openfn: project.openfn,
|
|
@@ -252,6 +257,10 @@ function to_json_default(project) {
|
|
|
252
257
|
};
|
|
253
258
|
}
|
|
254
259
|
|
|
260
|
+
// src/serialize/to-app-state.ts
|
|
261
|
+
import { pick, omitBy, isNil } from "lodash-es";
|
|
262
|
+
import { randomUUID } from "node:crypto";
|
|
263
|
+
|
|
255
264
|
// src/util/rename-keys.ts
|
|
256
265
|
function renameKeys(props = {}, keyMap) {
|
|
257
266
|
return Object.fromEntries(
|
|
@@ -277,19 +286,16 @@ function jsonToYaml(json) {
|
|
|
277
286
|
}
|
|
278
287
|
|
|
279
288
|
// src/serialize/to-app-state.ts
|
|
280
|
-
import { randomUUID } from "node:crypto";
|
|
281
289
|
function to_app_state_default(project, options = {}) {
|
|
282
|
-
const { uuid
|
|
283
|
-
const state =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
workflows: project.workflows.map(mapWorkflow)
|
|
292
|
-
};
|
|
290
|
+
const { uuid, endpoint, env, ...rest } = project.openfn ?? {};
|
|
291
|
+
const state = omitBy(
|
|
292
|
+
pick(project, ["name", "description", "collections"]),
|
|
293
|
+
isNil
|
|
294
|
+
);
|
|
295
|
+
state.id = uuid;
|
|
296
|
+
Object.assign(state, rest, project.options);
|
|
297
|
+
state.project_credentials = project.credentials ?? [];
|
|
298
|
+
state.workflows = project.workflows.map(mapWorkflow);
|
|
293
299
|
const shouldReturnYaml = options.format === "yaml" || !options.format && project.config.formats.project === "yaml";
|
|
294
300
|
if (shouldReturnYaml) {
|
|
295
301
|
return jsonToYaml(state);
|
|
@@ -304,11 +310,15 @@ var mapWorkflow = (workflow) => {
|
|
|
304
310
|
const wfState = {
|
|
305
311
|
...originalOpenfnProps,
|
|
306
312
|
id: workflow.openfn?.uuid ?? randomUUID(),
|
|
307
|
-
name: workflow.name,
|
|
308
313
|
jobs: [],
|
|
309
314
|
triggers: [],
|
|
310
|
-
edges: []
|
|
315
|
+
edges: [],
|
|
316
|
+
lock_version: workflow.openfn?.lock_version ?? null
|
|
317
|
+
// TODO needs testing
|
|
311
318
|
};
|
|
319
|
+
if (workflow.name) {
|
|
320
|
+
wfState.name = workflow.name;
|
|
321
|
+
}
|
|
312
322
|
const lookup = workflow.steps.reduce((obj, next) => {
|
|
313
323
|
if (!next.openfn?.uuid) {
|
|
314
324
|
next.openfn ??= {};
|
|
@@ -318,7 +328,7 @@ var mapWorkflow = (workflow) => {
|
|
|
318
328
|
return obj;
|
|
319
329
|
}, {});
|
|
320
330
|
workflow.steps.forEach((s) => {
|
|
321
|
-
let isTrigger;
|
|
331
|
+
let isTrigger = false;
|
|
322
332
|
let node;
|
|
323
333
|
if (s.type && !s.expression) {
|
|
324
334
|
isTrigger = true;
|
|
@@ -328,23 +338,28 @@ var mapWorkflow = (workflow) => {
|
|
|
328
338
|
};
|
|
329
339
|
wfState.triggers.push(node);
|
|
330
340
|
} else {
|
|
331
|
-
node =
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
341
|
+
node = omitBy(pick(s, ["name", "adaptor"]), isNil);
|
|
342
|
+
const { uuid: uuid2, ...otherOpenFnProps } = s.openfn ?? {};
|
|
343
|
+
node.id = uuid2;
|
|
344
|
+
Object.assign(node, otherOpenFnProps);
|
|
345
|
+
if (s.expression) {
|
|
346
|
+
node.body = s.expression;
|
|
347
|
+
}
|
|
348
|
+
node.project_credential_id = s.openfn?.project_credential_id ?? null;
|
|
349
|
+
node.keychain_credential_id = null;
|
|
337
350
|
wfState.jobs.push(node);
|
|
338
351
|
}
|
|
339
352
|
Object.keys(s.next ?? {}).forEach((next) => {
|
|
340
353
|
const rules = s.next[next];
|
|
354
|
+
const { uuid: uuid2, ...otherOpenFnProps } = rules.openfn ?? {};
|
|
341
355
|
const e = {
|
|
342
|
-
id:
|
|
356
|
+
id: uuid2 ?? randomUUID(),
|
|
343
357
|
target_job_id: lookup[next],
|
|
344
358
|
enabled: !rules.disabled,
|
|
345
359
|
source_trigger_id: null
|
|
346
360
|
// lightning complains if this isn't set, even if its falsy :(
|
|
347
361
|
};
|
|
362
|
+
Object.assign(e, otherOpenFnProps);
|
|
348
363
|
if (isTrigger) {
|
|
349
364
|
e.source_trigger_id = node.id;
|
|
350
365
|
} else {
|
|
@@ -368,13 +383,12 @@ import nodepath from "path";
|
|
|
368
383
|
// src/util/config.ts
|
|
369
384
|
import { readFileSync } from "node:fs";
|
|
370
385
|
import path from "node:path";
|
|
371
|
-
import { pickBy, isNil } from "lodash-es";
|
|
386
|
+
import { pickBy, isNil as isNil2 } from "lodash-es";
|
|
372
387
|
var buildConfig = (config = {}) => ({
|
|
373
388
|
...config,
|
|
374
389
|
dirs: {
|
|
375
|
-
projects: ".projects",
|
|
376
|
-
|
|
377
|
-
workflows: "workflows"
|
|
390
|
+
projects: config.dirs?.projects ?? ".projects",
|
|
391
|
+
workflows: config.dirs?.workflows ?? "workflows"
|
|
378
392
|
},
|
|
379
393
|
formats: {
|
|
380
394
|
openfn: config.formats?.openfn ?? "yaml",
|
|
@@ -384,7 +398,8 @@ var buildConfig = (config = {}) => ({
|
|
|
384
398
|
});
|
|
385
399
|
var extractConfig = (source) => {
|
|
386
400
|
const project = {
|
|
387
|
-
...source.openfn || {}
|
|
401
|
+
...source.openfn || {},
|
|
402
|
+
id: source.id
|
|
388
403
|
};
|
|
389
404
|
const workspace = {
|
|
390
405
|
...source.config
|
|
@@ -429,7 +444,7 @@ var loadWorkspaceFile = (contents, format = "yaml") => {
|
|
|
429
444
|
formats,
|
|
430
445
|
dirs
|
|
431
446
|
},
|
|
432
|
-
(value) => !
|
|
447
|
+
(value) => !isNil2(value)
|
|
433
448
|
);
|
|
434
449
|
} else {
|
|
435
450
|
project = json.project ?? {};
|
|
@@ -438,13 +453,10 @@ var loadWorkspaceFile = (contents, format = "yaml") => {
|
|
|
438
453
|
return { project, workspace };
|
|
439
454
|
};
|
|
440
455
|
var findWorkspaceFile = (dir = ".") => {
|
|
441
|
-
console.log({ dir });
|
|
442
456
|
let content, type;
|
|
443
457
|
try {
|
|
444
458
|
type = "yaml";
|
|
445
|
-
console.log(path.resolve(path.join(dir, "openfn.yaml")));
|
|
446
459
|
content = readFileSync(path.resolve(path.join(dir, "openfn.yaml")), "utf8");
|
|
447
|
-
console.log({ content });
|
|
448
460
|
} catch (e) {
|
|
449
461
|
try {
|
|
450
462
|
type = "json";
|
|
@@ -453,7 +465,6 @@ var findWorkspaceFile = (dir = ".") => {
|
|
|
453
465
|
content = JSON.parse(file);
|
|
454
466
|
}
|
|
455
467
|
} catch (e2) {
|
|
456
|
-
console.log(e2);
|
|
457
468
|
throw e2;
|
|
458
469
|
}
|
|
459
470
|
}
|
|
@@ -479,13 +490,13 @@ function to_fs_default(project) {
|
|
|
479
490
|
}
|
|
480
491
|
return files;
|
|
481
492
|
}
|
|
482
|
-
var extractWorkflow = (project,
|
|
493
|
+
var extractWorkflow = (project, workflowId) => {
|
|
483
494
|
const format = project.config.formats.workflow;
|
|
484
|
-
const workflow = project.getWorkflow(
|
|
495
|
+
const workflow = project.getWorkflow(workflowId);
|
|
485
496
|
if (!workflow) {
|
|
486
|
-
throw new Error(`workflow not found: ${
|
|
497
|
+
throw new Error(`workflow not found: ${workflowId}`);
|
|
487
498
|
}
|
|
488
|
-
const root = project.config.dirs.
|
|
499
|
+
const root = project.config.dirs.workflows ?? project.config.workflowRoot ?? "workflows/";
|
|
489
500
|
const path5 = nodepath.join(root, workflow.id, workflow.id);
|
|
490
501
|
const wf = {
|
|
491
502
|
id: workflow.id,
|
|
@@ -503,17 +514,17 @@ var extractWorkflow = (project, workflowId2) => {
|
|
|
503
514
|
};
|
|
504
515
|
return handleOutput(wf, path5, format);
|
|
505
516
|
};
|
|
506
|
-
var extractStep = (project,
|
|
507
|
-
const workflow = project.getWorkflow(
|
|
517
|
+
var extractStep = (project, workflowId, stepId) => {
|
|
518
|
+
const workflow = project.getWorkflow(workflowId);
|
|
508
519
|
if (!workflow) {
|
|
509
|
-
throw new Error(`workflow not found: ${
|
|
520
|
+
throw new Error(`workflow not found: ${workflowId}`);
|
|
510
521
|
}
|
|
511
522
|
const step = workflow.steps.find((s) => s.id === stepId);
|
|
512
523
|
if (!step) {
|
|
513
524
|
throw new Error(`step not found: ${stepId}`);
|
|
514
525
|
}
|
|
515
526
|
if (step.expression) {
|
|
516
|
-
const root = project.config?.workflowRoot ?? "workflows/";
|
|
527
|
+
const root = project.config?.dirs.workflows ?? project.config?.workflowRoot ?? "workflows/";
|
|
517
528
|
const path5 = nodepath.join(root, `${workflow.id}/${step.id}.js`);
|
|
518
529
|
const content = step.expression;
|
|
519
530
|
return { path: path5, content };
|
|
@@ -533,17 +544,18 @@ var handleOutput = (data, filePath, format) => {
|
|
|
533
544
|
};
|
|
534
545
|
|
|
535
546
|
// src/parse/from-app-state.ts
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
var from_app_state_default = (state, config) => {
|
|
547
|
+
var from_app_state_default = (state, meta, config = {}) => {
|
|
548
|
+
let stateJson;
|
|
540
549
|
if (typeof state === "string") {
|
|
541
|
-
if (config
|
|
542
|
-
|
|
550
|
+
if (config.format === "yaml") {
|
|
551
|
+
stateJson = yamlToJson(state);
|
|
543
552
|
} else {
|
|
544
|
-
|
|
553
|
+
stateJson = JSON.parse(state);
|
|
545
554
|
}
|
|
555
|
+
} else {
|
|
556
|
+
stateJson = state;
|
|
546
557
|
}
|
|
558
|
+
delete config.format;
|
|
547
559
|
const {
|
|
548
560
|
id,
|
|
549
561
|
name,
|
|
@@ -554,27 +566,25 @@ var from_app_state_default = (state, config) => {
|
|
|
554
566
|
inserted_at,
|
|
555
567
|
updated_at,
|
|
556
568
|
...options
|
|
557
|
-
} =
|
|
569
|
+
} = stateJson;
|
|
558
570
|
const proj = {
|
|
559
571
|
name,
|
|
560
|
-
description,
|
|
572
|
+
description: description ?? void 0,
|
|
561
573
|
collections,
|
|
562
574
|
credentials,
|
|
563
|
-
options
|
|
575
|
+
options,
|
|
576
|
+
config
|
|
564
577
|
};
|
|
578
|
+
const { id: _ignore, ...restMeta } = meta;
|
|
565
579
|
proj.openfn = {
|
|
580
|
+
// @ts-ignore
|
|
566
581
|
uuid: id,
|
|
567
|
-
|
|
568
|
-
endpoint: config.endpoint,
|
|
569
|
-
env: config.env,
|
|
582
|
+
...restMeta,
|
|
570
583
|
inserted_at,
|
|
571
584
|
updated_at
|
|
572
585
|
};
|
|
573
|
-
proj.
|
|
574
|
-
|
|
575
|
-
};
|
|
576
|
-
proj.workflows = state.workflows.map(mapWorkflow2);
|
|
577
|
-
return new Project(proj, config?.config);
|
|
586
|
+
proj.workflows = stateJson.workflows.map(mapWorkflow2);
|
|
587
|
+
return new Project(proj, config);
|
|
578
588
|
};
|
|
579
589
|
var mapTriggerEdgeCondition = (edge) => {
|
|
580
590
|
const e = {
|
|
@@ -600,7 +610,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
600
610
|
openfn: renameKeys(remoteProps, { id: "uuid" })
|
|
601
611
|
};
|
|
602
612
|
if (workflow.name) {
|
|
603
|
-
mapped.id =
|
|
613
|
+
mapped.id = slugify(workflow.name);
|
|
604
614
|
}
|
|
605
615
|
workflow.triggers.forEach((trigger) => {
|
|
606
616
|
const { type, ...otherProps } = trigger;
|
|
@@ -616,7 +626,7 @@ var mapWorkflow2 = (workflow) => {
|
|
|
616
626
|
if (!target) {
|
|
617
627
|
throw new Error(`Failed to find ${edge.target_job_id}`);
|
|
618
628
|
}
|
|
619
|
-
obj[
|
|
629
|
+
obj[slugify(target.name)] = mapTriggerEdgeCondition(edge);
|
|
620
630
|
return obj;
|
|
621
631
|
}, {})
|
|
622
632
|
});
|
|
@@ -627,16 +637,17 @@ var mapWorkflow2 = (workflow) => {
|
|
|
627
637
|
);
|
|
628
638
|
const { body: expression, name: name2, adaptor, ...remoteProps2 } = step;
|
|
629
639
|
const s = {
|
|
630
|
-
id:
|
|
640
|
+
id: slugify(name2),
|
|
631
641
|
name: name2,
|
|
632
642
|
expression,
|
|
633
643
|
adaptor,
|
|
644
|
+
// TODO is this wrong?
|
|
634
645
|
openfn: renameKeys(remoteProps2, { id: "uuid" })
|
|
635
646
|
};
|
|
636
647
|
if (outboundEdges.length) {
|
|
637
648
|
s.next = outboundEdges.reduce((next, edge) => {
|
|
638
649
|
const target = jobs.find((j) => j.id === edge.target_job_id);
|
|
639
|
-
next[
|
|
650
|
+
next[slugify(target.name)] = mapTriggerEdgeCondition(edge);
|
|
640
651
|
return next;
|
|
641
652
|
}, {});
|
|
642
653
|
}
|
|
@@ -648,13 +659,10 @@ var mapWorkflow2 = (workflow) => {
|
|
|
648
659
|
// src/parse/from-path.ts
|
|
649
660
|
import { extname } from "node:path";
|
|
650
661
|
import { readFile } from "node:fs/promises";
|
|
651
|
-
|
|
662
|
+
import { omit } from "lodash-es";
|
|
663
|
+
var from_path_default = async (path5, config = {}) => {
|
|
652
664
|
const ext = extname(path5).toLowerCase();
|
|
653
665
|
const source = await readFile(path5, "utf8");
|
|
654
|
-
const config = {
|
|
655
|
-
format: null,
|
|
656
|
-
config: options.config
|
|
657
|
-
};
|
|
658
666
|
let state;
|
|
659
667
|
if (ext === ".json") {
|
|
660
668
|
config.format = "json";
|
|
@@ -665,7 +673,8 @@ var from_path_default = async (path5, options = {}) => {
|
|
|
665
673
|
} else {
|
|
666
674
|
throw new Error(`Cannot load a project from a ${ext} file`);
|
|
667
675
|
}
|
|
668
|
-
|
|
676
|
+
const meta = {};
|
|
677
|
+
return from_app_state_default(state, meta, omit(config, ["format"]));
|
|
669
678
|
};
|
|
670
679
|
|
|
671
680
|
// src/parse/from-fs.ts
|
|
@@ -687,18 +696,18 @@ var get_identifier_default = (config = {}) => {
|
|
|
687
696
|
};
|
|
688
697
|
|
|
689
698
|
// src/parse/from-fs.ts
|
|
690
|
-
var parseProject = async (options
|
|
699
|
+
var parseProject = async (options) => {
|
|
691
700
|
const { root } = options;
|
|
692
701
|
const { type, content } = findWorkspaceFile(root);
|
|
693
702
|
const context = loadWorkspaceFile(content, type);
|
|
694
703
|
const config = buildConfig(context.workspace);
|
|
695
|
-
let state;
|
|
704
|
+
let state = null;
|
|
696
705
|
const identifier = get_identifier_default({
|
|
697
706
|
endpoint: context.project?.endpoint,
|
|
698
707
|
env: context.project?.env
|
|
699
708
|
});
|
|
700
709
|
try {
|
|
701
|
-
const format = config.formats?.project ?? config.formats?.
|
|
710
|
+
const format = config.formats?.project ?? config.formats?.project ?? "yaml";
|
|
702
711
|
const statePath = path2.join(
|
|
703
712
|
root,
|
|
704
713
|
config.dirs?.projects ?? ".projects",
|
|
@@ -710,6 +719,7 @@ var parseProject = async (options = {}) => {
|
|
|
710
719
|
console.warn(`Failed to find state file for ${identifier}`);
|
|
711
720
|
}
|
|
712
721
|
const proj = {
|
|
722
|
+
name: state?.name,
|
|
713
723
|
openfn: context.project,
|
|
714
724
|
config,
|
|
715
725
|
workflows: []
|
|
@@ -720,7 +730,6 @@ var parseProject = async (options = {}) => {
|
|
|
720
730
|
const candidateWfs = await glob(pattern, {
|
|
721
731
|
ignore: ["**node_modules/**", "**tmp**"]
|
|
722
732
|
});
|
|
723
|
-
const workflows = [];
|
|
724
733
|
for (const filePath of candidateWfs) {
|
|
725
734
|
const candidate = await fs.readFile(filePath, "utf-8");
|
|
726
735
|
try {
|
|
@@ -729,7 +738,7 @@ var parseProject = async (options = {}) => {
|
|
|
729
738
|
const wfState = (state && state.getWorkflow(wf.id)) ?? {};
|
|
730
739
|
wf.openfn = {
|
|
731
740
|
uuid: wfState.openfn?.uuid ?? null
|
|
732
|
-
// TODO do we need to transfer more stuff?
|
|
741
|
+
// TODO do we need to transfer more stuff? Options maybe?
|
|
733
742
|
};
|
|
734
743
|
for (const step of wf.steps) {
|
|
735
744
|
if (step.expression && step.expression.endsWith(".js")) {
|
|
@@ -781,9 +790,7 @@ var getUuidForStep = (project, workflow, stepId) => {
|
|
|
781
790
|
var getUuidForEdge = (project, workflow, from, to) => {
|
|
782
791
|
const wf = typeof workflow === "string" ? project.getWorkflow(workflow) : workflow;
|
|
783
792
|
if (!wf) {
|
|
784
|
-
throw new Error(
|
|
785
|
-
`Workflow "${workflowId} not found in project ${project.id}`
|
|
786
|
-
);
|
|
793
|
+
throw new Error(`Workflow "${workflow} not found in project ${project.id}`);
|
|
787
794
|
}
|
|
788
795
|
for (const step of wf.steps) {
|
|
789
796
|
if (step.id === from) {
|
|
@@ -802,43 +809,48 @@ var getUuidForEdge = (project, workflow, from, to) => {
|
|
|
802
809
|
import { defaultsDeep, isEmpty } from "lodash-es";
|
|
803
810
|
|
|
804
811
|
// src/util/base-merge.ts
|
|
805
|
-
import { pick, assign } from "lodash-es";
|
|
812
|
+
import { pick as pick2, assign } from "lodash-es";
|
|
806
813
|
function baseMerge(target, source, sourceKeys, assigns = {}) {
|
|
807
|
-
const pickedSource = sourceKeys ?
|
|
814
|
+
const pickedSource = sourceKeys ? pick2(source, sourceKeys) : source;
|
|
808
815
|
return assign(target, { ...pickedSource, ...assigns });
|
|
809
816
|
}
|
|
810
817
|
|
|
811
818
|
// src/merge/merge-node.ts
|
|
819
|
+
var clone2 = (obj) => JSON.parse(JSON.stringify(obj));
|
|
812
820
|
function mergeWorkflows(source, target, mappings) {
|
|
813
821
|
const targetNodes = {};
|
|
814
|
-
for (const
|
|
815
|
-
targetNodes[
|
|
822
|
+
for (const targetStep of target.steps) {
|
|
823
|
+
targetNodes[targetStep.openfn?.uuid || targetStep.id] = targetStep;
|
|
824
|
+
}
|
|
816
825
|
const steps = [];
|
|
817
|
-
for (const
|
|
818
|
-
let newNode =
|
|
819
|
-
if (
|
|
820
|
-
const preservedId = mappings.nodes[
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
)
|
|
825
|
-
const key =
|
|
826
|
-
if (
|
|
826
|
+
for (const sourceStep of source.steps) {
|
|
827
|
+
let newNode = clone2(sourceStep);
|
|
828
|
+
if (sourceStep.id in mappings.nodes) {
|
|
829
|
+
const preservedId = mappings.nodes[sourceStep.id];
|
|
830
|
+
const toNodeIds = Object.keys(
|
|
831
|
+
typeof sourceStep.next === "string" ? { [sourceStep.next]: true } : sourceStep.next || {}
|
|
832
|
+
);
|
|
833
|
+
for (const toNode of toNodeIds) {
|
|
834
|
+
const key = sourceStep.id + "-" + toNode;
|
|
835
|
+
if (key in mappings.edges) {
|
|
827
836
|
const preservedEdgeId = mappings.edges[key];
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
...
|
|
831
|
-
openfn: {
|
|
837
|
+
const edge = sourceStep.next?.[toNode] || {};
|
|
838
|
+
sourceStep.next[toNode] = {
|
|
839
|
+
...edge,
|
|
840
|
+
openfn: Object.assign({}, edge?.openfn, {
|
|
841
|
+
uuid: preservedEdgeId
|
|
842
|
+
})
|
|
832
843
|
};
|
|
833
844
|
}
|
|
834
845
|
}
|
|
835
|
-
newNode = baseMerge(targetNodes[preservedId],
|
|
846
|
+
newNode = baseMerge(targetNodes[preservedId], sourceStep, [
|
|
836
847
|
"id",
|
|
837
848
|
"name",
|
|
849
|
+
// @ts-ignore
|
|
838
850
|
"adaptor",
|
|
851
|
+
"adaptors",
|
|
839
852
|
"expression",
|
|
840
|
-
"next"
|
|
841
|
-
"previous"
|
|
853
|
+
"next"
|
|
842
854
|
]);
|
|
843
855
|
} else {
|
|
844
856
|
}
|
|
@@ -962,7 +974,7 @@ function findBestMatch(sourceStep, candidates, sourceEdges, targetEdges, getMapp
|
|
|
962
974
|
}
|
|
963
975
|
return null;
|
|
964
976
|
}
|
|
965
|
-
function mapEdges(sourceEdges,
|
|
977
|
+
function mapEdges(sourceEdges, _targetEdges, idMap, getTargetUUID) {
|
|
966
978
|
const edgeMapping = {};
|
|
967
979
|
for (const [parentId, children] of Object.entries(sourceEdges)) {
|
|
968
980
|
for (const childId of children) {
|
|
@@ -978,7 +990,7 @@ function mapEdges(sourceEdges, targetEdges, idMap, getTargetUUID) {
|
|
|
978
990
|
return edgeMapping;
|
|
979
991
|
}
|
|
980
992
|
function getStepUuid(step) {
|
|
981
|
-
return step?.openfn?.uuid
|
|
993
|
+
return step?.openfn?.uuid;
|
|
982
994
|
}
|
|
983
995
|
function mapStepsById(source, target) {
|
|
984
996
|
const targetIndex = {};
|
|
@@ -1083,8 +1095,7 @@ function mapStepByChildren(sourceStep, candidates, sourceEdges, targetEdges, get
|
|
|
1083
1095
|
};
|
|
1084
1096
|
}
|
|
1085
1097
|
function mapStepByExpression(sourceStep, candidates) {
|
|
1086
|
-
|
|
1087
|
-
return findByExpression(expression, candidates);
|
|
1098
|
+
return findByExpression(sourceStep.expression, candidates);
|
|
1088
1099
|
}
|
|
1089
1100
|
|
|
1090
1101
|
// src/util/get-duplicates.ts
|
|
@@ -1102,15 +1113,20 @@ function getDuplicates(arr) {
|
|
|
1102
1113
|
}
|
|
1103
1114
|
|
|
1104
1115
|
// src/merge/merge-project.ts
|
|
1105
|
-
|
|
1116
|
+
var UnsafeMergeError = class extends Error {
|
|
1117
|
+
};
|
|
1118
|
+
function merge(source, target, opts) {
|
|
1106
1119
|
const defaultOptions = {
|
|
1107
1120
|
workflowMappings: {},
|
|
1108
1121
|
removeUnmapped: false,
|
|
1109
1122
|
force: true
|
|
1110
1123
|
};
|
|
1111
|
-
options = defaultsDeep(
|
|
1124
|
+
const options = defaultsDeep(
|
|
1125
|
+
opts,
|
|
1126
|
+
defaultOptions
|
|
1127
|
+
);
|
|
1112
1128
|
const dupTargetMappings = getDuplicates(
|
|
1113
|
-
Object.values(options
|
|
1129
|
+
Object.values(options.workflowMappings ?? {})
|
|
1114
1130
|
);
|
|
1115
1131
|
if (dupTargetMappings.length) {
|
|
1116
1132
|
throw new Error(
|
|
@@ -1132,11 +1148,11 @@ function merge(source, target, options) {
|
|
|
1132
1148
|
const targetId = options.workflowMappings?.[sourceWorkflow.id] ?? sourceWorkflow.id;
|
|
1133
1149
|
const targetWorkflow = target.getWorkflow(targetId);
|
|
1134
1150
|
if (targetWorkflow && !sourceWorkflow.canMergeInto(targetWorkflow)) {
|
|
1135
|
-
potentialConflicts[sourceWorkflow.
|
|
1151
|
+
potentialConflicts[sourceWorkflow.id] = targetWorkflow?.id;
|
|
1136
1152
|
}
|
|
1137
1153
|
}
|
|
1138
1154
|
if (Object.keys(potentialConflicts).length && !options?.force) {
|
|
1139
|
-
throw new
|
|
1155
|
+
throw new UnsafeMergeError(
|
|
1140
1156
|
`The below workflows can't be merged directly without losing data
|
|
1141
1157
|
${Object.entries(
|
|
1142
1158
|
potentialConflicts
|
|
@@ -1151,6 +1167,7 @@ Pass --force to force the merge anyway`
|
|
|
1151
1167
|
usedTargetIds.add(targetWorkflow.id);
|
|
1152
1168
|
const mappings = map_uuids_default(sourceWorkflow, targetWorkflow);
|
|
1153
1169
|
finalWorkflows.push(
|
|
1170
|
+
// @ts-ignore
|
|
1154
1171
|
mergeWorkflows(sourceWorkflow, targetWorkflow, mappings)
|
|
1155
1172
|
);
|
|
1156
1173
|
} else {
|
|
@@ -1165,7 +1182,9 @@ Pass --force to force the merge anyway`
|
|
|
1165
1182
|
}
|
|
1166
1183
|
}
|
|
1167
1184
|
return new Project(
|
|
1168
|
-
baseMerge(target, source, ["collections"], {
|
|
1185
|
+
baseMerge(target, source, ["collections"], {
|
|
1186
|
+
workflows: finalWorkflows
|
|
1187
|
+
})
|
|
1169
1188
|
);
|
|
1170
1189
|
}
|
|
1171
1190
|
|
|
@@ -1175,10 +1194,12 @@ var Project = class {
|
|
|
1175
1194
|
// what schema version is this?
|
|
1176
1195
|
// And how are we tracking this?
|
|
1177
1196
|
// version;
|
|
1178
|
-
/** project name */
|
|
1197
|
+
/** Human readable project name. This corresponds to the label in Lightning */
|
|
1179
1198
|
name;
|
|
1199
|
+
/** Project id. Must be url safe. May be derived from the name. NOT a UUID */
|
|
1200
|
+
id;
|
|
1180
1201
|
description;
|
|
1181
|
-
// array of version
|
|
1202
|
+
// array of version hashes
|
|
1182
1203
|
history = [];
|
|
1183
1204
|
workflows;
|
|
1184
1205
|
// option strings saved by the app
|
|
@@ -1191,25 +1212,20 @@ var Project = class {
|
|
|
1191
1212
|
openfn;
|
|
1192
1213
|
workspace;
|
|
1193
1214
|
config;
|
|
1194
|
-
// load a project from a state file (project.json)
|
|
1195
|
-
// or from a path (the file system)
|
|
1196
|
-
// TODO presumably we can detect a state file? Not a big deal?
|
|
1197
|
-
// collections for the project
|
|
1198
|
-
// TODO to be well typed
|
|
1199
1215
|
collections;
|
|
1200
|
-
|
|
1216
|
+
credentials;
|
|
1217
|
+
static async from(type, data, ...rest) {
|
|
1201
1218
|
if (type === "state") {
|
|
1202
|
-
return from_app_state_default(data,
|
|
1219
|
+
return from_app_state_default(data, rest[0], rest[1]);
|
|
1203
1220
|
} else if (type === "fs") {
|
|
1204
|
-
return parseProject(data
|
|
1221
|
+
return parseProject(data);
|
|
1205
1222
|
} else if (type === "path") {
|
|
1206
|
-
return from_path_default(data,
|
|
1223
|
+
return from_path_default(data, rest[0]);
|
|
1207
1224
|
}
|
|
1208
1225
|
throw new Error(`Didn't recognize type ${type}`);
|
|
1209
1226
|
}
|
|
1210
1227
|
// Diff two projects
|
|
1211
|
-
static diff(a, b) {
|
|
1212
|
-
}
|
|
1228
|
+
// /static diff(a: Project, b: Project) {}
|
|
1213
1229
|
// Merge a source project (staging) into the target project (main)
|
|
1214
1230
|
// Returns a new Project
|
|
1215
1231
|
// TODO: throw if histories have diverged
|
|
@@ -1221,16 +1237,16 @@ var Project = class {
|
|
|
1221
1237
|
// maybe this second arg is config - like env, branch rules, serialisation rules
|
|
1222
1238
|
// stuff that's external to the actual project and managed by the repo
|
|
1223
1239
|
// TODO maybe the constructor is (data, Workspace)
|
|
1224
|
-
constructor(data,
|
|
1225
|
-
this.
|
|
1240
|
+
constructor(data, config) {
|
|
1241
|
+
this.config = buildConfig(config);
|
|
1242
|
+
this.id = data.id ?? (data.name ? slugify(data.name) : humanId({ separator: "-", capitalize: false }));
|
|
1226
1243
|
this.name = data.name;
|
|
1227
|
-
this.description = data.description;
|
|
1244
|
+
this.description = data.description ?? void 0;
|
|
1228
1245
|
this.openfn = data.openfn;
|
|
1229
1246
|
this.options = data.options;
|
|
1230
1247
|
this.workflows = data.workflows?.map(maybeCreateWorkflow) ?? [];
|
|
1231
1248
|
this.collections = data.collections;
|
|
1232
1249
|
this.credentials = data.credentials;
|
|
1233
|
-
this.meta = data.meta;
|
|
1234
1250
|
}
|
|
1235
1251
|
setConfig(config) {
|
|
1236
1252
|
this.config = buildConfig(config);
|
|
@@ -1241,16 +1257,9 @@ var Project = class {
|
|
|
1241
1257
|
}
|
|
1242
1258
|
throw new Error(`Cannot serialize ${type}`);
|
|
1243
1259
|
}
|
|
1244
|
-
//
|
|
1245
|
-
// stamp? id? sha?
|
|
1246
|
-
// this builds a version string for the current state
|
|
1247
|
-
getVersionHash() {
|
|
1248
|
-
}
|
|
1249
|
-
// what else might we need?
|
|
1250
|
-
// get workflow by name or id
|
|
1251
|
-
// this is fuzzy, but is that wrong?
|
|
1260
|
+
// get workflow by name, id or uuid
|
|
1252
1261
|
getWorkflow(idOrName) {
|
|
1253
|
-
return this.workflows.find((wf) => wf.id == idOrName) || this.workflows.find((wf) => wf.name === idOrName);
|
|
1262
|
+
return this.workflows.find((wf) => wf.id == idOrName) || this.workflows.find((wf) => wf.name === idOrName) || this.workflows.find((wf) => wf.openfn?.uuid === idOrName);
|
|
1254
1263
|
}
|
|
1255
1264
|
// it's the name of the project.yaml file
|
|
1256
1265
|
// qualified name? Remote name? App name?
|
|
@@ -1259,8 +1268,7 @@ var Project = class {
|
|
|
1259
1268
|
return get_identifier_default(this.openfn);
|
|
1260
1269
|
}
|
|
1261
1270
|
// Compare this project with another and return a diff
|
|
1262
|
-
compare(proj) {
|
|
1263
|
-
}
|
|
1271
|
+
// compare(proj: Project) {}
|
|
1264
1272
|
// find the UUID for a given node or edge
|
|
1265
1273
|
// returns null if it doesn't exist
|
|
1266
1274
|
getUUID(workflow, stepId, otherStep) {
|
|
@@ -1272,7 +1280,7 @@ var Project = class {
|
|
|
1272
1280
|
/**
|
|
1273
1281
|
* Returns a map of ids:uuids for everything in the project
|
|
1274
1282
|
*/
|
|
1275
|
-
getUUIDMap(
|
|
1283
|
+
getUUIDMap() {
|
|
1276
1284
|
const result = {};
|
|
1277
1285
|
for (const wf of this.workflows) {
|
|
1278
1286
|
result[wf.id] = {
|
|
@@ -1305,10 +1313,10 @@ function pathExists(fpath, type) {
|
|
|
1305
1313
|
}
|
|
1306
1314
|
|
|
1307
1315
|
// src/Workspace.ts
|
|
1308
|
-
var PROJECT_EXTENSIONS = [".yaml", ".yml"];
|
|
1309
1316
|
var Workspace = class {
|
|
1317
|
+
// @ts-ignore config not defininitely assigned - it sure is
|
|
1310
1318
|
config;
|
|
1311
|
-
|
|
1319
|
+
activeProject;
|
|
1312
1320
|
projects = [];
|
|
1313
1321
|
projectPaths = /* @__PURE__ */ new Map();
|
|
1314
1322
|
isValid = false;
|
|
@@ -1316,26 +1324,38 @@ var Workspace = class {
|
|
|
1316
1324
|
let context;
|
|
1317
1325
|
try {
|
|
1318
1326
|
const { type, content } = findWorkspaceFile(workspacePath);
|
|
1319
|
-
console.log(content);
|
|
1320
1327
|
context = loadWorkspaceFile(content, type);
|
|
1321
1328
|
this.isValid = true;
|
|
1322
1329
|
} catch (e) {
|
|
1323
|
-
console.
|
|
1330
|
+
console.error(e);
|
|
1324
1331
|
return;
|
|
1325
1332
|
}
|
|
1326
1333
|
this.config = buildConfig(context.workspace);
|
|
1327
|
-
this.
|
|
1334
|
+
this.activeProject = context.project;
|
|
1328
1335
|
const projectsPath = path3.join(workspacePath, this.config.dirs.projects);
|
|
1329
1336
|
if (this.isValid && pathExists(projectsPath, "directory")) {
|
|
1337
|
+
const ext = `.${this.config.formats.project}`;
|
|
1330
1338
|
const stateFiles = fs3.readdirSync(projectsPath).filter(
|
|
1331
|
-
(fileName) =>
|
|
1339
|
+
(fileName) => path3.extname(fileName) === ext && path3.parse(fileName).name !== "openfn"
|
|
1332
1340
|
);
|
|
1333
1341
|
this.projects = stateFiles.map((file) => {
|
|
1334
1342
|
const stateFilePath = path3.join(projectsPath, file);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1343
|
+
try {
|
|
1344
|
+
const data = fs3.readFileSync(stateFilePath, "utf-8");
|
|
1345
|
+
const project = from_app_state_default(
|
|
1346
|
+
data,
|
|
1347
|
+
{},
|
|
1348
|
+
{
|
|
1349
|
+
...this.config,
|
|
1350
|
+
format: this.config?.formats.project
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
this.projectPaths.set(project.id, stateFilePath);
|
|
1354
|
+
return project;
|
|
1355
|
+
} catch (e) {
|
|
1356
|
+
console.warn(`Failed to load project from ${stateFilePath}`);
|
|
1357
|
+
console.warn(e);
|
|
1358
|
+
}
|
|
1339
1359
|
}).filter((s) => s);
|
|
1340
1360
|
}
|
|
1341
1361
|
}
|
|
@@ -1349,15 +1369,15 @@ var Workspace = class {
|
|
|
1349
1369
|
list() {
|
|
1350
1370
|
return this.projects;
|
|
1351
1371
|
}
|
|
1352
|
-
|
|
1372
|
+
/** Get a project by its id or UUID */
|
|
1353
1373
|
get(id) {
|
|
1354
|
-
return this.projects.find((p) => p.
|
|
1374
|
+
return this.projects.find((p) => p.id === id) ?? this.projects.find((p) => p.openfn?.uuid === id);
|
|
1355
1375
|
}
|
|
1356
1376
|
getProjectPath(id) {
|
|
1357
1377
|
return this.projectPaths.get(id);
|
|
1358
1378
|
}
|
|
1359
1379
|
getActiveProject() {
|
|
1360
|
-
return this.projects.find((p) => p.
|
|
1380
|
+
return this.projects.find((p) => p.id === this.activeProject?.id) ?? this.projects.find((p) => p.openfn?.uuid === this.activeProject?.uuid);
|
|
1361
1381
|
}
|
|
1362
1382
|
// TODO this needs to return default values
|
|
1363
1383
|
// We should always rely on the workspace to load these values
|
|
@@ -1365,7 +1385,7 @@ var Workspace = class {
|
|
|
1365
1385
|
return this.config;
|
|
1366
1386
|
}
|
|
1367
1387
|
get activeProjectId() {
|
|
1368
|
-
return this.
|
|
1388
|
+
return this.activeProject?.id;
|
|
1369
1389
|
}
|
|
1370
1390
|
get valid() {
|
|
1371
1391
|
return this.isValid;
|
|
@@ -1377,6 +1397,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
|
|
|
1377
1397
|
import path4 from "node:path";
|
|
1378
1398
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1379
1399
|
import { grammar } from "ohm-js";
|
|
1400
|
+
import { isNil as isNil3 } from "lodash-es";
|
|
1380
1401
|
var parser;
|
|
1381
1402
|
var initOperations = (options = {}) => {
|
|
1382
1403
|
let nodes = {};
|
|
@@ -1496,6 +1517,10 @@ function generateWorkflow(def, options = {}) {
|
|
|
1496
1517
|
if (!parser) {
|
|
1497
1518
|
parser = createParser();
|
|
1498
1519
|
}
|
|
1520
|
+
let uuid;
|
|
1521
|
+
if (options.openfnUuid) {
|
|
1522
|
+
uuid = options.uuidSeed ? options.uuidSeed++ : randomUUID2();
|
|
1523
|
+
}
|
|
1499
1524
|
const raw = parser.parse(def, options);
|
|
1500
1525
|
if (!raw.name) {
|
|
1501
1526
|
raw.name = "Workflow";
|
|
@@ -1503,9 +1528,12 @@ function generateWorkflow(def, options = {}) {
|
|
|
1503
1528
|
if (!raw.id) {
|
|
1504
1529
|
raw.id = "workflow";
|
|
1505
1530
|
}
|
|
1506
|
-
if (options.
|
|
1531
|
+
if (options.uuidMap && raw.id in options.uuidMap) {
|
|
1532
|
+
uuid = options.uuidMap[raw.id];
|
|
1533
|
+
}
|
|
1534
|
+
if (!isNil3(uuid) && options.openfnUuid) {
|
|
1507
1535
|
raw.openfn ??= {};
|
|
1508
|
-
raw.openfn.uuid =
|
|
1536
|
+
raw.openfn.uuid = uuid;
|
|
1509
1537
|
}
|
|
1510
1538
|
const wf = new Workflow_default(raw);
|
|
1511
1539
|
return wf;
|
|
@@ -1520,7 +1548,9 @@ function generateProject(name, workflowDefs, options = {}) {
|
|
|
1520
1548
|
return new Project_default({
|
|
1521
1549
|
name,
|
|
1522
1550
|
workflows,
|
|
1523
|
-
openfn:
|
|
1551
|
+
openfn: {
|
|
1552
|
+
uuid: options.uuid ?? (options.openfnUuid ? randomUUID2() : void 0)
|
|
1553
|
+
}
|
|
1524
1554
|
});
|
|
1525
1555
|
}
|
|
1526
1556
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/project",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Read, serialize, replicate and sync OpenFn projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,13 +27,15 @@
|
|
|
27
27
|
"typescript": "^5.9.2"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@types/lodash-es": "^4.17.12",
|
|
30
31
|
"glob": "^11.0.2",
|
|
32
|
+
"human-id": "^4.1.1",
|
|
31
33
|
"lodash": "^4.17.21",
|
|
32
34
|
"lodash-es": "^4.17.21",
|
|
33
35
|
"ohm-js": "^17.2.1",
|
|
34
36
|
"yaml": "^2.2.2",
|
|
35
|
-
"@openfn/
|
|
36
|
-
"@openfn/
|
|
37
|
+
"@openfn/logger": "1.0.6",
|
|
38
|
+
"@openfn/lexicon": "^1.2.6"
|
|
37
39
|
},
|
|
38
40
|
"files": [
|
|
39
41
|
"dist",
|