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