@akanjs/devkit 2.3.9-rc.4 → 2.3.9-rc.6
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/devkitUtils.test.ts +2 -2
- package/executors.ts +17 -13
- package/getModelFileData.ts +5 -2
- package/package.json +2 -2
- package/workflow/artifacts.ts +30 -2
- package/workflow/executor.ts +137 -9
- package/workflow/index.ts +1 -0
- package/workflow/plan.ts +198 -33
- package/workflow/render.ts +92 -11
- package/workflow/source.ts +270 -18
- package/workflow/types.ts +26 -4
- package/workflow/uiPolicy.ts +45 -0
package/workflow/plan.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { capitalize } from "akanjs/common";
|
|
2
|
-
import {
|
|
2
|
+
import { coerceFieldDefault, moduleSourcePaths } from "./source";
|
|
3
3
|
import type {
|
|
4
4
|
WorkflowDiagnostic,
|
|
5
5
|
WorkflowInputSpec,
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
WorkflowSpec,
|
|
11
11
|
WorkflowSurfaceMode,
|
|
12
12
|
} from "./types";
|
|
13
|
+
import { addFieldUiPolicyForType } from "./uiPolicy";
|
|
13
14
|
|
|
14
15
|
const surfaceModes = new Set<WorkflowSurfaceMode>(["infer", "include", "skip"]);
|
|
15
16
|
|
|
@@ -31,6 +32,14 @@ const normalizeInputValue = (name: string, spec: WorkflowInputSpec, value: unkno
|
|
|
31
32
|
const values = parseStringList(value);
|
|
32
33
|
return values && values.length > 0 ? values : null;
|
|
33
34
|
}
|
|
35
|
+
if (spec.type === "boolean") {
|
|
36
|
+
if (typeof value === "boolean") return value;
|
|
37
|
+
if (typeof value !== "string") return null;
|
|
38
|
+
const lowered = value.trim().toLowerCase();
|
|
39
|
+
if (lowered === "true") return true;
|
|
40
|
+
if (lowered === "false") return false;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
34
43
|
if (typeof value === "string" && surfaceModes.has(value as WorkflowSurfaceMode)) return value as WorkflowSurfaceMode;
|
|
35
44
|
throw new Error(`Unsupported workflow input value for ${name}`);
|
|
36
45
|
};
|
|
@@ -44,13 +53,47 @@ export const getWorkflowSpec = (specs: readonly WorkflowSpec[], name: string) =>
|
|
|
44
53
|
export const compactWorkflowInputs = (inputs: WorkflowPlanInputs) =>
|
|
45
54
|
Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
|
|
46
55
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
const addFieldTargetRoot = (app: string | null) => (app ? `apps/${app}` : "*");
|
|
57
|
+
|
|
58
|
+
const addFieldPaths = (inputs: Record<string, WorkflowInputValue>) => {
|
|
59
|
+
const app = typeof inputs.app === "string" ? inputs.app : null;
|
|
60
|
+
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
61
|
+
const paths = moduleSourcePaths(module);
|
|
62
|
+
const root = addFieldTargetRoot(app);
|
|
63
|
+
return {
|
|
64
|
+
constant: `${root}/${paths.constant}`,
|
|
65
|
+
dictionary: `${root}/${paths.dictionary}`,
|
|
66
|
+
template: `${root}/${paths.template}`,
|
|
67
|
+
unit: `${root}/${paths.unit}`,
|
|
68
|
+
view: `${root}/${paths.view}`,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const selectedAddFieldSurfaces = (inputs: Record<string, WorkflowInputValue>) =>
|
|
73
|
+
Array.isArray(inputs.surfaces) ? new Set(inputs.surfaces) : null;
|
|
74
|
+
|
|
75
|
+
const addFieldDefaultCoercion = (inputs: Record<string, WorkflowInputValue>) => {
|
|
76
|
+
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
77
|
+
const defaultValue = typeof inputs.default === "string" ? inputs.default : null;
|
|
78
|
+
if (!typeName || defaultValue === null) return null;
|
|
79
|
+
if (typeName.toLowerCase() === "number" || typeName.toLowerCase() === "numeric") return null;
|
|
80
|
+
const enumValues = Array.isArray(inputs.values) ? inputs.values : null;
|
|
81
|
+
return coerceFieldDefault(typeName.toLowerCase() === "enum" ? "enum" : typeName, defaultValue, { enumValues });
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const createAddFieldDefaultRecommendations = (inputs: Record<string, WorkflowInputValue>): WorkflowRecommendation[] => {
|
|
85
|
+
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
86
|
+
const coercion = addFieldDefaultCoercion(inputs);
|
|
87
|
+
if (!coercion?.normalized || !coercion.expression) return [];
|
|
88
|
+
return [
|
|
89
|
+
{
|
|
90
|
+
code: "add-field-default-normalized",
|
|
91
|
+
kind: "manual-action",
|
|
92
|
+
confidence: "high",
|
|
93
|
+
message: `Default for ${field} will be normalized to ${coercion.normalizedType} literal ${coercion.expression}.`,
|
|
94
|
+
action: "Review the normalized default in the apply report if this value should stay unset.",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
54
97
|
};
|
|
55
98
|
|
|
56
99
|
const createAddFieldRecommendations = (inputs: Record<string, WorkflowInputValue>): WorkflowRecommendation[] => {
|
|
@@ -60,17 +103,19 @@ const createAddFieldRecommendations = (inputs: Record<string, WorkflowInputValue
|
|
|
60
103
|
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
61
104
|
if (!typeName) return [];
|
|
62
105
|
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
106
|
+
const policy = addFieldUiPolicyForType(typeName);
|
|
107
|
+
const normalizedType = policy.normalizedType;
|
|
108
|
+
const paths = addFieldPaths(inputs);
|
|
109
|
+
const surfaces = selectedAddFieldSurfaces(inputs);
|
|
110
|
+
const templateRequested = surfaces?.has("template") ?? false;
|
|
111
|
+
const includeInLight = inputs.includeInLight === true;
|
|
67
112
|
return [
|
|
68
113
|
...(normalizedType === "Int" || normalizedType === "Float"
|
|
69
114
|
? [
|
|
70
115
|
{
|
|
71
116
|
code: "add-field-import",
|
|
72
117
|
kind: "import" as const,
|
|
73
|
-
target:
|
|
118
|
+
target: paths.constant,
|
|
74
119
|
confidence: "high" as const,
|
|
75
120
|
message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`,
|
|
76
121
|
},
|
|
@@ -79,45 +124,147 @@ const createAddFieldRecommendations = (inputs: Record<string, WorkflowInputValue
|
|
|
79
124
|
{
|
|
80
125
|
code: "add-field-placement-constant",
|
|
81
126
|
kind: "placement",
|
|
82
|
-
target:
|
|
127
|
+
target: paths.constant,
|
|
83
128
|
confidence: "high",
|
|
84
129
|
message: `Insert ${field}: field(${normalizedType}) in ${capitalize(module)}Input.`,
|
|
85
130
|
},
|
|
86
131
|
{
|
|
87
132
|
code: "add-field-placement-dictionary",
|
|
88
133
|
kind: "placement",
|
|
89
|
-
target:
|
|
134
|
+
target: paths.dictionary,
|
|
90
135
|
confidence: "high",
|
|
91
136
|
message: `Add dictionary labels for ${module}.${field}.`,
|
|
92
137
|
},
|
|
93
138
|
{
|
|
94
139
|
code: "add-field-component",
|
|
95
140
|
kind: "ui-component",
|
|
96
|
-
target:
|
|
97
|
-
confidence:
|
|
98
|
-
action:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
message:
|
|
103
|
-
normalizedType === "Int" || normalizedType === "Float"
|
|
104
|
-
? `Numeric UI for ${field} (${normalizedType}) requires manual review; a safe numeric input component pattern is not yet detected.`
|
|
105
|
-
: `Recommended UI component for ${field} (${normalizedType}): ${addFieldComponentForType(typeName)}.`,
|
|
141
|
+
target: paths.template,
|
|
142
|
+
confidence: policy.confidence,
|
|
143
|
+
action: templateRequested
|
|
144
|
+
? `apply_workflow will try to add ${policy.component} to Template when the existing Template has a safe generated field-list pattern.`
|
|
145
|
+
: `Recommended component is ${policy.component}. Pass surfaces=["template"] when this field should be rendered in the Template form.`,
|
|
146
|
+
message: `Recommended UI component for ${field} (${normalizedType}): ${policy.component}.`,
|
|
106
147
|
},
|
|
148
|
+
...(!surfaces
|
|
149
|
+
? [
|
|
150
|
+
{
|
|
151
|
+
code: "add-field-template-surface-choice",
|
|
152
|
+
kind: "manual-action" as const,
|
|
153
|
+
target: paths.template,
|
|
154
|
+
action: `Choose whether to expose ${field} in Template by passing surfaces=["template"].`,
|
|
155
|
+
confidence: "medium" as const,
|
|
156
|
+
message: `Template exposure for ${module}.${field} is not selected yet.`,
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
: []),
|
|
160
|
+
...(!includeInLight
|
|
161
|
+
? [
|
|
162
|
+
{
|
|
163
|
+
code: "add-field-light-projection-choice",
|
|
164
|
+
kind: "manual-action" as const,
|
|
165
|
+
target: paths.constant,
|
|
166
|
+
action: `Pass includeInLight=true when ${field} should appear in Light${capitalize(module)} list/card projections.`,
|
|
167
|
+
confidence: "medium" as const,
|
|
168
|
+
message: `Light projection exposure for ${module}.${field} is not selected yet.`,
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
: []),
|
|
172
|
+
...(includeInLight
|
|
173
|
+
? [
|
|
174
|
+
{
|
|
175
|
+
code: "add-field-light-projection",
|
|
176
|
+
kind: "placement" as const,
|
|
177
|
+
target: paths.constant,
|
|
178
|
+
confidence: "high" as const,
|
|
179
|
+
message: `Add ${field} to Light${capitalize(module)} projection fields.`,
|
|
180
|
+
},
|
|
181
|
+
]
|
|
182
|
+
: []),
|
|
183
|
+
...(surfaces && !templateRequested
|
|
184
|
+
? [
|
|
185
|
+
{
|
|
186
|
+
code: "add-field-ui-surface-skip",
|
|
187
|
+
kind: "manual-action" as const,
|
|
188
|
+
target: paths.template,
|
|
189
|
+
confidence: "medium" as const,
|
|
190
|
+
message: `Template is not included in surfaces, so ${module}.${field} will not be auto-rendered there.`,
|
|
191
|
+
},
|
|
192
|
+
]
|
|
193
|
+
: []),
|
|
107
194
|
{
|
|
108
195
|
code: "add-field-ui-manual-review",
|
|
109
196
|
kind: "manual-action",
|
|
110
|
-
target:
|
|
111
|
-
action: `Review ${app}:${module} Template
|
|
197
|
+
target: paths.template,
|
|
198
|
+
action: `Review ${app}:${module} UI after apply. Template auto-edit requires a generated ${module}Form hook and Layout.Template field list; Unit/View are not auto-edited because list/card placement depends on local layout. Candidate positions: Layout.Template before the closing tag, Light${capitalize(
|
|
199
|
+
module,
|
|
200
|
+
)} projection array for list data, and Unit/View card sections for display.`,
|
|
112
201
|
confidence: "medium",
|
|
113
|
-
message:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
202
|
+
message: "UI manual review includes the reason auto-edit may be skipped and candidate insertion positions.",
|
|
203
|
+
},
|
|
204
|
+
...createAddFieldDefaultRecommendations(inputs),
|
|
205
|
+
];
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const createWorkflowPlanPredictedChanges = (
|
|
209
|
+
spec: WorkflowSpec,
|
|
210
|
+
inputs: Record<string, WorkflowInputValue>,
|
|
211
|
+
): WorkflowPlan["predictedChanges"] => {
|
|
212
|
+
if (spec.name !== "add-field") return spec.predictedChanges;
|
|
213
|
+
const paths = addFieldPaths(inputs);
|
|
214
|
+
const surfaces = selectedAddFieldSurfaces(inputs);
|
|
215
|
+
const includeInLight = inputs.includeInLight === true;
|
|
216
|
+
return [
|
|
217
|
+
{
|
|
218
|
+
target: paths.constant,
|
|
219
|
+
action: "modify",
|
|
220
|
+
applyScope: "auto",
|
|
221
|
+
reason: includeInLight ? "Field shape and Light projection are added." : "Field shape is added.",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
target: paths.dictionary,
|
|
225
|
+
action: "modify",
|
|
226
|
+
applyScope: "auto",
|
|
227
|
+
reason: "Field label is added.",
|
|
228
|
+
},
|
|
229
|
+
...(!surfaces || surfaces.has("template")
|
|
230
|
+
? [
|
|
231
|
+
{
|
|
232
|
+
target: paths.template,
|
|
233
|
+
action: "modify" as const,
|
|
234
|
+
applyScope: surfaces?.has("template") ? ("auto" as const) : ("manual-review" as const),
|
|
235
|
+
reason: surfaces?.has("template")
|
|
236
|
+
? "Template form is selected for safe-pattern auto insertion."
|
|
237
|
+
: "Form surface may include the field.",
|
|
238
|
+
},
|
|
239
|
+
]
|
|
240
|
+
: []),
|
|
241
|
+
{
|
|
242
|
+
target: `${addFieldTargetRoot(typeof inputs.app === "string" ? inputs.app : null)}/lib/cnst.ts`,
|
|
243
|
+
action: "sync",
|
|
244
|
+
applyScope: "generated-sync",
|
|
245
|
+
reason: "Generated constants may change after sync.",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
target: `${addFieldTargetRoot(typeof inputs.app === "string" ? inputs.app : null)}/lib/dict.ts`,
|
|
249
|
+
action: "sync",
|
|
250
|
+
applyScope: "generated-sync",
|
|
251
|
+
reason: "Generated dictionary barrel may change after sync.",
|
|
117
252
|
},
|
|
118
253
|
];
|
|
119
254
|
};
|
|
120
255
|
|
|
256
|
+
const createWorkflowPlanOptionalSurfaces = (
|
|
257
|
+
spec: WorkflowSpec,
|
|
258
|
+
inputs: Record<string, WorkflowInputValue>,
|
|
259
|
+
): Record<string, WorkflowSurfaceMode> => {
|
|
260
|
+
const optionalSurfaces = spec.optionalSurfaces ?? {};
|
|
261
|
+
const surfaces = selectedAddFieldSurfaces(inputs);
|
|
262
|
+
if (spec.name !== "add-field" || !surfaces) return optionalSurfaces;
|
|
263
|
+
return Object.fromEntries(
|
|
264
|
+
Object.entries(optionalSurfaces).map(([surface, mode]) => [surface, surfaces.has(surface) ? "include" : mode]),
|
|
265
|
+
);
|
|
266
|
+
};
|
|
267
|
+
|
|
121
268
|
const createWorkflowPlanRecommendations = (
|
|
122
269
|
spec: WorkflowSpec,
|
|
123
270
|
inputs: Record<string, WorkflowInputValue>,
|
|
@@ -193,15 +340,33 @@ export const createWorkflowPlan = (spec: WorkflowSpec, rawInputs: Record<string,
|
|
|
193
340
|
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
|
|
194
341
|
});
|
|
195
342
|
}
|
|
343
|
+
if (spec.name === "add-field") {
|
|
344
|
+
const defaultCoercion = addFieldDefaultCoercion(inputs);
|
|
345
|
+
if (defaultCoercion?.diagnostic) {
|
|
346
|
+
diagnostics.push({
|
|
347
|
+
...defaultCoercion.diagnostic,
|
|
348
|
+
code: "workflow-default-value-invalid",
|
|
349
|
+
message: `Plan input default is not valid for ${defaultCoercion.normalizedType}: ${defaultCoercion.diagnostic.message}`,
|
|
350
|
+
});
|
|
351
|
+
} else if (defaultCoercion?.normalized && defaultCoercion.expression) {
|
|
352
|
+
diagnostics.push({
|
|
353
|
+
severity: "warning",
|
|
354
|
+
code: "workflow-default-value-normalized",
|
|
355
|
+
input: "default",
|
|
356
|
+
failureScope: "source-change",
|
|
357
|
+
message: `Plan input default will be written as ${defaultCoercion.normalizedType} literal ${defaultCoercion.expression}.`,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
196
361
|
|
|
197
362
|
return {
|
|
198
363
|
schemaVersion: 1,
|
|
199
364
|
workflow: spec.name,
|
|
200
365
|
mode: "plan",
|
|
201
366
|
inputs,
|
|
202
|
-
optionalSurfaces: spec
|
|
367
|
+
optionalSurfaces: createWorkflowPlanOptionalSurfaces(spec, inputs),
|
|
203
368
|
steps: spec.steps,
|
|
204
|
-
predictedChanges: spec
|
|
369
|
+
predictedChanges: createWorkflowPlanPredictedChanges(spec, inputs),
|
|
205
370
|
validation: spec.validation,
|
|
206
371
|
diagnostics,
|
|
207
372
|
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
package/workflow/render.ts
CHANGED
|
@@ -84,19 +84,72 @@ export const renderWorkflowPlan = (plan: WorkflowPlan) =>
|
|
|
84
84
|
"",
|
|
85
85
|
].join("\n");
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
const renderRecommendation = (recommendation: WorkflowApplyReport["recommendations"][number]) => {
|
|
88
|
+
const target = recommendation.target ? ` ${recommendation.target}` : "";
|
|
89
|
+
const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
|
|
90
|
+
return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const renderDiagnostic = (diagnostic: WorkflowApplyReport["diagnostics"][number]) =>
|
|
94
|
+
`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
|
|
95
|
+
|
|
96
|
+
const applySourceStatus = (report: WorkflowApplyReport) =>
|
|
97
|
+
report.diagnostics.some(
|
|
98
|
+
(diagnostic) =>
|
|
99
|
+
diagnostic.severity === "error" &&
|
|
100
|
+
(diagnostic.failureScope === "source-change" ||
|
|
101
|
+
!diagnostic.failureScope ||
|
|
102
|
+
diagnostic.failureScope === "unknown"),
|
|
103
|
+
)
|
|
104
|
+
? "failed"
|
|
105
|
+
: "passed";
|
|
106
|
+
|
|
107
|
+
export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
108
|
+
const manualReviewItems = [
|
|
109
|
+
...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
|
|
110
|
+
...report.recommendations
|
|
111
|
+
.filter((recommendation) => recommendation.kind === "manual-action")
|
|
112
|
+
.map(renderRecommendation),
|
|
113
|
+
];
|
|
114
|
+
const validationBlockers = report.diagnostics
|
|
115
|
+
.filter(
|
|
116
|
+
(diagnostic) =>
|
|
117
|
+
diagnostic.severity === "error" &&
|
|
118
|
+
(diagnostic.failureScope === "workspace-config" || diagnostic.failureScope === "environment"),
|
|
119
|
+
)
|
|
120
|
+
.map(renderDiagnostic);
|
|
121
|
+
const sourceBlockers = report.diagnostics
|
|
122
|
+
.filter(
|
|
123
|
+
(diagnostic) =>
|
|
124
|
+
diagnostic.severity === "error" &&
|
|
125
|
+
(diagnostic.failureScope === "source-change" ||
|
|
126
|
+
!diagnostic.failureScope ||
|
|
127
|
+
diagnostic.failureScope === "unknown"),
|
|
128
|
+
)
|
|
129
|
+
.map(renderDiagnostic);
|
|
130
|
+
return [
|
|
89
131
|
`# Workflow Apply: ${report.workflow}`,
|
|
90
132
|
"",
|
|
91
133
|
`- Mode: ${report.mode}`,
|
|
92
134
|
`- Status: ${report.status}`,
|
|
135
|
+
`- Source-change status: ${applySourceStatus(report)}`,
|
|
136
|
+
`- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
|
|
137
|
+
...(report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : []),
|
|
93
138
|
"",
|
|
94
|
-
"##
|
|
139
|
+
"## Apply Checks",
|
|
140
|
+
...(report.postApplyChecks?.length
|
|
141
|
+
? report.postApplyChecks.map((check) => `- [${check.status}] ${check.code} ${check.target}: ${check.message}`)
|
|
142
|
+
: ["- not run"]),
|
|
143
|
+
"",
|
|
144
|
+
"## Source Change Blockers",
|
|
145
|
+
...(sourceBlockers.length ? sourceBlockers : ["- none"]),
|
|
146
|
+
"",
|
|
147
|
+
"## Automatically Modified",
|
|
95
148
|
...(report.changedFiles.length
|
|
96
149
|
? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
97
150
|
: ["- none"]),
|
|
98
151
|
"",
|
|
99
|
-
"## Generated
|
|
152
|
+
"## Generated Sync",
|
|
100
153
|
...(report.generatedFiles.length
|
|
101
154
|
? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
102
155
|
: ["- none"]),
|
|
@@ -111,15 +164,21 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) =>
|
|
|
111
164
|
? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
112
165
|
: ["- none"]),
|
|
113
166
|
"",
|
|
167
|
+
"## User Review Required",
|
|
168
|
+
...(manualReviewItems.length ? manualReviewItems : ["- none"]),
|
|
169
|
+
"",
|
|
170
|
+
"## Validation Blockers",
|
|
171
|
+
...(validationBlockers.length
|
|
172
|
+
? validationBlockers
|
|
173
|
+
: report.recommendedValidationCommands.length
|
|
174
|
+
? ["- none detected during apply; run validation with the validation target for command-level blockers."]
|
|
175
|
+
: ["- none"]),
|
|
176
|
+
"",
|
|
114
177
|
"## Diagnostics",
|
|
115
|
-
...(report.diagnostics.length
|
|
116
|
-
? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
|
|
117
|
-
: ["- none"]),
|
|
178
|
+
...(report.diagnostics.length ? report.diagnostics.map(renderDiagnostic) : ["- none"]),
|
|
118
179
|
"",
|
|
119
180
|
"## Recommendations",
|
|
120
|
-
...(report.recommendations.length
|
|
121
|
-
? report.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`)
|
|
122
|
-
: ["- none"]),
|
|
181
|
+
...(report.recommendations.length ? report.recommendations.slice(0, 3).map(renderRecommendation) : ["- none"]),
|
|
123
182
|
"",
|
|
124
183
|
"## Next Actions",
|
|
125
184
|
...(report.nextActions.length
|
|
@@ -127,6 +186,7 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) =>
|
|
|
127
186
|
: ["- none"]),
|
|
128
187
|
"",
|
|
129
188
|
].join("\n");
|
|
189
|
+
};
|
|
130
190
|
|
|
131
191
|
export const renderWorkflowApply = (report: WorkflowApplyReport, format: WorkflowFormat = "markdown") =>
|
|
132
192
|
format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
@@ -141,12 +201,33 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
|
|
|
141
201
|
`- Workspace status: ${report.workspaceStatus}`,
|
|
142
202
|
`- Overall status: ${report.overallStatus}`,
|
|
143
203
|
"",
|
|
204
|
+
"## Status Summary",
|
|
205
|
+
`- Source-change: ${report.summary.sourceChange}`,
|
|
206
|
+
`- Generated sync: ${report.summary.generatedSync}`,
|
|
207
|
+
`- Workspace config: ${report.summary.workspaceConfig}`,
|
|
208
|
+
`- Environment: ${report.summary.environment}`,
|
|
209
|
+
"",
|
|
210
|
+
"## Source Change Diagnostics",
|
|
211
|
+
...(report.workflowDiagnostics?.length
|
|
212
|
+
? report.workflowDiagnostics.map(
|
|
213
|
+
(diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
|
|
214
|
+
)
|
|
215
|
+
: ["- none"]),
|
|
216
|
+
"",
|
|
217
|
+
"## Existing Workspace Blockers",
|
|
218
|
+
...(report.baselineDiagnostics?.length
|
|
219
|
+
? report.baselineDiagnostics.map(
|
|
220
|
+
(diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
|
|
221
|
+
)
|
|
222
|
+
: ["- none"]),
|
|
223
|
+
"",
|
|
144
224
|
"## Known Blockers",
|
|
145
225
|
...(report.knownBlockers.length
|
|
146
226
|
? report.knownBlockers.map((blocker) => {
|
|
147
227
|
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
148
228
|
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
149
|
-
|
|
229
|
+
const known = blocker.known ? " known" : "";
|
|
230
|
+
return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
|
|
150
231
|
})
|
|
151
232
|
: ["- none"]),
|
|
152
233
|
"",
|