Skip to content

Commit a1fa351

Browse files
josephjclarkdoc-han
authored andcommitted
first spike of generating a version hash
1 parent 6946618 commit a1fa351

3 files changed

Lines changed: 109 additions & 1 deletion

File tree

packages/project/src/Project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export class Project {
133133
static from(
134134
type: 'state' | 'path' | 'fs',
135135
data: any,
136-
options?: Partial<l.ProjectConfig>
136+
options: Partial<l.ProjectConfig> = {}
137137
): Project {
138138
if (type === 'state') {
139139
return fromAppState(data, options);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import crypto from 'node:crypto';
2+
3+
const SHORT_HASH_LENGTH = 12;
4+
5+
export const project = () => {};
6+
7+
export const workflow = (workflow: l.Workflow, source = 'cli') => {
8+
const parts = [];
9+
10+
// These are the keys we hash against
11+
const wfkeys = ['name', 'credentials'].sort();
12+
const stepKeys = [
13+
'name',
14+
'adaptors',
15+
'expression',
16+
'configuration', // assumes a string credential id
17+
'expression',
18+
19+
// TODO need to model trigger types in this, which I think are currently ignored
20+
].sort();
21+
const edgeKeys = [
22+
'condition',
23+
'label',
24+
'disabled', // This feels more like an option - should be excluded?
25+
].sort();
26+
27+
for (const step of workflow.steps) {
28+
stepKeys.forEach((key) => {
29+
if (typeof step[key] === 'string') {
30+
parts.push(key, step[key]);
31+
}
32+
});
33+
34+
// if (step.next) {
35+
// for (const edge of step.next) {
36+
// // TODO I don't think we can handle this well right now
37+
// }
38+
// }
39+
}
40+
41+
const str = parts.join('');
42+
const hash = crypto.hash('sha256', str);
43+
44+
return `${source}:${hash.substr(0, 12)}`;
45+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import test from 'ava';
2+
import { Project } from '../../src/Project';
3+
import { createWorkflow } from '../util';
4+
import { workflow } from '../../src/util/version';
5+
6+
test('generate an 12 character version hash for a basic workflow', (t) => {
7+
// Keep workflows in lightning state format so that tests are easier to port
8+
const project = {
9+
id: 'p',
10+
workflows: [
11+
{
12+
id: '72ca3eb0-042c-47a0-a2a1-a545ed4a8406',
13+
name: 'a',
14+
edges: [
15+
{
16+
enabled: true,
17+
id: 'a9a3adef-b394-4405-814d-3ac4323f4b4b',
18+
source_trigger_id: '4a06289c-15aa-4662-8dc6-f0aaacd8a058',
19+
condition_type: 'always',
20+
target_job_id: '66add020-e6eb-4eec-836b-20008afca816',
21+
},
22+
],
23+
concurrency: null,
24+
inserted_at: '2025-04-23T11:19:32Z',
25+
updated_at: '2025-04-23T11:19:32Z',
26+
jobs: [
27+
{
28+
id: '66add020-e6eb-4eec-836b-20008afca816',
29+
name: 'Transform data',
30+
body: '// Check out the Job Writing Guide for help getting started:\n// https://docs.openfn.org/documentation/jobs/job-writing-guide\n',
31+
adaptor: '@openfn/language-common@latest',
32+
project_credential_id: null,
33+
},
34+
],
35+
triggers: [
36+
{
37+
enabled: true,
38+
id: '4a06289c-15aa-4662-8dc6-f0aaacd8a058',
39+
type: 'webhook',
40+
},
41+
],
42+
lock_version: 1,
43+
deleted_at: null,
44+
},
45+
],
46+
};
47+
48+
const wf = Project.from('state', project).getWorkflow('a');
49+
50+
const hash = workflow(wf);
51+
t.log(hash);
52+
t.is(hash, 'cli:625fcedf07d4');
53+
});
54+
55+
/**
56+
*
57+
* different name/adaptor/credential/expression should generate different hash
58+
* Other keys should be ignored
59+
*
60+
* key order doesn't matter (arbitrary order, sort, inverse sort)
61+
*
62+
* include a prefix
63+
*/

0 commit comments

Comments
 (0)