@openfn/project 0.9.3 → 0.10.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/dist/index.d.ts +21 -6
- package/dist/index.js +164 -111
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -33,14 +33,17 @@ declare class Workflow {
|
|
|
33
33
|
|
|
34
34
|
type fromAppStateConfig = Partial<l.WorkspaceConfig> & {
|
|
35
35
|
format?: 'yaml' | 'json';
|
|
36
|
+
alias?: string;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
type FromPathConfig = l.WorkspaceConfig & {
|
|
39
40
|
format: 'json' | 'yaml';
|
|
41
|
+
alias?: string;
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
type FromFsConfig = {
|
|
43
45
|
root: string;
|
|
46
|
+
logger?: Logger;
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
type SerializedProject = Omit<Partial<l.Project>, 'workflows'> & {
|
|
@@ -67,11 +70,11 @@ declare class Workspace {
|
|
|
67
70
|
private projectPaths;
|
|
68
71
|
private isValid;
|
|
69
72
|
private logger;
|
|
70
|
-
constructor(workspacePath: string, logger?: Logger);
|
|
73
|
+
constructor(workspacePath: string, logger?: Logger, validate?: boolean);
|
|
71
74
|
loadProject(): void;
|
|
72
75
|
list(): Project[];
|
|
73
|
-
/** Get a project by its id or UUID */
|
|
74
|
-
get(
|
|
76
|
+
/** Get a project by its alias, id or UUID. Can also include a UUID */
|
|
77
|
+
get(nameyThing: string): Project | null;
|
|
75
78
|
getProjectPath(id: string): string | undefined;
|
|
76
79
|
getActiveProject(): Project | undefined;
|
|
77
80
|
getConfig(): Partial<l.WorkspaceConfig>;
|
|
@@ -87,6 +90,10 @@ type UUIDMap = {
|
|
|
87
90
|
};
|
|
88
91
|
};
|
|
89
92
|
};
|
|
93
|
+
type CLIMeta = {
|
|
94
|
+
version?: number;
|
|
95
|
+
alias?: string;
|
|
96
|
+
};
|
|
90
97
|
declare class Project {
|
|
91
98
|
/** Human readable project name. This corresponds to the label in Lightning */
|
|
92
99
|
name?: string;
|
|
@@ -96,7 +103,10 @@ declare class Project {
|
|
|
96
103
|
history: string[];
|
|
97
104
|
workflows: Workflow[];
|
|
98
105
|
options: any;
|
|
99
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Local metadata used by the CLI but not synced to Lightning
|
|
108
|
+
*/
|
|
109
|
+
cli: CLIMeta;
|
|
100
110
|
openfn?: l__default.ProjectMeta;
|
|
101
111
|
workspace?: Workspace;
|
|
102
112
|
config: l__default.WorkspaceConfig;
|
|
@@ -109,13 +119,18 @@ declare class Project {
|
|
|
109
119
|
config?: FromPathConfig;
|
|
110
120
|
}): Promise<Project>;
|
|
111
121
|
static merge(source: Project, target: Project, options?: Partial<MergeProjectOptions>): Project;
|
|
112
|
-
constructor(data
|
|
122
|
+
constructor(data?: Partial<l__default.Project>, meta?: Partial<l__default.WorkspaceConfig> & CLIMeta);
|
|
123
|
+
/** Local alias for the project. Comes from the file name. Not shared with Lightning. */
|
|
124
|
+
get alias(): string;
|
|
125
|
+
get uuid(): string | undefined;
|
|
126
|
+
get host(): string | undefined;
|
|
113
127
|
setConfig(config: Partial<WorkspaceConfig>): void;
|
|
114
128
|
serialize(type: 'project', options?: any): SerializedProject | string;
|
|
115
129
|
serialize(type: 'state', options?: any): Provisioner.Project | string;
|
|
116
130
|
serialize(type: 'fs', options?: any): Record<string, string>;
|
|
117
131
|
getWorkflow(idOrName: string): Workflow | undefined;
|
|
118
|
-
|
|
132
|
+
/** Returns a fully qualified name for the project, id, alias@domain */
|
|
133
|
+
get qname(): string;
|
|
119
134
|
getUUID(workflow: string | Workflow, stepId: string, otherStep?: string): any;
|
|
120
135
|
/**
|
|
121
136
|
* Returns a map of ids:uuids for everything in the project
|
package/dist/index.js
CHANGED
|
@@ -269,6 +269,8 @@ function jsonToYaml(json) {
|
|
|
269
269
|
|
|
270
270
|
// src/serialize/to-app-state.ts
|
|
271
271
|
var defaultJobProps = {
|
|
272
|
+
// TODO why does the provisioner throw if these keys are not set?
|
|
273
|
+
// Ok, 90% of jobs will have a credenial, but it's still optional right?
|
|
272
274
|
keychain_credential_id: null,
|
|
273
275
|
project_credential_id: null
|
|
274
276
|
};
|
|
@@ -337,6 +339,9 @@ var mapWorkflow = (workflow) => {
|
|
|
337
339
|
if (s.expression) {
|
|
338
340
|
node.body = s.expression;
|
|
339
341
|
}
|
|
342
|
+
if (typeof s.configuration === "string" && !s.configuration.endsWith(".json")) {
|
|
343
|
+
otherOpenFnProps.project_credential_id = s.configuration;
|
|
344
|
+
}
|
|
340
345
|
Object.assign(node, defaultJobProps, otherOpenFnProps);
|
|
341
346
|
wfState.jobs.push(node);
|
|
342
347
|
}
|
|
@@ -468,16 +473,16 @@ var findWorkspaceFile = (dir = ".") => {
|
|
|
468
473
|
var stringify = (json) => JSON.stringify(json, null, 2);
|
|
469
474
|
function to_fs_default(project) {
|
|
470
475
|
const files = {};
|
|
471
|
-
const { path:
|
|
472
|
-
files[
|
|
476
|
+
const { path: path6, content } = extractConfig(project);
|
|
477
|
+
files[path6] = content;
|
|
473
478
|
for (const wf of project.workflows) {
|
|
474
|
-
const { path:
|
|
475
|
-
files[
|
|
479
|
+
const { path: path7, content: content2 } = extractWorkflow(project, wf.id);
|
|
480
|
+
files[path7] = content2;
|
|
476
481
|
for (const s of wf.steps) {
|
|
477
482
|
const result = extractStep(project, wf.id, s.id);
|
|
478
483
|
if (result) {
|
|
479
|
-
const { path:
|
|
480
|
-
files[
|
|
484
|
+
const { path: path8, content: content3 } = result;
|
|
485
|
+
files[path8] = content3;
|
|
481
486
|
}
|
|
482
487
|
}
|
|
483
488
|
}
|
|
@@ -490,7 +495,7 @@ var extractWorkflow = (project, workflowId) => {
|
|
|
490
495
|
throw new Error(`workflow not found: ${workflowId}`);
|
|
491
496
|
}
|
|
492
497
|
const root = project.config.dirs.workflows ?? project.config.workflowRoot ?? "workflows/";
|
|
493
|
-
const
|
|
498
|
+
const path6 = nodepath.join(root, workflow.id, workflow.id);
|
|
494
499
|
const wf = {
|
|
495
500
|
id: workflow.id,
|
|
496
501
|
name: workflow.name,
|
|
@@ -511,7 +516,7 @@ var extractWorkflow = (project, workflowId) => {
|
|
|
511
516
|
return mapped;
|
|
512
517
|
})
|
|
513
518
|
};
|
|
514
|
-
return handleOutput(wf,
|
|
519
|
+
return handleOutput(wf, path6, format);
|
|
515
520
|
};
|
|
516
521
|
var extractStep = (project, workflowId, stepId) => {
|
|
517
522
|
const workflow = project.getWorkflow(workflowId);
|
|
@@ -524,13 +529,13 @@ var extractStep = (project, workflowId, stepId) => {
|
|
|
524
529
|
}
|
|
525
530
|
if (step.expression) {
|
|
526
531
|
const root = project.config?.dirs.workflows ?? project.config?.workflowRoot ?? "workflows/";
|
|
527
|
-
const
|
|
532
|
+
const path6 = nodepath.join(root, `${workflow.id}/${step.id}.js`);
|
|
528
533
|
const content = step.expression;
|
|
529
|
-
return { path:
|
|
534
|
+
return { path: path6, content };
|
|
530
535
|
}
|
|
531
536
|
};
|
|
532
537
|
var handleOutput = (data, filePath, format) => {
|
|
533
|
-
const
|
|
538
|
+
const path6 = `${filePath}.${format}`;
|
|
534
539
|
let content;
|
|
535
540
|
if (format === "json") {
|
|
536
541
|
content = stringify(data);
|
|
@@ -539,42 +544,51 @@ var handleOutput = (data, filePath, format) => {
|
|
|
539
544
|
} else {
|
|
540
545
|
throw new Error(`Unrecognised format: ${format}`);
|
|
541
546
|
}
|
|
542
|
-
return { path:
|
|
547
|
+
return { path: path6, content };
|
|
543
548
|
};
|
|
544
549
|
|
|
545
550
|
// src/serialize/to-project.ts
|
|
551
|
+
import { omitBy as omitBy3, isNil as isNil4 } from "lodash-es";
|
|
552
|
+
|
|
553
|
+
// src/util/omit-nil.ts
|
|
546
554
|
import { omitBy as omitBy2, isNil as isNil3 } from "lodash-es";
|
|
555
|
+
var omitNil = (obj, key) => {
|
|
556
|
+
if (obj[key]) {
|
|
557
|
+
obj[key] = omitBy2(obj[key], isNil3);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
var tidyOpenfn = (obj) => omitNil(obj, "openfn");
|
|
561
|
+
|
|
562
|
+
// src/serialize/to-project.ts
|
|
547
563
|
var SERIALIZE_VERSION = 2;
|
|
548
564
|
var to_project_default = (project, options = {}) => {
|
|
549
|
-
const
|
|
565
|
+
const { alias, ...cliWithoutAlias } = project.cli;
|
|
566
|
+
const proj = omitBy3(
|
|
550
567
|
{
|
|
551
568
|
id: project.id,
|
|
552
569
|
name: project.name,
|
|
553
|
-
|
|
554
|
-
|
|
570
|
+
cli: {
|
|
571
|
+
...cliWithoutAlias,
|
|
572
|
+
version: SERIALIZE_VERSION
|
|
573
|
+
// important!
|
|
574
|
+
},
|
|
555
575
|
description: project.description,
|
|
556
576
|
collections: project.collections,
|
|
557
577
|
credentials: project.credentials,
|
|
558
|
-
openfn:
|
|
559
|
-
|
|
560
|
-
options: omitBy2(project.options, isNil3),
|
|
578
|
+
openfn: omitBy3(project.openfn, isNil4),
|
|
579
|
+
options: omitBy3(project.options, isNil4),
|
|
561
580
|
workflows: project.workflows.map((w) => {
|
|
562
581
|
const obj = w.toJSON();
|
|
563
|
-
|
|
564
|
-
obj.openfn = omitBy2(obj.openfn, isNil3);
|
|
565
|
-
}
|
|
582
|
+
tidyOpenfn(obj);
|
|
566
583
|
if (obj.steps) {
|
|
567
584
|
obj.steps = obj.steps.sort((a, b) => {
|
|
568
585
|
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
569
586
|
});
|
|
570
587
|
obj.steps.forEach((s) => {
|
|
571
|
-
|
|
588
|
+
tidyOpenfn(s);
|
|
572
589
|
if (s.next && typeof s.next !== "string") {
|
|
573
590
|
for (const id in s.next) {
|
|
574
|
-
|
|
575
|
-
if (edge.openfn) {
|
|
576
|
-
edge.openfn = omitBy2(edge.openfn, isNil3);
|
|
577
|
-
}
|
|
591
|
+
tidyOpenfn(s.next[id]);
|
|
578
592
|
}
|
|
579
593
|
}
|
|
580
594
|
});
|
|
@@ -582,7 +596,7 @@ var to_project_default = (project, options = {}) => {
|
|
|
582
596
|
return obj;
|
|
583
597
|
})
|
|
584
598
|
},
|
|
585
|
-
|
|
599
|
+
isNil4
|
|
586
600
|
);
|
|
587
601
|
const format = options.format ?? proj.config?.formats.project;
|
|
588
602
|
if (format === "json") {
|
|
@@ -688,7 +702,13 @@ var mapWorkflow2 = (workflow) => {
|
|
|
688
702
|
const outboundEdges = edges.filter(
|
|
689
703
|
(e) => e.source_job_id === step.id || e.source_trigger_id === step.id
|
|
690
704
|
);
|
|
691
|
-
const {
|
|
705
|
+
const {
|
|
706
|
+
body: expression,
|
|
707
|
+
name: name2,
|
|
708
|
+
adaptor,
|
|
709
|
+
project_credential_id,
|
|
710
|
+
...remoteProps2
|
|
711
|
+
} = step;
|
|
692
712
|
const s = {
|
|
693
713
|
id: slugify(name2),
|
|
694
714
|
name: name2,
|
|
@@ -697,6 +717,9 @@ var mapWorkflow2 = (workflow) => {
|
|
|
697
717
|
// TODO is this wrong?
|
|
698
718
|
openfn: renameKeys(remoteProps2, { id: "uuid" })
|
|
699
719
|
};
|
|
720
|
+
if (project_credential_id) {
|
|
721
|
+
s.configuration = project_credential_id;
|
|
722
|
+
}
|
|
700
723
|
if (outboundEdges.length) {
|
|
701
724
|
s.next = outboundEdges.reduce((next, edge) => {
|
|
702
725
|
const target = jobs.find((j) => j.id === edge.target_job_id);
|
|
@@ -711,12 +734,13 @@ var mapWorkflow2 = (workflow) => {
|
|
|
711
734
|
|
|
712
735
|
// src/parse/from-path.ts
|
|
713
736
|
import { readFile } from "node:fs/promises";
|
|
737
|
+
import path2 from "node:path";
|
|
714
738
|
|
|
715
739
|
// src/parse/from-project.ts
|
|
716
740
|
var from_project_default = (data, config) => {
|
|
717
741
|
let rawJson = ensure_json_default(data);
|
|
718
742
|
let json;
|
|
719
|
-
if (rawJson.version) {
|
|
743
|
+
if (rawJson.cli?.version ?? rawJson.version) {
|
|
720
744
|
json = from_v2(rawJson);
|
|
721
745
|
} else {
|
|
722
746
|
json = from_v1(rawJson);
|
|
@@ -733,55 +757,33 @@ var from_v2 = (data) => {
|
|
|
733
757
|
};
|
|
734
758
|
|
|
735
759
|
// src/parse/from-path.ts
|
|
736
|
-
var
|
|
737
|
-
const
|
|
738
|
-
|
|
760
|
+
var extractAliasFromFilename = (filename) => {
|
|
761
|
+
const basename = path2.basename(filename, path2.extname(filename));
|
|
762
|
+
const atIndex = basename.indexOf("@");
|
|
763
|
+
if (atIndex > 0) {
|
|
764
|
+
return basename.substring(0, atIndex);
|
|
765
|
+
}
|
|
766
|
+
return basename;
|
|
767
|
+
};
|
|
768
|
+
var from_path_default = async (filePath, config = {}) => {
|
|
769
|
+
const source = await readFile(filePath, "utf8");
|
|
770
|
+
const alias = config.alias ?? extractAliasFromFilename(filePath);
|
|
771
|
+
return from_project_default(source, { ...config, alias });
|
|
739
772
|
};
|
|
740
773
|
|
|
741
774
|
// src/parse/from-fs.ts
|
|
742
775
|
import fs from "node:fs/promises";
|
|
743
|
-
import
|
|
776
|
+
import path3 from "node:path";
|
|
744
777
|
import { glob } from "glob";
|
|
745
|
-
|
|
746
|
-
// src/util/get-identifier.ts
|
|
747
|
-
var get_identifier_default = (config = {}) => {
|
|
748
|
-
const endpoint = config.endpoint || "local";
|
|
749
|
-
const name = config.env ?? "main";
|
|
750
|
-
let host;
|
|
751
|
-
try {
|
|
752
|
-
host = new URL(endpoint).hostname;
|
|
753
|
-
} catch (e) {
|
|
754
|
-
host = endpoint;
|
|
755
|
-
}
|
|
756
|
-
return `${name}@${host}`;
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
// src/parse/from-fs.ts
|
|
760
778
|
import { omit as omit2 } from "lodash-es";
|
|
761
779
|
var parseProject = async (options) => {
|
|
762
|
-
const { root } = options;
|
|
780
|
+
const { root, logger } = options;
|
|
763
781
|
const { type, content } = findWorkspaceFile(root);
|
|
764
782
|
const context = loadWorkspaceFile(content, type);
|
|
765
783
|
const config = buildConfig(context.workspace);
|
|
766
|
-
let state = null;
|
|
767
|
-
const identifier = get_identifier_default({
|
|
768
|
-
endpoint: context.project?.endpoint,
|
|
769
|
-
env: context.project?.env
|
|
770
|
-
});
|
|
771
|
-
try {
|
|
772
|
-
const format = config.formats?.project ?? config.formats?.project ?? "yaml";
|
|
773
|
-
const statePath = path2.join(
|
|
774
|
-
root,
|
|
775
|
-
config.dirs?.projects ?? ".projects",
|
|
776
|
-
`${identifier}.${format}`
|
|
777
|
-
);
|
|
778
|
-
const stateFile = await fs.readFile(statePath, "utf8");
|
|
779
|
-
state = from_project_default(stateFile, config);
|
|
780
|
-
} catch (e) {
|
|
781
|
-
console.warn(`Failed to find state file for ${identifier}`);
|
|
782
|
-
}
|
|
783
784
|
const proj = {
|
|
784
|
-
|
|
785
|
+
id: context.project?.id,
|
|
786
|
+
name: context.project?.name,
|
|
785
787
|
openfn: omit2(context.project, ["id"]),
|
|
786
788
|
config,
|
|
787
789
|
workflows: []
|
|
@@ -797,36 +799,28 @@ var parseProject = async (options) => {
|
|
|
797
799
|
try {
|
|
798
800
|
const wf = fileType === "yaml" ? yamlToJson(candidate) : JSON.parse(candidate);
|
|
799
801
|
if (wf.id && Array.isArray(wf.steps)) {
|
|
800
|
-
const wfState = state?.getWorkflow(wf.id);
|
|
801
|
-
wf.openfn = Object.assign({}, wfState?.openfn, {
|
|
802
|
-
uuid: wfState?.openfn?.uuid ?? null
|
|
803
|
-
});
|
|
804
802
|
for (const step of wf.steps) {
|
|
805
|
-
const stateStep = wfState?.get(step.id);
|
|
806
803
|
if (step.expression && step.expression.endsWith(".js")) {
|
|
807
|
-
const dir =
|
|
808
|
-
const exprPath =
|
|
804
|
+
const dir = path3.dirname(filePath);
|
|
805
|
+
const exprPath = path3.join(dir, step.expression);
|
|
809
806
|
try {
|
|
810
|
-
|
|
807
|
+
logger?.debug(`Loaded expression from ${exprPath}`);
|
|
811
808
|
step.expression = await fs.readFile(exprPath, "utf-8");
|
|
812
809
|
} catch (e) {
|
|
813
|
-
|
|
810
|
+
logger?.error(`Error loading expression from ${exprPath}`);
|
|
814
811
|
}
|
|
815
812
|
}
|
|
816
|
-
step.openfn = Object.assign({}, stateStep?.openfn);
|
|
817
813
|
for (const target in step.next || {}) {
|
|
818
814
|
if (typeof step.next[target] === "boolean") {
|
|
819
815
|
const bool = step.next[target];
|
|
820
816
|
step.next[target] = { condition: bool };
|
|
821
817
|
}
|
|
822
|
-
const uuid = state?.getUUID(wf.id, step.id, target) ?? null;
|
|
823
|
-
step.next[target].openfn = { uuid };
|
|
824
818
|
}
|
|
825
819
|
}
|
|
826
820
|
proj.workflows.push(wf);
|
|
827
821
|
}
|
|
828
822
|
} catch (e) {
|
|
829
|
-
|
|
823
|
+
logger?.log(e);
|
|
830
824
|
continue;
|
|
831
825
|
}
|
|
832
826
|
}
|
|
@@ -1266,10 +1260,10 @@ var Project = class {
|
|
|
1266
1260
|
// option strings saved by the app
|
|
1267
1261
|
// these are all (?) unused clientside
|
|
1268
1262
|
options;
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1263
|
+
/**
|
|
1264
|
+
* Local metadata used by the CLI but not synced to Lightning
|
|
1265
|
+
*/
|
|
1266
|
+
cli;
|
|
1273
1267
|
// this contains meta about the connected openfn project
|
|
1274
1268
|
openfn;
|
|
1275
1269
|
workspace;
|
|
@@ -1305,9 +1299,16 @@ var Project = class {
|
|
|
1305
1299
|
// maybe this second arg is config - like env, branch rules, serialisation rules
|
|
1306
1300
|
// stuff that's external to the actual project and managed by the repo
|
|
1307
1301
|
// TODO maybe the constructor is (data, Workspace)
|
|
1308
|
-
constructor(data,
|
|
1309
|
-
this.config = buildConfig(config);
|
|
1302
|
+
constructor(data = {}, meta) {
|
|
1310
1303
|
this.id = data.id ?? (data.name ? slugify(data.name) : humanId({ separator: "-", capitalize: false }));
|
|
1304
|
+
const { version, alias = "main", ...otherConfig } = meta ?? {};
|
|
1305
|
+
this.cli = Object.assign(
|
|
1306
|
+
{
|
|
1307
|
+
alias
|
|
1308
|
+
},
|
|
1309
|
+
data.cli
|
|
1310
|
+
);
|
|
1311
|
+
this.config = buildConfig(otherConfig);
|
|
1311
1312
|
this.name = data.name;
|
|
1312
1313
|
this.description = data.description ?? void 0;
|
|
1313
1314
|
this.openfn = data.openfn;
|
|
@@ -1316,6 +1317,20 @@ var Project = class {
|
|
|
1316
1317
|
this.collections = data.collections;
|
|
1317
1318
|
this.credentials = data.credentials;
|
|
1318
1319
|
}
|
|
1320
|
+
/** Local alias for the project. Comes from the file name. Not shared with Lightning. */
|
|
1321
|
+
get alias() {
|
|
1322
|
+
return this.cli.alias ?? "main";
|
|
1323
|
+
}
|
|
1324
|
+
get uuid() {
|
|
1325
|
+
return this.openfn?.uuid ? `${this.openfn.uuid}` : void 0;
|
|
1326
|
+
}
|
|
1327
|
+
// Helper to extract hostname from endpoint
|
|
1328
|
+
get host() {
|
|
1329
|
+
const { endpoint } = this.openfn ?? {};
|
|
1330
|
+
if (endpoint) {
|
|
1331
|
+
return new URL(endpoint).hostname;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1319
1334
|
setConfig(config) {
|
|
1320
1335
|
this.config = buildConfig(config);
|
|
1321
1336
|
}
|
|
@@ -1329,11 +1344,13 @@ var Project = class {
|
|
|
1329
1344
|
getWorkflow(idOrName) {
|
|
1330
1345
|
return this.workflows.find((wf) => wf.id == idOrName) || this.workflows.find((wf) => wf.name === idOrName) || this.workflows.find((wf) => wf.openfn?.uuid === idOrName);
|
|
1331
1346
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1347
|
+
/** Returns a fully qualified name for the project, id, alias@domain */
|
|
1348
|
+
get qname() {
|
|
1349
|
+
const { alias, host } = this;
|
|
1350
|
+
if (host) {
|
|
1351
|
+
return `${alias}@${host}`;
|
|
1352
|
+
}
|
|
1353
|
+
return alias;
|
|
1337
1354
|
}
|
|
1338
1355
|
// Compare this project with another and return a diff
|
|
1339
1356
|
// compare(proj: Project) {}
|
|
@@ -1377,7 +1394,7 @@ var Project_default = Project;
|
|
|
1377
1394
|
|
|
1378
1395
|
// src/Workspace.ts
|
|
1379
1396
|
import createLogger from "@openfn/logger";
|
|
1380
|
-
import
|
|
1397
|
+
import path4 from "node:path";
|
|
1381
1398
|
import fs3 from "node:fs";
|
|
1382
1399
|
|
|
1383
1400
|
// src/util/path-exists.ts
|
|
@@ -1395,16 +1412,46 @@ function pathExists(fpath, type) {
|
|
|
1395
1412
|
}
|
|
1396
1413
|
}
|
|
1397
1414
|
|
|
1415
|
+
// src/util/match-project.ts
|
|
1416
|
+
var MultipleMatchingProjectsError = class extends Error {
|
|
1417
|
+
};
|
|
1418
|
+
var matchProject = (name, candidates) => {
|
|
1419
|
+
const [searchTerm, domain] = `${name}`.split("@");
|
|
1420
|
+
const matchingProjects = {};
|
|
1421
|
+
let multipleIdMatches = false;
|
|
1422
|
+
candidates = candidates.filter(
|
|
1423
|
+
(project) => !domain || project.host === domain
|
|
1424
|
+
);
|
|
1425
|
+
const re = new RegExp(searchTerm, "i");
|
|
1426
|
+
for (const project of candidates) {
|
|
1427
|
+
if (project.id === searchTerm || project.alias === searchTerm || project.uuid && re.test(project.uuid)) {
|
|
1428
|
+
matchingProjects[project.id] ??= [];
|
|
1429
|
+
matchingProjects[project.id].push(project);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
const matches = Object.values(matchingProjects).flat();
|
|
1433
|
+
if (multipleIdMatches || matches.length > 1) {
|
|
1434
|
+
throw new MultipleMatchingProjectsError(
|
|
1435
|
+
`Failed to resolve unique identifier for "${name}", clashes with: ${matches.map((p) => p.id).join(", ")}`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
return matches.length ? matches[0] : null;
|
|
1439
|
+
};
|
|
1440
|
+
var match_project_default = matchProject;
|
|
1441
|
+
|
|
1398
1442
|
// src/Workspace.ts
|
|
1399
1443
|
var Workspace = class {
|
|
1400
1444
|
// @ts-ignore config not definitely assigned - it sure is
|
|
1401
1445
|
config;
|
|
1446
|
+
// TODO activeProject should be the actual project
|
|
1402
1447
|
activeProject;
|
|
1403
1448
|
projects = [];
|
|
1404
1449
|
projectPaths = /* @__PURE__ */ new Map();
|
|
1405
1450
|
isValid = false;
|
|
1406
1451
|
logger;
|
|
1407
|
-
|
|
1452
|
+
// Set validate to false to suppress warnings if a Workspace doesn't exist
|
|
1453
|
+
// This is appropriate if, say, fetching a project for the first time
|
|
1454
|
+
constructor(workspacePath, logger, validate = true) {
|
|
1408
1455
|
this.logger = logger ?? createLogger("Workspace", { level: "info" });
|
|
1409
1456
|
let context = { workspace: void 0, project: void 0 };
|
|
1410
1457
|
try {
|
|
@@ -1412,24 +1459,28 @@ var Workspace = class {
|
|
|
1412
1459
|
context = loadWorkspaceFile(content, type);
|
|
1413
1460
|
this.isValid = true;
|
|
1414
1461
|
} catch (e) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1462
|
+
if (validate) {
|
|
1463
|
+
this.logger.warn(
|
|
1464
|
+
`Could not find openfn.yaml at ${workspacePath}. Using default values.`
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1418
1467
|
}
|
|
1419
1468
|
this.config = buildConfig(context.workspace);
|
|
1420
1469
|
this.activeProject = context.project;
|
|
1421
|
-
const projectsPath =
|
|
1470
|
+
const projectsPath = path4.join(workspacePath, this.config.dirs.projects);
|
|
1422
1471
|
if (pathExists(projectsPath, "directory")) {
|
|
1423
1472
|
const ext = `.${this.config.formats.project}`;
|
|
1424
1473
|
const stateFiles = fs3.readdirSync(projectsPath).filter(
|
|
1425
|
-
(fileName) =>
|
|
1474
|
+
(fileName) => path4.extname(fileName) === ext && path4.parse(fileName).name !== "openfn"
|
|
1426
1475
|
);
|
|
1427
1476
|
this.projects = stateFiles.map((file) => {
|
|
1428
|
-
const stateFilePath =
|
|
1477
|
+
const stateFilePath = path4.join(projectsPath, file);
|
|
1429
1478
|
try {
|
|
1430
1479
|
const data = fs3.readFileSync(stateFilePath, "utf-8");
|
|
1480
|
+
const alias = extractAliasFromFilename(file);
|
|
1431
1481
|
const project = from_project_default(data, {
|
|
1432
|
-
...this.config
|
|
1482
|
+
...this.config,
|
|
1483
|
+
alias
|
|
1433
1484
|
});
|
|
1434
1485
|
this.projectPaths.set(project.id, stateFilePath);
|
|
1435
1486
|
return project;
|
|
@@ -1439,9 +1490,11 @@ var Workspace = class {
|
|
|
1439
1490
|
}
|
|
1440
1491
|
}).filter((s) => s);
|
|
1441
1492
|
} else {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1493
|
+
if (validate) {
|
|
1494
|
+
this.logger.warn(
|
|
1495
|
+
`No projects found: directory at ${projectsPath} does not exist`
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1445
1498
|
}
|
|
1446
1499
|
}
|
|
1447
1500
|
// TODO
|
|
@@ -1454,15 +1507,15 @@ var Workspace = class {
|
|
|
1454
1507
|
list() {
|
|
1455
1508
|
return this.projects;
|
|
1456
1509
|
}
|
|
1457
|
-
/** Get a project by its id or UUID */
|
|
1458
|
-
get(
|
|
1459
|
-
return
|
|
1510
|
+
/** Get a project by its alias, id or UUID. Can also include a UUID */
|
|
1511
|
+
get(nameyThing) {
|
|
1512
|
+
return match_project_default(nameyThing, this.projects);
|
|
1460
1513
|
}
|
|
1461
1514
|
getProjectPath(id) {
|
|
1462
1515
|
return this.projectPaths.get(id);
|
|
1463
1516
|
}
|
|
1464
1517
|
getActiveProject() {
|
|
1465
|
-
return this.projects.find((p) => p.
|
|
1518
|
+
return this.projects.find((p) => p.openfn?.uuid === this.activeProject?.uuid) ?? this.projects.find((p) => p.id === this.activeProject?.id);
|
|
1466
1519
|
}
|
|
1467
1520
|
// TODO this needs to return default values
|
|
1468
1521
|
// We should always rely on the workspace to load these values
|
|
@@ -1479,10 +1532,10 @@ var Workspace = class {
|
|
|
1479
1532
|
|
|
1480
1533
|
// src/gen/generator.ts
|
|
1481
1534
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1482
|
-
import
|
|
1535
|
+
import path5 from "node:path";
|
|
1483
1536
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1484
1537
|
import { grammar } from "ohm-js";
|
|
1485
|
-
import { isNil as
|
|
1538
|
+
import { isNil as isNil5, set } from "lodash-es";
|
|
1486
1539
|
var parser;
|
|
1487
1540
|
var expectedNodeProps = [
|
|
1488
1541
|
// TODO need to clarify adaptor/adaptors confusion
|
|
@@ -1614,7 +1667,7 @@ var initOperations = (options = {}) => {
|
|
|
1614
1667
|
return operations;
|
|
1615
1668
|
};
|
|
1616
1669
|
var createParser = () => {
|
|
1617
|
-
const grammarPath =
|
|
1670
|
+
const grammarPath = path5.resolve(import.meta.dirname, "workflow.ohm");
|
|
1618
1671
|
const contents = readFileSync2(grammarPath, "utf-8");
|
|
1619
1672
|
const parser2 = grammar(contents);
|
|
1620
1673
|
return {
|
|
@@ -1653,7 +1706,7 @@ function generateWorkflow(def, options = {}) {
|
|
|
1653
1706
|
if (options.uuidMap && raw.id in options.uuidMap) {
|
|
1654
1707
|
uuid = options.uuidMap[raw.id];
|
|
1655
1708
|
}
|
|
1656
|
-
if (!
|
|
1709
|
+
if (!isNil5(uuid) && options.openfnUuid) {
|
|
1657
1710
|
raw.openfn ??= {};
|
|
1658
1711
|
raw.openfn.uuid = uuid;
|
|
1659
1712
|
}
|