@akanjs/cli 2.3.9-rc.0 → 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/incrementalBuilder.proc.js +907 -18
- package/index.js +3928 -1486
- package/package.json +2 -2
- package/templates/appSample/common/formatters.ts +1 -1
- package/templates/appSample/common/validators.ts +1 -1
- package/templates/appSample/lib/_noti/noti.dictionary.ts +1 -1
- package/templates/appSample/lib/_noti/noti.service.ts +1 -1
- package/templates/appSample/lib/_noti/noti.signal.ts +1 -1
- package/templates/appSample/lib/_noti/noti.store.ts +1 -1
- package/templates/appSample/lib/task/task.abstract.ts +1 -1
- package/templates/appSample/lib/task/task.constant.ts +1 -1
- package/templates/appSample/lib/task/task.dictionary.ts +1 -1
- package/templates/appSample/lib/task/task.document.ts +3 -3
- package/templates/appSample/lib/task/task.service.ts +2 -2
- package/templates/appSample/lib/task/task.signal.spec.ts +70 -0
- package/templates/appSample/lib/task/task.signal.test.ts +20 -0
- package/templates/appSample/lib/task/task.signal.ts +2 -2
- package/templates/appSample/lib/task/task.store.ts +2 -2
- package/templates/appSample/srvkit/AuthGuard.ts +1 -1
- package/templates/appSample/srvkit/SessionInternalArg.ts +1 -1
- package/templates/appSample/ui/GlobalLoading.tsx +1 -1
- package/templates/appSample/ui/QuantityControl.tsx +1 -1
- package/templates/appSample/webkit/useDebounce.ts +1 -1
- package/templates/lib/useClient.ts +1 -4
- package/templates/module/__Model__.Unit.tsx +1 -4
- package/templates/workspaceRoot/.cursor/rules/{akan-scan-conventions.mdc.template → akan-sync-conventions.mdc.template} +3 -3
- package/templates/workspaceRoot/.cursor/rules/akan.mdc.template +5 -0
- package/templates/workspaceRoot/AGENTS.md.template +77 -58
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +51 -0
- package/templates/workspaceRoot/docs/GENERATED.md.template +54 -0
- package/templates/workspaceRoot/package.json.template +26 -0
- package/templates/workspaceRoot/Untitled +0 -1
|
@@ -2809,7 +2809,7 @@ class Executor {
|
|
|
2809
2809
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
2810
2810
|
return this;
|
|
2811
2811
|
}
|
|
2812
|
-
async writeFile(filePath, content, { overwrite = true } = {}) {
|
|
2812
|
+
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
2813
2813
|
const writePath = this.getPath(filePath);
|
|
2814
2814
|
const dir = path7.dirname(writePath);
|
|
2815
2815
|
if (!await FileSys.dirExists(dir))
|
|
@@ -2827,7 +2827,8 @@ class Executor {
|
|
|
2827
2827
|
}
|
|
2828
2828
|
} else {
|
|
2829
2829
|
await FileSys.writeText(writePath, contentStr);
|
|
2830
|
-
|
|
2830
|
+
if (!silent)
|
|
2831
|
+
this.logger.rawLog(chalk4.green(`File Create: ${filePath}`));
|
|
2831
2832
|
}
|
|
2832
2833
|
return { filePath: writePath, content: contentStr };
|
|
2833
2834
|
}
|
|
@@ -3491,14 +3492,15 @@ class AppExecutor extends SysExecutor {
|
|
|
3491
3492
|
getCommandEnv(env = {}) {
|
|
3492
3493
|
const basePort = 8282;
|
|
3493
3494
|
const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
|
|
3494
|
-
const PORT =
|
|
3495
|
+
const PORT = (basePort + portOffset).toString();
|
|
3495
3496
|
const AKAN_PUBLIC_SERVER_PORT = portOffset ? (8282 + portOffset).toString() : undefined;
|
|
3496
3497
|
return {
|
|
3497
3498
|
...process.env,
|
|
3498
3499
|
AKAN_PUBLIC_APP_NAME: this.name,
|
|
3499
3500
|
AKAN_WORKSPACE_ROOT: this.workspace.workspaceRoot,
|
|
3500
3501
|
NODE_NO_WARNINGS: "1",
|
|
3501
|
-
|
|
3502
|
+
PORT,
|
|
3503
|
+
AKAN_PUBLIC_CLIENT_PORT: PORT,
|
|
3502
3504
|
...AKAN_PUBLIC_SERVER_PORT ? { AKAN_PUBLIC_SERVER_PORT } : {},
|
|
3503
3505
|
...env
|
|
3504
3506
|
};
|
|
@@ -4777,17 +4779,91 @@ class AkanAppHost {
|
|
|
4777
4779
|
import { readdir } from "fs/promises";
|
|
4778
4780
|
import path10 from "path";
|
|
4779
4781
|
import { capitalize as capitalize2 } from "akanjs/common";
|
|
4782
|
+
var resourceList = [
|
|
4783
|
+
{ uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
|
|
4784
|
+
{ uri: "akan://guidelines/framework", name: "Framework guideline", mimeType: "text/markdown" },
|
|
4785
|
+
{ uri: "akan://guidelines/modelSignal", name: "Model signal guideline", mimeType: "text/markdown" },
|
|
4786
|
+
{ uri: "akan://workspace/summary", name: "Workspace summary", mimeType: "application/json" },
|
|
4787
|
+
{ uri: "akan://workspace/apps", name: "Workspace apps", mimeType: "application/json" },
|
|
4788
|
+
{ uri: "akan://workspace/modules", name: "Workspace modules", mimeType: "application/json" }
|
|
4789
|
+
];
|
|
4790
|
+
var cursorMcpConfigPath = ".cursor/mcp.json";
|
|
4791
|
+
var cursorWorkspaceFolder = "$" + "{workspaceFolder}";
|
|
4792
|
+
var createAkanCursorMcpServer = (mode = "readonly") => ({
|
|
4793
|
+
type: "stdio",
|
|
4794
|
+
command: "bash",
|
|
4795
|
+
args: ["-lc", `cd "${cursorWorkspaceFolder}" && akan mcp --mode ${mode}`]
|
|
4796
|
+
});
|
|
4797
|
+
var akanCursorMcpServer = createAkanCursorMcpServer();
|
|
4798
|
+
var renderDoctorText = (result) => {
|
|
4799
|
+
const lines = [`Akan doctor status: ${result.status}`];
|
|
4800
|
+
if (result.diagnostics.length === 0) {
|
|
4801
|
+
lines.push("", "No Akan workspace diagnostics found.");
|
|
4802
|
+
} else {
|
|
4803
|
+
lines.push("", ...result.diagnostics.map((diagnostic) => [
|
|
4804
|
+
`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
|
|
4805
|
+
diagnostic.path ? ` ${diagnostic.path}` : ""
|
|
4806
|
+
].filter(Boolean).join(`
|
|
4807
|
+
`)));
|
|
4808
|
+
}
|
|
4809
|
+
lines.push("", "Generated file freshness:", result.generatedFilesFreshness.message, `Refresh: ${result.generatedFilesFreshness.refreshCommand}`, "", "Repair actions:", ...result.repairActions.length ? result.repairActions.map((action) => `- ${action.command}`) : ["- none"], "", "Validation commands:", ...result.validationCommands.map((command) => `- ${command}`));
|
|
4810
|
+
return `${lines.join(`
|
|
4811
|
+
`)}
|
|
4812
|
+
`;
|
|
4813
|
+
};
|
|
4780
4814
|
var generatedFiles = [
|
|
4781
|
-
"
|
|
4782
|
-
"
|
|
4783
|
-
"
|
|
4784
|
-
"
|
|
4785
|
-
"
|
|
4786
|
-
"
|
|
4787
|
-
"
|
|
4788
|
-
"
|
|
4789
|
-
"
|
|
4815
|
+
"apps/*/client.ts",
|
|
4816
|
+
"apps/*/server.ts",
|
|
4817
|
+
"*/lib/cnst.ts",
|
|
4818
|
+
"*/lib/db.ts",
|
|
4819
|
+
"*/lib/dict.ts",
|
|
4820
|
+
"*/lib/option.ts",
|
|
4821
|
+
"*/lib/sig.ts",
|
|
4822
|
+
"*/lib/srv.ts",
|
|
4823
|
+
"*/lib/st.ts",
|
|
4824
|
+
"*/lib/useClient.ts",
|
|
4825
|
+
"*/lib/useServer.ts",
|
|
4826
|
+
"*/lib/**/index.ts",
|
|
4827
|
+
"*/ui/index.ts",
|
|
4828
|
+
"*/webkit/index.ts",
|
|
4829
|
+
"*/srvkit/index.ts",
|
|
4830
|
+
"*/common/index.ts"
|
|
4831
|
+
];
|
|
4832
|
+
var validationCommands = [
|
|
4833
|
+
"akan sync <app-or-lib>",
|
|
4834
|
+
"akan lint <app-or-lib-or-pkg>",
|
|
4835
|
+
"akan typecheck <app-name>",
|
|
4836
|
+
"akan test <app-or-lib-or-pkg>",
|
|
4837
|
+
"akan build <app-name>",
|
|
4838
|
+
"akan doctor --strict --format json"
|
|
4790
4839
|
];
|
|
4840
|
+
var generatedFilesFreshness = {
|
|
4841
|
+
status: "unknown",
|
|
4842
|
+
message: "Run sync before validation so generated Akan files match the current source conventions.",
|
|
4843
|
+
refreshCommand: "akan sync <app-or-lib>",
|
|
4844
|
+
verifyingCommands: ["akan lint <app-or-lib-or-pkg>", "akan build <app-name>"]
|
|
4845
|
+
};
|
|
4846
|
+
var repairAction = (kind, command, reason, safeToRun) => ({
|
|
4847
|
+
kind,
|
|
4848
|
+
command,
|
|
4849
|
+
reason,
|
|
4850
|
+
safeToRun
|
|
4851
|
+
});
|
|
4852
|
+
var moduleShapeFiles = (module) => {
|
|
4853
|
+
if (module.kind === "service") {
|
|
4854
|
+
return [`${module.name}.dictionary.ts`, `${module.name}.service.ts`, `${module.name}.signal.ts`];
|
|
4855
|
+
}
|
|
4856
|
+
if (module.kind === "scalar")
|
|
4857
|
+
return [`${module.name}.constant.ts`, `${module.name}.dictionary.ts`];
|
|
4858
|
+
return [
|
|
4859
|
+
`${module.name}.constant.ts`,
|
|
4860
|
+
`${module.name}.dictionary.ts`,
|
|
4861
|
+
`${module.name}.service.ts`,
|
|
4862
|
+
`${module.name}.store.ts`,
|
|
4863
|
+
`${module.name}.signal.ts`
|
|
4864
|
+
];
|
|
4865
|
+
};
|
|
4866
|
+
var constantFieldNames = (content) => [...content.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*field\(/g)].map((match) => match[1]).filter(Boolean);
|
|
4791
4867
|
var appRootAllowFiles = new Set([
|
|
4792
4868
|
"akan.app.json",
|
|
4793
4869
|
"akan.config.ts",
|
|
@@ -4945,39 +5021,95 @@ class AkanContextAnalyzer {
|
|
|
4945
5021
|
libs,
|
|
4946
5022
|
pkgs,
|
|
4947
5023
|
generatedFiles,
|
|
4948
|
-
validationCommands
|
|
5024
|
+
validationCommands
|
|
4949
5025
|
};
|
|
4950
5026
|
}
|
|
4951
5027
|
static async doctor(workspace, { strict = false } = {}) {
|
|
4952
5028
|
const context = await AkanContextAnalyzer.analyze(workspace);
|
|
4953
5029
|
const diagnostics = [];
|
|
5030
|
+
const repairActions = [
|
|
5031
|
+
repairAction("generated", "akan repair generated --app <app-or-lib>", "Refresh generated Akan files.", true),
|
|
5032
|
+
repairAction("format", "akan repair format --target <app-or-lib-or-pkg>", "Run the formatter/linter repair path.", true)
|
|
5033
|
+
];
|
|
4954
5034
|
for (const app of context.apps) {
|
|
4955
5035
|
const appPath = path10.join(workspace.workspaceRoot, app.path);
|
|
4956
5036
|
for (const entry of await safeReadDir(appPath)) {
|
|
4957
5037
|
const allowed = entry.isDirectory() ? appRootAllowDirs.has(entry.name) : appRootAllowFiles.has(entry.name);
|
|
4958
5038
|
if (!allowed) {
|
|
5039
|
+
const action = repairAction("module-shape", `akan repair module-shape --app ${app.name}`, "Review app root shape and remove or move the unknown entry.", false);
|
|
4959
5040
|
diagnostics.push({
|
|
4960
|
-
severity: "
|
|
5041
|
+
severity: "error",
|
|
4961
5042
|
code: "app-root-unknown-entry",
|
|
4962
5043
|
path: `${app.path}/${entry.name}`,
|
|
4963
|
-
message: `Unexpected ${entry.isDirectory() ? "folder" : "file"} in app root: ${app.path}/${entry.name}
|
|
5044
|
+
message: `Unexpected ${entry.isDirectory() ? "folder" : "file"} in app root: ${app.path}/${entry.name}`,
|
|
5045
|
+
repairActions: [action]
|
|
4964
5046
|
});
|
|
5047
|
+
repairActions.push(action);
|
|
4965
5048
|
}
|
|
4966
5049
|
}
|
|
4967
5050
|
}
|
|
4968
5051
|
for (const sys2 of [...context.apps, ...context.libs]) {
|
|
4969
5052
|
for (const module of sys2.modules) {
|
|
4970
5053
|
if (!module.abstract.exists) {
|
|
5054
|
+
const action = repairAction("module-shape", `akan repair module-shape --app ${sys2.name} --module ${module.name}`, "Create the missing module abstract or inspect required source files.", false);
|
|
4971
5055
|
diagnostics.push({
|
|
4972
5056
|
severity: strict ? "error" : "warning",
|
|
4973
5057
|
code: "module-abstract-missing",
|
|
4974
5058
|
path: module.abstract.path,
|
|
4975
|
-
message: `${capitalize2(module.kind)} module ${sys2.name}:${module.name} should include ${module.abstract.path}
|
|
5059
|
+
message: `${capitalize2(module.kind)} module ${sys2.name}:${module.name} should include ${module.abstract.path}`,
|
|
5060
|
+
repairActions: [action]
|
|
5061
|
+
});
|
|
5062
|
+
repairActions.push(action);
|
|
5063
|
+
}
|
|
5064
|
+
const missingFiles = moduleShapeFiles(module).filter((filename) => !module.files.includes(filename));
|
|
5065
|
+
if (missingFiles.length) {
|
|
5066
|
+
const action = repairAction("module-shape", `akan repair module-shape --app ${sys2.name} --module ${module.name}`, "Review missing required module source files.", false);
|
|
5067
|
+
diagnostics.push({
|
|
5068
|
+
severity: "error",
|
|
5069
|
+
code: "module-shape-invalid",
|
|
5070
|
+
path: module.path,
|
|
5071
|
+
message: `${capitalize2(module.kind)} module ${sys2.name}:${module.name} is missing required files: ${missingFiles.join(", ")}`,
|
|
5072
|
+
repairActions: [action]
|
|
4976
5073
|
});
|
|
5074
|
+
repairActions.push(action);
|
|
5075
|
+
}
|
|
5076
|
+
if (module.kind !== "service" && module.files.includes(`${module.name}.dictionary.ts`)) {
|
|
5077
|
+
const constantPath = path10.join(workspace.workspaceRoot, module.path, `${module.name}.constant.ts`);
|
|
5078
|
+
const dictionaryPath = path10.join(workspace.workspaceRoot, module.path, `${module.name}.dictionary.ts`);
|
|
5079
|
+
const [constantContent, dictionaryContent] = await Promise.all([
|
|
5080
|
+
safeReadText(constantPath),
|
|
5081
|
+
safeReadText(dictionaryPath)
|
|
5082
|
+
]);
|
|
5083
|
+
if (constantContent && dictionaryContent) {
|
|
5084
|
+
for (const fieldName of constantFieldNames(constantContent)) {
|
|
5085
|
+
if (new RegExp(`\\b${fieldName}\\s*:`).test(dictionaryContent))
|
|
5086
|
+
continue;
|
|
5087
|
+
const action = repairAction("dictionary", `akan repair dictionary --app ${sys2.name} --module ${module.name}`, "Add missing dictionary labels for source constant fields.", false);
|
|
5088
|
+
diagnostics.push({
|
|
5089
|
+
severity: "warning",
|
|
5090
|
+
code: "dictionary-label-missing",
|
|
5091
|
+
path: `${module.path}/${module.name}.dictionary.ts`,
|
|
5092
|
+
message: `Dictionary labels for ${sys2.name}:${module.name}.${fieldName} were not found.`,
|
|
5093
|
+
repairActions: [action]
|
|
5094
|
+
});
|
|
5095
|
+
repairActions.push(action);
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
4977
5098
|
}
|
|
4978
5099
|
}
|
|
4979
5100
|
}
|
|
4980
|
-
return {
|
|
5101
|
+
return {
|
|
5102
|
+
schemaVersion: 1,
|
|
5103
|
+
repoName: context.repoName,
|
|
5104
|
+
root: context.root,
|
|
5105
|
+
strict,
|
|
5106
|
+
status: diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed",
|
|
5107
|
+
diagnostics,
|
|
5108
|
+
generatedFiles: context.generatedFiles,
|
|
5109
|
+
generatedFilesFreshness,
|
|
5110
|
+
validationCommands: context.validationCommands,
|
|
5111
|
+
repairActions
|
|
5112
|
+
};
|
|
4981
5113
|
}
|
|
4982
5114
|
static findModules(context, moduleName) {
|
|
4983
5115
|
const modules = [...context.apps, ...context.libs].flatMap((sys2) => sys2.modules);
|
|
@@ -11115,6 +11247,8 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
|
|
|
11115
11247
|
} else if (sysType === "app") {
|
|
11116
11248
|
if (value && appNames.includes(value))
|
|
11117
11249
|
return AppExecutor.from(workspace, value);
|
|
11250
|
+
if (!value && appNames.length === 1 && appNames[0])
|
|
11251
|
+
return AppExecutor.from(workspace, appNames[0]);
|
|
11118
11252
|
const appName = await select3({ message: `Select the ${sysType} name`, choices: appNames });
|
|
11119
11253
|
return AppExecutor.from(workspace, appName);
|
|
11120
11254
|
} else if (sysType === "lib") {
|
|
@@ -11690,6 +11824,761 @@ import { Box as Box2, Newline, Text as Text2, useInput as useInput2 } from "ink"
|
|
|
11690
11824
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
11691
11825
|
import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
11692
11826
|
"use client";
|
|
11827
|
+
// pkgs/@akanjs/devkit/workflow/index.ts
|
|
11828
|
+
var jsonText = (value, { trailingNewline = true } = {}) => `${JSON.stringify(value, null, 2)}${trailingNewline ? `
|
|
11829
|
+
` : ""}`;
|
|
11830
|
+
var surfaceModes = new Set(["infer", "include", "skip"]);
|
|
11831
|
+
var parseStringList = (value) => {
|
|
11832
|
+
if (Array.isArray(value)) {
|
|
11833
|
+
const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
11834
|
+
return values.length === value.length ? values : null;
|
|
11835
|
+
}
|
|
11836
|
+
if (typeof value !== "string")
|
|
11837
|
+
return null;
|
|
11838
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
11839
|
+
};
|
|
11840
|
+
var normalizeInputValue = (name, spec, value) => {
|
|
11841
|
+
if (spec.type === "string")
|
|
11842
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
11843
|
+
if (spec.type === "string-list") {
|
|
11844
|
+
const values = parseStringList(value);
|
|
11845
|
+
return values && values.length > 0 ? values : null;
|
|
11846
|
+
}
|
|
11847
|
+
if (typeof value === "string" && surfaceModes.has(value))
|
|
11848
|
+
return value;
|
|
11849
|
+
throw new Error(`Unsupported workflow input value for ${name}`);
|
|
11850
|
+
};
|
|
11851
|
+
var listWorkflowSpecs = (specs) => [...specs].sort((a, b) => a.name.localeCompare(b.name));
|
|
11852
|
+
var getWorkflowSpec = (specs, name) => specs.find((spec) => spec.name === name) ?? null;
|
|
11853
|
+
var compactWorkflowInputs = (inputs) => Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
|
|
11854
|
+
var createWorkflowPlan = (spec, rawInputs) => {
|
|
11855
|
+
const inputs = {};
|
|
11856
|
+
const diagnostics = [];
|
|
11857
|
+
for (const [name, inputSpec] of Object.entries(spec.inputs)) {
|
|
11858
|
+
const rawValue = rawInputs[name];
|
|
11859
|
+
if (rawValue === undefined || rawValue === null || rawValue === "") {
|
|
11860
|
+
if (inputSpec.required) {
|
|
11861
|
+
diagnostics.push({
|
|
11862
|
+
severity: "error",
|
|
11863
|
+
code: "workflow-input-missing",
|
|
11864
|
+
input: name,
|
|
11865
|
+
message: `Workflow ${spec.name} requires input "${name}".`
|
|
11866
|
+
});
|
|
11867
|
+
}
|
|
11868
|
+
continue;
|
|
11869
|
+
}
|
|
11870
|
+
const value = normalizeInputValue(name, inputSpec, rawValue);
|
|
11871
|
+
if (value === null) {
|
|
11872
|
+
diagnostics.push({
|
|
11873
|
+
severity: "error",
|
|
11874
|
+
code: "workflow-input-invalid",
|
|
11875
|
+
input: name,
|
|
11876
|
+
message: `Workflow ${spec.name} input "${name}" must be ${inputSpec.type}.`
|
|
11877
|
+
});
|
|
11878
|
+
continue;
|
|
11879
|
+
}
|
|
11880
|
+
if (inputSpec.allowedValues && typeof value === "string" && !inputSpec.allowedValues.includes(value)) {
|
|
11881
|
+
diagnostics.push({
|
|
11882
|
+
severity: "error",
|
|
11883
|
+
code: "workflow-input-not-allowed",
|
|
11884
|
+
input: name,
|
|
11885
|
+
message: `Workflow ${spec.name} input "${name}" must be one of: ${inputSpec.allowedValues.join(", ")}.`
|
|
11886
|
+
});
|
|
11887
|
+
continue;
|
|
11888
|
+
}
|
|
11889
|
+
inputs[name] = value;
|
|
11890
|
+
}
|
|
11891
|
+
const fieldType = inputs.type;
|
|
11892
|
+
if (spec.name === "add-field" && typeof fieldType === "string" && (fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")) {
|
|
11893
|
+
diagnostics.push({
|
|
11894
|
+
severity: "error",
|
|
11895
|
+
code: "primitive-field-type-unsupported",
|
|
11896
|
+
input: "type",
|
|
11897
|
+
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`
|
|
11898
|
+
});
|
|
11899
|
+
}
|
|
11900
|
+
return {
|
|
11901
|
+
schemaVersion: 1,
|
|
11902
|
+
workflow: spec.name,
|
|
11903
|
+
mode: "plan",
|
|
11904
|
+
inputs,
|
|
11905
|
+
optionalSurfaces: spec.optionalSurfaces ?? {},
|
|
11906
|
+
steps: spec.steps,
|
|
11907
|
+
predictedChanges: spec.predictedChanges,
|
|
11908
|
+
validation: spec.validation,
|
|
11909
|
+
diagnostics,
|
|
11910
|
+
requiresApproval: true
|
|
11911
|
+
};
|
|
11912
|
+
};
|
|
11913
|
+
var renderWorkflowList = (specs) => [
|
|
11914
|
+
"# Akan Workflows",
|
|
11915
|
+
"",
|
|
11916
|
+
...specs.flatMap((spec) => [`- \`${spec.name}\`: ${spec.description}`, ` - When: ${spec.whenToUse}`]),
|
|
11917
|
+
""
|
|
11918
|
+
].join(`
|
|
11919
|
+
`);
|
|
11920
|
+
var renderWorkflowExplain = (spec) => [
|
|
11921
|
+
`# Workflow: ${spec.name}`,
|
|
11922
|
+
"",
|
|
11923
|
+
spec.description,
|
|
11924
|
+
"",
|
|
11925
|
+
"## When To Use",
|
|
11926
|
+
spec.whenToUse,
|
|
11927
|
+
"",
|
|
11928
|
+
"## Inputs",
|
|
11929
|
+
...Object.entries(spec.inputs).map(([name, input4]) => `- \`${name}\`${input4.required ? " (required)" : ""}: ${input4.description}${input4.allowedValues ? ` Allowed: ${input4.allowedValues.join(", ")}.` : ""}`),
|
|
11930
|
+
"",
|
|
11931
|
+
"## Optional Surfaces",
|
|
11932
|
+
...Object.entries(spec.optionalSurfaces ?? {}).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
11933
|
+
"",
|
|
11934
|
+
"## Steps",
|
|
11935
|
+
...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
11936
|
+
"",
|
|
11937
|
+
"## Validation",
|
|
11938
|
+
...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
11939
|
+
""
|
|
11940
|
+
].join(`
|
|
11941
|
+
`);
|
|
11942
|
+
var renderWorkflowPlan = (plan) => [
|
|
11943
|
+
`# Workflow Plan: ${plan.workflow}`,
|
|
11944
|
+
"",
|
|
11945
|
+
`- Mode: ${plan.mode}`,
|
|
11946
|
+
`- Requires approval: ${plan.requiresApproval}`,
|
|
11947
|
+
"",
|
|
11948
|
+
"## Inputs",
|
|
11949
|
+
...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
|
|
11950
|
+
"",
|
|
11951
|
+
"## Optional Surfaces",
|
|
11952
|
+
...Object.entries(plan.optionalSurfaces).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
11953
|
+
"",
|
|
11954
|
+
"## Steps",
|
|
11955
|
+
...plan.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
11956
|
+
"",
|
|
11957
|
+
"## Predicted Changes",
|
|
11958
|
+
...plan.predictedChanges.map((change) => `- \`${change.action}\` ${change.target}: ${change.reason}`),
|
|
11959
|
+
"",
|
|
11960
|
+
"## Validation",
|
|
11961
|
+
...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
11962
|
+
"",
|
|
11963
|
+
"## Diagnostics",
|
|
11964
|
+
...plan.diagnostics.length ? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
11965
|
+
""
|
|
11966
|
+
].join(`
|
|
11967
|
+
`);
|
|
11968
|
+
var workflowStatus = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed";
|
|
11969
|
+
var commandStatus = (commands) => commands.some((command3) => command3.status === "failed") ? "failed" : "passed";
|
|
11970
|
+
var uniqueBy = (values, key) => {
|
|
11971
|
+
const seen = new Set;
|
|
11972
|
+
return values.filter((value) => {
|
|
11973
|
+
const itemKey = key(value);
|
|
11974
|
+
if (seen.has(itemKey))
|
|
11975
|
+
return false;
|
|
11976
|
+
seen.add(itemKey);
|
|
11977
|
+
return true;
|
|
11978
|
+
});
|
|
11979
|
+
};
|
|
11980
|
+
var createWorkflowApplyReport = ({
|
|
11981
|
+
workflow,
|
|
11982
|
+
mode,
|
|
11983
|
+
changedFiles = [],
|
|
11984
|
+
generatedFiles: generatedFiles2 = [],
|
|
11985
|
+
appliedCommands = [],
|
|
11986
|
+
recommendedValidationCommands,
|
|
11987
|
+
commands = [],
|
|
11988
|
+
diagnostics = [],
|
|
11989
|
+
nextActions = [],
|
|
11990
|
+
plan
|
|
11991
|
+
}) => {
|
|
11992
|
+
const validationCommands2 = recommendedValidationCommands ?? commands;
|
|
11993
|
+
return {
|
|
11994
|
+
schemaVersion: 1,
|
|
11995
|
+
workflow,
|
|
11996
|
+
mode,
|
|
11997
|
+
status: workflowStatus(diagnostics),
|
|
11998
|
+
changedFiles: uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`),
|
|
11999
|
+
generatedFiles: uniqueBy(generatedFiles2, (file) => `${file.action}:${file.path}:${file.reason}`),
|
|
12000
|
+
appliedCommands: uniqueBy(appliedCommands, (command3) => command3.command),
|
|
12001
|
+
recommendedValidationCommands: uniqueBy(validationCommands2, (command3) => command3.command),
|
|
12002
|
+
commands: uniqueBy(validationCommands2, (command3) => command3.command),
|
|
12003
|
+
diagnostics,
|
|
12004
|
+
nextActions: uniqueBy(nextActions, (action) => action.command),
|
|
12005
|
+
plan
|
|
12006
|
+
};
|
|
12007
|
+
};
|
|
12008
|
+
var resolveWorkflowCommand = (command3, plan) => {
|
|
12009
|
+
const target = typeof plan.inputs.app === "string" ? plan.inputs.app : "<app-or-lib>";
|
|
12010
|
+
return command3.replaceAll("<app-or-lib-or-pkg>", target).replaceAll("<app-or-lib>", target).replaceAll("<app-name>", target);
|
|
12011
|
+
};
|
|
12012
|
+
var workflowCommandsForPlan = (plan) => plan.validation.map((validation) => ({
|
|
12013
|
+
command: resolveWorkflowCommand(validation.command, plan),
|
|
12014
|
+
reason: validation.reason,
|
|
12015
|
+
kind: validation.kind
|
|
12016
|
+
}));
|
|
12017
|
+
var workflowRunsDir = ".akan/workflows/runs";
|
|
12018
|
+
var createWorkflowRunId = (prefix = "run") => `${prefix}-${new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
12019
|
+
var getRunId = (artifact) => {
|
|
12020
|
+
if ("runId" in artifact)
|
|
12021
|
+
return artifact.runId;
|
|
12022
|
+
return createWorkflowRunId("mode" in artifact ? artifact.mode : artifact.kind);
|
|
12023
|
+
};
|
|
12024
|
+
var workflowRunArtifactPath = (runId) => `${workflowRunsDir}/${runId}.json`;
|
|
12025
|
+
var writeWorkflowRunArtifact = async (workspace, artifact) => {
|
|
12026
|
+
const runId = getRunId(artifact);
|
|
12027
|
+
const artifactPath = workflowRunArtifactPath(runId);
|
|
12028
|
+
await workspace.writeFile(artifactPath, jsonText(artifact), { silent: true });
|
|
12029
|
+
return { runId, path: artifactPath };
|
|
12030
|
+
};
|
|
12031
|
+
var readWorkflowRunArtifact = async (workspace, runId) => {
|
|
12032
|
+
const artifactPath = workflowRunArtifactPath(runId);
|
|
12033
|
+
if (!await workspace.exists(artifactPath))
|
|
12034
|
+
throw new Error(`Workflow run artifact does not exist: ${artifactPath}`);
|
|
12035
|
+
return await workspace.readJson(artifactPath);
|
|
12036
|
+
};
|
|
12037
|
+
var createWorkflowValidationRunReport = async ({
|
|
12038
|
+
runId = createWorkflowRunId("validation"),
|
|
12039
|
+
workflow,
|
|
12040
|
+
source,
|
|
12041
|
+
plan,
|
|
12042
|
+
commands,
|
|
12043
|
+
execute,
|
|
12044
|
+
diagnostics = [],
|
|
12045
|
+
repairActions = []
|
|
12046
|
+
}) => {
|
|
12047
|
+
const results = [];
|
|
12048
|
+
for (const command3 of commands) {
|
|
12049
|
+
results.push(await execute(command3));
|
|
12050
|
+
}
|
|
12051
|
+
const commandDiagnostics = results.flatMap((result) => result.status === "failed" ? [
|
|
12052
|
+
{
|
|
12053
|
+
severity: "error",
|
|
12054
|
+
code: "workflow-validation-command-failed",
|
|
12055
|
+
message: `Validation command failed: ${result.command}`,
|
|
12056
|
+
command: result.command,
|
|
12057
|
+
kind: result.kind,
|
|
12058
|
+
failureScope: result.failureScope
|
|
12059
|
+
}
|
|
12060
|
+
] : []);
|
|
12061
|
+
return {
|
|
12062
|
+
schemaVersion: 1,
|
|
12063
|
+
runId,
|
|
12064
|
+
workflow,
|
|
12065
|
+
mode: "validate",
|
|
12066
|
+
source,
|
|
12067
|
+
status: workflowStatus([...diagnostics, ...commandDiagnostics]) === "failed" ? "failed" : commandStatus(results),
|
|
12068
|
+
commands: results,
|
|
12069
|
+
diagnostics: [...diagnostics, ...commandDiagnostics],
|
|
12070
|
+
repairActions: uniqueBy(repairActions, (action) => action.command),
|
|
12071
|
+
nextActions: results.filter((result) => result.status === "failed").map((result) => ({ command: result.command, reason: "Re-run this validation command after repair." })),
|
|
12072
|
+
plan
|
|
12073
|
+
};
|
|
12074
|
+
};
|
|
12075
|
+
var createDryRunWorkflowApplyReport = (plan) => {
|
|
12076
|
+
const changedFiles = plan.predictedChanges.flatMap((change) => {
|
|
12077
|
+
if (change.action !== "create" && change.action !== "modify")
|
|
12078
|
+
return [];
|
|
12079
|
+
return [
|
|
12080
|
+
{
|
|
12081
|
+
path: change.target,
|
|
12082
|
+
action: change.action,
|
|
12083
|
+
reason: change.reason
|
|
12084
|
+
}
|
|
12085
|
+
];
|
|
12086
|
+
});
|
|
12087
|
+
const generatedFiles2 = plan.predictedChanges.flatMap((change) => {
|
|
12088
|
+
if (change.action !== "sync")
|
|
12089
|
+
return [];
|
|
12090
|
+
return [
|
|
12091
|
+
{
|
|
12092
|
+
path: change.target,
|
|
12093
|
+
action: "sync",
|
|
12094
|
+
reason: change.reason
|
|
12095
|
+
}
|
|
12096
|
+
];
|
|
12097
|
+
});
|
|
12098
|
+
const diagnostics = [...plan.diagnostics];
|
|
12099
|
+
return createWorkflowApplyReport({
|
|
12100
|
+
workflow: plan.workflow,
|
|
12101
|
+
mode: "dry-run",
|
|
12102
|
+
changedFiles,
|
|
12103
|
+
generatedFiles: generatedFiles2,
|
|
12104
|
+
commands: workflowCommandsForPlan(plan),
|
|
12105
|
+
diagnostics,
|
|
12106
|
+
nextActions: workflowCommandsForPlan(plan),
|
|
12107
|
+
plan
|
|
12108
|
+
});
|
|
12109
|
+
};
|
|
12110
|
+
var workflowStepKey = (workflow, stepId) => `${workflow}:${stepId}`;
|
|
12111
|
+
var primitiveReportToWorkflowStepResult = (report) => ({
|
|
12112
|
+
changedFiles: report.changedFiles,
|
|
12113
|
+
generatedFiles: report.generatedFiles,
|
|
12114
|
+
commands: report.validationCommands,
|
|
12115
|
+
diagnostics: report.diagnostics,
|
|
12116
|
+
nextActions: report.nextActions
|
|
12117
|
+
});
|
|
12118
|
+
var workflowStringInput = (value) => typeof value === "string" ? value : null;
|
|
12119
|
+
var workflowStringListInput = (value) => Array.isArray(value) ? value.join(",") : null;
|
|
12120
|
+
var resolveWorkflowSys = async (workspace, target) => {
|
|
12121
|
+
if (!target)
|
|
12122
|
+
return null;
|
|
12123
|
+
const [apps, libs] = await workspace.getSyss();
|
|
12124
|
+
if (apps.includes(target))
|
|
12125
|
+
return AppExecutor.from(workspace, target);
|
|
12126
|
+
if (libs.includes(target))
|
|
12127
|
+
return LibExecutor.from(workspace, target);
|
|
12128
|
+
return null;
|
|
12129
|
+
};
|
|
12130
|
+
var targetMissing = (input4 = "app") => ({
|
|
12131
|
+
severity: "error",
|
|
12132
|
+
code: "workflow-target-missing",
|
|
12133
|
+
input: input4,
|
|
12134
|
+
message: "Workflow target app or library was not found."
|
|
12135
|
+
});
|
|
12136
|
+
var inputMissing = (input4) => ({
|
|
12137
|
+
severity: "error",
|
|
12138
|
+
code: "workflow-input-missing",
|
|
12139
|
+
input: input4,
|
|
12140
|
+
message: `Workflow input "${input4}" is required for apply.`
|
|
12141
|
+
});
|
|
12142
|
+
var unsupportedInput = (input4, message) => ({
|
|
12143
|
+
severity: "error",
|
|
12144
|
+
code: "workflow-input-unsupported",
|
|
12145
|
+
input: input4,
|
|
12146
|
+
message
|
|
12147
|
+
});
|
|
12148
|
+
var createWorkflowStepRegistry = ({
|
|
12149
|
+
workspace,
|
|
12150
|
+
createModule,
|
|
12151
|
+
createScalar,
|
|
12152
|
+
createUi,
|
|
12153
|
+
addField,
|
|
12154
|
+
addEnumField
|
|
12155
|
+
}) => {
|
|
12156
|
+
const inspect = async () => {
|
|
12157
|
+
return;
|
|
12158
|
+
};
|
|
12159
|
+
const commandOnly = async () => {
|
|
12160
|
+
return;
|
|
12161
|
+
};
|
|
12162
|
+
return {
|
|
12163
|
+
inspectSystem: inspect,
|
|
12164
|
+
inspectModule: inspect,
|
|
12165
|
+
syncTarget: commandOnly,
|
|
12166
|
+
lintTarget: commandOnly,
|
|
12167
|
+
[workflowStepKey("create-module", "create-module")]: async (_step, plan) => {
|
|
12168
|
+
const app = workflowStringInput(plan.inputs.app);
|
|
12169
|
+
const module = workflowStringInput(plan.inputs.module);
|
|
12170
|
+
const sys3 = await resolveWorkflowSys(workspace, app);
|
|
12171
|
+
if (!sys3 || !module)
|
|
12172
|
+
return { diagnostics: [!sys3 ? targetMissing() : inputMissing("module")] };
|
|
12173
|
+
return primitiveReportToWorkflowStepResult(await createModule(sys3, module));
|
|
12174
|
+
},
|
|
12175
|
+
[workflowStepKey("create-scalar", "create-scalar")]: async (_step, plan) => {
|
|
12176
|
+
const app = workflowStringInput(plan.inputs.app);
|
|
12177
|
+
const scalar = workflowStringInput(plan.inputs.scalar);
|
|
12178
|
+
const sys3 = await resolveWorkflowSys(workspace, app);
|
|
12179
|
+
if (!sys3 || !scalar)
|
|
12180
|
+
return { diagnostics: [!sys3 ? targetMissing() : inputMissing("scalar")] };
|
|
12181
|
+
return primitiveReportToWorkflowStepResult(await createScalar(sys3, scalar));
|
|
12182
|
+
},
|
|
12183
|
+
[workflowStepKey("create-ui", "create-ui")]: async (_step, plan) => {
|
|
12184
|
+
const surface = workflowStringInput(plan.inputs.surface);
|
|
12185
|
+
if (surface !== "view" && surface !== "unit" && surface !== "template") {
|
|
12186
|
+
return {
|
|
12187
|
+
diagnostics: [
|
|
12188
|
+
unsupportedInput("surface", "Workflow apply currently supports create-ui surfaces: view, unit, template.")
|
|
12189
|
+
]
|
|
12190
|
+
};
|
|
12191
|
+
}
|
|
12192
|
+
return primitiveReportToWorkflowStepResult(await createUi({
|
|
12193
|
+
app: workflowStringInput(plan.inputs.app),
|
|
12194
|
+
module: workflowStringInput(plan.inputs.module),
|
|
12195
|
+
surface
|
|
12196
|
+
}));
|
|
12197
|
+
},
|
|
12198
|
+
[workflowStepKey("add-field", "update-constant")]: async (_step, plan) => {
|
|
12199
|
+
if (workflowStringInput(plan.inputs.type)?.toLowerCase() === "enum") {
|
|
12200
|
+
return primitiveReportToWorkflowStepResult(await addEnumField({
|
|
12201
|
+
app: workflowStringInput(plan.inputs.app),
|
|
12202
|
+
module: workflowStringInput(plan.inputs.module),
|
|
12203
|
+
field: workflowStringInput(plan.inputs.field),
|
|
12204
|
+
values: workflowStringListInput(plan.inputs.values),
|
|
12205
|
+
defaultValue: workflowStringInput(plan.inputs.default)
|
|
12206
|
+
}));
|
|
12207
|
+
}
|
|
12208
|
+
return primitiveReportToWorkflowStepResult(await addField({
|
|
12209
|
+
app: workflowStringInput(plan.inputs.app),
|
|
12210
|
+
module: workflowStringInput(plan.inputs.module),
|
|
12211
|
+
field: workflowStringInput(plan.inputs.field),
|
|
12212
|
+
type: workflowStringInput(plan.inputs.type),
|
|
12213
|
+
defaultValue: workflowStringInput(plan.inputs.default)
|
|
12214
|
+
}));
|
|
12215
|
+
},
|
|
12216
|
+
[workflowStepKey("add-field", "update-dictionary")]: inspect,
|
|
12217
|
+
[workflowStepKey("add-field", "update-ui-surfaces")]: inspect,
|
|
12218
|
+
[workflowStepKey("add-enum-field", "update-constant")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addEnumField({
|
|
12219
|
+
app: workflowStringInput(plan.inputs.app),
|
|
12220
|
+
module: workflowStringInput(plan.inputs.module),
|
|
12221
|
+
field: workflowStringInput(plan.inputs.field),
|
|
12222
|
+
values: workflowStringListInput(plan.inputs.values),
|
|
12223
|
+
defaultValue: workflowStringInput(plan.inputs.default)
|
|
12224
|
+
})),
|
|
12225
|
+
[workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
|
|
12226
|
+
[workflowStepKey("add-enum-field", "update-option")]: inspect
|
|
12227
|
+
};
|
|
12228
|
+
};
|
|
12229
|
+
var renderWorkflowApplyReport = (report) => [
|
|
12230
|
+
`# Workflow Apply: ${report.workflow}`,
|
|
12231
|
+
"",
|
|
12232
|
+
`- Mode: ${report.mode}`,
|
|
12233
|
+
`- Status: ${report.status}`,
|
|
12234
|
+
"",
|
|
12235
|
+
"## Changed Files",
|
|
12236
|
+
...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
12237
|
+
"",
|
|
12238
|
+
"## Generated Files",
|
|
12239
|
+
...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
12240
|
+
"",
|
|
12241
|
+
"## Applied Commands",
|
|
12242
|
+
...report.appliedCommands.length ? report.appliedCommands.map((command3) => `- \`${command3.command}\`: ${command3.reason}`) : ["- none"],
|
|
12243
|
+
"",
|
|
12244
|
+
"## Recommended Validation Commands",
|
|
12245
|
+
...report.recommendedValidationCommands.length ? report.recommendedValidationCommands.map((command3) => `- \`${command3.command}\`: ${command3.reason}`) : ["- none"],
|
|
12246
|
+
"",
|
|
12247
|
+
"## Diagnostics",
|
|
12248
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
12249
|
+
"",
|
|
12250
|
+
"## Next Actions",
|
|
12251
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
12252
|
+
""
|
|
12253
|
+
].join(`
|
|
12254
|
+
`);
|
|
12255
|
+
var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
12256
|
+
var renderWorkflowValidationRunReport = (report) => [
|
|
12257
|
+
`# Workflow Validation: ${report.workflow}`,
|
|
12258
|
+
"",
|
|
12259
|
+
`- Run: ${report.runId}`,
|
|
12260
|
+
`- Status: ${report.status}`,
|
|
12261
|
+
"",
|
|
12262
|
+
"## Commands",
|
|
12263
|
+
...report.commands.length ? report.commands.map((command3) => {
|
|
12264
|
+
const scope = command3.failureScope ? ` (${command3.failureScope})` : "";
|
|
12265
|
+
return `- [${command3.status}] \`${command3.command}\`${scope}: ${command3.reason}`;
|
|
12266
|
+
}) : ["- none"],
|
|
12267
|
+
"",
|
|
12268
|
+
"## Diagnostics",
|
|
12269
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
12270
|
+
"",
|
|
12271
|
+
"## Repair Actions",
|
|
12272
|
+
...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
12273
|
+
"",
|
|
12274
|
+
"## Next Actions",
|
|
12275
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
12276
|
+
""
|
|
12277
|
+
].join(`
|
|
12278
|
+
`);
|
|
12279
|
+
var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
|
|
12280
|
+
var renderWorkflowRunArtifact = (artifact, format = "markdown") => {
|
|
12281
|
+
if ("kind" in artifact)
|
|
12282
|
+
return renderRepairReport(artifact, format);
|
|
12283
|
+
if ("mode" in artifact && artifact.mode === "validate")
|
|
12284
|
+
return renderWorkflowValidation(artifact, format);
|
|
12285
|
+
if ("mode" in artifact && (artifact.mode === "apply" || artifact.mode === "dry-run")) {
|
|
12286
|
+
return renderWorkflowApply(artifact, format);
|
|
12287
|
+
}
|
|
12288
|
+
return jsonText(artifact);
|
|
12289
|
+
};
|
|
12290
|
+
var createRepairReport = ({
|
|
12291
|
+
command: command3,
|
|
12292
|
+
kind,
|
|
12293
|
+
target = null,
|
|
12294
|
+
diagnostics = [],
|
|
12295
|
+
repairActions = [],
|
|
12296
|
+
nextActions = [],
|
|
12297
|
+
commands = []
|
|
12298
|
+
}) => ({
|
|
12299
|
+
schemaVersion: 1,
|
|
12300
|
+
command: command3,
|
|
12301
|
+
kind,
|
|
12302
|
+
target,
|
|
12303
|
+
status: workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed",
|
|
12304
|
+
diagnostics,
|
|
12305
|
+
repairActions: uniqueBy(repairActions, (action) => action.command),
|
|
12306
|
+
nextActions: uniqueBy(nextActions, (action) => action.command),
|
|
12307
|
+
commands
|
|
12308
|
+
});
|
|
12309
|
+
var renderRepairReportMarkdown = (report) => [
|
|
12310
|
+
`# Akan Repair: ${report.kind}`,
|
|
12311
|
+
"",
|
|
12312
|
+
`- Status: ${report.status}`,
|
|
12313
|
+
`- Target: ${report.target ?? "none"}`,
|
|
12314
|
+
"",
|
|
12315
|
+
"## Commands",
|
|
12316
|
+
...report.commands.length ? report.commands.map((command3) => `- [${command3.status}] \`${command3.command}\`: ${command3.reason}`) : ["- none"],
|
|
12317
|
+
"",
|
|
12318
|
+
"## Diagnostics",
|
|
12319
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
12320
|
+
"",
|
|
12321
|
+
"## Next Actions",
|
|
12322
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
12323
|
+
""
|
|
12324
|
+
].join(`
|
|
12325
|
+
`);
|
|
12326
|
+
var renderRepairReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderRepairReportMarkdown(report);
|
|
12327
|
+
|
|
12328
|
+
class WorkflowExecutor {
|
|
12329
|
+
registry;
|
|
12330
|
+
constructor(registry) {
|
|
12331
|
+
this.registry = registry;
|
|
12332
|
+
}
|
|
12333
|
+
async apply(plan) {
|
|
12334
|
+
const changedFiles = [];
|
|
12335
|
+
const generatedFiles2 = [];
|
|
12336
|
+
const recommendedValidationCommands = [];
|
|
12337
|
+
const diagnostics = [...plan.diagnostics];
|
|
12338
|
+
const nextActions = [];
|
|
12339
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
12340
|
+
return createWorkflowApplyReport({
|
|
12341
|
+
workflow: plan.workflow,
|
|
12342
|
+
mode: "apply",
|
|
12343
|
+
changedFiles,
|
|
12344
|
+
generatedFiles: generatedFiles2,
|
|
12345
|
+
recommendedValidationCommands,
|
|
12346
|
+
diagnostics,
|
|
12347
|
+
nextActions,
|
|
12348
|
+
plan
|
|
12349
|
+
});
|
|
12350
|
+
}
|
|
12351
|
+
recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
|
|
12352
|
+
nextActions.push(...workflowCommandsForPlan(plan));
|
|
12353
|
+
for (const step of plan.steps) {
|
|
12354
|
+
const runner2 = this.registry[workflowStepKey(plan.workflow, step.id)] ?? this.registry[step.tool] ?? this.registry[step.id];
|
|
12355
|
+
if (!runner2) {
|
|
12356
|
+
diagnostics.push({
|
|
12357
|
+
severity: "error",
|
|
12358
|
+
code: "workflow-step-unsupported",
|
|
12359
|
+
message: `Workflow ${plan.workflow} step "${step.id}" is not supported by workflow apply yet.`
|
|
12360
|
+
});
|
|
12361
|
+
nextActions.push({
|
|
12362
|
+
command: `akan workflow explain ${plan.workflow}`,
|
|
12363
|
+
reason: "Review the unsupported workflow step before retrying apply."
|
|
12364
|
+
});
|
|
12365
|
+
break;
|
|
12366
|
+
}
|
|
12367
|
+
const result = await runner2(step, plan);
|
|
12368
|
+
if (!result)
|
|
12369
|
+
continue;
|
|
12370
|
+
changedFiles.push(...result.changedFiles ?? []);
|
|
12371
|
+
generatedFiles2.push(...result.generatedFiles ?? []);
|
|
12372
|
+
recommendedValidationCommands.push(...result.commands ?? []);
|
|
12373
|
+
diagnostics.push(...result.diagnostics ?? []);
|
|
12374
|
+
nextActions.push(...result.nextActions ?? []);
|
|
12375
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
|
|
12376
|
+
break;
|
|
12377
|
+
}
|
|
12378
|
+
return createWorkflowApplyReport({
|
|
12379
|
+
workflow: plan.workflow,
|
|
12380
|
+
mode: "apply",
|
|
12381
|
+
changedFiles,
|
|
12382
|
+
generatedFiles: generatedFiles2,
|
|
12383
|
+
recommendedValidationCommands,
|
|
12384
|
+
diagnostics,
|
|
12385
|
+
nextActions,
|
|
12386
|
+
plan
|
|
12387
|
+
});
|
|
12388
|
+
}
|
|
12389
|
+
}
|
|
12390
|
+
var createPrimitiveWriteReport = ({
|
|
12391
|
+
command: command3,
|
|
12392
|
+
status,
|
|
12393
|
+
changedFiles = [],
|
|
12394
|
+
generatedFiles: generatedFiles2 = [],
|
|
12395
|
+
validationCommands: validationCommands2 = [],
|
|
12396
|
+
diagnostics = [],
|
|
12397
|
+
nextActions = []
|
|
12398
|
+
}) => ({
|
|
12399
|
+
schemaVersion: 1,
|
|
12400
|
+
command: command3,
|
|
12401
|
+
status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
|
|
12402
|
+
changedFiles,
|
|
12403
|
+
generatedFiles: generatedFiles2,
|
|
12404
|
+
validationCommands: validationCommands2,
|
|
12405
|
+
diagnostics,
|
|
12406
|
+
nextActions
|
|
12407
|
+
});
|
|
12408
|
+
var renderPrimitiveWriteReport = (report) => [
|
|
12409
|
+
`# Primitive Write: ${report.command}`,
|
|
12410
|
+
"",
|
|
12411
|
+
`- Status: ${report.status}`,
|
|
12412
|
+
"",
|
|
12413
|
+
"## Changed Files",
|
|
12414
|
+
...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
12415
|
+
"",
|
|
12416
|
+
"## Generated Files",
|
|
12417
|
+
...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
12418
|
+
"",
|
|
12419
|
+
"## Validation Commands",
|
|
12420
|
+
...report.validationCommands.length ? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`) : ["- none"],
|
|
12421
|
+
"",
|
|
12422
|
+
"## Diagnostics",
|
|
12423
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
12424
|
+
"",
|
|
12425
|
+
"## Next Actions",
|
|
12426
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
12427
|
+
""
|
|
12428
|
+
].join(`
|
|
12429
|
+
`);
|
|
12430
|
+
var renderPrimitiveReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
|
|
12431
|
+
var getSysRoot = (sys3) => `${sys3.type}s/${sys3.name}`;
|
|
12432
|
+
var sourceFile = (sys3, path40, action, reason) => ({
|
|
12433
|
+
path: `${getSysRoot(sys3)}/${path40}`,
|
|
12434
|
+
action,
|
|
12435
|
+
reason
|
|
12436
|
+
});
|
|
12437
|
+
var generatedFilesForSync = (sys3, reason = "Generated files may change after sync.") => [
|
|
12438
|
+
{ path: `${getSysRoot(sys3)}/lib/cnst.ts`, action: "sync", reason },
|
|
12439
|
+
{ path: `${getSysRoot(sys3)}/lib/dict.ts`, action: "sync", reason },
|
|
12440
|
+
{ path: `${getSysRoot(sys3)}/lib/option.ts`, action: "sync", reason },
|
|
12441
|
+
{ path: `${getSysRoot(sys3)}/lib/index.ts`, action: "sync", reason }
|
|
12442
|
+
];
|
|
12443
|
+
var validationCommandsForTarget = (target) => [
|
|
12444
|
+
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files from source conventions." },
|
|
12445
|
+
{ command: `akan lint ${target}`, reason: "Validate formatting, imports, and static lint rules." }
|
|
12446
|
+
];
|
|
12447
|
+
var nextActionsForTarget = (target) => [
|
|
12448
|
+
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files after source changes." },
|
|
12449
|
+
{ command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." }
|
|
12450
|
+
];
|
|
12451
|
+
var createPassedPrimitiveReport = ({
|
|
12452
|
+
command: command3,
|
|
12453
|
+
changedFiles,
|
|
12454
|
+
generatedFiles: generatedFiles2,
|
|
12455
|
+
target,
|
|
12456
|
+
nextActions
|
|
12457
|
+
}) => createPrimitiveWriteReport({
|
|
12458
|
+
command: command3,
|
|
12459
|
+
changedFiles,
|
|
12460
|
+
generatedFiles: generatedFiles2 ?? [],
|
|
12461
|
+
validationCommands: validationCommandsForTarget(target),
|
|
12462
|
+
diagnostics: [],
|
|
12463
|
+
nextActions: nextActions ?? nextActionsForTarget(target)
|
|
12464
|
+
});
|
|
12465
|
+
var scalarChangedFiles = (sys3, scalarName, files) => Object.values(files).map((file) => sourceFile(sys3, `lib/__scalar/${scalarName}/${file.filename}`, "create", "Scalar source file was created."));
|
|
12466
|
+
var titleize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
12467
|
+
var lowerlize = (value) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
|
|
12468
|
+
var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!diagnostic);
|
|
12469
|
+
var normalizeFieldType = (typeName) => {
|
|
12470
|
+
const normalizedTypes = {
|
|
12471
|
+
string: "String",
|
|
12472
|
+
boolean: "Boolean",
|
|
12473
|
+
date: "Date",
|
|
12474
|
+
int: "Int",
|
|
12475
|
+
integer: "Int",
|
|
12476
|
+
float: "Float",
|
|
12477
|
+
double: "Float",
|
|
12478
|
+
decimal: "Float"
|
|
12479
|
+
};
|
|
12480
|
+
return normalizedTypes[typeName.toLowerCase()] ?? typeName;
|
|
12481
|
+
};
|
|
12482
|
+
var ensureBaseTypeImport = (content, typeName) => {
|
|
12483
|
+
if (typeName !== "Int" && typeName !== "Float")
|
|
12484
|
+
return content;
|
|
12485
|
+
if (new RegExp(`import \\{[^}]*\\b${typeName}\\b[^}]*\\} from "akanjs/base";`).test(content))
|
|
12486
|
+
return content;
|
|
12487
|
+
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
12488
|
+
if (baseImport) {
|
|
12489
|
+
const names = baseImport[1].split(",").map((name) => name.trim()).filter(Boolean);
|
|
12490
|
+
return content.replace(baseImport[0], `import { ${[...names, typeName].sort().join(", ")} } from "akanjs/base";`);
|
|
12491
|
+
}
|
|
12492
|
+
return `import { ${typeName} } from "akanjs/base";
|
|
12493
|
+
${content}`;
|
|
12494
|
+
};
|
|
12495
|
+
var fieldExpression = (typeName, defaultValue) => {
|
|
12496
|
+
const typeExpression = normalizeFieldType(typeName);
|
|
12497
|
+
const defaultOption = defaultValue ? `, { default: ${JSON.stringify(defaultValue)} }` : "";
|
|
12498
|
+
return `field(${typeExpression}${defaultOption})`;
|
|
12499
|
+
};
|
|
12500
|
+
var insertIntoObject = (content, className, line) => {
|
|
12501
|
+
const classIndex = content.indexOf(`export class ${className} extends via`);
|
|
12502
|
+
if (classIndex < 0)
|
|
12503
|
+
return null;
|
|
12504
|
+
const objectEndIndex = content.indexOf("}))", classIndex);
|
|
12505
|
+
if (objectEndIndex < 0)
|
|
12506
|
+
return null;
|
|
12507
|
+
const prefix = content.slice(0, objectEndIndex);
|
|
12508
|
+
const suffix = content.slice(objectEndIndex);
|
|
12509
|
+
const insertion = prefix.endsWith(`
|
|
12510
|
+
`) ? ` ${line}
|
|
12511
|
+
` : `
|
|
12512
|
+
${line}
|
|
12513
|
+
`;
|
|
12514
|
+
return `${prefix}${insertion}${suffix}`;
|
|
12515
|
+
};
|
|
12516
|
+
var ensureEnumImport = (content) => {
|
|
12517
|
+
if (content.includes("enumOf"))
|
|
12518
|
+
return content;
|
|
12519
|
+
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
12520
|
+
if (baseImport) {
|
|
12521
|
+
const names = baseImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
12522
|
+
return content.replace(baseImport[0], `import { ${[...names, "enumOf"].sort().join(", ")} } from "akanjs/base";`);
|
|
12523
|
+
}
|
|
12524
|
+
return `import { enumOf } from "akanjs/base";
|
|
12525
|
+
${content}`;
|
|
12526
|
+
};
|
|
12527
|
+
var insertEnumClass = (content, enumClassName, enumName, values) => {
|
|
12528
|
+
if (content.includes(`export class ${enumClassName} extends enumOf`))
|
|
12529
|
+
return content;
|
|
12530
|
+
const enumClass = `export class ${enumClassName} extends enumOf("${enumName}", [
|
|
12531
|
+
${values.map((value) => ` ${JSON.stringify(value)},`).join(`
|
|
12532
|
+
`)}
|
|
12533
|
+
] as const) {}
|
|
12534
|
+
|
|
12535
|
+
`;
|
|
12536
|
+
const firstClassIndex = content.indexOf("export class ");
|
|
12537
|
+
if (firstClassIndex < 0)
|
|
12538
|
+
return `${content}
|
|
12539
|
+
${enumClass}`;
|
|
12540
|
+
return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
|
|
12541
|
+
};
|
|
12542
|
+
var insertDictionaryModelField = (content, moduleClassName, fieldName) => {
|
|
12543
|
+
if (new RegExp(`\\b${fieldName}\\s*:`).test(content))
|
|
12544
|
+
return content;
|
|
12545
|
+
const label = titleize(fieldName);
|
|
12546
|
+
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
|
|
12547
|
+
if (modelIndex < 0)
|
|
12548
|
+
return null;
|
|
12549
|
+
const objectEndIndex = content.indexOf(" }))", modelIndex);
|
|
12550
|
+
if (objectEndIndex < 0)
|
|
12551
|
+
return null;
|
|
12552
|
+
return `${content.slice(0, objectEndIndex)} ${fieldName}: t([${JSON.stringify(label)}, ${JSON.stringify(label)}]).desc([${JSON.stringify(label)}, ${JSON.stringify(label)}]),
|
|
12553
|
+
${content.slice(objectEndIndex)}`;
|
|
12554
|
+
};
|
|
12555
|
+
var ensureConstantTypeImport = (content, constantPath, typeName) => {
|
|
12556
|
+
if (new RegExp(`import type \\{[^}]*\\b${typeName}\\b[^}]*\\} from "${constantPath}";`).test(content))
|
|
12557
|
+
return content;
|
|
12558
|
+
const importPattern = new RegExp(`import type \\{ ([^}]+) \\} from "${constantPath}";`);
|
|
12559
|
+
const existingImport = content.match(importPattern);
|
|
12560
|
+
if (existingImport !== null) {
|
|
12561
|
+
const names = existingImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
12562
|
+
return content.replace(existingImport[0], `import type { ${[...names, typeName].sort().join(", ")} } from "${constantPath}";`);
|
|
12563
|
+
}
|
|
12564
|
+
return `import type { ${typeName} } from "${constantPath}";
|
|
12565
|
+
${content}`;
|
|
12566
|
+
};
|
|
12567
|
+
var insertDictionaryEnum = (content, enumClassName, enumName, values) => {
|
|
12568
|
+
if (content.includes(`.enum<${enumClassName}>("${enumName}"`))
|
|
12569
|
+
return content;
|
|
12570
|
+
const enumBlock = ` .enum<${enumClassName}>("${enumName}", (t) => ({
|
|
12571
|
+
${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${JSON.stringify(titleize(value))}]),`).join(`
|
|
12572
|
+
`)}
|
|
12573
|
+
}))
|
|
12574
|
+
`;
|
|
12575
|
+
const chainEndIndex = content.lastIndexOf(";");
|
|
12576
|
+
const insertBeforeIndex = [content.indexOf(".error("), content.indexOf(".translate("), chainEndIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
12577
|
+
if (insertBeforeIndex === undefined)
|
|
12578
|
+
return null;
|
|
12579
|
+
return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
|
|
12580
|
+
};
|
|
12581
|
+
var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
11693
12582
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
11694
12583
|
import { Logger as Logger11 } from "akanjs/common";
|
|
11695
12584
|
class IncrementalBuilder {
|