@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 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(id: string): Project | undefined;
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
- meta: any;
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: Partial<l__default.Project>, config?: Partial<l__default.WorkspaceConfig>);
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
- getIdentifier(): string;
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: path5, content } = extractConfig(project);
472
- files[path5] = content;
476
+ const { path: path6, content } = extractConfig(project);
477
+ files[path6] = content;
473
478
  for (const wf of project.workflows) {
474
- const { path: path6, content: content2 } = extractWorkflow(project, wf.id);
475
- files[path6] = content2;
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: path7, content: content3 } = result;
480
- files[path7] = content3;
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 path5 = nodepath.join(root, workflow.id, workflow.id);
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, path5, format);
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 path5 = nodepath.join(root, `${workflow.id}/${step.id}.js`);
532
+ const path6 = nodepath.join(root, `${workflow.id}/${step.id}.js`);
528
533
  const content = step.expression;
529
- return { path: path5, content };
534
+ return { path: path6, content };
530
535
  }
531
536
  };
532
537
  var handleOutput = (data, filePath, format) => {
533
- const path5 = `${filePath}.${format}`;
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: path5, content };
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 proj = omitBy2(
565
+ const { alias, ...cliWithoutAlias } = project.cli;
566
+ const proj = omitBy3(
550
567
  {
551
568
  id: project.id,
552
569
  name: project.name,
553
- version: SERIALIZE_VERSION,
554
- // important!
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: omitBy2(project.openfn, isNil3),
559
- meta: project.meta,
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
- if (obj.openfn) {
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
- s.openfn = omitBy2(s.openfn, isNil3);
588
+ tidyOpenfn(s);
572
589
  if (s.next && typeof s.next !== "string") {
573
590
  for (const id in s.next) {
574
- const edge = s.next[id];
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
- isNil3
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 { body: expression, name: name2, adaptor, ...remoteProps2 } = step;
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 from_path_default = async (path5, config = {}) => {
737
- const source = await readFile(path5, "utf8");
738
- return from_project_default(source, config);
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 path2 from "node:path";
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
- name: state?.name,
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 = path2.dirname(filePath);
808
- const exprPath = path2.join(dir, step.expression);
804
+ const dir = path3.dirname(filePath);
805
+ const exprPath = path3.join(dir, step.expression);
809
806
  try {
810
- console.debug(`Loaded expression from ${exprPath}`);
807
+ logger?.debug(`Loaded expression from ${exprPath}`);
811
808
  step.expression = await fs.readFile(exprPath, "utf-8");
812
809
  } catch (e) {
813
- console.error(`Error loading expression from ${exprPath}`);
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
- console.log(e);
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
- // local metadata used by the CLI
1270
- // This stuff is not synced back to lightning
1271
- // TODO maybe rename cli or local
1272
- meta;
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, config) {
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
- // it's the name of the project.yaml file
1333
- // qualified name? Remote name? App name?
1334
- // every project in a repo need a unique identifier
1335
- getIdentifier() {
1336
- return get_identifier_default(this.openfn);
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 path3 from "node:path";
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
- constructor(workspacePath, logger) {
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
- this.logger.warn(
1416
- `Could not find openfn.yaml at ${workspacePath}. Using default values.`
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 = path3.join(workspacePath, this.config.dirs.projects);
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) => path3.extname(fileName) === ext && path3.parse(fileName).name !== "openfn"
1474
+ (fileName) => path4.extname(fileName) === ext && path4.parse(fileName).name !== "openfn"
1426
1475
  );
1427
1476
  this.projects = stateFiles.map((file) => {
1428
- const stateFilePath = path3.join(projectsPath, file);
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
- this.logger.warn(
1443
- `No projects found: directory at ${projectsPath} does not exist`
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(id) {
1459
- return this.projects.find((p) => p.id === id) ?? this.projects.find((p) => p.openfn?.uuid === id);
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.id === this.activeProject?.id) ?? this.projects.find((p) => p.openfn?.uuid === this.activeProject?.uuid);
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 path4 from "node:path";
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 isNil4, set } from "lodash-es";
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 = path4.resolve(import.meta.dirname, "workflow.ohm");
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 (!isNil4(uuid) && options.openfnUuid) {
1709
+ if (!isNil5(uuid) && options.openfnUuid) {
1657
1710
  raw.openfn ??= {};
1658
1711
  raw.openfn.uuid = uuid;
1659
1712
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/project",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "Read, serialize, replicate and sync OpenFn projects",
5
5
  "type": "module",
6
6
  "exports": {