@akanjs/devkit 2.3.9-rc.5 → 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/package.json +2 -2
- package/workflow/artifacts.ts +10 -1
- package/workflow/executor.ts +121 -1
- package/workflow/render.ts +49 -5
- package/workflow/source.ts +59 -9
- package/workflow/types.ts +9 -0
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
|
};
|
package/workflow/executor.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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";
|
|
@@ -12,7 +13,9 @@ import type {
|
|
|
12
13
|
WorkflowDiagnostic,
|
|
13
14
|
WorkflowInputValue,
|
|
14
15
|
WorkflowPlan,
|
|
16
|
+
WorkflowPostApplyCheck,
|
|
15
17
|
WorkflowPrimitiveOperations,
|
|
18
|
+
WorkflowRecommendation,
|
|
16
19
|
WorkflowStep,
|
|
17
20
|
WorkflowStepRegistry,
|
|
18
21
|
WorkflowStepResult,
|
|
@@ -36,6 +39,103 @@ const workflowStringListInput = (value: WorkflowInputValue | undefined) =>
|
|
|
36
39
|
const workflowStringArrayInput = (value: WorkflowInputValue | undefined) => (Array.isArray(value) ? value : null);
|
|
37
40
|
const workflowBooleanInput = (value: WorkflowInputValue | undefined) => (typeof value === "boolean" ? value : null);
|
|
38
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
|
+
};
|
|
138
|
+
|
|
39
139
|
const resolveWorkflowSys = async (workspace: Workspace, target: string | null): Promise<Sys | null> => {
|
|
40
140
|
if (!target) return null;
|
|
41
141
|
const [apps, libs] = await workspace.getSyss();
|
|
@@ -197,13 +297,17 @@ export const createWorkflowStepCommandResult = (
|
|
|
197
297
|
});
|
|
198
298
|
|
|
199
299
|
export class WorkflowExecutor {
|
|
200
|
-
constructor(
|
|
300
|
+
constructor(
|
|
301
|
+
private readonly registry: WorkflowStepRegistry,
|
|
302
|
+
private readonly workspace?: Workspace,
|
|
303
|
+
) {}
|
|
201
304
|
|
|
202
305
|
async apply(plan: WorkflowPlan) {
|
|
203
306
|
const changedFiles: PrimitiveChangedFile[] = [];
|
|
204
307
|
const generatedFiles: PrimitiveGeneratedFile[] = [];
|
|
205
308
|
const recommendedValidationCommands: WorkflowApplyCommand[] = [];
|
|
206
309
|
const diagnostics: WorkflowDiagnostic[] = [...plan.diagnostics];
|
|
310
|
+
const postApplyChecks: WorkflowPostApplyCheck[] = [];
|
|
207
311
|
const recommendations = [...plan.recommendations];
|
|
208
312
|
const nextActions: PrimitiveNextAction[] = [];
|
|
209
313
|
|
|
@@ -215,6 +319,7 @@ export class WorkflowExecutor {
|
|
|
215
319
|
generatedFiles,
|
|
216
320
|
recommendedValidationCommands,
|
|
217
321
|
diagnostics,
|
|
322
|
+
postApplyChecks,
|
|
218
323
|
recommendations,
|
|
219
324
|
nextActions,
|
|
220
325
|
plan,
|
|
@@ -251,6 +356,20 @@ export class WorkflowExecutor {
|
|
|
251
356
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) break;
|
|
252
357
|
}
|
|
253
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
|
+
|
|
254
373
|
return createWorkflowApplyReport({
|
|
255
374
|
workflow: plan.workflow,
|
|
256
375
|
mode: "apply",
|
|
@@ -258,6 +377,7 @@ export class WorkflowExecutor {
|
|
|
258
377
|
generatedFiles,
|
|
259
378
|
recommendedValidationCommands,
|
|
260
379
|
diagnostics,
|
|
380
|
+
postApplyChecks,
|
|
261
381
|
recommendations,
|
|
262
382
|
nextActions,
|
|
263
383
|
plan,
|
package/workflow/render.ts
CHANGED
|
@@ -93,6 +93,17 @@ const renderRecommendation = (recommendation: WorkflowApplyReport["recommendatio
|
|
|
93
93
|
const renderDiagnostic = (diagnostic: WorkflowApplyReport["diagnostics"][number]) =>
|
|
94
94
|
`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
|
|
95
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
|
+
|
|
96
107
|
export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
97
108
|
const manualReviewItems = [
|
|
98
109
|
...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
|
|
@@ -107,13 +118,32 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
|
107
118
|
(diagnostic.failureScope === "workspace-config" || diagnostic.failureScope === "environment"),
|
|
108
119
|
)
|
|
109
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);
|
|
110
130
|
return [
|
|
111
131
|
`# Workflow Apply: ${report.workflow}`,
|
|
112
132
|
"",
|
|
113
133
|
`- Mode: ${report.mode}`,
|
|
114
134
|
`- Status: ${report.status}`,
|
|
135
|
+
`- Source-change status: ${applySourceStatus(report)}`,
|
|
136
|
+
`- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
|
|
115
137
|
...(report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : []),
|
|
116
138
|
"",
|
|
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
|
+
"",
|
|
117
147
|
"## Automatically Modified",
|
|
118
148
|
...(report.changedFiles.length
|
|
119
149
|
? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
@@ -148,7 +178,7 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
|
|
|
148
178
|
...(report.diagnostics.length ? report.diagnostics.map(renderDiagnostic) : ["- none"]),
|
|
149
179
|
"",
|
|
150
180
|
"## Recommendations",
|
|
151
|
-
...(report.recommendations.length ? report.recommendations.map(renderRecommendation) : ["- none"]),
|
|
181
|
+
...(report.recommendations.length ? report.recommendations.slice(0, 3).map(renderRecommendation) : ["- none"]),
|
|
152
182
|
"",
|
|
153
183
|
"## Next Actions",
|
|
154
184
|
...(report.nextActions.length
|
|
@@ -172,10 +202,24 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
|
|
|
172
202
|
`- Overall status: ${report.overallStatus}`,
|
|
173
203
|
"",
|
|
174
204
|
"## Status Summary",
|
|
175
|
-
`-
|
|
176
|
-
`- Generated sync
|
|
177
|
-
`- Workspace
|
|
178
|
-
`- Environment
|
|
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"]),
|
|
179
223
|
"",
|
|
180
224
|
"## Known Blockers",
|
|
181
225
|
...(report.knownBlockers.length
|
package/workflow/source.ts
CHANGED
|
@@ -290,7 +290,7 @@ export const coerceFieldDefault = (
|
|
|
290
290
|
export const fieldExpression = (
|
|
291
291
|
typeName: string,
|
|
292
292
|
defaultValue?: FieldDefaultValue,
|
|
293
|
-
options: { enumValues?: readonly string[] | null } = {},
|
|
293
|
+
options: { enumValues?: readonly string[] | null; builderName?: string } = {},
|
|
294
294
|
) => {
|
|
295
295
|
const typeExpression = normalizeFieldType(typeName);
|
|
296
296
|
const defaultExpression = coerceFieldDefault(
|
|
@@ -299,7 +299,14 @@ export const fieldExpression = (
|
|
|
299
299
|
options,
|
|
300
300
|
).expression;
|
|
301
301
|
const defaultOption = defaultExpression ? `, { default: ${defaultExpression} }` : "";
|
|
302
|
-
return
|
|
302
|
+
return `${options.builderName ?? "field"}(${typeExpression}${defaultOption})`;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const viaBuilderParameterName = (content: string, className: string) => {
|
|
306
|
+
const classIndex = content.indexOf(`export class ${className} extends via`);
|
|
307
|
+
if (classIndex < 0) return null;
|
|
308
|
+
const signature = content.slice(classIndex, content.indexOf("=>", classIndex));
|
|
309
|
+
return /via\(\(\s*([A-Za-z_$][\w$]*)\s*\)/.exec(signature)?.[1] ?? null;
|
|
303
310
|
};
|
|
304
311
|
|
|
305
312
|
export const insertIntoObject = (content: string, className: string, line: string) => {
|
|
@@ -379,17 +386,60 @@ export const insertEnumClass = (content: string, enumClassName: string, enumName
|
|
|
379
386
|
return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
|
|
380
387
|
};
|
|
381
388
|
|
|
382
|
-
|
|
383
|
-
|
|
389
|
+
const findMatchingBrace = (content: string, openIndex: number) => {
|
|
390
|
+
let depth = 0;
|
|
391
|
+
let quote: '"' | "'" | "`" | null = null;
|
|
392
|
+
let escaped = false;
|
|
393
|
+
for (let index = openIndex; index < content.length; index++) {
|
|
394
|
+
const char = content[index];
|
|
395
|
+
if (quote) {
|
|
396
|
+
if (escaped) {
|
|
397
|
+
escaped = false;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (char === "\\") {
|
|
401
|
+
escaped = true;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (char === quote) quote = null;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
408
|
+
quote = char;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (char === "{") depth += 1;
|
|
412
|
+
if (char === "}") {
|
|
413
|
+
depth -= 1;
|
|
414
|
+
if (depth === 0) return index;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return -1;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const dictionaryModelFieldLine = (fieldName: string) => {
|
|
384
421
|
const label = bilingualLabelForField(fieldName);
|
|
385
422
|
const desc = bilingualDescriptionForField(fieldName);
|
|
386
|
-
|
|
423
|
+
return `${fieldName}: t([${JSON.stringify(label.en)}, ${JSON.stringify(label.ko)}]).desc([${JSON.stringify(
|
|
424
|
+
desc.en,
|
|
425
|
+
)}, ${JSON.stringify(desc.ko)}]),`;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
export const insertDictionaryModelField = (content: string, moduleClassName: string, fieldName: string) => {
|
|
429
|
+
if (new RegExp(`\\b${fieldName}\\s*:`).test(content)) return content;
|
|
430
|
+
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => (`);
|
|
387
431
|
if (modelIndex < 0) return null;
|
|
388
|
-
const
|
|
432
|
+
const objectStartIndex = content.indexOf("{", modelIndex);
|
|
433
|
+
if (objectStartIndex < 0) return null;
|
|
434
|
+
const objectEndIndex = findMatchingBrace(content, objectStartIndex);
|
|
389
435
|
if (objectEndIndex < 0) return null;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
436
|
+
const fieldLine = dictionaryModelFieldLine(fieldName);
|
|
437
|
+
const body = content.slice(objectStartIndex + 1, objectEndIndex);
|
|
438
|
+
if (body.trim().length === 0) {
|
|
439
|
+
return `${content.slice(0, objectStartIndex + 1)}\n ${fieldLine}\n ${content.slice(objectEndIndex)}`;
|
|
440
|
+
}
|
|
441
|
+
const insertion = body.endsWith("\n") ? ` ${fieldLine}\n` : `\n ${fieldLine}\n`;
|
|
442
|
+
return `${content.slice(0, objectEndIndex)}${insertion}${content.slice(objectEndIndex)}`;
|
|
393
443
|
};
|
|
394
444
|
|
|
395
445
|
export const ensureConstantTypeImport = (content: string, constantPath: string, typeName: string) => {
|
package/workflow/types.ts
CHANGED
|
@@ -177,6 +177,13 @@ export interface PrimitiveChangedFile {
|
|
|
177
177
|
reason: string;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
export interface WorkflowPostApplyCheck {
|
|
181
|
+
code: string;
|
|
182
|
+
target: string;
|
|
183
|
+
status: "passed" | "failed";
|
|
184
|
+
message: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
180
187
|
export interface PrimitiveGeneratedFile {
|
|
181
188
|
path: string;
|
|
182
189
|
action: "sync";
|
|
@@ -220,6 +227,7 @@ export interface WorkflowApplyReport {
|
|
|
220
227
|
recommendedValidationCommands: WorkflowApplyCommand[];
|
|
221
228
|
commands: WorkflowApplyCommand[];
|
|
222
229
|
diagnostics: WorkflowDiagnostic[];
|
|
230
|
+
postApplyChecks?: WorkflowPostApplyCheck[];
|
|
223
231
|
recommendations: WorkflowRecommendation[];
|
|
224
232
|
nextActions: PrimitiveNextAction[];
|
|
225
233
|
plan: WorkflowPlan;
|
|
@@ -279,6 +287,7 @@ export interface WorkflowStepResult {
|
|
|
279
287
|
generatedFiles?: PrimitiveGeneratedFile[];
|
|
280
288
|
commands?: WorkflowApplyCommand[];
|
|
281
289
|
diagnostics?: WorkflowDiagnostic[];
|
|
290
|
+
postApplyChecks?: WorkflowPostApplyCheck[];
|
|
282
291
|
recommendations?: WorkflowRecommendation[];
|
|
283
292
|
nextActions?: PrimitiveNextAction[];
|
|
284
293
|
}
|