patchrelay 0.7.10 → 0.8.1

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.
@@ -2,6 +2,9 @@ function normalize(value) {
2
2
  const trimmed = value?.trim();
3
3
  return trimmed ? trimmed.toLowerCase() : undefined;
4
4
  }
5
+ function normalizeWorkflowLabel(value) {
6
+ return normalize(value)?.replace(/[\s_-]+/g, "");
7
+ }
5
8
  function extractIssuePrefix(identifier) {
6
9
  const value = identifier?.trim();
7
10
  if (!value) {
@@ -10,25 +13,129 @@ function extractIssuePrefix(identifier) {
10
13
  const [prefix] = value.split("-", 1);
11
14
  return prefix ? prefix.toUpperCase() : undefined;
12
15
  }
13
- export function resolveWorkflow(project, stateName) {
16
+ function withWorkflowDefinitionId(workflowDefinitionId) {
17
+ return workflowDefinitionId ? { workflowDefinitionId } : undefined;
18
+ }
19
+ export function listProjectWorkflowDefinitions(project) {
20
+ if (project.workflowDefinitions && project.workflowDefinitions.length > 0) {
21
+ return project.workflowDefinitions;
22
+ }
23
+ return [
24
+ {
25
+ id: project.workflowSelection?.defaultWorkflowId ?? "default",
26
+ stages: project.workflows,
27
+ },
28
+ ];
29
+ }
30
+ export function resolveWorkflowDefinitionById(project, workflowDefinitionId) {
31
+ const normalized = normalize(workflowDefinitionId);
32
+ if (!normalized) {
33
+ return undefined;
34
+ }
35
+ return listProjectWorkflowDefinitions(project).find((definition) => normalize(definition.id) === normalized);
36
+ }
37
+ export function selectWorkflowDefinition(project, issue) {
38
+ const workflowDefinitions = listProjectWorkflowDefinitions(project);
39
+ if (workflowDefinitions.length === 0) {
40
+ return undefined;
41
+ }
42
+ const labelNames = new Set((issue?.labelNames ?? []).map((label) => label.trim().toLowerCase()).filter(Boolean));
43
+ const matchedWorkflowIds = new Set();
44
+ for (const rule of project.workflowSelection?.byLabel ?? []) {
45
+ if (labelNames.has(rule.label.trim().toLowerCase())) {
46
+ matchedWorkflowIds.add(rule.workflowId);
47
+ }
48
+ }
49
+ if (matchedWorkflowIds.size === 1) {
50
+ const [workflowId] = [...matchedWorkflowIds];
51
+ return resolveWorkflowDefinitionById(project, workflowId);
52
+ }
53
+ if (matchedWorkflowIds.size > 1) {
54
+ return undefined;
55
+ }
56
+ if (project.workflowSelection?.defaultWorkflowId) {
57
+ return resolveWorkflowDefinitionById(project, project.workflowSelection.defaultWorkflowId);
58
+ }
59
+ return workflowDefinitions[0];
60
+ }
61
+ function resolveStageList(project, options) {
62
+ if (options?.workflowDefinitionId) {
63
+ return resolveWorkflowDefinitionById(project, options.workflowDefinitionId)?.stages ?? [];
64
+ }
65
+ if (options?.issue) {
66
+ return selectWorkflowDefinition(project, options.issue)?.stages ?? [];
67
+ }
68
+ return project.workflows;
69
+ }
70
+ export function resolveWorkflow(project, stateName, options) {
14
71
  const normalized = normalize(stateName);
15
72
  if (!normalized) {
16
73
  return undefined;
17
74
  }
18
- return project.workflows.find((workflow) => normalize(workflow.whenState) === normalized);
75
+ return resolveStageList(project, options).find((workflow) => normalize(workflow.whenState) === normalized);
19
76
  }
20
- export function resolveWorkflowStage(project, stateName) {
21
- return resolveWorkflow(project, stateName)?.id;
77
+ export function resolveWorkflowStage(project, stateName, options) {
78
+ return resolveWorkflow(project, stateName, options)?.id;
22
79
  }
23
- export function resolveWorkflowById(project, workflowId) {
80
+ export function resolveWorkflowStageConfig(project, workflowId, workflowDefinitionId) {
24
81
  const normalized = normalize(workflowId);
25
82
  if (!normalized) {
26
83
  return undefined;
27
84
  }
28
- return project.workflows.find((workflow) => normalize(workflow.id) === normalized);
85
+ return resolveStageList(project, withWorkflowDefinitionId(workflowDefinitionId)).find((workflow) => normalize(workflow.id) === normalized);
86
+ }
87
+ export function listRunnableStates(project, options) {
88
+ return [...new Set(resolveStageList(project, options).map((workflow) => workflow.whenState))];
89
+ }
90
+ export function listWorkflowStageIds(project, workflowDefinitionId) {
91
+ return resolveStageList(project, withWorkflowDefinitionId(workflowDefinitionId)).map((workflow) => workflow.id);
92
+ }
93
+ export function resolveWorkflowIndex(project, workflowId, workflowDefinitionId) {
94
+ if (!workflowId) {
95
+ return -1;
96
+ }
97
+ return resolveStageList(project, withWorkflowDefinitionId(workflowDefinitionId)).findIndex((workflow) => workflow.id === workflowId);
29
98
  }
30
- export function listRunnableStates(project) {
31
- return project.workflows.map((workflow) => workflow.whenState);
99
+ export function resolveDefaultTransitionTarget(project, currentStage, workflowDefinitionId) {
100
+ const stages = resolveStageList(project, withWorkflowDefinitionId(workflowDefinitionId));
101
+ const currentIndex = resolveWorkflowIndex(project, currentStage, workflowDefinitionId);
102
+ if (currentIndex < 0) {
103
+ return undefined;
104
+ }
105
+ const nextStage = stages[currentIndex + 1]?.id;
106
+ return nextStage ?? "done";
107
+ }
108
+ export function listAllowedTransitionTargets(project, currentStage, workflowDefinitionId) {
109
+ const stages = resolveStageList(project, withWorkflowDefinitionId(workflowDefinitionId));
110
+ const currentIndex = resolveWorkflowIndex(project, currentStage, workflowDefinitionId);
111
+ if (currentIndex < 0) {
112
+ return ["human_needed"];
113
+ }
114
+ const targets = new Set(["human_needed"]);
115
+ const defaultTarget = resolveDefaultTransitionTarget(project, currentStage, workflowDefinitionId);
116
+ if (defaultTarget) {
117
+ targets.add(defaultTarget);
118
+ }
119
+ if (currentIndex > 0) {
120
+ targets.add(stages[currentIndex - 1].id);
121
+ }
122
+ if (currentIndex > 1) {
123
+ targets.add(stages[0].id);
124
+ }
125
+ return [...targets];
126
+ }
127
+ export function transitionTargetAllowed(project, currentStage, nextTarget, workflowDefinitionId) {
128
+ return listAllowedTransitionTargets(project, currentStage, workflowDefinitionId).includes(nextTarget);
129
+ }
130
+ export function resolveWorkflowStageCandidate(project, value, workflowDefinitionId) {
131
+ const normalized = normalizeWorkflowLabel(value);
132
+ if (!normalized) {
133
+ return undefined;
134
+ }
135
+ return resolveStageList(project, withWorkflowDefinitionId(workflowDefinitionId)).find((workflow) => {
136
+ const candidates = [workflow.id, workflow.whenState, workflow.activeState];
137
+ return candidates.some((candidate) => normalizeWorkflowLabel(candidate) === normalized);
138
+ })?.id;
32
139
  }
33
140
  export function matchesProject(issue, project) {
34
141
  const issuePrefix = extractIssuePrefix(issue.identifier);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.7.10",
3
+ "version": "0.8.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {