@akanjs/devkit 2.3.9-rc.1 → 2.3.9-rc.2
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/executors.ts +5 -4
- package/package.json +2 -2
- package/workflow/index.ts +98 -25
package/executors.ts
CHANGED
|
@@ -503,7 +503,7 @@ export class Executor {
|
|
|
503
503
|
async writeFile(
|
|
504
504
|
filePath: string,
|
|
505
505
|
content: string | object,
|
|
506
|
-
{ overwrite = true }: { overwrite?: boolean } = {},
|
|
506
|
+
{ overwrite = true, silent = false }: { overwrite?: boolean; silent?: boolean } = {},
|
|
507
507
|
): Promise<FileContent> {
|
|
508
508
|
const writePath = this.getPath(filePath);
|
|
509
509
|
const dir = path.dirname(writePath);
|
|
@@ -521,7 +521,7 @@ export class Executor {
|
|
|
521
521
|
}
|
|
522
522
|
} else {
|
|
523
523
|
await FileSys.writeText(writePath, contentStr);
|
|
524
|
-
this.logger.rawLog(chalk.green(`File Create: ${filePath}`));
|
|
524
|
+
if (!silent) this.logger.rawLog(chalk.green(`File Create: ${filePath}`));
|
|
525
525
|
}
|
|
526
526
|
return { filePath: writePath, content: contentStr };
|
|
527
527
|
}
|
|
@@ -1357,14 +1357,15 @@ export class AppExecutor extends SysExecutor {
|
|
|
1357
1357
|
getCommandEnv(env: Record<string, string> = {}): Record<string, string> {
|
|
1358
1358
|
const basePort = 8282;
|
|
1359
1359
|
const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
|
|
1360
|
-
const PORT =
|
|
1360
|
+
const PORT = (basePort + portOffset).toString();
|
|
1361
1361
|
const AKAN_PUBLIC_SERVER_PORT = portOffset ? (8282 + portOffset).toString() : undefined;
|
|
1362
1362
|
return {
|
|
1363
1363
|
...process.env,
|
|
1364
1364
|
AKAN_PUBLIC_APP_NAME: this.name,
|
|
1365
1365
|
AKAN_WORKSPACE_ROOT: this.workspace.workspaceRoot,
|
|
1366
1366
|
NODE_NO_WARNINGS: "1",
|
|
1367
|
-
|
|
1367
|
+
PORT,
|
|
1368
|
+
AKAN_PUBLIC_CLIENT_PORT: PORT,
|
|
1368
1369
|
...(AKAN_PUBLIC_SERVER_PORT ? { AKAN_PUBLIC_SERVER_PORT } : {}),
|
|
1369
1370
|
...env,
|
|
1370
1371
|
};
|
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.2",
|
|
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.2",
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"daisyui": "5.5.23",
|
package/workflow/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type WorkflowFormat = "markdown" | "json";
|
|
|
8
8
|
export type WorkflowPlanInputs = Record<string, string | null>;
|
|
9
9
|
export type PrimitiveFormat = "markdown" | "json";
|
|
10
10
|
export type UiSurface = "view" | "unit" | "template";
|
|
11
|
+
export type WorkflowValidationKind = "sync" | "lint" | "typecheck" | "doctor" | "custom";
|
|
12
|
+
export type WorkflowFailureScope = "workspace-config" | "environment" | "source-change" | "unknown";
|
|
11
13
|
|
|
12
14
|
export interface PrimitiveTargetInput {
|
|
13
15
|
app: string | null;
|
|
@@ -50,6 +52,7 @@ export interface WorkflowPredictedChange {
|
|
|
50
52
|
export interface WorkflowValidation {
|
|
51
53
|
command: string;
|
|
52
54
|
reason: string;
|
|
55
|
+
kind?: WorkflowValidationKind;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
export interface WorkflowSpec {
|
|
@@ -70,6 +73,9 @@ export interface WorkflowDiagnostic {
|
|
|
70
73
|
code: string;
|
|
71
74
|
message: string;
|
|
72
75
|
input?: string;
|
|
76
|
+
command?: string;
|
|
77
|
+
kind?: WorkflowValidationKind;
|
|
78
|
+
failureScope?: WorkflowFailureScope;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
export interface WorkflowPlan {
|
|
@@ -98,6 +104,7 @@ export interface WorkflowApplyCommand {
|
|
|
98
104
|
command: string;
|
|
99
105
|
reason: string;
|
|
100
106
|
stepId?: string;
|
|
107
|
+
kind?: WorkflowValidationKind;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
export type WorkflowRunSource =
|
|
@@ -108,8 +115,10 @@ export type WorkflowRunSource =
|
|
|
108
115
|
export interface WorkflowValidationCommandResult {
|
|
109
116
|
command: string;
|
|
110
117
|
reason: string;
|
|
118
|
+
kind?: WorkflowValidationKind;
|
|
111
119
|
status: "passed" | "failed";
|
|
112
120
|
exitCode: number;
|
|
121
|
+
failureScope?: WorkflowFailureScope;
|
|
113
122
|
stdout?: string;
|
|
114
123
|
stderr?: string;
|
|
115
124
|
}
|
|
@@ -163,6 +172,8 @@ export interface WorkflowApplyReport {
|
|
|
163
172
|
status: "passed" | "failed";
|
|
164
173
|
changedFiles: PrimitiveChangedFile[];
|
|
165
174
|
generatedFiles: PrimitiveGeneratedFile[];
|
|
175
|
+
appliedCommands: WorkflowApplyCommand[];
|
|
176
|
+
recommendedValidationCommands: WorkflowApplyCommand[];
|
|
166
177
|
commands: WorkflowApplyCommand[];
|
|
167
178
|
diagnostics: WorkflowDiagnostic[];
|
|
168
179
|
nextActions: PrimitiveNextAction[];
|
|
@@ -293,6 +304,20 @@ export const createWorkflowPlan = (spec: WorkflowSpec, rawInputs: Record<string,
|
|
|
293
304
|
inputs[name] = value;
|
|
294
305
|
}
|
|
295
306
|
|
|
307
|
+
const fieldType = inputs.type;
|
|
308
|
+
if (
|
|
309
|
+
spec.name === "add-field" &&
|
|
310
|
+
typeof fieldType === "string" &&
|
|
311
|
+
(fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")
|
|
312
|
+
) {
|
|
313
|
+
diagnostics.push({
|
|
314
|
+
severity: "error",
|
|
315
|
+
code: "primitive-field-type-unsupported",
|
|
316
|
+
input: "type",
|
|
317
|
+
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
296
321
|
return {
|
|
297
322
|
schemaVersion: 1,
|
|
298
323
|
workflow: spec.name,
|
|
@@ -395,22 +420,36 @@ export const createWorkflowApplyReport = ({
|
|
|
395
420
|
mode,
|
|
396
421
|
changedFiles = [],
|
|
397
422
|
generatedFiles = [],
|
|
423
|
+
appliedCommands = [],
|
|
424
|
+
recommendedValidationCommands,
|
|
398
425
|
commands = [],
|
|
399
426
|
diagnostics = [],
|
|
400
427
|
nextActions = [],
|
|
401
428
|
plan,
|
|
402
|
-
}: Omit<
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
429
|
+
}: Omit<
|
|
430
|
+
WorkflowApplyReport,
|
|
431
|
+
"schemaVersion" | "status" | "appliedCommands" | "recommendedValidationCommands" | "commands"
|
|
432
|
+
> & {
|
|
433
|
+
appliedCommands?: WorkflowApplyCommand[];
|
|
434
|
+
recommendedValidationCommands?: WorkflowApplyCommand[];
|
|
435
|
+
commands?: WorkflowApplyCommand[];
|
|
436
|
+
}): WorkflowApplyReport => {
|
|
437
|
+
const validationCommands = recommendedValidationCommands ?? commands;
|
|
438
|
+
return {
|
|
439
|
+
schemaVersion: 1,
|
|
440
|
+
workflow,
|
|
441
|
+
mode,
|
|
442
|
+
status: workflowStatus(diagnostics),
|
|
443
|
+
changedFiles: uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
|
|
444
|
+
generatedFiles: uniqueBy(generatedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
|
|
445
|
+
appliedCommands: uniqueBy(appliedCommands, (command) => command.command),
|
|
446
|
+
recommendedValidationCommands: uniqueBy(validationCommands, (command) => command.command),
|
|
447
|
+
commands: uniqueBy(validationCommands, (command) => command.command),
|
|
448
|
+
diagnostics,
|
|
449
|
+
nextActions: uniqueBy(nextActions, (action) => action.command),
|
|
450
|
+
plan,
|
|
451
|
+
};
|
|
452
|
+
};
|
|
414
453
|
|
|
415
454
|
export const resolveWorkflowCommand = (command: string, plan: WorkflowPlan) => {
|
|
416
455
|
const target = typeof plan.inputs.app === "string" ? plan.inputs.app : "<app-or-lib>";
|
|
@@ -424,6 +463,7 @@ export const workflowCommandsForPlan = (plan: WorkflowPlan) =>
|
|
|
424
463
|
plan.validation.map((validation) => ({
|
|
425
464
|
command: resolveWorkflowCommand(validation.command, plan),
|
|
426
465
|
reason: validation.reason,
|
|
466
|
+
kind: validation.kind,
|
|
427
467
|
})) satisfies WorkflowApplyCommand[];
|
|
428
468
|
|
|
429
469
|
export const workflowRunsDir = ".akan/workflows/runs";
|
|
@@ -444,7 +484,7 @@ export const workflowRunArtifactPath = (runId: string) => `${workflowRunsDir}/${
|
|
|
444
484
|
export const writeWorkflowRunArtifact = async (workspace: Workspace, artifact: WorkflowRunArtifact) => {
|
|
445
485
|
const runId = getRunId(artifact);
|
|
446
486
|
const artifactPath = workflowRunArtifactPath(runId);
|
|
447
|
-
await workspace.writeFile(artifactPath, jsonText(artifact));
|
|
487
|
+
await workspace.writeFile(artifactPath, jsonText(artifact), { silent: true });
|
|
448
488
|
return { runId, path: artifactPath };
|
|
449
489
|
};
|
|
450
490
|
|
|
@@ -488,6 +528,9 @@ export const createWorkflowValidationRunReport = async ({
|
|
|
488
528
|
severity: "error" as const,
|
|
489
529
|
code: "workflow-validation-command-failed",
|
|
490
530
|
message: `Validation command failed: ${result.command}`,
|
|
531
|
+
command: result.command,
|
|
532
|
+
kind: result.kind,
|
|
533
|
+
failureScope: result.failureScope,
|
|
491
534
|
},
|
|
492
535
|
]
|
|
493
536
|
: [],
|
|
@@ -698,9 +741,14 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) =>
|
|
|
698
741
|
? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
|
|
699
742
|
: ["- none"]),
|
|
700
743
|
"",
|
|
701
|
-
"## Commands",
|
|
702
|
-
...(report.
|
|
703
|
-
? report.
|
|
744
|
+
"## Applied Commands",
|
|
745
|
+
...(report.appliedCommands.length
|
|
746
|
+
? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
747
|
+
: ["- none"]),
|
|
748
|
+
"",
|
|
749
|
+
"## Recommended Validation Commands",
|
|
750
|
+
...(report.recommendedValidationCommands.length
|
|
751
|
+
? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
|
|
704
752
|
: ["- none"]),
|
|
705
753
|
"",
|
|
706
754
|
"## Diagnostics",
|
|
@@ -727,7 +775,10 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
|
|
|
727
775
|
"",
|
|
728
776
|
"## Commands",
|
|
729
777
|
...(report.commands.length
|
|
730
|
-
? report.commands.map((command) =>
|
|
778
|
+
? report.commands.map((command) => {
|
|
779
|
+
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
780
|
+
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
781
|
+
})
|
|
731
782
|
: ["- none"]),
|
|
732
783
|
"",
|
|
733
784
|
"## Diagnostics",
|
|
@@ -818,7 +869,7 @@ export class WorkflowExecutor {
|
|
|
818
869
|
async apply(plan: WorkflowPlan): Promise<WorkflowApplyReport> {
|
|
819
870
|
const changedFiles: PrimitiveChangedFile[] = [];
|
|
820
871
|
const generatedFiles: PrimitiveGeneratedFile[] = [];
|
|
821
|
-
const
|
|
872
|
+
const recommendedValidationCommands: WorkflowApplyCommand[] = [];
|
|
822
873
|
const diagnostics: WorkflowDiagnostic[] = [...plan.diagnostics];
|
|
823
874
|
const nextActions: PrimitiveNextAction[] = [];
|
|
824
875
|
|
|
@@ -828,14 +879,14 @@ export class WorkflowExecutor {
|
|
|
828
879
|
mode: "apply",
|
|
829
880
|
changedFiles,
|
|
830
881
|
generatedFiles,
|
|
831
|
-
|
|
882
|
+
recommendedValidationCommands,
|
|
832
883
|
diagnostics,
|
|
833
884
|
nextActions,
|
|
834
885
|
plan,
|
|
835
886
|
});
|
|
836
887
|
}
|
|
837
888
|
|
|
838
|
-
|
|
889
|
+
recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
|
|
839
890
|
nextActions.push(...workflowCommandsForPlan(plan));
|
|
840
891
|
|
|
841
892
|
for (const step of plan.steps) {
|
|
@@ -858,7 +909,7 @@ export class WorkflowExecutor {
|
|
|
858
909
|
if (!result) continue;
|
|
859
910
|
changedFiles.push(...(result.changedFiles ?? []));
|
|
860
911
|
generatedFiles.push(...(result.generatedFiles ?? []));
|
|
861
|
-
|
|
912
|
+
recommendedValidationCommands.push(...(result.commands ?? []));
|
|
862
913
|
diagnostics.push(...(result.diagnostics ?? []));
|
|
863
914
|
nextActions.push(...(result.nextActions ?? []));
|
|
864
915
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) break;
|
|
@@ -869,7 +920,7 @@ export class WorkflowExecutor {
|
|
|
869
920
|
mode: "apply",
|
|
870
921
|
changedFiles,
|
|
871
922
|
generatedFiles,
|
|
872
|
-
|
|
923
|
+
recommendedValidationCommands,
|
|
873
924
|
diagnostics,
|
|
874
925
|
nextActions,
|
|
875
926
|
plan,
|
|
@@ -1000,14 +1051,36 @@ export const lowerlize = (value: string) => `${value.slice(0, 1).toLowerCase()}$
|
|
|
1000
1051
|
export const compactDiagnostics = (diagnostics: Array<WorkflowDiagnostic | false | null | undefined>) =>
|
|
1001
1052
|
diagnostics.filter((diagnostic): diagnostic is WorkflowDiagnostic => !!diagnostic);
|
|
1002
1053
|
|
|
1003
|
-
export const
|
|
1054
|
+
export const normalizeFieldType = (typeName: string) => {
|
|
1004
1055
|
const normalizedTypes: Record<string, string> = {
|
|
1005
1056
|
string: "String",
|
|
1006
|
-
number: "Number",
|
|
1007
1057
|
boolean: "Boolean",
|
|
1008
1058
|
date: "Date",
|
|
1059
|
+
int: "Int",
|
|
1060
|
+
integer: "Int",
|
|
1061
|
+
float: "Float",
|
|
1062
|
+
double: "Float",
|
|
1063
|
+
decimal: "Float",
|
|
1009
1064
|
};
|
|
1010
|
-
|
|
1065
|
+
return normalizedTypes[typeName.toLowerCase()] ?? typeName;
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
export const ensureBaseTypeImport = (content: string, typeName: string) => {
|
|
1069
|
+
if (typeName !== "Int" && typeName !== "Float") return content;
|
|
1070
|
+
if (new RegExp(`import \\{[^}]*\\b${typeName}\\b[^}]*\\} from "akanjs/base";`).test(content)) return content;
|
|
1071
|
+
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
1072
|
+
if (baseImport) {
|
|
1073
|
+
const names = baseImport[1]
|
|
1074
|
+
.split(",")
|
|
1075
|
+
.map((name) => name.trim())
|
|
1076
|
+
.filter(Boolean);
|
|
1077
|
+
return content.replace(baseImport[0], `import { ${[...names, typeName].sort().join(", ")} } from "akanjs/base";`);
|
|
1078
|
+
}
|
|
1079
|
+
return `import { ${typeName} } from "akanjs/base";\n${content}`;
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
export const fieldExpression = (typeName: string, defaultValue?: string | null) => {
|
|
1083
|
+
const typeExpression = normalizeFieldType(typeName);
|
|
1011
1084
|
const defaultOption = defaultValue ? `, { default: ${JSON.stringify(defaultValue)} }` : "";
|
|
1012
1085
|
return `field(${typeExpression}${defaultOption})`;
|
|
1013
1086
|
};
|