@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 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 = basePort ? (basePort + portOffset).toString() : undefined;
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
- ...(PORT ? { PORT, AKAN_PUBLIC_CLIENT_PORT: PORT } : {}),
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.1",
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.1",
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<WorkflowApplyReport, "schemaVersion" | "status">): WorkflowApplyReport => ({
403
- schemaVersion: 1,
404
- workflow,
405
- mode,
406
- status: workflowStatus(diagnostics),
407
- changedFiles: uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
408
- generatedFiles: uniqueBy(generatedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
409
- commands: uniqueBy(commands, (command) => command.command),
410
- diagnostics,
411
- nextActions: uniqueBy(nextActions, (action) => action.command),
412
- plan,
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.commands.length
703
- ? report.commands.map((command) => `- \`${command.command}\`: ${command.reason}`)
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) => `- [${command.status}] \`${command.command}\`: ${command.reason}`)
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 commands: WorkflowApplyCommand[] = [];
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
- commands,
882
+ recommendedValidationCommands,
832
883
  diagnostics,
833
884
  nextActions,
834
885
  plan,
835
886
  });
836
887
  }
837
888
 
838
- commands.push(...workflowCommandsForPlan(plan));
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
- commands.push(...(result.commands ?? []));
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
- commands,
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 fieldExpression = (typeName: string, defaultValue?: string | null) => {
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
- const typeExpression = normalizedTypes[typeName.toLowerCase()] ?? typeName;
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
  };