@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/devkitUtils.test.ts
CHANGED
|
@@ -269,11 +269,11 @@ describe("getModelFileData", () => {
|
|
|
269
269
|
].join("\n"),
|
|
270
270
|
);
|
|
271
271
|
await write(
|
|
272
|
-
path.join(root, "apps/demo/lib/post/
|
|
272
|
+
path.join(root, "apps/demo/lib/post/Post.Unit.tsx"),
|
|
273
273
|
"export default function Unit() { return null; }\n",
|
|
274
274
|
);
|
|
275
275
|
await write(
|
|
276
|
-
path.join(root, "apps/demo/lib/post/
|
|
276
|
+
path.join(root, "apps/demo/lib/post/Post.View.tsx"),
|
|
277
277
|
"export default function View() { return null; }\n",
|
|
278
278
|
);
|
|
279
279
|
|
package/executors.ts
CHANGED
|
@@ -626,14 +626,14 @@ export class Executor {
|
|
|
626
626
|
overwrite?: boolean;
|
|
627
627
|
},
|
|
628
628
|
dict: { [key: string]: string } = {},
|
|
629
|
-
options: { [key: string]:
|
|
629
|
+
options: { [key: string]: unknown } = {},
|
|
630
630
|
): Promise<FileContent | null> {
|
|
631
631
|
if (targetPath.endsWith(".ts") || targetPath.endsWith(".tsx")) {
|
|
632
632
|
const getContent = (await import(templatePath)) as {
|
|
633
633
|
default: (
|
|
634
634
|
scanInfo: AppInfo | LibInfo | null,
|
|
635
635
|
dict: { [key: string]: string },
|
|
636
|
-
options?: { [key: string]:
|
|
636
|
+
options?: { [key: string]: unknown },
|
|
637
637
|
) => Promise<string | null | { filename: string; content: string }>;
|
|
638
638
|
};
|
|
639
639
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
@@ -686,7 +686,7 @@ export class Executor {
|
|
|
686
686
|
template: string;
|
|
687
687
|
scanInfo?: AppInfo | LibInfo | null;
|
|
688
688
|
dict?: { [key: string]: string };
|
|
689
|
-
options?: { [key: string]:
|
|
689
|
+
options?: { [key: string]: unknown };
|
|
690
690
|
overwrite?: boolean;
|
|
691
691
|
}): Promise<FileContent[]> {
|
|
692
692
|
const templateRoot = await this.#resolveTemplateRoot();
|
|
@@ -752,7 +752,7 @@ export class Executor {
|
|
|
752
752
|
basePath: string;
|
|
753
753
|
template: string;
|
|
754
754
|
dict?: { [key: string]: string };
|
|
755
|
-
options?: { [key: string]:
|
|
755
|
+
options?: { [key: string]: unknown };
|
|
756
756
|
overwrite?: boolean;
|
|
757
757
|
}): Promise<FileContent[]> {
|
|
758
758
|
const dict = {
|
|
@@ -827,10 +827,10 @@ export class Executor {
|
|
|
827
827
|
filePath: string,
|
|
828
828
|
{ fix = false, dryRun = false }: { fix?: boolean; dryRun?: boolean } = {},
|
|
829
829
|
): Promise<{
|
|
830
|
-
results:
|
|
830
|
+
results: unknown[]; // ESLint.LintResult[];
|
|
831
831
|
message: string;
|
|
832
|
-
errors:
|
|
833
|
-
warnings:
|
|
832
|
+
errors: unknown[]; // ESLintLinter.LintMessage[];
|
|
833
|
+
warnings: unknown[]; // ESLintLinter.LintMessage[];
|
|
834
834
|
}> {
|
|
835
835
|
const path = this.getPath(filePath);
|
|
836
836
|
const linter = this.getLinter();
|
|
@@ -1225,40 +1225,44 @@ export class SysExecutor extends Executor {
|
|
|
1225
1225
|
async getViewComponents() {
|
|
1226
1226
|
const viewComponents = (await this.readdir("lib"))
|
|
1227
1227
|
.filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
|
|
1228
|
-
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.View.tsx`).exists());
|
|
1228
|
+
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.View.tsx`).exists());
|
|
1229
1229
|
return viewComponents;
|
|
1230
1230
|
}
|
|
1231
1231
|
|
|
1232
1232
|
async getUnitComponents() {
|
|
1233
1233
|
const unitComponents = (await this.readdir("lib"))
|
|
1234
1234
|
.filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
|
|
1235
|
-
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`).exists());
|
|
1235
|
+
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Unit.tsx`).exists());
|
|
1236
1236
|
return unitComponents;
|
|
1237
1237
|
}
|
|
1238
1238
|
async getTemplateComponents() {
|
|
1239
1239
|
const templateComponents = (await this.readdir("lib"))
|
|
1240
1240
|
.filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
|
|
1241
|
-
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`).exists());
|
|
1241
|
+
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Template.tsx`).exists());
|
|
1242
1242
|
return templateComponents;
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
1245
|
async getViewsSourceCode() {
|
|
1246
1246
|
const viewComponents = await this.getViewComponents();
|
|
1247
1247
|
return Promise.all(
|
|
1248
|
-
viewComponents.map((viewComponent) =>
|
|
1248
|
+
viewComponents.map((viewComponent) =>
|
|
1249
|
+
this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`),
|
|
1250
|
+
),
|
|
1249
1251
|
);
|
|
1250
1252
|
}
|
|
1251
1253
|
async getUnitsSourceCode() {
|
|
1252
1254
|
const unitComponents = await this.getUnitComponents();
|
|
1253
1255
|
return Promise.all(
|
|
1254
|
-
unitComponents.map((unitComponent) =>
|
|
1256
|
+
unitComponents.map((unitComponent) =>
|
|
1257
|
+
this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`),
|
|
1258
|
+
),
|
|
1255
1259
|
);
|
|
1256
1260
|
}
|
|
1257
1261
|
async getTemplatesSourceCode() {
|
|
1258
1262
|
const templateComponents = await this.getTemplateComponents();
|
|
1259
1263
|
return Promise.all(
|
|
1260
1264
|
templateComponents.map((templateComponent) =>
|
|
1261
|
-
this.getLocalFile(`lib/${templateComponent}/${templateComponent}.Template.tsx`),
|
|
1265
|
+
this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`),
|
|
1262
1266
|
),
|
|
1263
1267
|
);
|
|
1264
1268
|
}
|
package/getModelFileData.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { capitalize } from "akanjs/common";
|
|
2
|
+
|
|
1
3
|
interface ModelFileData {
|
|
2
4
|
moduleType: "lib" | "app";
|
|
3
5
|
moduleName: string;
|
|
@@ -16,9 +18,10 @@ interface ModelFileData {
|
|
|
16
18
|
export const getModelFileData = async (modulePath: string, modelName: string): Promise<ModelFileData> => {
|
|
17
19
|
const moduleType = modulePath.startsWith("apps") ? "app" : "lib";
|
|
18
20
|
const moduleName = modulePath.split("/")[1] ?? "";
|
|
21
|
+
const modelComponentName = capitalize(modelName);
|
|
19
22
|
const constantFilePath = `${modulePath}/lib/${modelName}/${modelName}.constant.ts`;
|
|
20
|
-
const unitFilePath = `${modulePath}/lib/${modelName}/${
|
|
21
|
-
const viewFilePath = `${modulePath}/lib/${modelName}/${
|
|
23
|
+
const unitFilePath = `${modulePath}/lib/${modelName}/${modelComponentName}.Unit.tsx`;
|
|
24
|
+
const viewFilePath = `${modulePath}/lib/${modelName}/${modelComponentName}.View.tsx`;
|
|
22
25
|
const [constantFileStr, unitFileStr, viewFileStr] = await Promise.all([
|
|
23
26
|
Bun.file(constantFilePath).text(),
|
|
24
27
|
Bun.file(unitFilePath).text(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "2.3.9-rc.
|
|
3
|
+
"version": "2.3.9-rc.6",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@langchain/openai": "^1.4.6",
|
|
33
33
|
"@tailwindcss/node": "^4.3.0",
|
|
34
34
|
"@trapezedev/project": "^7.1.4",
|
|
35
|
-
"akanjs": "2.3.9-rc.
|
|
35
|
+
"akanjs": "2.3.9-rc.6",
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"daisyui": "5.5.23",
|
package/workflow/artifacts.ts
CHANGED
|
@@ -28,6 +28,7 @@ export const createWorkflowApplyReport = ({
|
|
|
28
28
|
recommendedValidationCommands,
|
|
29
29
|
commands = [],
|
|
30
30
|
diagnostics = [],
|
|
31
|
+
postApplyChecks = [],
|
|
31
32
|
recommendations = [],
|
|
32
33
|
nextActions = [],
|
|
33
34
|
plan,
|
|
@@ -41,14 +42,21 @@ export const createWorkflowApplyReport = ({
|
|
|
41
42
|
| "appliedCommands"
|
|
42
43
|
| "recommendedValidationCommands"
|
|
43
44
|
| "commands"
|
|
45
|
+
| "postApplyChecks"
|
|
44
46
|
| "recommendations"
|
|
45
47
|
> & {
|
|
46
48
|
appliedCommands?: WorkflowApplyCommand[];
|
|
47
49
|
recommendedValidationCommands?: WorkflowApplyCommand[];
|
|
48
50
|
commands?: WorkflowApplyCommand[];
|
|
51
|
+
postApplyChecks?: WorkflowApplyReport["postApplyChecks"];
|
|
49
52
|
recommendations?: WorkflowApplyReport["recommendations"];
|
|
50
53
|
}): WorkflowApplyReport => {
|
|
51
54
|
const validationCommands = recommendedValidationCommands ?? commands;
|
|
55
|
+
const orderedNextActions = uniqueBy(nextActions, (action) => action.command).sort((left, right) => {
|
|
56
|
+
const leftRepair = left.command.startsWith("akan workflow explain") ? 0 : 1;
|
|
57
|
+
const rightRepair = right.command.startsWith("akan workflow explain") ? 0 : 1;
|
|
58
|
+
return leftRepair - rightRepair;
|
|
59
|
+
});
|
|
52
60
|
return {
|
|
53
61
|
schemaVersion: 1,
|
|
54
62
|
workflow,
|
|
@@ -60,8 +68,9 @@ export const createWorkflowApplyReport = ({
|
|
|
60
68
|
recommendedValidationCommands: uniqueBy(validationCommands, (command) => command.command),
|
|
61
69
|
commands: uniqueBy(validationCommands, (command) => command.command),
|
|
62
70
|
diagnostics,
|
|
71
|
+
postApplyChecks,
|
|
63
72
|
recommendations: uniqueBy(recommendations, (recommendation) => `${recommendation.kind}:${recommendation.code}`),
|
|
64
|
-
nextActions:
|
|
73
|
+
nextActions: orderedNextActions.slice(0, 3),
|
|
65
74
|
plan,
|
|
66
75
|
};
|
|
67
76
|
};
|
|
@@ -186,6 +195,15 @@ const statusForScope = (
|
|
|
186
195
|
return hasScopeFailure(scopes, expectedScope, diagnostics) ? "failed" : "passed";
|
|
187
196
|
};
|
|
188
197
|
|
|
198
|
+
const statusForValidationKind = (
|
|
199
|
+
commands: readonly WorkflowValidationCommandResult[],
|
|
200
|
+
kind: WorkflowValidationCommandResult["kind"],
|
|
201
|
+
): WorkflowValidationStatus => {
|
|
202
|
+
const matching = commands.filter((command) => command.kind === kind);
|
|
203
|
+
if (matching.length === 0) return "unknown";
|
|
204
|
+
return matching.some((command) => command.status === "failed") ? "failed" : "passed";
|
|
205
|
+
};
|
|
206
|
+
|
|
189
207
|
const createKnownBlockers = (
|
|
190
208
|
commands: readonly WorkflowValidationCommandResult[],
|
|
191
209
|
diagnostics: readonly WorkflowDiagnostic[],
|
|
@@ -251,7 +269,17 @@ const createValidationStatuses = (
|
|
|
251
269
|
: workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed"
|
|
252
270
|
? "failed"
|
|
253
271
|
: "passed";
|
|
254
|
-
return {
|
|
272
|
+
return {
|
|
273
|
+
sourceStatus,
|
|
274
|
+
workspaceStatus,
|
|
275
|
+
overallStatus,
|
|
276
|
+
summary: {
|
|
277
|
+
sourceChange: sourceStatus,
|
|
278
|
+
generatedSync: statusForValidationKind(commands, "sync"),
|
|
279
|
+
workspaceConfig: statusForScope(commands, diagnostics, scopes, "workspace-config"),
|
|
280
|
+
environment: statusForScope(commands, diagnostics, scopes, "environment"),
|
|
281
|
+
},
|
|
282
|
+
};
|
|
255
283
|
};
|
|
256
284
|
|
|
257
285
|
export const createWorkflowValidationRunReport = async ({
|
package/workflow/executor.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { capitalize } from "akanjs/common";
|
|
2
|
+
import ts from "typescript";
|
|
2
3
|
import type { Sys, Workspace } from "../commandDecorators";
|
|
3
4
|
import { AppExecutor, LibExecutor } from "../executors";
|
|
4
5
|
import { createWorkflowApplyReport, workflowCommandsForPlan } from "./artifacts";
|
|
6
|
+
import { moduleSourcePaths } from "./source";
|
|
5
7
|
import type {
|
|
6
8
|
PrimitiveChangedFile,
|
|
7
9
|
PrimitiveGeneratedFile,
|
|
@@ -11,11 +13,14 @@ import type {
|
|
|
11
13
|
WorkflowDiagnostic,
|
|
12
14
|
WorkflowInputValue,
|
|
13
15
|
WorkflowPlan,
|
|
16
|
+
WorkflowPostApplyCheck,
|
|
14
17
|
WorkflowPrimitiveOperations,
|
|
18
|
+
WorkflowRecommendation,
|
|
15
19
|
WorkflowStep,
|
|
16
20
|
WorkflowStepRegistry,
|
|
17
21
|
WorkflowStepResult,
|
|
18
22
|
} from "./types";
|
|
23
|
+
import { addFieldUiPolicyForType } from "./uiPolicy";
|
|
19
24
|
|
|
20
25
|
export const workflowStepKey = (workflow: string, stepId: string) => `${workflow}:${stepId}`;
|
|
21
26
|
|
|
@@ -31,6 +36,105 @@ export const primitiveReportToWorkflowStepResult = (report: PrimitiveWriteReport
|
|
|
31
36
|
const workflowStringInput = (value: WorkflowInputValue | undefined) => (typeof value === "string" ? value : null);
|
|
32
37
|
const workflowStringListInput = (value: WorkflowInputValue | undefined) =>
|
|
33
38
|
Array.isArray(value) ? value.join(",") : null;
|
|
39
|
+
const workflowStringArrayInput = (value: WorkflowInputValue | undefined) => (Array.isArray(value) ? value : null);
|
|
40
|
+
const workflowBooleanInput = (value: WorkflowInputValue | undefined) => (typeof value === "boolean" ? value : null);
|
|
41
|
+
|
|
42
|
+
const postApplyDiagnostic = (code: string, message: string, target: string): WorkflowDiagnostic => ({
|
|
43
|
+
severity: "error",
|
|
44
|
+
code,
|
|
45
|
+
message,
|
|
46
|
+
failureScope: "source-change",
|
|
47
|
+
context: { target },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const sourceKindForPath = (filePath: string) => {
|
|
51
|
+
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
52
|
+
if (filePath.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const checkPathCasing = async (workspace: Workspace, filePath: string) => {
|
|
57
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
58
|
+
let current = ".";
|
|
59
|
+
for (const segment of segments) {
|
|
60
|
+
const entries = await workspace.readdir(current);
|
|
61
|
+
if (entries.includes(segment)) {
|
|
62
|
+
current = current === "." ? segment : `${current}/${segment}`;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const caseInsensitiveMatch = entries.find((entry) => entry.toLowerCase() === segment.toLowerCase());
|
|
66
|
+
return caseInsensitiveMatch
|
|
67
|
+
? {
|
|
68
|
+
code: "workflow-path-casing-mismatch",
|
|
69
|
+
message: `Reported path segment "${segment}" does not match actual casing "${caseInsensitiveMatch}" in ${filePath}.`,
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
code: "workflow-path-missing",
|
|
73
|
+
message: `Reported path does not exist: ${filePath}.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const checkTypeScriptSyntax = async (workspace: Workspace, filePath: string) => {
|
|
80
|
+
const scriptKind = sourceKindForPath(filePath);
|
|
81
|
+
if (!scriptKind) return null;
|
|
82
|
+
const content = await workspace.readFile(filePath);
|
|
83
|
+
const source = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
84
|
+
const diagnostic = source.parseDiagnostics[0];
|
|
85
|
+
if (!diagnostic) return null;
|
|
86
|
+
const position = diagnostic.file?.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
87
|
+
const location = position ? `:${position.line + 1}:${position.character + 1}` : "";
|
|
88
|
+
return {
|
|
89
|
+
code: "workflow-post-apply-syntax-error",
|
|
90
|
+
message: `Generated source has a TypeScript syntax error at ${filePath}${location}: ${ts.flattenDiagnosticMessageText(
|
|
91
|
+
diagnostic.messageText,
|
|
92
|
+
" ",
|
|
93
|
+
)}`,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const checkChangedFile = async (workspace: Workspace, file: PrimitiveChangedFile): Promise<WorkflowStepResult> => {
|
|
98
|
+
if (file.action === "remove") return { postApplyChecks: [] };
|
|
99
|
+
const diagnostics: WorkflowDiagnostic[] = [];
|
|
100
|
+
const checks: WorkflowPostApplyCheck[] = [];
|
|
101
|
+
const pathIssue = await checkPathCasing(workspace, file.path);
|
|
102
|
+
if (pathIssue) {
|
|
103
|
+
diagnostics.push(postApplyDiagnostic(pathIssue.code, pathIssue.message, file.path));
|
|
104
|
+
checks.push({ ...pathIssue, target: file.path, status: "failed" });
|
|
105
|
+
return { diagnostics, postApplyChecks: checks };
|
|
106
|
+
}
|
|
107
|
+
const syntaxIssue = await checkTypeScriptSyntax(workspace, file.path);
|
|
108
|
+
if (syntaxIssue) {
|
|
109
|
+
diagnostics.push(postApplyDiagnostic(syntaxIssue.code, syntaxIssue.message, file.path));
|
|
110
|
+
checks.push({ ...syntaxIssue, target: file.path, status: "failed" });
|
|
111
|
+
return { diagnostics, postApplyChecks: checks };
|
|
112
|
+
}
|
|
113
|
+
checks.push({
|
|
114
|
+
code: "workflow-post-apply-file-valid",
|
|
115
|
+
target: file.path,
|
|
116
|
+
status: "passed",
|
|
117
|
+
message: "Changed file exists with exact casing and parses as source when applicable.",
|
|
118
|
+
});
|
|
119
|
+
return { postApplyChecks: checks };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const checkRecommendationPath = async (
|
|
123
|
+
workspace: Workspace,
|
|
124
|
+
recommendation: WorkflowRecommendation,
|
|
125
|
+
): Promise<WorkflowDiagnostic | null> => {
|
|
126
|
+
if (!recommendation.target || recommendation.target.includes("*") || recommendation.target.startsWith("<"))
|
|
127
|
+
return null;
|
|
128
|
+
const pathIssue = await checkPathCasing(workspace, recommendation.target);
|
|
129
|
+
if (!pathIssue) return null;
|
|
130
|
+
return {
|
|
131
|
+
severity: "warning",
|
|
132
|
+
code: "workflow-recommendation-path-unverified",
|
|
133
|
+
message: `${pathIssue.message} Recommendation "${recommendation.code}" should not be treated as an exact file target until the path is corrected.`,
|
|
134
|
+
failureScope: "source-change",
|
|
135
|
+
context: { target: recommendation.target },
|
|
136
|
+
};
|
|
137
|
+
};
|
|
34
138
|
|
|
35
139
|
const resolveWorkflowSys = async (workspace: Workspace, target: string | null): Promise<Sys | null> => {
|
|
36
140
|
if (!target) return null;
|
|
@@ -66,22 +170,22 @@ const addFieldUiSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult =>
|
|
|
66
170
|
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
67
171
|
const field = workflowStringInput(plan.inputs.field) ?? "<field>";
|
|
68
172
|
const typeName = workflowStringInput(plan.inputs.type);
|
|
69
|
-
const
|
|
173
|
+
const policy = addFieldUiPolicyForType(typeName ?? "String");
|
|
174
|
+
const surfaces = workflowStringArrayInput(plan.inputs.surfaces);
|
|
175
|
+
const templateRequested = surfaces?.includes("template") ?? false;
|
|
70
176
|
const moduleClassName = capitalize(module);
|
|
71
|
-
const target = `${app ? `apps/${app}` : "*"}
|
|
177
|
+
const target = `${app ? `apps/${app}` : "*"}/${moduleSourcePaths(module).template}`;
|
|
72
178
|
return {
|
|
73
179
|
recommendations: [
|
|
74
180
|
{
|
|
75
181
|
code: "add-field-ui-surface-review",
|
|
76
182
|
kind: "manual-action",
|
|
77
183
|
target,
|
|
78
|
-
action:
|
|
79
|
-
? `
|
|
80
|
-
: `
|
|
184
|
+
action: templateRequested
|
|
185
|
+
? `Template was requested for ${field}. If no Template file changed, auto-edit was skipped because the file was missing, the generated ${module}Form/Layout.Template pattern was not found, or ${policy.component} needs option binding. Candidate position: inside Layout.Template near the existing Field components.`
|
|
186
|
+
: `Template was not selected, so UI files are intentionally left unchanged. Candidate positions if you expose it later: Layout.Template field list for editing, Light${moduleClassName} projection for list/card data, and Unit/View card sections for display.`,
|
|
81
187
|
confidence: "medium",
|
|
82
|
-
message:
|
|
83
|
-
? `No UI file was modified automatically because a safe numeric input component pattern is not yet detected for ${module}.${field}.`
|
|
84
|
-
: `Review Template/Unit/View/Store surfaces for ${module}.${field}; no UI file was modified automatically.`,
|
|
188
|
+
message: `Review UI surfaces for ${module}.${field}; recommended component is ${policy.component}, with manual reasons and candidate positions in action.`,
|
|
85
189
|
},
|
|
86
190
|
],
|
|
87
191
|
nextActions: [
|
|
@@ -149,6 +253,8 @@ export const createWorkflowStepRegistry = ({
|
|
|
149
253
|
field: workflowStringInput(plan.inputs.field),
|
|
150
254
|
values: workflowStringListInput(plan.inputs.values),
|
|
151
255
|
defaultValue: workflowStringInput(plan.inputs.default),
|
|
256
|
+
surfaces: workflowStringArrayInput(plan.inputs.surfaces),
|
|
257
|
+
includeInLight: workflowBooleanInput(plan.inputs.includeInLight),
|
|
152
258
|
}),
|
|
153
259
|
);
|
|
154
260
|
}
|
|
@@ -159,6 +265,8 @@ export const createWorkflowStepRegistry = ({
|
|
|
159
265
|
field: workflowStringInput(plan.inputs.field),
|
|
160
266
|
type: workflowStringInput(plan.inputs.type),
|
|
161
267
|
defaultValue: workflowStringInput(plan.inputs.default),
|
|
268
|
+
surfaces: workflowStringArrayInput(plan.inputs.surfaces),
|
|
269
|
+
includeInLight: workflowBooleanInput(plan.inputs.includeInLight),
|
|
162
270
|
}),
|
|
163
271
|
);
|
|
164
272
|
},
|
|
@@ -189,13 +297,17 @@ export const createWorkflowStepCommandResult = (
|
|
|
189
297
|
});
|
|
190
298
|
|
|
191
299
|
export class WorkflowExecutor {
|
|
192
|
-
constructor(
|
|
300
|
+
constructor(
|
|
301
|
+
private readonly registry: WorkflowStepRegistry,
|
|
302
|
+
private readonly workspace?: Workspace,
|
|
303
|
+
) {}
|
|
193
304
|
|
|
194
305
|
async apply(plan: WorkflowPlan) {
|
|
195
306
|
const changedFiles: PrimitiveChangedFile[] = [];
|
|
196
307
|
const generatedFiles: PrimitiveGeneratedFile[] = [];
|
|
197
308
|
const recommendedValidationCommands: WorkflowApplyCommand[] = [];
|
|
198
309
|
const diagnostics: WorkflowDiagnostic[] = [...plan.diagnostics];
|
|
310
|
+
const postApplyChecks: WorkflowPostApplyCheck[] = [];
|
|
199
311
|
const recommendations = [...plan.recommendations];
|
|
200
312
|
const nextActions: PrimitiveNextAction[] = [];
|
|
201
313
|
|
|
@@ -207,6 +319,7 @@ export class WorkflowExecutor {
|
|
|
207
319
|
generatedFiles,
|
|
208
320
|
recommendedValidationCommands,
|
|
209
321
|
diagnostics,
|
|
322
|
+
postApplyChecks,
|
|
210
323
|
recommendations,
|
|
211
324
|
nextActions,
|
|
212
325
|
plan,
|
|
@@ -243,6 +356,20 @@ export class WorkflowExecutor {
|
|
|
243
356
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) break;
|
|
244
357
|
}
|
|
245
358
|
|
|
359
|
+
if (this.workspace && !diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
360
|
+
for (const file of changedFiles) {
|
|
361
|
+
const result = await checkChangedFile(this.workspace, file);
|
|
362
|
+
postApplyChecks.push(...(result.postApplyChecks ?? []));
|
|
363
|
+
diagnostics.push(...(result.diagnostics ?? []));
|
|
364
|
+
}
|
|
365
|
+
const recommendationDiagnostics = await Promise.all(
|
|
366
|
+
recommendations.map((recommendation) => checkRecommendationPath(this.workspace as Workspace, recommendation)),
|
|
367
|
+
);
|
|
368
|
+
diagnostics.push(
|
|
369
|
+
...recommendationDiagnostics.filter((diagnostic): diagnostic is WorkflowDiagnostic => !!diagnostic),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
246
373
|
return createWorkflowApplyReport({
|
|
247
374
|
workflow: plan.workflow,
|
|
248
375
|
mode: "apply",
|
|
@@ -250,6 +377,7 @@ export class WorkflowExecutor {
|
|
|
250
377
|
generatedFiles,
|
|
251
378
|
recommendedValidationCommands,
|
|
252
379
|
diagnostics,
|
|
380
|
+
postApplyChecks,
|
|
253
381
|
recommendations,
|
|
254
382
|
nextActions,
|
|
255
383
|
plan,
|