@akanjs/cli 2.3.9-rc.3 → 2.3.9-rc.5
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 +1265 -724
- package/index.js +1558 -761
- package/package.json +2 -2
- package/templates/module/__model__.constant.ts +1 -2
- package/templates/module/__model__.dictionary.ts +10 -4
package/index.js
CHANGED
|
@@ -3394,28 +3394,28 @@ class SysExecutor extends Executor {
|
|
|
3394
3394
|
return scalarModules;
|
|
3395
3395
|
}
|
|
3396
3396
|
async getViewComponents() {
|
|
3397
|
-
const viewComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.View.tsx`).exists());
|
|
3397
|
+
const viewComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.View.tsx`).exists());
|
|
3398
3398
|
return viewComponents;
|
|
3399
3399
|
}
|
|
3400
3400
|
async getUnitComponents() {
|
|
3401
|
-
const unitComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`).exists());
|
|
3401
|
+
const unitComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Unit.tsx`).exists());
|
|
3402
3402
|
return unitComponents;
|
|
3403
3403
|
}
|
|
3404
3404
|
async getTemplateComponents() {
|
|
3405
|
-
const templateComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`).exists());
|
|
3405
|
+
const templateComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Template.tsx`).exists());
|
|
3406
3406
|
return templateComponents;
|
|
3407
3407
|
}
|
|
3408
3408
|
async getViewsSourceCode() {
|
|
3409
3409
|
const viewComponents = await this.getViewComponents();
|
|
3410
|
-
return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${viewComponent}.View.tsx`)));
|
|
3410
|
+
return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`)));
|
|
3411
3411
|
}
|
|
3412
3412
|
async getUnitsSourceCode() {
|
|
3413
3413
|
const unitComponents = await this.getUnitComponents();
|
|
3414
|
-
return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${unitComponent}.Unit.tsx`)));
|
|
3414
|
+
return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`)));
|
|
3415
3415
|
}
|
|
3416
3416
|
async getTemplatesSourceCode() {
|
|
3417
3417
|
const templateComponents = await this.getTemplateComponents();
|
|
3418
|
-
return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${templateComponent}.Template.tsx`)));
|
|
3418
|
+
return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`)));
|
|
3419
3419
|
}
|
|
3420
3420
|
async getScalarConstantFiles() {
|
|
3421
3421
|
const scalarModules = await this.getScalarModules();
|
|
@@ -4776,238 +4776,11 @@ class AkanAppHost {
|
|
|
4776
4776
|
// pkgs/@akanjs/devkit/akanContext.ts
|
|
4777
4777
|
import { readdir } from "fs/promises";
|
|
4778
4778
|
import path10 from "path";
|
|
4779
|
-
import { capitalize as
|
|
4779
|
+
import { capitalize as capitalize4 } from "akanjs/common";
|
|
4780
4780
|
|
|
4781
|
-
// pkgs/@akanjs/devkit/workflow/
|
|
4782
|
-
import { capitalize as capitalize2 } from "akanjs/common";
|
|
4781
|
+
// pkgs/@akanjs/devkit/workflow/utils.ts
|
|
4783
4782
|
var jsonText = (value, { trailingNewline = true } = {}) => `${JSON.stringify(value, null, 2)}${trailingNewline ? `
|
|
4784
4783
|
` : ""}`;
|
|
4785
|
-
var surfaceModes = new Set(["infer", "include", "skip"]);
|
|
4786
|
-
var parseStringList = (value) => {
|
|
4787
|
-
if (Array.isArray(value)) {
|
|
4788
|
-
const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
4789
|
-
return values.length === value.length ? values : null;
|
|
4790
|
-
}
|
|
4791
|
-
if (typeof value !== "string")
|
|
4792
|
-
return null;
|
|
4793
|
-
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
4794
|
-
};
|
|
4795
|
-
var normalizeInputValue = (name, spec, value) => {
|
|
4796
|
-
if (spec.type === "string")
|
|
4797
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
4798
|
-
if (spec.type === "string-list") {
|
|
4799
|
-
const values = parseStringList(value);
|
|
4800
|
-
return values && values.length > 0 ? values : null;
|
|
4801
|
-
}
|
|
4802
|
-
if (typeof value === "string" && surfaceModes.has(value))
|
|
4803
|
-
return value;
|
|
4804
|
-
throw new Error(`Unsupported workflow input value for ${name}`);
|
|
4805
|
-
};
|
|
4806
|
-
var listWorkflowSpecs = (specs) => [...specs].sort((a, b) => a.name.localeCompare(b.name));
|
|
4807
|
-
var getWorkflowSpec = (specs, name) => specs.find((spec) => spec.name === name) ?? null;
|
|
4808
|
-
var compactWorkflowInputs = (inputs) => Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
|
|
4809
|
-
var addFieldComponentForType = (typeName) => {
|
|
4810
|
-
const normalizedType = normalizeFieldType(typeName);
|
|
4811
|
-
if (normalizedType === "Boolean")
|
|
4812
|
-
return "Field.ToggleSelect";
|
|
4813
|
-
if (normalizedType === "Date")
|
|
4814
|
-
return "Field.Date";
|
|
4815
|
-
if (normalizedType === "Int" || normalizedType === "Float")
|
|
4816
|
-
return "Field.ToggleSelect or Field.Text";
|
|
4817
|
-
if (typeName.toLowerCase() === "enum")
|
|
4818
|
-
return "Field.ToggleSelect";
|
|
4819
|
-
return "Field.Text";
|
|
4820
|
-
};
|
|
4821
|
-
var createAddFieldRecommendations = (inputs) => {
|
|
4822
|
-
const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
|
|
4823
|
-
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
4824
|
-
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
4825
|
-
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
4826
|
-
if (!typeName)
|
|
4827
|
-
return [];
|
|
4828
|
-
const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
|
|
4829
|
-
const constantPath = `*/lib/${module}/${module}.constant.ts`;
|
|
4830
|
-
const dictionaryPath = `*/lib/${module}/${module}.dictionary.ts`;
|
|
4831
|
-
const templatePath = `*/lib/${module}/${capitalize2(module)}.Template.tsx`;
|
|
4832
|
-
return [
|
|
4833
|
-
...normalizedType === "Int" || normalizedType === "Float" ? [
|
|
4834
|
-
{
|
|
4835
|
-
code: "add-field-import",
|
|
4836
|
-
kind: "import",
|
|
4837
|
-
target: constantPath,
|
|
4838
|
-
confidence: "high",
|
|
4839
|
-
message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`
|
|
4840
|
-
}
|
|
4841
|
-
] : [],
|
|
4842
|
-
{
|
|
4843
|
-
code: "add-field-placement-constant",
|
|
4844
|
-
kind: "placement",
|
|
4845
|
-
target: constantPath,
|
|
4846
|
-
confidence: "high",
|
|
4847
|
-
message: `Insert ${field}: field(${normalizedType}) in ${capitalize2(module)}Input.`
|
|
4848
|
-
},
|
|
4849
|
-
{
|
|
4850
|
-
code: "add-field-placement-dictionary",
|
|
4851
|
-
kind: "placement",
|
|
4852
|
-
target: dictionaryPath,
|
|
4853
|
-
confidence: "high",
|
|
4854
|
-
message: `Add dictionary labels for ${module}.${field}.`
|
|
4855
|
-
},
|
|
4856
|
-
{
|
|
4857
|
-
code: "add-field-component",
|
|
4858
|
-
kind: "ui-component",
|
|
4859
|
-
target: templatePath,
|
|
4860
|
-
confidence: normalizedType === "Int" || normalizedType === "Float" ? "medium" : "high",
|
|
4861
|
-
message: `Recommended UI component for ${field} (${normalizedType}): ${addFieldComponentForType(typeName)}.`
|
|
4862
|
-
},
|
|
4863
|
-
{
|
|
4864
|
-
code: "add-field-ui-manual-review",
|
|
4865
|
-
kind: "manual-action",
|
|
4866
|
-
target: templatePath,
|
|
4867
|
-
action: `Review ${app}:${module} Template/Unit/View/Store surfaces and add ${field} only where the existing pattern is clear.`,
|
|
4868
|
-
confidence: "medium",
|
|
4869
|
-
message: "UI surface edits are planned as manual-review unless a safe existing field-list pattern is detected."
|
|
4870
|
-
}
|
|
4871
|
-
];
|
|
4872
|
-
};
|
|
4873
|
-
var createWorkflowPlanRecommendations = (spec, inputs) => [
|
|
4874
|
-
{
|
|
4875
|
-
code: "workflow-apply-first",
|
|
4876
|
-
kind: "auto-apply",
|
|
4877
|
-
confidence: "high",
|
|
4878
|
-
action: "Call apply_workflow with the MCP planPath before editing source files directly.",
|
|
4879
|
-
message: `Apply the ${spec.name} plan through apply_workflow when MCP returns planPath or next.tool=apply_workflow.`
|
|
4880
|
-
},
|
|
4881
|
-
{
|
|
4882
|
-
code: "workflow-validate-apply-report",
|
|
4883
|
-
kind: "validation",
|
|
4884
|
-
confidence: "high",
|
|
4885
|
-
action: "After apply_workflow, call run_validation with validationTarget when present; otherwise use applyReportPath.",
|
|
4886
|
-
message: "Validate the apply report artifact so diagnostics and recommendations follow the actual apply result."
|
|
4887
|
-
},
|
|
4888
|
-
...spec.name === "add-field" ? createAddFieldRecommendations(inputs) : []
|
|
4889
|
-
];
|
|
4890
|
-
var createWorkflowPlan = (spec, rawInputs) => {
|
|
4891
|
-
const inputs = {};
|
|
4892
|
-
const diagnostics = [];
|
|
4893
|
-
for (const [name, inputSpec] of Object.entries(spec.inputs)) {
|
|
4894
|
-
const rawValue = rawInputs[name];
|
|
4895
|
-
if (rawValue === undefined || rawValue === null || rawValue === "") {
|
|
4896
|
-
if (inputSpec.required) {
|
|
4897
|
-
diagnostics.push({
|
|
4898
|
-
severity: "error",
|
|
4899
|
-
code: "workflow-input-missing",
|
|
4900
|
-
input: name,
|
|
4901
|
-
message: `Workflow ${spec.name} requires input "${name}".`
|
|
4902
|
-
});
|
|
4903
|
-
}
|
|
4904
|
-
continue;
|
|
4905
|
-
}
|
|
4906
|
-
const value = normalizeInputValue(name, inputSpec, rawValue);
|
|
4907
|
-
if (value === null) {
|
|
4908
|
-
diagnostics.push({
|
|
4909
|
-
severity: "error",
|
|
4910
|
-
code: "workflow-input-invalid",
|
|
4911
|
-
input: name,
|
|
4912
|
-
message: `Workflow ${spec.name} input "${name}" must be ${inputSpec.type}.`
|
|
4913
|
-
});
|
|
4914
|
-
continue;
|
|
4915
|
-
}
|
|
4916
|
-
if (inputSpec.allowedValues && typeof value === "string" && !inputSpec.allowedValues.includes(value)) {
|
|
4917
|
-
diagnostics.push({
|
|
4918
|
-
severity: "error",
|
|
4919
|
-
code: "workflow-input-not-allowed",
|
|
4920
|
-
input: name,
|
|
4921
|
-
message: `Workflow ${spec.name} input "${name}" must be one of: ${inputSpec.allowedValues.join(", ")}.`
|
|
4922
|
-
});
|
|
4923
|
-
continue;
|
|
4924
|
-
}
|
|
4925
|
-
inputs[name] = value;
|
|
4926
|
-
}
|
|
4927
|
-
const fieldType = inputs.type;
|
|
4928
|
-
if (spec.name === "add-field" && typeof fieldType === "string" && (fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")) {
|
|
4929
|
-
diagnostics.push({
|
|
4930
|
-
severity: "error",
|
|
4931
|
-
code: "primitive-field-type-unsupported",
|
|
4932
|
-
input: "type",
|
|
4933
|
-
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`
|
|
4934
|
-
});
|
|
4935
|
-
}
|
|
4936
|
-
return {
|
|
4937
|
-
schemaVersion: 1,
|
|
4938
|
-
workflow: spec.name,
|
|
4939
|
-
mode: "plan",
|
|
4940
|
-
inputs,
|
|
4941
|
-
optionalSurfaces: spec.optionalSurfaces ?? {},
|
|
4942
|
-
steps: spec.steps,
|
|
4943
|
-
predictedChanges: spec.predictedChanges,
|
|
4944
|
-
validation: spec.validation,
|
|
4945
|
-
diagnostics,
|
|
4946
|
-
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
|
4947
|
-
requiresApproval: true
|
|
4948
|
-
};
|
|
4949
|
-
};
|
|
4950
|
-
var renderWorkflowList = (specs) => [
|
|
4951
|
-
"# Akan Workflows",
|
|
4952
|
-
"",
|
|
4953
|
-
...specs.flatMap((spec) => [`- \`${spec.name}\`: ${spec.description}`, ` - When: ${spec.whenToUse}`]),
|
|
4954
|
-
""
|
|
4955
|
-
].join(`
|
|
4956
|
-
`);
|
|
4957
|
-
var renderWorkflowExplain = (spec) => [
|
|
4958
|
-
`# Workflow: ${spec.name}`,
|
|
4959
|
-
"",
|
|
4960
|
-
spec.description,
|
|
4961
|
-
"",
|
|
4962
|
-
"## When To Use",
|
|
4963
|
-
spec.whenToUse,
|
|
4964
|
-
"",
|
|
4965
|
-
"## Inputs",
|
|
4966
|
-
...Object.entries(spec.inputs).map(([name, input2]) => `- \`${name}\`${input2.required ? " (required)" : ""}: ${input2.description}${input2.allowedValues ? ` Allowed: ${input2.allowedValues.join(", ")}.` : ""}`),
|
|
4967
|
-
"",
|
|
4968
|
-
"## Optional Surfaces",
|
|
4969
|
-
...Object.entries(spec.optionalSurfaces ?? {}).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
4970
|
-
"",
|
|
4971
|
-
"## Steps",
|
|
4972
|
-
...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
4973
|
-
"",
|
|
4974
|
-
"## Validation",
|
|
4975
|
-
...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
4976
|
-
""
|
|
4977
|
-
].join(`
|
|
4978
|
-
`);
|
|
4979
|
-
var renderWorkflowPlan = (plan) => [
|
|
4980
|
-
`# Workflow Plan: ${plan.workflow}`,
|
|
4981
|
-
"",
|
|
4982
|
-
`- Mode: ${plan.mode}`,
|
|
4983
|
-
`- Requires approval: ${plan.requiresApproval}`,
|
|
4984
|
-
"",
|
|
4985
|
-
"## Inputs",
|
|
4986
|
-
...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
|
|
4987
|
-
"",
|
|
4988
|
-
"## Optional Surfaces",
|
|
4989
|
-
...Object.entries(plan.optionalSurfaces).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
4990
|
-
"",
|
|
4991
|
-
"## Steps",
|
|
4992
|
-
...plan.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
4993
|
-
"",
|
|
4994
|
-
"## Predicted Changes",
|
|
4995
|
-
...plan.predictedChanges.map((change) => {
|
|
4996
|
-
const scope = change.applyScope ? ` (${change.applyScope})` : "";
|
|
4997
|
-
return `- \`${change.action}\`${scope} ${change.target}: ${change.reason}`;
|
|
4998
|
-
}),
|
|
4999
|
-
"",
|
|
5000
|
-
"## Validation",
|
|
5001
|
-
...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
5002
|
-
"",
|
|
5003
|
-
"## Diagnostics",
|
|
5004
|
-
...plan.diagnostics.length ? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
5005
|
-
"",
|
|
5006
|
-
"## Recommendations",
|
|
5007
|
-
...plan.recommendations.length ? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`) : ["- none"],
|
|
5008
|
-
""
|
|
5009
|
-
].join(`
|
|
5010
|
-
`);
|
|
5011
4784
|
var workflowStatus = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed";
|
|
5012
4785
|
var commandStatus = (commands) => commands.some((command) => command.status === "failed") ? "failed" : "passed";
|
|
5013
4786
|
var uniqueBy = (values, key) => {
|
|
@@ -5020,6 +4793,9 @@ var uniqueBy = (values, key) => {
|
|
|
5020
4793
|
return true;
|
|
5021
4794
|
});
|
|
5022
4795
|
};
|
|
4796
|
+
var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!diagnostic);
|
|
4797
|
+
|
|
4798
|
+
// pkgs/@akanjs/devkit/workflow/artifacts.ts
|
|
5023
4799
|
var createWorkflowApplyReport = ({
|
|
5024
4800
|
workflow,
|
|
5025
4801
|
mode,
|
|
@@ -5102,6 +4878,66 @@ var readWorkflowRunArtifact = async (workspace, runId) => {
|
|
|
5102
4878
|
throw new Error(`Workflow run artifact does not exist: ${artifactPath}`);
|
|
5103
4879
|
return await workspace.readJson(artifactPath);
|
|
5104
4880
|
};
|
|
4881
|
+
var failedCommandScopes = (commands) => commands.filter((command) => command.status === "failed").map((command) => command.failureScope ?? "unknown");
|
|
4882
|
+
var errorDiagnosticScopes = (diagnostics) => diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.failureScope ?? (diagnostic.scope === "baseline" ? "workspace-config" : diagnostic.scope === "workflow" ? "source-change" : "unknown"));
|
|
4883
|
+
var hasScopeFailure = (scopes, scope, diagnostics = []) => scopes.includes(scope) || diagnostics.some((diagnostic) => diagnostic.severity === "error" && diagnostic.failureScope === scope);
|
|
4884
|
+
var statusForScope = (commands, diagnostics, scopes, expectedScope) => {
|
|
4885
|
+
const hasCommands = commands.length > 0 || diagnostics.length > 0;
|
|
4886
|
+
if (!hasCommands)
|
|
4887
|
+
return "unknown";
|
|
4888
|
+
return hasScopeFailure(scopes, expectedScope, diagnostics) ? "failed" : "passed";
|
|
4889
|
+
};
|
|
4890
|
+
var statusForValidationKind = (commands, kind) => {
|
|
4891
|
+
const matching = commands.filter((command) => command.kind === kind);
|
|
4892
|
+
if (matching.length === 0)
|
|
4893
|
+
return "unknown";
|
|
4894
|
+
return matching.some((command) => command.status === "failed") ? "failed" : "passed";
|
|
4895
|
+
};
|
|
4896
|
+
var createKnownBlockers = (commands, diagnostics) => {
|
|
4897
|
+
const commandBlockers = commands.filter((command) => command.status === "failed" && (command.failureScope === "workspace-config" || command.failureScope === "environment")).map((command) => ({
|
|
4898
|
+
code: `workflow-validation-${command.failureScope}`,
|
|
4899
|
+
message: `${command.failureScope === "environment" ? "Environment" : "Workspace configuration"} blocker: ${command.command}`,
|
|
4900
|
+
failureScope: command.failureScope ?? "unknown",
|
|
4901
|
+
command: command.command,
|
|
4902
|
+
kind: command.kind,
|
|
4903
|
+
count: 1
|
|
4904
|
+
}));
|
|
4905
|
+
const diagnosticBlockers = diagnostics.filter((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "workspace-config" || diagnostic.failureScope === "environment" || diagnostic.scope === "baseline")).map((diagnostic) => ({
|
|
4906
|
+
code: diagnostic.code,
|
|
4907
|
+
message: diagnostic.message,
|
|
4908
|
+
failureScope: diagnostic.failureScope ?? "workspace-config",
|
|
4909
|
+
command: diagnostic.command,
|
|
4910
|
+
kind: diagnostic.kind,
|
|
4911
|
+
count: 1
|
|
4912
|
+
}));
|
|
4913
|
+
const grouped = new Map;
|
|
4914
|
+
for (const blocker of [...commandBlockers, ...diagnosticBlockers]) {
|
|
4915
|
+
const key = `${blocker.failureScope}:${blocker.code}:${blocker.command ?? ""}:${blocker.message}`;
|
|
4916
|
+
const existing = grouped.get(key);
|
|
4917
|
+
if (existing)
|
|
4918
|
+
existing.count += blocker.count;
|
|
4919
|
+
else
|
|
4920
|
+
grouped.set(key, blocker);
|
|
4921
|
+
}
|
|
4922
|
+
return [...grouped.values()];
|
|
4923
|
+
};
|
|
4924
|
+
var createValidationStatuses = (commands, diagnostics) => {
|
|
4925
|
+
const scopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(diagnostics)];
|
|
4926
|
+
const sourceStatus = statusForScope(commands, diagnostics, scopes, "source-change");
|
|
4927
|
+
const workspaceStatus = hasScopeFailure(scopes, "workspace-config", diagnostics) || hasScopeFailure(scopes, "environment", diagnostics) ? "failed" : commands.length || diagnostics.length ? "passed" : "unknown";
|
|
4928
|
+
const overallStatus = hasScopeFailure(scopes, "source-change", diagnostics) ? "failed" : hasScopeFailure(scopes, "workspace-config", diagnostics) ? "blocked-by-workspace-config" : hasScopeFailure(scopes, "environment", diagnostics) ? "blocked-by-environment" : workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed";
|
|
4929
|
+
return {
|
|
4930
|
+
sourceStatus,
|
|
4931
|
+
workspaceStatus,
|
|
4932
|
+
overallStatus,
|
|
4933
|
+
summary: {
|
|
4934
|
+
sourceChange: sourceStatus,
|
|
4935
|
+
generatedSync: statusForValidationKind(commands, "sync"),
|
|
4936
|
+
workspaceConfig: statusForScope(commands, diagnostics, scopes, "workspace-config"),
|
|
4937
|
+
environment: statusForScope(commands, diagnostics, scopes, "environment")
|
|
4938
|
+
}
|
|
4939
|
+
};
|
|
4940
|
+
};
|
|
5105
4941
|
var createWorkflowValidationRunReport = async ({
|
|
5106
4942
|
runId = createWorkflowRunId("validation"),
|
|
5107
4943
|
workflow,
|
|
@@ -5128,15 +4964,20 @@ var createWorkflowValidationRunReport = async ({
|
|
|
5128
4964
|
failureScope: result.failureScope
|
|
5129
4965
|
}
|
|
5130
4966
|
] : []);
|
|
4967
|
+
const reportDiagnostics = [...diagnostics, ...commandDiagnostics];
|
|
4968
|
+
const scopedDiagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
|
|
4969
|
+
const statuses = createValidationStatuses(results, scopedDiagnostics);
|
|
5131
4970
|
return {
|
|
5132
4971
|
schemaVersion: 1,
|
|
5133
4972
|
runId,
|
|
5134
4973
|
workflow,
|
|
5135
4974
|
mode: "validate",
|
|
5136
4975
|
source,
|
|
5137
|
-
status:
|
|
4976
|
+
status: statuses.overallStatus === "passed" ? "passed" : "failed",
|
|
4977
|
+
...statuses,
|
|
4978
|
+
knownBlockers: createKnownBlockers(results, scopedDiagnostics),
|
|
5138
4979
|
commands: results,
|
|
5139
|
-
diagnostics:
|
|
4980
|
+
diagnostics: reportDiagnostics,
|
|
5140
4981
|
baselineDiagnostics,
|
|
5141
4982
|
workflowDiagnostics,
|
|
5142
4983
|
repairActions: uniqueBy(repairActions, (action) => action.command),
|
|
@@ -5180,25 +5021,512 @@ var createDryRunWorkflowApplyReport = (plan) => {
|
|
|
5180
5021
|
plan
|
|
5181
5022
|
});
|
|
5182
5023
|
};
|
|
5183
|
-
var
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5024
|
+
var createRepairReport = ({
|
|
5025
|
+
command,
|
|
5026
|
+
kind,
|
|
5027
|
+
target = null,
|
|
5028
|
+
diagnostics = [],
|
|
5029
|
+
repairActions = [],
|
|
5030
|
+
nextActions = [],
|
|
5031
|
+
commands = [],
|
|
5032
|
+
generatedFiles = [],
|
|
5033
|
+
syncedAt
|
|
5034
|
+
}) => ({
|
|
5035
|
+
schemaVersion: 1,
|
|
5036
|
+
command,
|
|
5037
|
+
kind,
|
|
5038
|
+
target,
|
|
5039
|
+
status: workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed",
|
|
5040
|
+
diagnostics,
|
|
5041
|
+
repairActions: uniqueBy(repairActions, (action) => action.command),
|
|
5042
|
+
nextActions: uniqueBy(nextActions, (action) => action.command),
|
|
5043
|
+
commands,
|
|
5044
|
+
generatedFiles,
|
|
5045
|
+
...syncedAt ? { syncedAt } : {}
|
|
5191
5046
|
});
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5047
|
+
// pkgs/@akanjs/devkit/workflow/executor.ts
|
|
5048
|
+
import { capitalize as capitalize2 } from "akanjs/common";
|
|
5049
|
+
|
|
5050
|
+
// pkgs/@akanjs/devkit/workflow/primitive.ts
|
|
5051
|
+
var createPrimitiveWriteReport = ({
|
|
5052
|
+
command,
|
|
5053
|
+
status,
|
|
5054
|
+
changedFiles = [],
|
|
5055
|
+
generatedFiles = [],
|
|
5056
|
+
validationCommands = [],
|
|
5057
|
+
diagnostics = [],
|
|
5058
|
+
nextActions = []
|
|
5059
|
+
}) => ({
|
|
5060
|
+
schemaVersion: 1,
|
|
5061
|
+
command,
|
|
5062
|
+
status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
|
|
5063
|
+
changedFiles,
|
|
5064
|
+
generatedFiles,
|
|
5065
|
+
validationCommands,
|
|
5066
|
+
diagnostics,
|
|
5067
|
+
nextActions
|
|
5068
|
+
});
|
|
5069
|
+
var renderPrimitiveWriteReport = (report) => [
|
|
5070
|
+
`# Primitive Write: ${report.command}`,
|
|
5071
|
+
"",
|
|
5072
|
+
`- Status: ${report.status}`,
|
|
5073
|
+
"",
|
|
5074
|
+
"## Changed Files",
|
|
5075
|
+
...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
5076
|
+
"",
|
|
5077
|
+
"## Generated Files",
|
|
5078
|
+
...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
5079
|
+
"",
|
|
5080
|
+
"## Validation Commands",
|
|
5081
|
+
...report.validationCommands.length ? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`) : ["- none"],
|
|
5082
|
+
"",
|
|
5083
|
+
"## Diagnostics",
|
|
5084
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
5085
|
+
"",
|
|
5086
|
+
"## Next Actions",
|
|
5087
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
5088
|
+
""
|
|
5089
|
+
].join(`
|
|
5090
|
+
`);
|
|
5091
|
+
var renderPrimitiveReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
|
|
5092
|
+
|
|
5093
|
+
// pkgs/@akanjs/devkit/workflow/source.ts
|
|
5094
|
+
var getSysRoot = (sys2) => `${sys2.type}s/${sys2.name}`;
|
|
5095
|
+
var sourceFile = (sys2, path10, action, reason) => ({
|
|
5096
|
+
path: `${getSysRoot(sys2)}/${path10}`,
|
|
5097
|
+
action,
|
|
5098
|
+
reason
|
|
5099
|
+
});
|
|
5100
|
+
var moduleComponentName = (moduleName) => `${moduleName.slice(0, 1).toUpperCase()}${moduleName.slice(1)}`;
|
|
5101
|
+
var moduleSourcePaths = (moduleName) => {
|
|
5102
|
+
const componentName = moduleComponentName(moduleName);
|
|
5103
|
+
return {
|
|
5104
|
+
abstract: `lib/${moduleName}/${moduleName}.abstract.md`,
|
|
5105
|
+
constant: `lib/${moduleName}/${moduleName}.constant.ts`,
|
|
5106
|
+
dictionary: `lib/${moduleName}/${moduleName}.dictionary.ts`,
|
|
5107
|
+
service: `lib/${moduleName}/${moduleName}.service.ts`,
|
|
5108
|
+
signal: `lib/${moduleName}/${moduleName}.signal.ts`,
|
|
5109
|
+
store: `lib/${moduleName}/${moduleName}.store.ts`,
|
|
5110
|
+
template: `lib/${moduleName}/${componentName}.Template.tsx`,
|
|
5111
|
+
unit: `lib/${moduleName}/${componentName}.Unit.tsx`,
|
|
5112
|
+
util: `lib/${moduleName}/${componentName}.Util.tsx`,
|
|
5113
|
+
view: `lib/${moduleName}/${componentName}.View.tsx`,
|
|
5114
|
+
zone: `lib/${moduleName}/${componentName}.Zone.tsx`
|
|
5115
|
+
};
|
|
5116
|
+
};
|
|
5117
|
+
var generatedFilesForSync = (sys2, reason = "Generated files may change after sync.") => generatedFilePathsForTarget(getSysRoot(sys2), reason);
|
|
5118
|
+
var validationCommandsForTarget = (target) => [
|
|
5119
|
+
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files from source conventions." },
|
|
5120
|
+
{ command: `akan lint ${target}`, reason: "Validate formatting, imports, and static lint rules." }
|
|
5121
|
+
];
|
|
5122
|
+
var nextActionsForTarget = (target) => [
|
|
5123
|
+
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files after source changes." },
|
|
5124
|
+
{ command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." }
|
|
5125
|
+
];
|
|
5126
|
+
var createPassedPrimitiveReport = ({
|
|
5127
|
+
command,
|
|
5128
|
+
changedFiles,
|
|
5129
|
+
generatedFiles,
|
|
5130
|
+
target,
|
|
5131
|
+
nextActions
|
|
5132
|
+
}) => createPrimitiveWriteReport({
|
|
5133
|
+
command,
|
|
5134
|
+
changedFiles,
|
|
5135
|
+
generatedFiles: generatedFiles ?? [],
|
|
5136
|
+
validationCommands: validationCommandsForTarget(target),
|
|
5137
|
+
diagnostics: [],
|
|
5138
|
+
nextActions: nextActions ?? nextActionsForTarget(target)
|
|
5139
|
+
});
|
|
5140
|
+
var scalarChangedFiles = (sys2, scalarName, files) => Object.values(files).map((file) => sourceFile(sys2, `lib/__scalar/${scalarName}/${file.filename}`, "create", "Scalar source file was created."));
|
|
5141
|
+
var titleize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
5142
|
+
var lowerlize = (value) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
|
|
5143
|
+
var koLabels = {
|
|
5144
|
+
amount: "\uAE08\uC561",
|
|
5145
|
+
budget: "\uC608\uC0B0",
|
|
5146
|
+
category: "\uCE74\uD14C\uACE0\uB9AC",
|
|
5147
|
+
content: "\uB0B4\uC6A9",
|
|
5148
|
+
count: "\uAC1C\uC218",
|
|
5149
|
+
createdAt: "\uC0DD\uC131\uC77C",
|
|
5150
|
+
date: "\uB0A0\uC9DC",
|
|
5151
|
+
description: "\uC124\uBA85",
|
|
5152
|
+
due: "\uB9C8\uAC10\uC77C",
|
|
5153
|
+
dueAt: "\uB9C8\uAC10\uC77C",
|
|
5154
|
+
email: "\uC774\uBA54\uC77C",
|
|
5155
|
+
enabled: "\uD65C\uC131\uD654",
|
|
5156
|
+
endAt: "\uC885\uB8CC\uC77C",
|
|
5157
|
+
id: "ID",
|
|
5158
|
+
name: "\uC774\uB984",
|
|
5159
|
+
owner: "\uB2F4\uB2F9\uC790",
|
|
5160
|
+
priority: "\uC6B0\uC120\uC21C\uC704",
|
|
5161
|
+
project: "\uD504\uB85C\uC81D\uD2B8",
|
|
5162
|
+
rating: "\uD3C9\uC810",
|
|
5163
|
+
startAt: "\uC2DC\uC791\uC77C",
|
|
5164
|
+
status: "\uC0C1\uD0DC",
|
|
5165
|
+
title: "\uC81C\uBAA9",
|
|
5166
|
+
updatedAt: "\uC218\uC815\uC77C"
|
|
5167
|
+
};
|
|
5168
|
+
var splitFieldWords = (fieldName) => fieldName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").split(/\s+/).map((word) => word.trim()).filter(Boolean);
|
|
5169
|
+
var koLabelForField = (fieldName) => {
|
|
5170
|
+
if (koLabels[fieldName])
|
|
5171
|
+
return koLabels[fieldName];
|
|
5172
|
+
const words = splitFieldWords(fieldName);
|
|
5173
|
+
const translated = words.map((word) => koLabels[word] ?? koLabels[lowerlize(word)] ?? null);
|
|
5174
|
+
return translated.every(Boolean) ? translated.join(" ") : null;
|
|
5175
|
+
};
|
|
5176
|
+
var bilingualLabelForField = (fieldName) => {
|
|
5177
|
+
const en = titleize(fieldName);
|
|
5178
|
+
return { en, ko: koLabelForField(fieldName) ?? en };
|
|
5179
|
+
};
|
|
5180
|
+
var bilingualDescriptionForField = (fieldName) => {
|
|
5181
|
+
const label = bilingualLabelForField(fieldName);
|
|
5182
|
+
return {
|
|
5183
|
+
en: `Enter ${label.en.toLowerCase()}.`,
|
|
5184
|
+
ko: `${label.ko} \uAC12\uC744 \uC785\uB825\uD569\uB2C8\uB2E4.`
|
|
5185
|
+
};
|
|
5186
|
+
};
|
|
5187
|
+
var normalizeFieldType = (typeName) => {
|
|
5188
|
+
const normalizedTypes = {
|
|
5189
|
+
string: "String",
|
|
5190
|
+
boolean: "Boolean",
|
|
5191
|
+
date: "Date",
|
|
5192
|
+
int: "Int",
|
|
5193
|
+
integer: "Int",
|
|
5194
|
+
float: "Float",
|
|
5195
|
+
double: "Float",
|
|
5196
|
+
decimal: "Float"
|
|
5197
|
+
};
|
|
5198
|
+
return normalizedTypes[typeName.toLowerCase()] ?? typeName;
|
|
5199
|
+
};
|
|
5200
|
+
var ensureBaseTypeImport = (content, typeName) => {
|
|
5201
|
+
if (typeName !== "Int" && typeName !== "Float")
|
|
5202
|
+
return content;
|
|
5203
|
+
if (new RegExp(`import \\{[^}]*\\b${typeName}\\b[^}]*\\} from "akanjs/base";`).test(content))
|
|
5204
|
+
return content;
|
|
5205
|
+
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
5206
|
+
if (baseImport) {
|
|
5207
|
+
const names = baseImport[1].split(",").map((name) => name.trim()).filter(Boolean);
|
|
5208
|
+
return content.replace(baseImport[0], `import { ${[...names, typeName].sort().join(", ")} } from "akanjs/base";`);
|
|
5209
|
+
}
|
|
5210
|
+
return `import { ${typeName} } from "akanjs/base";
|
|
5211
|
+
${content}`;
|
|
5212
|
+
};
|
|
5213
|
+
var numericDefault = (typeName, rawDefault) => {
|
|
5214
|
+
if (typeof rawDefault === "number") {
|
|
5215
|
+
if (!Number.isFinite(rawDefault))
|
|
5216
|
+
return null;
|
|
5217
|
+
if (typeName === "Int" && !Number.isInteger(rawDefault))
|
|
5218
|
+
return null;
|
|
5219
|
+
return String(rawDefault);
|
|
5220
|
+
}
|
|
5221
|
+
if (typeof rawDefault !== "string")
|
|
5222
|
+
return null;
|
|
5223
|
+
const trimmed = rawDefault.trim();
|
|
5224
|
+
if (!trimmed || !Number.isFinite(Number(trimmed)))
|
|
5225
|
+
return null;
|
|
5226
|
+
if (typeName === "Int" && !/^-?\d+$/.test(trimmed))
|
|
5227
|
+
return null;
|
|
5228
|
+
return trimmed;
|
|
5229
|
+
};
|
|
5230
|
+
var booleanDefault = (rawDefault) => {
|
|
5231
|
+
if (typeof rawDefault === "boolean")
|
|
5232
|
+
return String(rawDefault);
|
|
5233
|
+
if (typeof rawDefault !== "string")
|
|
5234
|
+
return null;
|
|
5235
|
+
const lowered = rawDefault.trim().toLowerCase();
|
|
5236
|
+
return lowered === "true" || lowered === "false" ? lowered : null;
|
|
5237
|
+
};
|
|
5238
|
+
var dateDefault = (rawDefault) => {
|
|
5239
|
+
if (typeof rawDefault === "number" && Number.isFinite(rawDefault))
|
|
5240
|
+
return `new Date(${rawDefault})`;
|
|
5241
|
+
if (typeof rawDefault !== "string")
|
|
5242
|
+
return null;
|
|
5243
|
+
const trimmed = rawDefault.trim();
|
|
5244
|
+
if (!trimmed)
|
|
5245
|
+
return null;
|
|
5246
|
+
if (trimmed === "now")
|
|
5247
|
+
return "new Date()";
|
|
5248
|
+
if (!Number.isNaN(Date.parse(trimmed)))
|
|
5249
|
+
return `new Date(${JSON.stringify(trimmed)})`;
|
|
5250
|
+
return null;
|
|
5251
|
+
};
|
|
5252
|
+
var coerceFieldDefault = (typeName, defaultValue, options = {}) => {
|
|
5253
|
+
const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
|
|
5254
|
+
if (defaultValue === undefined || defaultValue === null || defaultValue === "") {
|
|
5255
|
+
return { expression: null, normalized: false, normalizedType };
|
|
5256
|
+
}
|
|
5257
|
+
if (normalizedType === "Int" || normalizedType === "Float") {
|
|
5258
|
+
const expression = numericDefault(normalizedType, defaultValue);
|
|
5259
|
+
if (expression !== null)
|
|
5260
|
+
return { expression, normalized: typeof defaultValue === "string", normalizedType };
|
|
5261
|
+
return {
|
|
5262
|
+
expression: null,
|
|
5263
|
+
normalized: false,
|
|
5264
|
+
normalizedType,
|
|
5265
|
+
diagnostic: {
|
|
5266
|
+
severity: "error",
|
|
5267
|
+
code: "primitive-default-value-invalid",
|
|
5268
|
+
input: "default",
|
|
5269
|
+
failureScope: "source-change",
|
|
5270
|
+
message: `Default value for ${normalizedType} must be a numeric literal. Received: ${JSON.stringify(defaultValue)}.`
|
|
5271
|
+
}
|
|
5272
|
+
};
|
|
5273
|
+
}
|
|
5274
|
+
if (normalizedType === "Boolean") {
|
|
5275
|
+
const expression = booleanDefault(defaultValue);
|
|
5276
|
+
if (expression !== null)
|
|
5277
|
+
return { expression, normalized: typeof defaultValue === "string", normalizedType };
|
|
5278
|
+
return {
|
|
5279
|
+
expression: null,
|
|
5280
|
+
normalized: false,
|
|
5281
|
+
normalizedType,
|
|
5282
|
+
diagnostic: {
|
|
5283
|
+
severity: "error",
|
|
5284
|
+
code: "primitive-default-value-invalid",
|
|
5285
|
+
input: "default",
|
|
5286
|
+
failureScope: "source-change",
|
|
5287
|
+
message: `Default value for Boolean must be true or false. Received: ${JSON.stringify(defaultValue)}.`
|
|
5288
|
+
}
|
|
5289
|
+
};
|
|
5290
|
+
}
|
|
5291
|
+
if (normalizedType === "Date") {
|
|
5292
|
+
const expression = dateDefault(defaultValue);
|
|
5293
|
+
if (expression !== null)
|
|
5294
|
+
return { expression, normalized: true, normalizedType };
|
|
5295
|
+
return {
|
|
5296
|
+
expression: null,
|
|
5297
|
+
normalized: false,
|
|
5298
|
+
normalizedType,
|
|
5299
|
+
diagnostic: {
|
|
5300
|
+
severity: "error",
|
|
5301
|
+
code: "primitive-default-value-invalid",
|
|
5302
|
+
input: "default",
|
|
5303
|
+
failureScope: "source-change",
|
|
5304
|
+
message: `Default value for Date must be "now", a timestamp, or a parseable date string. Received: ${JSON.stringify(defaultValue)}.`
|
|
5305
|
+
}
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5308
|
+
if (normalizedType === "enum") {
|
|
5309
|
+
if (typeof defaultValue === "string" && options.enumValues?.includes(defaultValue)) {
|
|
5310
|
+
return { expression: JSON.stringify(defaultValue), normalized: false, normalizedType };
|
|
5311
|
+
}
|
|
5312
|
+
return {
|
|
5313
|
+
expression: null,
|
|
5314
|
+
normalized: false,
|
|
5315
|
+
normalizedType,
|
|
5316
|
+
diagnostic: {
|
|
5317
|
+
severity: "error",
|
|
5318
|
+
code: "primitive-default-value-invalid",
|
|
5319
|
+
input: "default",
|
|
5320
|
+
failureScope: "source-change",
|
|
5321
|
+
message: `Default value for enum must be one of: ${(options.enumValues ?? []).join(", ")}. Received: ${JSON.stringify(defaultValue)}.`
|
|
5322
|
+
}
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
return {
|
|
5326
|
+
expression: JSON.stringify(String(defaultValue)),
|
|
5327
|
+
normalized: typeof defaultValue !== "string",
|
|
5328
|
+
normalizedType
|
|
5329
|
+
};
|
|
5330
|
+
};
|
|
5331
|
+
var fieldExpression = (typeName, defaultValue, options = {}) => {
|
|
5332
|
+
const typeExpression = normalizeFieldType(typeName);
|
|
5333
|
+
const defaultExpression = coerceFieldDefault(options.enumValues ? "enum" : typeExpression, defaultValue, options).expression;
|
|
5334
|
+
const defaultOption = defaultExpression ? `, { default: ${defaultExpression} }` : "";
|
|
5335
|
+
return `field(${typeExpression}${defaultOption})`;
|
|
5336
|
+
};
|
|
5337
|
+
var insertIntoObject = (content, className, line) => {
|
|
5338
|
+
const classIndex = content.indexOf(`export class ${className} extends via`);
|
|
5339
|
+
if (classIndex < 0)
|
|
5340
|
+
return null;
|
|
5341
|
+
const objectEndIndex = content.indexOf("}))", classIndex);
|
|
5342
|
+
if (objectEndIndex < 0)
|
|
5343
|
+
return null;
|
|
5344
|
+
const prefix = content.slice(0, objectEndIndex);
|
|
5345
|
+
const suffix = content.slice(objectEndIndex);
|
|
5346
|
+
const insertion = prefix.endsWith(`
|
|
5347
|
+
`) ? ` ${line}
|
|
5348
|
+
` : `
|
|
5349
|
+
${line}
|
|
5350
|
+
`;
|
|
5351
|
+
return `${prefix}${insertion}${suffix}`;
|
|
5352
|
+
};
|
|
5353
|
+
var insertLightProjectionField = (content, moduleClassName, fieldName) => {
|
|
5354
|
+
const classIndex = content.indexOf(`export class Light${moduleClassName} extends via`);
|
|
5355
|
+
if (classIndex < 0)
|
|
5356
|
+
return null;
|
|
5357
|
+
const arrayMatch = /\[([\s\S]*?)\]\s+as const/.exec(content.slice(classIndex));
|
|
5358
|
+
if (!arrayMatch || arrayMatch.index === undefined)
|
|
5359
|
+
return null;
|
|
5360
|
+
const arrayStart = classIndex + arrayMatch.index;
|
|
5361
|
+
const arrayEnd = arrayStart + arrayMatch[0].length;
|
|
5362
|
+
const fields = [...arrayMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]).filter(Boolean);
|
|
5363
|
+
if (fields.includes(fieldName))
|
|
5364
|
+
return content;
|
|
5365
|
+
const nextFields = [...fields, fieldName];
|
|
5366
|
+
const nextArray = nextFields.length === 0 ? "[] as const" : `[
|
|
5367
|
+
${nextFields.map((field) => ` ${JSON.stringify(field)},`).join(`
|
|
5368
|
+
`)}
|
|
5369
|
+
] as const`;
|
|
5370
|
+
return `${content.slice(0, arrayStart)}${nextArray}${content.slice(arrayEnd)}`;
|
|
5371
|
+
};
|
|
5372
|
+
var insertTemplateField = ({
|
|
5373
|
+
content,
|
|
5374
|
+
moduleName,
|
|
5375
|
+
moduleClassName,
|
|
5376
|
+
fieldName,
|
|
5377
|
+
component
|
|
5378
|
+
}) => {
|
|
5379
|
+
if (content.includes(`l("${moduleName}.${fieldName}")`) || content.includes(`.${fieldName}`))
|
|
5380
|
+
return content;
|
|
5381
|
+
const layoutEndIndex = content.indexOf(" </Layout.Template>");
|
|
5382
|
+
if (layoutEndIndex < 0)
|
|
5383
|
+
return null;
|
|
5384
|
+
const formName = `${moduleName}Form`;
|
|
5385
|
+
if (!content.includes(`const ${formName} = st.use.${moduleName}Form();`))
|
|
5386
|
+
return null;
|
|
5387
|
+
const fieldSetter = `st.do.set${moduleComponentName(fieldName)}On${moduleClassName}`;
|
|
5388
|
+
const fieldBlock = ` <${component}
|
|
5389
|
+
label={l("${moduleName}.${fieldName}")}
|
|
5390
|
+
desc={l("${moduleName}.${fieldName}.desc")}
|
|
5391
|
+
value={${formName}.${fieldName}}
|
|
5392
|
+
onChange={${fieldSetter}}
|
|
5393
|
+
/>
|
|
5394
|
+
`;
|
|
5395
|
+
return `${content.slice(0, layoutEndIndex)}${fieldBlock}${content.slice(layoutEndIndex)}`;
|
|
5396
|
+
};
|
|
5397
|
+
var ensureEnumImport = (content) => {
|
|
5398
|
+
if (content.includes("enumOf"))
|
|
5399
|
+
return content;
|
|
5400
|
+
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
5401
|
+
if (baseImport) {
|
|
5402
|
+
const names = baseImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
5403
|
+
return content.replace(baseImport[0], `import { ${[...names, "enumOf"].sort().join(", ")} } from "akanjs/base";`);
|
|
5404
|
+
}
|
|
5405
|
+
return `import { enumOf } from "akanjs/base";
|
|
5406
|
+
${content}`;
|
|
5407
|
+
};
|
|
5408
|
+
var insertEnumClass = (content, enumClassName, enumName, values) => {
|
|
5409
|
+
if (content.includes(`export class ${enumClassName} extends enumOf`))
|
|
5410
|
+
return content;
|
|
5411
|
+
const enumClass = `export class ${enumClassName} extends enumOf("${enumName}", [
|
|
5412
|
+
${values.map((value) => ` ${JSON.stringify(value)},`).join(`
|
|
5413
|
+
`)}
|
|
5414
|
+
] as const) {}
|
|
5415
|
+
|
|
5416
|
+
`;
|
|
5417
|
+
const firstClassIndex = content.indexOf("export class ");
|
|
5418
|
+
if (firstClassIndex < 0)
|
|
5419
|
+
return `${content}
|
|
5420
|
+
${enumClass}`;
|
|
5421
|
+
return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
|
|
5422
|
+
};
|
|
5423
|
+
var insertDictionaryModelField = (content, moduleClassName, fieldName) => {
|
|
5424
|
+
if (new RegExp(`\\b${fieldName}\\s*:`).test(content))
|
|
5425
|
+
return content;
|
|
5426
|
+
const label = bilingualLabelForField(fieldName);
|
|
5427
|
+
const desc = bilingualDescriptionForField(fieldName);
|
|
5428
|
+
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
|
|
5429
|
+
if (modelIndex < 0)
|
|
5430
|
+
return null;
|
|
5431
|
+
const objectEndIndex = content.indexOf(" }))", modelIndex);
|
|
5432
|
+
if (objectEndIndex < 0)
|
|
5433
|
+
return null;
|
|
5434
|
+
return `${content.slice(0, objectEndIndex)} ${fieldName}: t([${JSON.stringify(label.en)}, ${JSON.stringify(label.ko)}]).desc([${JSON.stringify(desc.en)}, ${JSON.stringify(desc.ko)}]),
|
|
5435
|
+
${content.slice(objectEndIndex)}`;
|
|
5436
|
+
};
|
|
5437
|
+
var ensureConstantTypeImport = (content, constantPath, typeName) => {
|
|
5438
|
+
if (new RegExp(`import type \\{[^}]*\\b${typeName}\\b[^}]*\\} from "${constantPath}";`).test(content))
|
|
5439
|
+
return content;
|
|
5440
|
+
const importPattern = new RegExp(`import type \\{ ([^}]+) \\} from "${constantPath}";`);
|
|
5441
|
+
const existingImport = content.match(importPattern);
|
|
5442
|
+
if (existingImport !== null) {
|
|
5443
|
+
const names = existingImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
5444
|
+
return content.replace(existingImport[0], `import type { ${[...names, typeName].sort().join(", ")} } from "${constantPath}";`);
|
|
5445
|
+
}
|
|
5446
|
+
return `import type { ${typeName} } from "${constantPath}";
|
|
5447
|
+
${content}`;
|
|
5448
|
+
};
|
|
5449
|
+
var insertDictionaryEnum = (content, enumClassName, enumName, values) => {
|
|
5450
|
+
if (content.includes(`.enum<${enumClassName}>("${enumName}"`))
|
|
5451
|
+
return content;
|
|
5452
|
+
const enumBlock = ` .enum<${enumClassName}>("${enumName}", (t) => ({
|
|
5453
|
+
${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${JSON.stringify(titleize(value))}]),`).join(`
|
|
5454
|
+
`)}
|
|
5455
|
+
}))
|
|
5456
|
+
`;
|
|
5457
|
+
const chainEndIndex = content.lastIndexOf(";");
|
|
5458
|
+
const insertBeforeIndex = [content.indexOf(".error("), content.indexOf(".translate("), chainEndIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
5459
|
+
if (insertBeforeIndex === undefined)
|
|
5460
|
+
return null;
|
|
5461
|
+
return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
|
|
5462
|
+
};
|
|
5463
|
+
var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
5464
|
+
|
|
5465
|
+
// pkgs/@akanjs/devkit/workflow/uiPolicy.ts
|
|
5466
|
+
var addFieldUiPolicyForType = (typeName) => {
|
|
5467
|
+
const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
|
|
5468
|
+
if (normalizedType === "Int" || normalizedType === "Float") {
|
|
5469
|
+
return {
|
|
5470
|
+
normalizedType,
|
|
5471
|
+
component: "Field.Number",
|
|
5472
|
+
confidence: "high",
|
|
5473
|
+
autoTemplateSupported: true
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
if (normalizedType === "Date") {
|
|
5477
|
+
return {
|
|
5478
|
+
normalizedType,
|
|
5479
|
+
component: "Field.Date",
|
|
5480
|
+
confidence: "high",
|
|
5481
|
+
autoTemplateSupported: true
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
5484
|
+
if (normalizedType === "Boolean" || normalizedType === "enum") {
|
|
5485
|
+
return {
|
|
5486
|
+
normalizedType,
|
|
5487
|
+
component: "Field.ToggleSelect",
|
|
5488
|
+
confidence: "medium",
|
|
5489
|
+
autoTemplateSupported: false
|
|
5490
|
+
};
|
|
5491
|
+
}
|
|
5492
|
+
if (normalizedType === "String") {
|
|
5493
|
+
return {
|
|
5494
|
+
normalizedType,
|
|
5495
|
+
component: "Field.Text",
|
|
5496
|
+
confidence: "high",
|
|
5497
|
+
autoTemplateSupported: true
|
|
5498
|
+
};
|
|
5499
|
+
}
|
|
5500
|
+
return {
|
|
5501
|
+
normalizedType,
|
|
5502
|
+
component: "Field.Text",
|
|
5503
|
+
confidence: "low",
|
|
5504
|
+
autoTemplateSupported: false
|
|
5505
|
+
};
|
|
5506
|
+
};
|
|
5507
|
+
|
|
5508
|
+
// pkgs/@akanjs/devkit/workflow/executor.ts
|
|
5509
|
+
var workflowStepKey = (workflow, stepId) => `${workflow}:${stepId}`;
|
|
5510
|
+
var primitiveReportToWorkflowStepResult = (report) => ({
|
|
5511
|
+
changedFiles: report.changedFiles,
|
|
5512
|
+
generatedFiles: report.generatedFiles,
|
|
5513
|
+
commands: report.validationCommands,
|
|
5514
|
+
diagnostics: report.diagnostics,
|
|
5515
|
+
recommendations: [],
|
|
5516
|
+
nextActions: report.nextActions
|
|
5517
|
+
});
|
|
5518
|
+
var workflowStringInput = (value) => typeof value === "string" ? value : null;
|
|
5519
|
+
var workflowStringListInput = (value) => Array.isArray(value) ? value.join(",") : null;
|
|
5520
|
+
var workflowStringArrayInput = (value) => Array.isArray(value) ? value : null;
|
|
5521
|
+
var workflowBooleanInput = (value) => typeof value === "boolean" ? value : null;
|
|
5522
|
+
var resolveWorkflowSys = async (workspace, target) => {
|
|
5523
|
+
if (!target)
|
|
5524
|
+
return null;
|
|
5525
|
+
const [apps, libs] = await workspace.getSyss();
|
|
5526
|
+
if (apps.includes(target))
|
|
5527
|
+
return AppExecutor.from(workspace, target);
|
|
5528
|
+
if (libs.includes(target))
|
|
5529
|
+
return LibExecutor.from(workspace, target);
|
|
5202
5530
|
return null;
|
|
5203
5531
|
};
|
|
5204
5532
|
var targetMissing = (input2 = "app") => ({
|
|
@@ -5220,19 +5548,24 @@ var unsupportedInput = (input2, message) => ({
|
|
|
5220
5548
|
message
|
|
5221
5549
|
});
|
|
5222
5550
|
var addFieldUiSurfaceInspection = (plan) => {
|
|
5551
|
+
const app = workflowStringInput(plan.inputs.app);
|
|
5223
5552
|
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
5224
5553
|
const field = workflowStringInput(plan.inputs.field) ?? "<field>";
|
|
5554
|
+
const typeName = workflowStringInput(plan.inputs.type);
|
|
5555
|
+
const policy = addFieldUiPolicyForType(typeName ?? "String");
|
|
5556
|
+
const surfaces = workflowStringArrayInput(plan.inputs.surfaces);
|
|
5557
|
+
const templateRequested = surfaces?.includes("template") ?? false;
|
|
5225
5558
|
const moduleClassName = capitalize2(module);
|
|
5226
|
-
const target =
|
|
5559
|
+
const target = `${app ? `apps/${app}` : "*"}/${moduleSourcePaths(module).template}`;
|
|
5227
5560
|
return {
|
|
5228
5561
|
recommendations: [
|
|
5229
5562
|
{
|
|
5230
5563
|
code: "add-field-ui-surface-review",
|
|
5231
5564
|
kind: "manual-action",
|
|
5232
5565
|
target,
|
|
5233
|
-
action: `
|
|
5566
|
+
action: templateRequested ? `Template was requested for ${field}. If no Template file changed, auto-edit was skipped because the file was missing, the generated ${module}Form/Layout.Template pattern was not found, or ${policy.component} needs option binding. Candidate position: inside Layout.Template near the existing Field components.` : `Template was not selected, so UI files are intentionally left unchanged. Candidate positions if you expose it later: Layout.Template field list for editing, Light${moduleClassName} projection for list/card data, and Unit/View card sections for display.`,
|
|
5234
5567
|
confidence: "medium",
|
|
5235
|
-
message: `Review
|
|
5568
|
+
message: `Review UI surfaces for ${module}.${field}; recommended component is ${policy.component}, with manual reasons and candidate positions in action.`
|
|
5236
5569
|
}
|
|
5237
5570
|
],
|
|
5238
5571
|
nextActions: [
|
|
@@ -5300,7 +5633,9 @@ var createWorkflowStepRegistry = ({
|
|
|
5300
5633
|
module: workflowStringInput(plan.inputs.module),
|
|
5301
5634
|
field: workflowStringInput(plan.inputs.field),
|
|
5302
5635
|
values: workflowStringListInput(plan.inputs.values),
|
|
5303
|
-
defaultValue: workflowStringInput(plan.inputs.default)
|
|
5636
|
+
defaultValue: workflowStringInput(plan.inputs.default),
|
|
5637
|
+
surfaces: workflowStringArrayInput(plan.inputs.surfaces),
|
|
5638
|
+
includeInLight: workflowBooleanInput(plan.inputs.includeInLight)
|
|
5304
5639
|
}));
|
|
5305
5640
|
}
|
|
5306
5641
|
return primitiveReportToWorkflowStepResult(await addField({
|
|
@@ -5308,7 +5643,9 @@ var createWorkflowStepRegistry = ({
|
|
|
5308
5643
|
module: workflowStringInput(plan.inputs.module),
|
|
5309
5644
|
field: workflowStringInput(plan.inputs.field),
|
|
5310
5645
|
type: workflowStringInput(plan.inputs.type),
|
|
5311
|
-
defaultValue: workflowStringInput(plan.inputs.default)
|
|
5646
|
+
defaultValue: workflowStringInput(plan.inputs.default),
|
|
5647
|
+
surfaces: workflowStringArrayInput(plan.inputs.surfaces),
|
|
5648
|
+
includeInLight: workflowBooleanInput(plan.inputs.includeInLight)
|
|
5312
5649
|
}));
|
|
5313
5650
|
},
|
|
5314
5651
|
[workflowStepKey("add-field", "update-dictionary")]: inspect,
|
|
@@ -5324,209 +5661,548 @@ var createWorkflowStepRegistry = ({
|
|
|
5324
5661
|
[workflowStepKey("add-enum-field", "update-option")]: inspect
|
|
5325
5662
|
};
|
|
5326
5663
|
};
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5664
|
+
class WorkflowExecutor {
|
|
5665
|
+
registry;
|
|
5666
|
+
constructor(registry) {
|
|
5667
|
+
this.registry = registry;
|
|
5668
|
+
}
|
|
5669
|
+
async apply(plan) {
|
|
5670
|
+
const changedFiles = [];
|
|
5671
|
+
const generatedFiles = [];
|
|
5672
|
+
const recommendedValidationCommands = [];
|
|
5673
|
+
const diagnostics = [...plan.diagnostics];
|
|
5674
|
+
const recommendations = [...plan.recommendations];
|
|
5675
|
+
const nextActions = [];
|
|
5676
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
5677
|
+
return createWorkflowApplyReport({
|
|
5678
|
+
workflow: plan.workflow,
|
|
5679
|
+
mode: "apply",
|
|
5680
|
+
changedFiles,
|
|
5681
|
+
generatedFiles,
|
|
5682
|
+
recommendedValidationCommands,
|
|
5683
|
+
diagnostics,
|
|
5684
|
+
recommendations,
|
|
5685
|
+
nextActions,
|
|
5686
|
+
plan
|
|
5687
|
+
});
|
|
5688
|
+
}
|
|
5689
|
+
recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
|
|
5690
|
+
nextActions.push(...workflowCommandsForPlan(plan));
|
|
5691
|
+
for (const step of plan.steps) {
|
|
5692
|
+
const runner = this.registry[workflowStepKey(plan.workflow, step.id)] ?? this.registry[step.tool] ?? this.registry[step.id];
|
|
5693
|
+
if (!runner) {
|
|
5694
|
+
diagnostics.push({
|
|
5695
|
+
severity: "error",
|
|
5696
|
+
code: "workflow-step-unsupported",
|
|
5697
|
+
message: `Workflow ${plan.workflow} step "${step.id}" is not supported by workflow apply yet.`
|
|
5698
|
+
});
|
|
5699
|
+
nextActions.push({
|
|
5700
|
+
command: `akan workflow explain ${plan.workflow}`,
|
|
5701
|
+
reason: "Review the unsupported workflow step before retrying apply."
|
|
5702
|
+
});
|
|
5703
|
+
break;
|
|
5704
|
+
}
|
|
5705
|
+
const result = await runner(step, plan);
|
|
5706
|
+
if (!result)
|
|
5707
|
+
continue;
|
|
5708
|
+
changedFiles.push(...result.changedFiles ?? []);
|
|
5709
|
+
generatedFiles.push(...result.generatedFiles ?? []);
|
|
5710
|
+
recommendedValidationCommands.push(...result.commands ?? []);
|
|
5711
|
+
diagnostics.push(...result.diagnostics ?? []);
|
|
5712
|
+
recommendations.push(...result.recommendations ?? []);
|
|
5713
|
+
nextActions.push(...result.nextActions ?? []);
|
|
5714
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
|
|
5715
|
+
break;
|
|
5716
|
+
}
|
|
5717
|
+
return createWorkflowApplyReport({
|
|
5718
|
+
workflow: plan.workflow,
|
|
5719
|
+
mode: "apply",
|
|
5720
|
+
changedFiles,
|
|
5721
|
+
generatedFiles,
|
|
5722
|
+
recommendedValidationCommands,
|
|
5723
|
+
diagnostics,
|
|
5724
|
+
recommendations,
|
|
5725
|
+
nextActions,
|
|
5726
|
+
plan
|
|
5727
|
+
});
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
// pkgs/@akanjs/devkit/workflow/plan.ts
|
|
5731
|
+
import { capitalize as capitalize3 } from "akanjs/common";
|
|
5732
|
+
var surfaceModes = new Set(["infer", "include", "skip"]);
|
|
5733
|
+
var parseStringList = (value) => {
|
|
5734
|
+
if (Array.isArray(value)) {
|
|
5735
|
+
const values = value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
5736
|
+
return values.length === value.length ? values : null;
|
|
5737
|
+
}
|
|
5738
|
+
if (typeof value !== "string")
|
|
5739
|
+
return null;
|
|
5740
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
5741
|
+
};
|
|
5742
|
+
var normalizeInputValue = (name, spec, value) => {
|
|
5743
|
+
if (spec.type === "string")
|
|
5744
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
5745
|
+
if (spec.type === "string-list") {
|
|
5746
|
+
const values = parseStringList(value);
|
|
5747
|
+
return values && values.length > 0 ? values : null;
|
|
5748
|
+
}
|
|
5749
|
+
if (spec.type === "boolean") {
|
|
5750
|
+
if (typeof value === "boolean")
|
|
5751
|
+
return value;
|
|
5752
|
+
if (typeof value !== "string")
|
|
5753
|
+
return null;
|
|
5754
|
+
const lowered = value.trim().toLowerCase();
|
|
5755
|
+
if (lowered === "true")
|
|
5756
|
+
return true;
|
|
5757
|
+
if (lowered === "false")
|
|
5758
|
+
return false;
|
|
5759
|
+
return null;
|
|
5760
|
+
}
|
|
5761
|
+
if (typeof value === "string" && surfaceModes.has(value))
|
|
5762
|
+
return value;
|
|
5763
|
+
throw new Error(`Unsupported workflow input value for ${name}`);
|
|
5764
|
+
};
|
|
5765
|
+
var listWorkflowSpecs = (specs) => [...specs].sort((a, b) => a.name.localeCompare(b.name));
|
|
5766
|
+
var getWorkflowSpec = (specs, name) => specs.find((spec) => spec.name === name) ?? null;
|
|
5767
|
+
var compactWorkflowInputs = (inputs) => Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
|
|
5768
|
+
var addFieldTargetRoot = (app) => app ? `apps/${app}` : "*";
|
|
5769
|
+
var addFieldPaths = (inputs) => {
|
|
5770
|
+
const app = typeof inputs.app === "string" ? inputs.app : null;
|
|
5771
|
+
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
5772
|
+
const paths = moduleSourcePaths(module);
|
|
5773
|
+
const root = addFieldTargetRoot(app);
|
|
5774
|
+
return {
|
|
5775
|
+
constant: `${root}/${paths.constant}`,
|
|
5776
|
+
dictionary: `${root}/${paths.dictionary}`,
|
|
5777
|
+
template: `${root}/${paths.template}`,
|
|
5778
|
+
unit: `${root}/${paths.unit}`,
|
|
5779
|
+
view: `${root}/${paths.view}`
|
|
5780
|
+
};
|
|
5781
|
+
};
|
|
5782
|
+
var selectedAddFieldSurfaces = (inputs) => Array.isArray(inputs.surfaces) ? new Set(inputs.surfaces) : null;
|
|
5783
|
+
var addFieldDefaultCoercion = (inputs) => {
|
|
5784
|
+
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
5785
|
+
const defaultValue = typeof inputs.default === "string" ? inputs.default : null;
|
|
5786
|
+
if (!typeName || defaultValue === null)
|
|
5787
|
+
return null;
|
|
5788
|
+
if (typeName.toLowerCase() === "number" || typeName.toLowerCase() === "numeric")
|
|
5789
|
+
return null;
|
|
5790
|
+
const enumValues = Array.isArray(inputs.values) ? inputs.values : null;
|
|
5791
|
+
return coerceFieldDefault(typeName.toLowerCase() === "enum" ? "enum" : typeName, defaultValue, { enumValues });
|
|
5792
|
+
};
|
|
5793
|
+
var createAddFieldDefaultRecommendations = (inputs) => {
|
|
5794
|
+
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
5795
|
+
const coercion = addFieldDefaultCoercion(inputs);
|
|
5796
|
+
if (!coercion?.normalized || !coercion.expression)
|
|
5797
|
+
return [];
|
|
5798
|
+
return [
|
|
5799
|
+
{
|
|
5800
|
+
code: "add-field-default-normalized",
|
|
5801
|
+
kind: "manual-action",
|
|
5802
|
+
confidence: "high",
|
|
5803
|
+
message: `Default for ${field} will be normalized to ${coercion.normalizedType} literal ${coercion.expression}.`,
|
|
5804
|
+
action: "Review the normalized default in the apply report if this value should stay unset."
|
|
5805
|
+
}
|
|
5806
|
+
];
|
|
5807
|
+
};
|
|
5808
|
+
var createAddFieldRecommendations = (inputs) => {
|
|
5809
|
+
const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
|
|
5810
|
+
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
5811
|
+
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
5812
|
+
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
5813
|
+
if (!typeName)
|
|
5814
|
+
return [];
|
|
5815
|
+
const policy = addFieldUiPolicyForType(typeName);
|
|
5816
|
+
const normalizedType = policy.normalizedType;
|
|
5817
|
+
const paths = addFieldPaths(inputs);
|
|
5818
|
+
const surfaces = selectedAddFieldSurfaces(inputs);
|
|
5819
|
+
const templateRequested = surfaces?.has("template") ?? false;
|
|
5820
|
+
const includeInLight = inputs.includeInLight === true;
|
|
5821
|
+
return [
|
|
5822
|
+
...normalizedType === "Int" || normalizedType === "Float" ? [
|
|
5823
|
+
{
|
|
5824
|
+
code: "add-field-import",
|
|
5825
|
+
kind: "import",
|
|
5826
|
+
target: paths.constant,
|
|
5827
|
+
confidence: "high",
|
|
5828
|
+
message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`
|
|
5829
|
+
}
|
|
5830
|
+
] : [],
|
|
5831
|
+
{
|
|
5832
|
+
code: "add-field-placement-constant",
|
|
5833
|
+
kind: "placement",
|
|
5834
|
+
target: paths.constant,
|
|
5835
|
+
confidence: "high",
|
|
5836
|
+
message: `Insert ${field}: field(${normalizedType}) in ${capitalize3(module)}Input.`
|
|
5837
|
+
},
|
|
5838
|
+
{
|
|
5839
|
+
code: "add-field-placement-dictionary",
|
|
5840
|
+
kind: "placement",
|
|
5841
|
+
target: paths.dictionary,
|
|
5842
|
+
confidence: "high",
|
|
5843
|
+
message: `Add dictionary labels for ${module}.${field}.`
|
|
5844
|
+
},
|
|
5845
|
+
{
|
|
5846
|
+
code: "add-field-component",
|
|
5847
|
+
kind: "ui-component",
|
|
5848
|
+
target: paths.template,
|
|
5849
|
+
confidence: policy.confidence,
|
|
5850
|
+
action: templateRequested ? `apply_workflow will try to add ${policy.component} to Template when the existing Template has a safe generated field-list pattern.` : `Recommended component is ${policy.component}. Pass surfaces=["template"] when this field should be rendered in the Template form.`,
|
|
5851
|
+
message: `Recommended UI component for ${field} (${normalizedType}): ${policy.component}.`
|
|
5852
|
+
},
|
|
5853
|
+
...!surfaces ? [
|
|
5854
|
+
{
|
|
5855
|
+
code: "add-field-template-surface-choice",
|
|
5856
|
+
kind: "manual-action",
|
|
5857
|
+
target: paths.template,
|
|
5858
|
+
action: `Choose whether to expose ${field} in Template by passing surfaces=["template"].`,
|
|
5859
|
+
confidence: "medium",
|
|
5860
|
+
message: `Template exposure for ${module}.${field} is not selected yet.`
|
|
5861
|
+
}
|
|
5862
|
+
] : [],
|
|
5863
|
+
...!includeInLight ? [
|
|
5864
|
+
{
|
|
5865
|
+
code: "add-field-light-projection-choice",
|
|
5866
|
+
kind: "manual-action",
|
|
5867
|
+
target: paths.constant,
|
|
5868
|
+
action: `Pass includeInLight=true when ${field} should appear in Light${capitalize3(module)} list/card projections.`,
|
|
5869
|
+
confidence: "medium",
|
|
5870
|
+
message: `Light projection exposure for ${module}.${field} is not selected yet.`
|
|
5871
|
+
}
|
|
5872
|
+
] : [],
|
|
5873
|
+
...includeInLight ? [
|
|
5874
|
+
{
|
|
5875
|
+
code: "add-field-light-projection",
|
|
5876
|
+
kind: "placement",
|
|
5877
|
+
target: paths.constant,
|
|
5878
|
+
confidence: "high",
|
|
5879
|
+
message: `Add ${field} to Light${capitalize3(module)} projection fields.`
|
|
5880
|
+
}
|
|
5881
|
+
] : [],
|
|
5882
|
+
...surfaces && !templateRequested ? [
|
|
5883
|
+
{
|
|
5884
|
+
code: "add-field-ui-surface-skip",
|
|
5885
|
+
kind: "manual-action",
|
|
5886
|
+
target: paths.template,
|
|
5887
|
+
confidence: "medium",
|
|
5888
|
+
message: `Template is not included in surfaces, so ${module}.${field} will not be auto-rendered there.`
|
|
5889
|
+
}
|
|
5890
|
+
] : [],
|
|
5891
|
+
{
|
|
5892
|
+
code: "add-field-ui-manual-review",
|
|
5893
|
+
kind: "manual-action",
|
|
5894
|
+
target: paths.template,
|
|
5895
|
+
action: `Review ${app}:${module} UI after apply. Template auto-edit requires a generated ${module}Form hook and Layout.Template field list; Unit/View are not auto-edited because list/card placement depends on local layout. Candidate positions: Layout.Template before the closing tag, Light${capitalize3(module)} projection array for list data, and Unit/View card sections for display.`,
|
|
5896
|
+
confidence: "medium",
|
|
5897
|
+
message: "UI manual review includes the reason auto-edit may be skipped and candidate insertion positions."
|
|
5898
|
+
},
|
|
5899
|
+
...createAddFieldDefaultRecommendations(inputs)
|
|
5900
|
+
];
|
|
5901
|
+
};
|
|
5902
|
+
var createWorkflowPlanPredictedChanges = (spec, inputs) => {
|
|
5903
|
+
if (spec.name !== "add-field")
|
|
5904
|
+
return spec.predictedChanges;
|
|
5905
|
+
const paths = addFieldPaths(inputs);
|
|
5906
|
+
const surfaces = selectedAddFieldSurfaces(inputs);
|
|
5907
|
+
const includeInLight = inputs.includeInLight === true;
|
|
5908
|
+
return [
|
|
5909
|
+
{
|
|
5910
|
+
target: paths.constant,
|
|
5911
|
+
action: "modify",
|
|
5912
|
+
applyScope: "auto",
|
|
5913
|
+
reason: includeInLight ? "Field shape and Light projection are added." : "Field shape is added."
|
|
5914
|
+
},
|
|
5915
|
+
{
|
|
5916
|
+
target: paths.dictionary,
|
|
5917
|
+
action: "modify",
|
|
5918
|
+
applyScope: "auto",
|
|
5919
|
+
reason: "Field label is added."
|
|
5920
|
+
},
|
|
5921
|
+
...!surfaces || surfaces.has("template") ? [
|
|
5922
|
+
{
|
|
5923
|
+
target: paths.template,
|
|
5924
|
+
action: "modify",
|
|
5925
|
+
applyScope: surfaces?.has("template") ? "auto" : "manual-review",
|
|
5926
|
+
reason: surfaces?.has("template") ? "Template form is selected for safe-pattern auto insertion." : "Form surface may include the field."
|
|
5927
|
+
}
|
|
5928
|
+
] : [],
|
|
5929
|
+
{
|
|
5930
|
+
target: `${addFieldTargetRoot(typeof inputs.app === "string" ? inputs.app : null)}/lib/cnst.ts`,
|
|
5931
|
+
action: "sync",
|
|
5932
|
+
applyScope: "generated-sync",
|
|
5933
|
+
reason: "Generated constants may change after sync."
|
|
5934
|
+
},
|
|
5935
|
+
{
|
|
5936
|
+
target: `${addFieldTargetRoot(typeof inputs.app === "string" ? inputs.app : null)}/lib/dict.ts`,
|
|
5937
|
+
action: "sync",
|
|
5938
|
+
applyScope: "generated-sync",
|
|
5939
|
+
reason: "Generated dictionary barrel may change after sync."
|
|
5940
|
+
}
|
|
5941
|
+
];
|
|
5942
|
+
};
|
|
5943
|
+
var createWorkflowPlanOptionalSurfaces = (spec, inputs) => {
|
|
5944
|
+
const optionalSurfaces = spec.optionalSurfaces ?? {};
|
|
5945
|
+
const surfaces = selectedAddFieldSurfaces(inputs);
|
|
5946
|
+
if (spec.name !== "add-field" || !surfaces)
|
|
5947
|
+
return optionalSurfaces;
|
|
5948
|
+
return Object.fromEntries(Object.entries(optionalSurfaces).map(([surface, mode]) => [surface, surfaces.has(surface) ? "include" : mode]));
|
|
5949
|
+
};
|
|
5950
|
+
var createWorkflowPlanRecommendations = (spec, inputs) => [
|
|
5951
|
+
{
|
|
5952
|
+
code: "workflow-apply-first",
|
|
5953
|
+
kind: "auto-apply",
|
|
5954
|
+
confidence: "high",
|
|
5955
|
+
action: "Call apply_workflow with the MCP planPath before editing source files directly.",
|
|
5956
|
+
message: `Apply the ${spec.name} plan through apply_workflow when MCP returns planPath or next.tool=apply_workflow.`
|
|
5957
|
+
},
|
|
5958
|
+
{
|
|
5959
|
+
code: "workflow-validate-apply-report",
|
|
5960
|
+
kind: "validation",
|
|
5961
|
+
confidence: "high",
|
|
5962
|
+
action: "After apply_workflow, call run_validation with validationTarget when present; otherwise use applyReportPath.",
|
|
5963
|
+
message: "Validate the apply report artifact so diagnostics and recommendations follow the actual apply result."
|
|
5964
|
+
},
|
|
5965
|
+
...spec.name === "add-field" ? createAddFieldRecommendations(inputs) : []
|
|
5966
|
+
];
|
|
5967
|
+
var createWorkflowPlan = (spec, rawInputs) => {
|
|
5968
|
+
const inputs = {};
|
|
5969
|
+
const diagnostics = [];
|
|
5970
|
+
for (const [name, inputSpec] of Object.entries(spec.inputs)) {
|
|
5971
|
+
const rawValue = rawInputs[name];
|
|
5972
|
+
if (rawValue === undefined || rawValue === null || rawValue === "") {
|
|
5973
|
+
if (inputSpec.required) {
|
|
5974
|
+
diagnostics.push({
|
|
5975
|
+
severity: "error",
|
|
5976
|
+
code: "workflow-input-missing",
|
|
5977
|
+
input: name,
|
|
5978
|
+
message: `Workflow ${spec.name} requires input "${name}".`
|
|
5979
|
+
});
|
|
5980
|
+
}
|
|
5981
|
+
continue;
|
|
5982
|
+
}
|
|
5983
|
+
const value = normalizeInputValue(name, inputSpec, rawValue);
|
|
5984
|
+
if (value === null) {
|
|
5985
|
+
diagnostics.push({
|
|
5986
|
+
severity: "error",
|
|
5987
|
+
code: "workflow-input-invalid",
|
|
5988
|
+
input: name,
|
|
5989
|
+
message: `Workflow ${spec.name} input "${name}" must be ${inputSpec.type}.`
|
|
5990
|
+
});
|
|
5991
|
+
continue;
|
|
5992
|
+
}
|
|
5993
|
+
if (inputSpec.allowedValues && typeof value === "string" && !inputSpec.allowedValues.includes(value)) {
|
|
5994
|
+
diagnostics.push({
|
|
5995
|
+
severity: "error",
|
|
5996
|
+
code: "workflow-input-not-allowed",
|
|
5997
|
+
input: name,
|
|
5998
|
+
message: `Workflow ${spec.name} input "${name}" must be one of: ${inputSpec.allowedValues.join(", ")}.`
|
|
5999
|
+
});
|
|
6000
|
+
continue;
|
|
6001
|
+
}
|
|
6002
|
+
inputs[name] = value;
|
|
6003
|
+
}
|
|
6004
|
+
const fieldType = inputs.type;
|
|
6005
|
+
if (spec.name === "add-field" && typeof fieldType === "string" && (fieldType.toLowerCase() === "number" || fieldType.toLowerCase() === "numeric")) {
|
|
6006
|
+
diagnostics.push({
|
|
6007
|
+
severity: "error",
|
|
6008
|
+
code: "primitive-field-type-unsupported",
|
|
6009
|
+
input: "type",
|
|
6010
|
+
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
if (spec.name === "add-field") {
|
|
6014
|
+
const defaultCoercion = addFieldDefaultCoercion(inputs);
|
|
6015
|
+
if (defaultCoercion?.diagnostic) {
|
|
6016
|
+
diagnostics.push({
|
|
6017
|
+
...defaultCoercion.diagnostic,
|
|
6018
|
+
code: "workflow-default-value-invalid",
|
|
6019
|
+
message: `Plan input default is not valid for ${defaultCoercion.normalizedType}: ${defaultCoercion.diagnostic.message}`
|
|
6020
|
+
});
|
|
6021
|
+
} else if (defaultCoercion?.normalized && defaultCoercion.expression) {
|
|
6022
|
+
diagnostics.push({
|
|
6023
|
+
severity: "warning",
|
|
6024
|
+
code: "workflow-default-value-normalized",
|
|
6025
|
+
input: "default",
|
|
6026
|
+
failureScope: "source-change",
|
|
6027
|
+
message: `Plan input default will be written as ${defaultCoercion.normalizedType} literal ${defaultCoercion.expression}.`
|
|
6028
|
+
});
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
return {
|
|
6032
|
+
schemaVersion: 1,
|
|
6033
|
+
workflow: spec.name,
|
|
6034
|
+
mode: "plan",
|
|
6035
|
+
inputs,
|
|
6036
|
+
optionalSurfaces: createWorkflowPlanOptionalSurfaces(spec, inputs),
|
|
6037
|
+
steps: spec.steps,
|
|
6038
|
+
predictedChanges: createWorkflowPlanPredictedChanges(spec, inputs),
|
|
6039
|
+
validation: spec.validation,
|
|
6040
|
+
diagnostics,
|
|
6041
|
+
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
|
6042
|
+
requiresApproval: true
|
|
6043
|
+
};
|
|
6044
|
+
};
|
|
6045
|
+
// pkgs/@akanjs/devkit/workflow/render.ts
|
|
6046
|
+
var renderWorkflowList = (specs) => [
|
|
6047
|
+
"# Akan Workflows",
|
|
5332
6048
|
"",
|
|
5333
|
-
|
|
5334
|
-
|
|
6049
|
+
...specs.flatMap((spec) => [`- \`${spec.name}\`: ${spec.description}`, ` - When: ${spec.whenToUse}`]),
|
|
6050
|
+
""
|
|
6051
|
+
].join(`
|
|
6052
|
+
`);
|
|
6053
|
+
var renderWorkflowExplain = (spec) => [
|
|
6054
|
+
`# Workflow: ${spec.name}`,
|
|
5335
6055
|
"",
|
|
5336
|
-
|
|
5337
|
-
...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
6056
|
+
spec.description,
|
|
5338
6057
|
"",
|
|
5339
|
-
"##
|
|
5340
|
-
|
|
6058
|
+
"## When To Use",
|
|
6059
|
+
spec.whenToUse,
|
|
5341
6060
|
"",
|
|
5342
|
-
"##
|
|
5343
|
-
...
|
|
6061
|
+
"## Inputs",
|
|
6062
|
+
...Object.entries(spec.inputs).map(([name, input2]) => `- \`${name}\`${input2.required ? " (required)" : ""}: ${input2.description}${input2.allowedValues ? ` Allowed: ${input2.allowedValues.join(", ")}.` : ""}`),
|
|
5344
6063
|
"",
|
|
5345
|
-
"##
|
|
5346
|
-
...
|
|
6064
|
+
"## Optional Surfaces",
|
|
6065
|
+
...Object.entries(spec.optionalSurfaces ?? {}).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
5347
6066
|
"",
|
|
5348
|
-
"##
|
|
5349
|
-
...
|
|
6067
|
+
"## Steps",
|
|
6068
|
+
...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
5350
6069
|
"",
|
|
5351
|
-
"##
|
|
5352
|
-
...
|
|
6070
|
+
"## Validation",
|
|
6071
|
+
...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
5353
6072
|
""
|
|
5354
6073
|
].join(`
|
|
5355
6074
|
`);
|
|
5356
|
-
var
|
|
5357
|
-
|
|
5358
|
-
`# Workflow Validation: ${report.workflow}`,
|
|
6075
|
+
var renderWorkflowPlan = (plan) => [
|
|
6076
|
+
`# Workflow Plan: ${plan.workflow}`,
|
|
5359
6077
|
"",
|
|
5360
|
-
`-
|
|
5361
|
-
`-
|
|
6078
|
+
`- Mode: ${plan.mode}`,
|
|
6079
|
+
`- Requires approval: ${plan.requiresApproval}`,
|
|
5362
6080
|
"",
|
|
5363
|
-
"##
|
|
5364
|
-
...
|
|
5365
|
-
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
5366
|
-
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
5367
|
-
}) : ["- none"],
|
|
6081
|
+
"## Inputs",
|
|
6082
|
+
...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
|
|
5368
6083
|
"",
|
|
5369
|
-
"##
|
|
5370
|
-
...
|
|
6084
|
+
"## Optional Surfaces",
|
|
6085
|
+
...Object.entries(plan.optionalSurfaces).map(([name, mode]) => `- \`${name}\`: ${mode}`),
|
|
5371
6086
|
"",
|
|
5372
|
-
"##
|
|
5373
|
-
...
|
|
6087
|
+
"## Steps",
|
|
6088
|
+
...plan.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
|
|
5374
6089
|
"",
|
|
5375
|
-
"##
|
|
5376
|
-
...
|
|
6090
|
+
"## Predicted Changes",
|
|
6091
|
+
...plan.predictedChanges.map((change) => {
|
|
6092
|
+
const scope = change.applyScope ? ` (${change.applyScope})` : "";
|
|
6093
|
+
return `- \`${change.action}\`${scope} ${change.target}: ${change.reason}`;
|
|
6094
|
+
}),
|
|
6095
|
+
"",
|
|
6096
|
+
"## Validation",
|
|
6097
|
+
...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
|
|
6098
|
+
"",
|
|
6099
|
+
"## Diagnostics",
|
|
6100
|
+
...plan.diagnostics.length ? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
6101
|
+
"",
|
|
6102
|
+
"## Recommendations",
|
|
6103
|
+
...plan.recommendations.length ? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`) : ["- none"],
|
|
5377
6104
|
""
|
|
5378
6105
|
].join(`
|
|
5379
6106
|
`);
|
|
5380
|
-
var
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
if ("mode" in artifact && artifact.mode === "validate")
|
|
5385
|
-
return renderWorkflowValidation(artifact, format);
|
|
5386
|
-
if ("mode" in artifact && (artifact.mode === "apply" || artifact.mode === "dry-run")) {
|
|
5387
|
-
return renderWorkflowApply(artifact, format);
|
|
5388
|
-
}
|
|
5389
|
-
return jsonText(artifact);
|
|
6107
|
+
var renderRecommendation = (recommendation) => {
|
|
6108
|
+
const target = recommendation.target ? ` ${recommendation.target}` : "";
|
|
6109
|
+
const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
|
|
6110
|
+
return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
|
|
5390
6111
|
};
|
|
5391
|
-
var
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
6112
|
+
var renderDiagnostic = (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
|
|
6113
|
+
var renderWorkflowApplyReport = (report) => {
|
|
6114
|
+
const manualReviewItems = [
|
|
6115
|
+
...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
|
|
6116
|
+
...report.recommendations.filter((recommendation) => recommendation.kind === "manual-action").map(renderRecommendation)
|
|
6117
|
+
];
|
|
6118
|
+
const validationBlockers = report.diagnostics.filter((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "workspace-config" || diagnostic.failureScope === "environment")).map(renderDiagnostic);
|
|
6119
|
+
return [
|
|
6120
|
+
`# Workflow Apply: ${report.workflow}`,
|
|
6121
|
+
"",
|
|
6122
|
+
`- Mode: ${report.mode}`,
|
|
6123
|
+
`- Status: ${report.status}`,
|
|
6124
|
+
...report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : [],
|
|
6125
|
+
"",
|
|
6126
|
+
"## Automatically Modified",
|
|
6127
|
+
...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
6128
|
+
"",
|
|
6129
|
+
"## Generated Sync",
|
|
6130
|
+
...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
6131
|
+
"",
|
|
6132
|
+
"## Applied Commands",
|
|
6133
|
+
...report.appliedCommands.length ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
|
|
6134
|
+
"",
|
|
6135
|
+
"## Recommended Validation Commands",
|
|
6136
|
+
...report.recommendedValidationCommands.length ? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
|
|
6137
|
+
"",
|
|
6138
|
+
"## User Review Required",
|
|
6139
|
+
...manualReviewItems.length ? manualReviewItems : ["- none"],
|
|
6140
|
+
"",
|
|
6141
|
+
"## Validation Blockers",
|
|
6142
|
+
...validationBlockers.length ? validationBlockers : report.recommendedValidationCommands.length ? ["- none detected during apply; run validation with the validation target for command-level blockers."] : ["- none"],
|
|
6143
|
+
"",
|
|
6144
|
+
"## Diagnostics",
|
|
6145
|
+
...report.diagnostics.length ? report.diagnostics.map(renderDiagnostic) : ["- none"],
|
|
6146
|
+
"",
|
|
6147
|
+
"## Recommendations",
|
|
6148
|
+
...report.recommendations.length ? report.recommendations.map(renderRecommendation) : ["- none"],
|
|
6149
|
+
"",
|
|
6150
|
+
"## Next Actions",
|
|
6151
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
6152
|
+
""
|
|
6153
|
+
].join(`
|
|
6154
|
+
`);
|
|
6155
|
+
};
|
|
6156
|
+
var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
6157
|
+
var renderWorkflowValidationRunReport = (report) => [
|
|
6158
|
+
`# Workflow Validation: ${report.workflow}`,
|
|
5416
6159
|
"",
|
|
6160
|
+
`- Run: ${report.runId}`,
|
|
5417
6161
|
`- Status: ${report.status}`,
|
|
5418
|
-
`-
|
|
6162
|
+
`- Source status: ${report.sourceStatus}`,
|
|
6163
|
+
`- Workspace status: ${report.workspaceStatus}`,
|
|
6164
|
+
`- Overall status: ${report.overallStatus}`,
|
|
5419
6165
|
"",
|
|
5420
|
-
"##
|
|
5421
|
-
|
|
6166
|
+
"## Status Summary",
|
|
6167
|
+
`- Required source-change validation: ${report.summary.sourceChange}`,
|
|
6168
|
+
`- Generated sync validation: ${report.summary.generatedSync}`,
|
|
6169
|
+
`- Workspace configuration validation: ${report.summary.workspaceConfig}`,
|
|
6170
|
+
`- Environment validation: ${report.summary.environment}`,
|
|
5422
6171
|
"",
|
|
5423
|
-
"##
|
|
5424
|
-
...report.
|
|
6172
|
+
"## Known Blockers",
|
|
6173
|
+
...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
|
|
6174
|
+
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
6175
|
+
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
6176
|
+
const known = blocker.known ? " known" : "";
|
|
6177
|
+
return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
|
|
6178
|
+
}) : ["- none"],
|
|
5425
6179
|
"",
|
|
5426
|
-
"##
|
|
5427
|
-
...report.
|
|
5428
|
-
|
|
5429
|
-
].
|
|
5430
|
-
|
|
5431
|
-
var renderRepairReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderRepairReportMarkdown(report);
|
|
5432
|
-
|
|
5433
|
-
class WorkflowExecutor {
|
|
5434
|
-
registry;
|
|
5435
|
-
constructor(registry) {
|
|
5436
|
-
this.registry = registry;
|
|
5437
|
-
}
|
|
5438
|
-
async apply(plan) {
|
|
5439
|
-
const changedFiles = [];
|
|
5440
|
-
const generatedFiles = [];
|
|
5441
|
-
const recommendedValidationCommands = [];
|
|
5442
|
-
const diagnostics = [...plan.diagnostics];
|
|
5443
|
-
const recommendations = [...plan.recommendations];
|
|
5444
|
-
const nextActions = [];
|
|
5445
|
-
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
5446
|
-
return createWorkflowApplyReport({
|
|
5447
|
-
workflow: plan.workflow,
|
|
5448
|
-
mode: "apply",
|
|
5449
|
-
changedFiles,
|
|
5450
|
-
generatedFiles,
|
|
5451
|
-
recommendedValidationCommands,
|
|
5452
|
-
diagnostics,
|
|
5453
|
-
recommendations,
|
|
5454
|
-
nextActions,
|
|
5455
|
-
plan
|
|
5456
|
-
});
|
|
5457
|
-
}
|
|
5458
|
-
recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
|
|
5459
|
-
nextActions.push(...workflowCommandsForPlan(plan));
|
|
5460
|
-
for (const step of plan.steps) {
|
|
5461
|
-
const runner = this.registry[workflowStepKey(plan.workflow, step.id)] ?? this.registry[step.tool] ?? this.registry[step.id];
|
|
5462
|
-
if (!runner) {
|
|
5463
|
-
diagnostics.push({
|
|
5464
|
-
severity: "error",
|
|
5465
|
-
code: "workflow-step-unsupported",
|
|
5466
|
-
message: `Workflow ${plan.workflow} step "${step.id}" is not supported by workflow apply yet.`
|
|
5467
|
-
});
|
|
5468
|
-
nextActions.push({
|
|
5469
|
-
command: `akan workflow explain ${plan.workflow}`,
|
|
5470
|
-
reason: "Review the unsupported workflow step before retrying apply."
|
|
5471
|
-
});
|
|
5472
|
-
break;
|
|
5473
|
-
}
|
|
5474
|
-
const result = await runner(step, plan);
|
|
5475
|
-
if (!result)
|
|
5476
|
-
continue;
|
|
5477
|
-
changedFiles.push(...result.changedFiles ?? []);
|
|
5478
|
-
generatedFiles.push(...result.generatedFiles ?? []);
|
|
5479
|
-
recommendedValidationCommands.push(...result.commands ?? []);
|
|
5480
|
-
diagnostics.push(...result.diagnostics ?? []);
|
|
5481
|
-
recommendations.push(...result.recommendations ?? []);
|
|
5482
|
-
nextActions.push(...result.nextActions ?? []);
|
|
5483
|
-
if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
|
|
5484
|
-
break;
|
|
5485
|
-
}
|
|
5486
|
-
return createWorkflowApplyReport({
|
|
5487
|
-
workflow: plan.workflow,
|
|
5488
|
-
mode: "apply",
|
|
5489
|
-
changedFiles,
|
|
5490
|
-
generatedFiles,
|
|
5491
|
-
recommendedValidationCommands,
|
|
5492
|
-
diagnostics,
|
|
5493
|
-
recommendations,
|
|
5494
|
-
nextActions,
|
|
5495
|
-
plan
|
|
5496
|
-
});
|
|
5497
|
-
}
|
|
5498
|
-
}
|
|
5499
|
-
var createPrimitiveWriteReport = ({
|
|
5500
|
-
command,
|
|
5501
|
-
status,
|
|
5502
|
-
changedFiles = [],
|
|
5503
|
-
generatedFiles = [],
|
|
5504
|
-
validationCommands = [],
|
|
5505
|
-
diagnostics = [],
|
|
5506
|
-
nextActions = []
|
|
5507
|
-
}) => ({
|
|
5508
|
-
schemaVersion: 1,
|
|
5509
|
-
command,
|
|
5510
|
-
status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
|
|
5511
|
-
changedFiles,
|
|
5512
|
-
generatedFiles,
|
|
5513
|
-
validationCommands,
|
|
5514
|
-
diagnostics,
|
|
5515
|
-
nextActions
|
|
5516
|
-
});
|
|
5517
|
-
var renderPrimitiveWriteReport = (report) => [
|
|
5518
|
-
`# Primitive Write: ${report.command}`,
|
|
6180
|
+
"## Commands",
|
|
6181
|
+
...report.commands.length ? report.commands.map((command) => {
|
|
6182
|
+
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
6183
|
+
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
6184
|
+
}) : ["- none"],
|
|
5519
6185
|
"",
|
|
5520
|
-
|
|
6186
|
+
"## Diagnostics",
|
|
6187
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
5521
6188
|
"",
|
|
5522
|
-
"##
|
|
5523
|
-
...report.
|
|
6189
|
+
"## Repair Actions",
|
|
6190
|
+
...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
5524
6191
|
"",
|
|
5525
|
-
"##
|
|
5526
|
-
...report.
|
|
6192
|
+
"## Next Actions",
|
|
6193
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
6194
|
+
""
|
|
6195
|
+
].join(`
|
|
6196
|
+
`);
|
|
6197
|
+
var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
|
|
6198
|
+
var renderRepairReportMarkdown = (report) => [
|
|
6199
|
+
`# Akan Repair: ${report.kind}`,
|
|
5527
6200
|
"",
|
|
5528
|
-
|
|
5529
|
-
|
|
6201
|
+
`- Status: ${report.status}`,
|
|
6202
|
+
`- Target: ${report.target ?? "none"}`,
|
|
6203
|
+
"",
|
|
6204
|
+
"## Commands",
|
|
6205
|
+
...report.commands.length ? report.commands.map((command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`) : ["- none"],
|
|
5530
6206
|
"",
|
|
5531
6207
|
"## Diagnostics",
|
|
5532
6208
|
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
@@ -5536,154 +6212,17 @@ var renderPrimitiveWriteReport = (report) => [
|
|
|
5536
6212
|
""
|
|
5537
6213
|
].join(`
|
|
5538
6214
|
`);
|
|
5539
|
-
var
|
|
5540
|
-
var
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
var validationCommandsForTarget = (target) => [
|
|
5548
|
-
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files from source conventions." },
|
|
5549
|
-
{ command: `akan lint ${target}`, reason: "Validate formatting, imports, and static lint rules." }
|
|
5550
|
-
];
|
|
5551
|
-
var nextActionsForTarget = (target) => [
|
|
5552
|
-
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files after source changes." },
|
|
5553
|
-
{ command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." }
|
|
5554
|
-
];
|
|
5555
|
-
var createPassedPrimitiveReport = ({
|
|
5556
|
-
command,
|
|
5557
|
-
changedFiles,
|
|
5558
|
-
generatedFiles,
|
|
5559
|
-
target,
|
|
5560
|
-
nextActions
|
|
5561
|
-
}) => createPrimitiveWriteReport({
|
|
5562
|
-
command,
|
|
5563
|
-
changedFiles,
|
|
5564
|
-
generatedFiles: generatedFiles ?? [],
|
|
5565
|
-
validationCommands: validationCommandsForTarget(target),
|
|
5566
|
-
diagnostics: [],
|
|
5567
|
-
nextActions: nextActions ?? nextActionsForTarget(target)
|
|
5568
|
-
});
|
|
5569
|
-
var scalarChangedFiles = (sys2, scalarName, files) => Object.values(files).map((file) => sourceFile(sys2, `lib/__scalar/${scalarName}/${file.filename}`, "create", "Scalar source file was created."));
|
|
5570
|
-
var titleize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
5571
|
-
var lowerlize = (value) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
|
|
5572
|
-
var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!diagnostic);
|
|
5573
|
-
var normalizeFieldType = (typeName) => {
|
|
5574
|
-
const normalizedTypes = {
|
|
5575
|
-
string: "String",
|
|
5576
|
-
boolean: "Boolean",
|
|
5577
|
-
date: "Date",
|
|
5578
|
-
int: "Int",
|
|
5579
|
-
integer: "Int",
|
|
5580
|
-
float: "Float",
|
|
5581
|
-
double: "Float",
|
|
5582
|
-
decimal: "Float"
|
|
5583
|
-
};
|
|
5584
|
-
return normalizedTypes[typeName.toLowerCase()] ?? typeName;
|
|
5585
|
-
};
|
|
5586
|
-
var ensureBaseTypeImport = (content, typeName) => {
|
|
5587
|
-
if (typeName !== "Int" && typeName !== "Float")
|
|
5588
|
-
return content;
|
|
5589
|
-
if (new RegExp(`import \\{[^}]*\\b${typeName}\\b[^}]*\\} from "akanjs/base";`).test(content))
|
|
5590
|
-
return content;
|
|
5591
|
-
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
5592
|
-
if (baseImport) {
|
|
5593
|
-
const names = baseImport[1].split(",").map((name) => name.trim()).filter(Boolean);
|
|
5594
|
-
return content.replace(baseImport[0], `import { ${[...names, typeName].sort().join(", ")} } from "akanjs/base";`);
|
|
5595
|
-
}
|
|
5596
|
-
return `import { ${typeName} } from "akanjs/base";
|
|
5597
|
-
${content}`;
|
|
5598
|
-
};
|
|
5599
|
-
var fieldExpression = (typeName, defaultValue) => {
|
|
5600
|
-
const typeExpression = normalizeFieldType(typeName);
|
|
5601
|
-
const defaultOption = defaultValue ? `, { default: ${JSON.stringify(defaultValue)} }` : "";
|
|
5602
|
-
return `field(${typeExpression}${defaultOption})`;
|
|
5603
|
-
};
|
|
5604
|
-
var insertIntoObject = (content, className, line) => {
|
|
5605
|
-
const classIndex = content.indexOf(`export class ${className} extends via`);
|
|
5606
|
-
if (classIndex < 0)
|
|
5607
|
-
return null;
|
|
5608
|
-
const objectEndIndex = content.indexOf("}))", classIndex);
|
|
5609
|
-
if (objectEndIndex < 0)
|
|
5610
|
-
return null;
|
|
5611
|
-
const prefix = content.slice(0, objectEndIndex);
|
|
5612
|
-
const suffix = content.slice(objectEndIndex);
|
|
5613
|
-
const insertion = prefix.endsWith(`
|
|
5614
|
-
`) ? ` ${line}
|
|
5615
|
-
` : `
|
|
5616
|
-
${line}
|
|
5617
|
-
`;
|
|
5618
|
-
return `${prefix}${insertion}${suffix}`;
|
|
5619
|
-
};
|
|
5620
|
-
var ensureEnumImport = (content) => {
|
|
5621
|
-
if (content.includes("enumOf"))
|
|
5622
|
-
return content;
|
|
5623
|
-
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
5624
|
-
if (baseImport) {
|
|
5625
|
-
const names = baseImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
5626
|
-
return content.replace(baseImport[0], `import { ${[...names, "enumOf"].sort().join(", ")} } from "akanjs/base";`);
|
|
5627
|
-
}
|
|
5628
|
-
return `import { enumOf } from "akanjs/base";
|
|
5629
|
-
${content}`;
|
|
5630
|
-
};
|
|
5631
|
-
var insertEnumClass = (content, enumClassName, enumName, values) => {
|
|
5632
|
-
if (content.includes(`export class ${enumClassName} extends enumOf`))
|
|
5633
|
-
return content;
|
|
5634
|
-
const enumClass = `export class ${enumClassName} extends enumOf("${enumName}", [
|
|
5635
|
-
${values.map((value) => ` ${JSON.stringify(value)},`).join(`
|
|
5636
|
-
`)}
|
|
5637
|
-
] as const) {}
|
|
5638
|
-
|
|
5639
|
-
`;
|
|
5640
|
-
const firstClassIndex = content.indexOf("export class ");
|
|
5641
|
-
if (firstClassIndex < 0)
|
|
5642
|
-
return `${content}
|
|
5643
|
-
${enumClass}`;
|
|
5644
|
-
return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
|
|
5645
|
-
};
|
|
5646
|
-
var insertDictionaryModelField = (content, moduleClassName, fieldName) => {
|
|
5647
|
-
if (new RegExp(`\\b${fieldName}\\s*:`).test(content))
|
|
5648
|
-
return content;
|
|
5649
|
-
const label = titleize(fieldName);
|
|
5650
|
-
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
|
|
5651
|
-
if (modelIndex < 0)
|
|
5652
|
-
return null;
|
|
5653
|
-
const objectEndIndex = content.indexOf(" }))", modelIndex);
|
|
5654
|
-
if (objectEndIndex < 0)
|
|
5655
|
-
return null;
|
|
5656
|
-
return `${content.slice(0, objectEndIndex)} ${fieldName}: t([${JSON.stringify(label)}, ${JSON.stringify(label)}]).desc([${JSON.stringify(label)}, ${JSON.stringify(label)}]),
|
|
5657
|
-
${content.slice(objectEndIndex)}`;
|
|
5658
|
-
};
|
|
5659
|
-
var ensureConstantTypeImport = (content, constantPath, typeName) => {
|
|
5660
|
-
if (new RegExp(`import type \\{[^}]*\\b${typeName}\\b[^}]*\\} from "${constantPath}";`).test(content))
|
|
5661
|
-
return content;
|
|
5662
|
-
const importPattern = new RegExp(`import type \\{ ([^}]+) \\} from "${constantPath}";`);
|
|
5663
|
-
const existingImport = content.match(importPattern);
|
|
5664
|
-
if (existingImport !== null) {
|
|
5665
|
-
const names = existingImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
5666
|
-
return content.replace(existingImport[0], `import type { ${[...names, typeName].sort().join(", ")} } from "${constantPath}";`);
|
|
6215
|
+
var renderRepairReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderRepairReportMarkdown(report);
|
|
6216
|
+
var renderWorkflowRunArtifact = (artifact, format = "markdown") => {
|
|
6217
|
+
if ("kind" in artifact)
|
|
6218
|
+
return renderRepairReport(artifact, format);
|
|
6219
|
+
if ("mode" in artifact && artifact.mode === "validate")
|
|
6220
|
+
return renderWorkflowValidation(artifact, format);
|
|
6221
|
+
if ("mode" in artifact && (artifact.mode === "apply" || artifact.mode === "dry-run")) {
|
|
6222
|
+
return renderWorkflowApply(artifact, format);
|
|
5667
6223
|
}
|
|
5668
|
-
return
|
|
5669
|
-
${content}`;
|
|
5670
|
-
};
|
|
5671
|
-
var insertDictionaryEnum = (content, enumClassName, enumName, values) => {
|
|
5672
|
-
if (content.includes(`.enum<${enumClassName}>("${enumName}"`))
|
|
5673
|
-
return content;
|
|
5674
|
-
const enumBlock = ` .enum<${enumClassName}>("${enumName}", (t) => ({
|
|
5675
|
-
${values.map((value) => ` ${value}: t([${JSON.stringify(titleize(value))}, ${JSON.stringify(titleize(value))}]),`).join(`
|
|
5676
|
-
`)}
|
|
5677
|
-
}))
|
|
5678
|
-
`;
|
|
5679
|
-
const chainEndIndex = content.lastIndexOf(";");
|
|
5680
|
-
const insertBeforeIndex = [content.indexOf(".error("), content.indexOf(".translate("), chainEndIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
5681
|
-
if (insertBeforeIndex === undefined)
|
|
5682
|
-
return null;
|
|
5683
|
-
return `${content.slice(0, insertBeforeIndex)}${enumBlock}${content.slice(insertBeforeIndex)}`;
|
|
6224
|
+
return jsonText(artifact);
|
|
5684
6225
|
};
|
|
5685
|
-
var parseValues = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
5686
|
-
|
|
5687
6226
|
// pkgs/@akanjs/devkit/akanContext.ts
|
|
5688
6227
|
var resourceList = [
|
|
5689
6228
|
{ uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
|
|
@@ -5819,17 +6358,17 @@ var safeReadJson = async (filePath) => {
|
|
|
5819
6358
|
var isWorkflowPlan = (value) => typeof value === "object" && value !== null && ("schemaVersion" in value) && value.schemaVersion === 1 && ("mode" in value) && value.mode === "plan";
|
|
5820
6359
|
var isWorkflowApplyReport = (value) => typeof value === "object" && value !== null && ("schemaVersion" in value) && value.schemaVersion === 1 && ("mode" in value) && (value.mode === "apply" || value.mode === "dry-run");
|
|
5821
6360
|
var isWorkflowRunArtifact = (value) => typeof value === "object" && value !== null && ("schemaVersion" in value) && value.schemaVersion === 1;
|
|
5822
|
-
var planInputString = (
|
|
5823
|
-
const value =
|
|
6361
|
+
var planInputString = (plan2, key) => {
|
|
6362
|
+
const value = plan2.inputs[key];
|
|
5824
6363
|
return typeof value === "string" ? value : "";
|
|
5825
6364
|
};
|
|
5826
|
-
var expandWorkflowTarget = (target,
|
|
5827
|
-
const app = planInputString(
|
|
5828
|
-
const module = planInputString(
|
|
5829
|
-
const moduleClass = module ?
|
|
6365
|
+
var expandWorkflowTarget = (target, plan2) => {
|
|
6366
|
+
const app = planInputString(plan2, "app");
|
|
6367
|
+
const module = planInputString(plan2, "module");
|
|
6368
|
+
const moduleClass = module ? capitalize4(module) : "<Module>";
|
|
5830
6369
|
return target.replace(/^\*\//, app ? `apps/${app}/` : "").replaceAll("<module>", module || "<module>").replaceAll("<Module>", moduleClass);
|
|
5831
6370
|
};
|
|
5832
|
-
var workflowPathsForPlan = (
|
|
6371
|
+
var workflowPathsForPlan = (plan2) => plan2.predictedChanges.map((change) => expandWorkflowTarget(change.target, plan2));
|
|
5833
6372
|
var workflowPathsForArtifact = (artifact) => {
|
|
5834
6373
|
if (isWorkflowPlan(artifact))
|
|
5835
6374
|
return workflowPathsForPlan(artifact);
|
|
@@ -6044,7 +6583,7 @@ class AkanContextAnalyzer {
|
|
|
6044
6583
|
severity: strict ? "error" : "warning",
|
|
6045
6584
|
code: "module-abstract-missing",
|
|
6046
6585
|
path: module.abstract.path,
|
|
6047
|
-
message: `${
|
|
6586
|
+
message: `${capitalize4(module.kind)} module ${sys2.name}:${module.name} should include ${module.abstract.path}`,
|
|
6048
6587
|
repairActions: [action]
|
|
6049
6588
|
});
|
|
6050
6589
|
repairActions.push(action);
|
|
@@ -6056,7 +6595,7 @@ class AkanContextAnalyzer {
|
|
|
6056
6595
|
severity: "error",
|
|
6057
6596
|
code: "module-shape-invalid",
|
|
6058
6597
|
path: module.path,
|
|
6059
|
-
message: `${
|
|
6598
|
+
message: `${capitalize4(module.kind)} module ${sys2.name}:${module.name} is missing required files: ${missingFiles.join(", ")}`,
|
|
6060
6599
|
repairActions: [action]
|
|
6061
6600
|
});
|
|
6062
6601
|
repairActions.push(action);
|
|
@@ -6107,8 +6646,8 @@ class AkanContextAnalyzer {
|
|
|
6107
6646
|
...workflowPaths.length ? { baselineDiagnostics, workflowDiagnostics } : {}
|
|
6108
6647
|
};
|
|
6109
6648
|
}
|
|
6110
|
-
static findModules(context, moduleName) {
|
|
6111
|
-
const modules = [...context.apps, ...context.libs].flatMap((sys2) => sys2.modules);
|
|
6649
|
+
static findModules(context, moduleName, { app = null } = {}) {
|
|
6650
|
+
const modules = [...context.apps, ...context.libs].filter((sys2) => !app || sys2.name === app).flatMap((sys2) => sys2.modules);
|
|
6112
6651
|
return moduleName ? modules.filter((module) => module.name === moduleName || module.folderName === moduleName) : modules;
|
|
6113
6652
|
}
|
|
6114
6653
|
static renderMarkdown(context, { module: moduleName } = {}) {
|
|
@@ -6298,7 +6837,7 @@ void st;
|
|
|
6298
6837
|
` : `const UserLayout = ({ children }) => children;
|
|
6299
6838
|
const userLayout = {};
|
|
6300
6839
|
`;
|
|
6301
|
-
const
|
|
6840
|
+
const source2 = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
6302
6841
|
import { loadFonts } from "akanjs/client";
|
|
6303
6842
|
import { System } from "akanjs/ui";
|
|
6304
6843
|
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
@@ -6374,7 +6913,7 @@ export default function GeneratedLayout({ children, params, searchParams }: Layo
|
|
|
6374
6913
|
return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
|
|
6375
6914
|
}
|
|
6376
6915
|
`;
|
|
6377
|
-
await Bun.write(absPath,
|
|
6916
|
+
await Bun.write(absPath, source2);
|
|
6378
6917
|
return absPath;
|
|
6379
6918
|
}
|
|
6380
6919
|
async function resolveSsrPageEntries(opts) {
|
|
@@ -6531,23 +7070,23 @@ class BarrelAnalyzer {
|
|
|
6531
7070
|
if (visited.has(absFile))
|
|
6532
7071
|
return;
|
|
6533
7072
|
visited.add(absFile);
|
|
6534
|
-
const
|
|
6535
|
-
if (
|
|
7073
|
+
const source2 = await readIfExists(absFile);
|
|
7074
|
+
if (source2 === null)
|
|
6536
7075
|
return;
|
|
6537
7076
|
const currentSubpath = this.#subpathFor(pkg, absFile);
|
|
6538
7077
|
if (!currentSubpath)
|
|
6539
7078
|
return;
|
|
6540
|
-
const authoritative = this.#scanExports(
|
|
7079
|
+
const authoritative = this.#scanExports(source2, absFile);
|
|
6541
7080
|
authoritative.delete("default");
|
|
6542
7081
|
const attributed = new Set;
|
|
6543
7082
|
REEXPORT_RE.lastIndex = 0;
|
|
6544
|
-
let m = REEXPORT_RE.exec(
|
|
7083
|
+
let m = REEXPORT_RE.exec(source2);
|
|
6545
7084
|
while (m !== null) {
|
|
6546
7085
|
const star = m[1];
|
|
6547
7086
|
const nsAs = m[2];
|
|
6548
7087
|
const namedList = m[3];
|
|
6549
7088
|
const spec = m[5] ?? "";
|
|
6550
|
-
m = REEXPORT_RE.exec(
|
|
7089
|
+
m = REEXPORT_RE.exec(source2);
|
|
6551
7090
|
if (!isRelative(spec))
|
|
6552
7091
|
continue;
|
|
6553
7092
|
if (star) {
|
|
@@ -6583,10 +7122,10 @@ class BarrelAnalyzer {
|
|
|
6583
7122
|
}
|
|
6584
7123
|
}
|
|
6585
7124
|
LOCAL_NAMED_RE.lastIndex = 0;
|
|
6586
|
-
let n = LOCAL_NAMED_RE.exec(
|
|
7125
|
+
let n = LOCAL_NAMED_RE.exec(source2);
|
|
6587
7126
|
while (n !== null) {
|
|
6588
7127
|
const body = n[1] ?? "";
|
|
6589
|
-
n = LOCAL_NAMED_RE.exec(
|
|
7128
|
+
n = LOCAL_NAMED_RE.exec(source2);
|
|
6590
7129
|
for (const item of parseNamedList(body)) {
|
|
6591
7130
|
if (item.isType)
|
|
6592
7131
|
continue;
|
|
@@ -6608,10 +7147,10 @@ class BarrelAnalyzer {
|
|
|
6608
7147
|
map.set(name, { subpath: currentSubpath, originalName: name });
|
|
6609
7148
|
}
|
|
6610
7149
|
}
|
|
6611
|
-
#scanExports(
|
|
7150
|
+
#scanExports(source2, absFile) {
|
|
6612
7151
|
try {
|
|
6613
7152
|
const transpiler = [".tsx", ".jsx"].includes(path13.extname(absFile)) ? this.#tsxTranspiler : this.#tsTranspiler;
|
|
6614
|
-
const { exports } = transpiler.scan(
|
|
7153
|
+
const { exports } = transpiler.scan(source2);
|
|
6615
7154
|
return new Set(exports);
|
|
6616
7155
|
} catch (err) {
|
|
6617
7156
|
this.#logger.error(`scan failed: ${err.message}`);
|
|
@@ -6723,28 +7262,28 @@ var createBarrelImportsPlugin = async (app, { skipPath = defaultSkipPath, pipeAf
|
|
|
6723
7262
|
const raw = await Bun.file(realPath).text();
|
|
6724
7263
|
return { contents: raw, loader };
|
|
6725
7264
|
}
|
|
6726
|
-
let
|
|
6727
|
-
const hasMacroAttr = MACRO_ATTR_RE.test(
|
|
7265
|
+
let source2 = await Bun.file(realPath).text();
|
|
7266
|
+
const hasMacroAttr = MACRO_ATTR_RE.test(source2);
|
|
6728
7267
|
if (!hasMacroAttr && barrels.length > 0) {
|
|
6729
7268
|
let maybe = false;
|
|
6730
7269
|
for (const b of barrels) {
|
|
6731
|
-
if (
|
|
7270
|
+
if (source2.includes(b)) {
|
|
6732
7271
|
maybe = true;
|
|
6733
7272
|
break;
|
|
6734
7273
|
}
|
|
6735
7274
|
}
|
|
6736
7275
|
if (maybe) {
|
|
6737
|
-
const rewritten = await rewriteBarrelImports(
|
|
7276
|
+
const rewritten = await rewriteBarrelImports(source2, barrels, analyzer);
|
|
6738
7277
|
if (rewritten !== null)
|
|
6739
|
-
|
|
7278
|
+
source2 = rewritten;
|
|
6740
7279
|
}
|
|
6741
7280
|
}
|
|
6742
7281
|
if (pipeAfter) {
|
|
6743
|
-
const piped = await pipeAfter(
|
|
7282
|
+
const piped = await pipeAfter(source2, { path: realPath });
|
|
6744
7283
|
if (piped !== null)
|
|
6745
|
-
|
|
7284
|
+
source2 = piped;
|
|
6746
7285
|
}
|
|
6747
|
-
return { contents:
|
|
7286
|
+
return { contents: source2, loader };
|
|
6748
7287
|
});
|
|
6749
7288
|
}
|
|
6750
7289
|
};
|
|
@@ -6920,12 +7459,12 @@ var resolveFileCandidate = async (candidate) => {
|
|
|
6920
7459
|
return null;
|
|
6921
7460
|
};
|
|
6922
7461
|
var MACRO_ATTR_RE = /with\s*\{\s*type\s*:\s*["']macro["']\s*\}/;
|
|
6923
|
-
var rewriteBarrelImports = async (
|
|
6924
|
-
const statements = findImportStatements(
|
|
7462
|
+
var rewriteBarrelImports = async (source2, barrels, analyzer) => {
|
|
7463
|
+
const statements = findImportStatements(source2);
|
|
6925
7464
|
if (statements.length === 0)
|
|
6926
7465
|
return null;
|
|
6927
7466
|
let changed = false;
|
|
6928
|
-
let out =
|
|
7467
|
+
let out = source2;
|
|
6929
7468
|
for (let i = statements.length - 1;i >= 0; i--) {
|
|
6930
7469
|
const stmt = statements[i];
|
|
6931
7470
|
if (!stmt)
|
|
@@ -6943,9 +7482,9 @@ var rewriteBarrelImports = async (source, barrels, analyzer) => {
|
|
|
6943
7482
|
}
|
|
6944
7483
|
return changed ? out : null;
|
|
6945
7484
|
};
|
|
6946
|
-
var findImportStatements = (
|
|
7485
|
+
var findImportStatements = (source2) => {
|
|
6947
7486
|
const statements = [];
|
|
6948
|
-
const sourceFile2 = ts4.createSourceFile("barrel-imports.tsx",
|
|
7487
|
+
const sourceFile2 = ts4.createSourceFile("barrel-imports.tsx", source2, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
|
|
6949
7488
|
for (const statement of sourceFile2.statements) {
|
|
6950
7489
|
if (!ts4.isImportDeclaration(statement))
|
|
6951
7490
|
continue;
|
|
@@ -6959,10 +7498,10 @@ var findImportStatements = (source) => {
|
|
|
6959
7498
|
statements.push({
|
|
6960
7499
|
start: statementStart,
|
|
6961
7500
|
end: statementEnd,
|
|
6962
|
-
clause:
|
|
7501
|
+
clause: source2.slice(importClause.getStart(sourceFile2), importClause.end).trim(),
|
|
6963
7502
|
specifier: statement.moduleSpecifier.text,
|
|
6964
|
-
trailingSemicolon:
|
|
6965
|
-
raw:
|
|
7503
|
+
trailingSemicolon: source2.slice(statement.moduleSpecifier.end, statement.end).includes(";"),
|
|
7504
|
+
raw: source2.slice(statementStart, statementEnd)
|
|
6966
7505
|
});
|
|
6967
7506
|
}
|
|
6968
7507
|
return statements;
|
|
@@ -7212,13 +7751,13 @@ class GraphClientEntryDiscovery {
|
|
|
7212
7751
|
}
|
|
7213
7752
|
return cached;
|
|
7214
7753
|
}
|
|
7215
|
-
async#getImports(file,
|
|
7754
|
+
async#getImports(file, source2) {
|
|
7216
7755
|
const absPath = path15.resolve(file);
|
|
7217
7756
|
let cached = this.#importCache.get(absPath);
|
|
7218
7757
|
if (!cached) {
|
|
7219
7758
|
cached = Promise.resolve().then(() => {
|
|
7220
7759
|
try {
|
|
7221
|
-
return this.#tsTranspiler.scanImports(
|
|
7760
|
+
return this.#tsTranspiler.scanImports(source2);
|
|
7222
7761
|
} catch {
|
|
7223
7762
|
return [];
|
|
7224
7763
|
}
|
|
@@ -7243,8 +7782,8 @@ class GraphClientEntryDiscovery {
|
|
|
7243
7782
|
entries.add(absPath);
|
|
7244
7783
|
return this.#finishDiscovery(absPath, visiting, entries);
|
|
7245
7784
|
}
|
|
7246
|
-
const
|
|
7247
|
-
const imports = await this.#getImports(absPath,
|
|
7785
|
+
const source2 = await this.#getRewrittenSource(absPath, content);
|
|
7786
|
+
const imports = await this.#getImports(absPath, source2);
|
|
7248
7787
|
const importerDir = path15.dirname(absPath);
|
|
7249
7788
|
for (const imp of imports) {
|
|
7250
7789
|
const spec = imp.path;
|
|
@@ -7280,13 +7819,13 @@ var IMPLICIT_ROOT_LAYOUT_RE = /[/\\]\.akan[/\\]generated[/\\](?:implicit-root-la
|
|
|
7280
7819
|
function toClientReferencePath(absPath, workspaceRoot) {
|
|
7281
7820
|
return path16.relative(path16.resolve(workspaceRoot), path16.resolve(absPath)).split(path16.sep).join("/");
|
|
7282
7821
|
}
|
|
7283
|
-
function transformUseClient(
|
|
7284
|
-
if (!USE_CLIENT_RE2.test(
|
|
7822
|
+
function transformUseClient(source2, args) {
|
|
7823
|
+
if (!USE_CLIENT_RE2.test(source2))
|
|
7285
7824
|
return null;
|
|
7286
7825
|
if (IMPLICIT_ROOT_LAYOUT_RE.test(args.path))
|
|
7287
7826
|
return null;
|
|
7288
7827
|
const transpiler = new Bun.Transpiler({ loader: loaderFor2(args.path) });
|
|
7289
|
-
const { exports } = transpiler.scan(
|
|
7828
|
+
const { exports } = transpiler.scan(source2);
|
|
7290
7829
|
if (exports.length === 0)
|
|
7291
7830
|
return null;
|
|
7292
7831
|
const referencePath = args.workspaceRoot ? toClientReferencePath(args.path, args.workspaceRoot) : args.path;
|
|
@@ -7432,9 +7971,9 @@ ${absEntry}`).toString(36);
|
|
|
7432
7971
|
`;
|
|
7433
7972
|
}
|
|
7434
7973
|
async#scanEntryExportNames(absEntry) {
|
|
7435
|
-
const
|
|
7974
|
+
const source2 = await Bun.file(absEntry).text();
|
|
7436
7975
|
const transpiler = new Bun.Transpiler({ loader: this.#loaderForEntry(absEntry) });
|
|
7437
|
-
return transpiler.scan(
|
|
7976
|
+
return transpiler.scan(source2).exports;
|
|
7438
7977
|
}
|
|
7439
7978
|
#loaderForEntry(absPath) {
|
|
7440
7979
|
if (absPath.endsWith(".tsx"))
|
|
@@ -7481,14 +8020,14 @@ ${absEntry}`).toString(36);
|
|
|
7481
8020
|
if (Object.keys(this.#externalAliases).length === 0)
|
|
7482
8021
|
return;
|
|
7483
8022
|
await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
|
|
7484
|
-
const
|
|
7485
|
-
const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(
|
|
7486
|
-
if (rewritten !==
|
|
8023
|
+
const source2 = await Bun.file(output.path).text();
|
|
8024
|
+
const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source2, this.#externalAliases);
|
|
8025
|
+
if (rewritten !== source2)
|
|
7487
8026
|
await Bun.write(output.path, rewritten);
|
|
7488
8027
|
}));
|
|
7489
8028
|
}
|
|
7490
|
-
static rewriteExternalImportSpecifiers(
|
|
7491
|
-
let rewritten =
|
|
8029
|
+
static rewriteExternalImportSpecifiers(source2, aliases) {
|
|
8030
|
+
let rewritten = source2;
|
|
7492
8031
|
for (const [specifier, alias] of Object.entries(aliases)) {
|
|
7493
8032
|
if (!alias)
|
|
7494
8033
|
continue;
|
|
@@ -7743,9 +8282,9 @@ ${absEntry}`).toString(36);
|
|
|
7743
8282
|
return { buildEntries, originalByBuildEntry };
|
|
7744
8283
|
}
|
|
7745
8284
|
async#scanExportNames(absEntry) {
|
|
7746
|
-
const
|
|
8285
|
+
const source2 = await Bun.file(absEntry).text();
|
|
7747
8286
|
const transpiler = new Bun.Transpiler({ loader: this.#loaderFor(absEntry) });
|
|
7748
|
-
return transpiler.scan(
|
|
8287
|
+
return transpiler.scan(source2).exports;
|
|
7749
8288
|
}
|
|
7750
8289
|
#loaderFor(absPath) {
|
|
7751
8290
|
if (absPath.endsWith(".tsx"))
|
|
@@ -7756,10 +8295,10 @@ ${absEntry}`).toString(36);
|
|
|
7756
8295
|
return "ts";
|
|
7757
8296
|
return "js";
|
|
7758
8297
|
}
|
|
7759
|
-
static normalizeNamedDefaultFunctionForFastRefresh(
|
|
8298
|
+
static normalizeNamedDefaultFunctionForFastRefresh(source2) {
|
|
7760
8299
|
let changed = false;
|
|
7761
8300
|
const defaultNames = [];
|
|
7762
|
-
const next =
|
|
8301
|
+
const next = source2.replace(/(^|\n)(\s*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g, (match, lineStart, indent, asyncKeyword, name) => {
|
|
7763
8302
|
changed = true;
|
|
7764
8303
|
defaultNames.push(name);
|
|
7765
8304
|
return `${lineStart}${indent}${asyncKeyword ?? ""}function ${name}`;
|
|
@@ -7987,8 +8526,8 @@ ${entries.join(`
|
|
|
7987
8526
|
}
|
|
7988
8527
|
static #hasAsyncDefaultExport(moduleAbsPath) {
|
|
7989
8528
|
try {
|
|
7990
|
-
const
|
|
7991
|
-
const sourceFile2 = ts5.createSourceFile(moduleAbsPath,
|
|
8529
|
+
const source2 = fs3.readFileSync(path21.resolve(moduleAbsPath), "utf8");
|
|
8530
|
+
const sourceFile2 = ts5.createSourceFile(moduleAbsPath, source2, ts5.ScriptTarget.Latest, true, PagesEntrySourceGenerator.#scriptKind(moduleAbsPath));
|
|
7992
8531
|
return PagesEntrySourceGenerator.#sourceFileHasAsyncDefaultExport(sourceFile2);
|
|
7993
8532
|
} catch {
|
|
7994
8533
|
return false;
|
|
@@ -8248,8 +8787,8 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
|
8248
8787
|
return html;
|
|
8249
8788
|
return result + html.slice(lastIndex);
|
|
8250
8789
|
}
|
|
8251
|
-
static escapeInlineScript(
|
|
8252
|
-
return
|
|
8790
|
+
static escapeInlineScript(source2) {
|
|
8791
|
+
return source2.replace(/<\/script/gi, "<\\/script");
|
|
8253
8792
|
}
|
|
8254
8793
|
static resolveHtmlAssetPath(htmlPath, src) {
|
|
8255
8794
|
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
|
|
@@ -8480,17 +9019,17 @@ class CssCompiler {
|
|
|
8480
9019
|
} catch {
|
|
8481
9020
|
continue;
|
|
8482
9021
|
}
|
|
8483
|
-
let
|
|
9022
|
+
let source2 = content;
|
|
8484
9023
|
if (akanConfig2.barrelImports.length > 0) {
|
|
8485
9024
|
try {
|
|
8486
9025
|
const rewritten = await rewriteBarrelImports(content, akanConfig2.barrelImports, analyzer);
|
|
8487
9026
|
if (rewritten !== null)
|
|
8488
|
-
|
|
9027
|
+
source2 = rewritten;
|
|
8489
9028
|
} catch {}
|
|
8490
9029
|
}
|
|
8491
9030
|
let imports;
|
|
8492
9031
|
try {
|
|
8493
|
-
imports = this.#transpiler.scanImports(
|
|
9032
|
+
imports = this.#transpiler.scanImports(source2);
|
|
8494
9033
|
} catch {
|
|
8495
9034
|
continue;
|
|
8496
9035
|
}
|
|
@@ -8929,7 +9468,7 @@ class DevGeneratedIndexSync {
|
|
|
8929
9468
|
const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
|
|
8930
9469
|
if (!rawModel)
|
|
8931
9470
|
return null;
|
|
8932
|
-
const modelName =
|
|
9471
|
+
const modelName = capitalize5(rawModel);
|
|
8933
9472
|
const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
|
|
8934
9473
|
const fileTypes = [];
|
|
8935
9474
|
for (const type of allowedTypes) {
|
|
@@ -8946,7 +9485,7 @@ export const ${modelName} = { ${fileTypes.join(", ")} };`;
|
|
|
8946
9485
|
}
|
|
8947
9486
|
}
|
|
8948
9487
|
var exists = async (file) => stat3(file).then(() => true).catch(() => false);
|
|
8949
|
-
var
|
|
9488
|
+
var capitalize5 = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
8950
9489
|
var formatError = (err) => err instanceof Error ? err.message : String(err);
|
|
8951
9490
|
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
8952
9491
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
@@ -9023,8 +9562,8 @@ class FontOptimizer {
|
|
|
9023
9562
|
if (faceCss.length > 0)
|
|
9024
9563
|
this.#cssParts.push(...faceCss, this.#buildRootVariableRule(font));
|
|
9025
9564
|
}
|
|
9026
|
-
#extractFontsExport(
|
|
9027
|
-
const sourceFile2 = ts6.createSourceFile(filePath,
|
|
9565
|
+
#extractFontsExport(source2, filePath) {
|
|
9566
|
+
const sourceFile2 = ts6.createSourceFile(filePath, source2, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TSX);
|
|
9028
9567
|
const fonts = [];
|
|
9029
9568
|
for (const statement of sourceFile2.statements) {
|
|
9030
9569
|
if (!ts6.isVariableStatement(statement))
|
|
@@ -9580,13 +10119,13 @@ function createUseClientBundlePlugin(options = {}) {
|
|
|
9580
10119
|
build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
|
|
9581
10120
|
if (args.path.includes("/node_modules/"))
|
|
9582
10121
|
return;
|
|
9583
|
-
let
|
|
10122
|
+
let source2;
|
|
9584
10123
|
try {
|
|
9585
|
-
|
|
10124
|
+
source2 = await Bun.file(args.path).text();
|
|
9586
10125
|
} catch {
|
|
9587
10126
|
return;
|
|
9588
10127
|
}
|
|
9589
|
-
const stubbed = transformUseClient(
|
|
10128
|
+
const stubbed = transformUseClient(source2, {
|
|
9590
10129
|
path: args.path,
|
|
9591
10130
|
workspaceRoot: options.workspaceRoot
|
|
9592
10131
|
});
|
|
@@ -9644,7 +10183,7 @@ class PagesBundleBuilder {
|
|
|
9644
10183
|
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
9645
10184
|
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig2.externalLibs }),
|
|
9646
10185
|
akanConfig2.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
9647
|
-
pipeAfter: (
|
|
10186
|
+
pipeAfter: (source2, args) => transformUseClient(source2, {
|
|
9648
10187
|
path: args.path,
|
|
9649
10188
|
workspaceRoot
|
|
9650
10189
|
})
|
|
@@ -9685,7 +10224,7 @@ class PagesBundleBuilder {
|
|
|
9685
10224
|
...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
|
|
9686
10225
|
};
|
|
9687
10226
|
}
|
|
9688
|
-
static createPagesEntryPlugin(
|
|
10227
|
+
static createPagesEntryPlugin(source2) {
|
|
9689
10228
|
return {
|
|
9690
10229
|
name: "akan-pages-entry",
|
|
9691
10230
|
setup(build) {
|
|
@@ -9694,7 +10233,7 @@ class PagesBundleBuilder {
|
|
|
9694
10233
|
namespace: "akan-virtual"
|
|
9695
10234
|
}));
|
|
9696
10235
|
build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
|
|
9697
|
-
contents:
|
|
10236
|
+
contents: source2,
|
|
9698
10237
|
loader: "tsx"
|
|
9699
10238
|
}));
|
|
9700
10239
|
}
|
|
@@ -9716,19 +10255,19 @@ class PagesBundleBuilder {
|
|
|
9716
10255
|
name: "akan-server-use-client-fetch",
|
|
9717
10256
|
setup(build) {
|
|
9718
10257
|
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
9719
|
-
const
|
|
9720
|
-
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(
|
|
9721
|
-
if (transformed ===
|
|
10258
|
+
const source2 = await Bun.file(args.path).text();
|
|
10259
|
+
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source2);
|
|
10260
|
+
if (transformed === source2)
|
|
9722
10261
|
return;
|
|
9723
10262
|
return { contents: transformed, loader: loaderFor4(args.path) };
|
|
9724
10263
|
});
|
|
9725
10264
|
}
|
|
9726
10265
|
};
|
|
9727
10266
|
}
|
|
9728
|
-
static transformServerUseClientFetchSource(
|
|
9729
|
-
if (!
|
|
9730
|
-
return
|
|
9731
|
-
return
|
|
10267
|
+
static transformServerUseClientFetchSource(source2) {
|
|
10268
|
+
if (!source2.includes(`with { type: "macro" }`) || !source2.includes("FetchClient.build"))
|
|
10269
|
+
return source2;
|
|
10270
|
+
return source2.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
|
|
9732
10271
|
}
|
|
9733
10272
|
}
|
|
9734
10273
|
function loaderFor4(absPath) {
|
|
@@ -10494,8 +11033,8 @@ class Builder {
|
|
|
10494
11033
|
#executor;
|
|
10495
11034
|
#distExecutor;
|
|
10496
11035
|
#pkgJson;
|
|
10497
|
-
constructor({ executor, distExecutor, pkgJson }) {
|
|
10498
|
-
this.#executor =
|
|
11036
|
+
constructor({ executor: executor2, distExecutor, pkgJson }) {
|
|
11037
|
+
this.#executor = executor2;
|
|
10499
11038
|
this.#distExecutor = distExecutor;
|
|
10500
11039
|
this.#pkgJson = pkgJson;
|
|
10501
11040
|
}
|
|
@@ -10621,7 +11160,7 @@ import os from "os";
|
|
|
10621
11160
|
import path38 from "path";
|
|
10622
11161
|
import { select as select2 } from "@inquirer/prompts";
|
|
10623
11162
|
import { MobileProject } from "@trapezedev/project";
|
|
10624
|
-
import { capitalize as
|
|
11163
|
+
import { capitalize as capitalize6 } from "akanjs/common";
|
|
10625
11164
|
|
|
10626
11165
|
// pkgs/@akanjs/devkit/fileEditor.ts
|
|
10627
11166
|
class FileEditor {
|
|
@@ -11712,7 +12251,7 @@ ${error.message}`;
|
|
|
11712
12251
|
this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
|
|
11713
12252
|
}
|
|
11714
12253
|
async#setPermissionInIos(permissions) {
|
|
11715
|
-
const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${
|
|
12254
|
+
const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize6(key)}`, value]));
|
|
11716
12255
|
await Promise.all([
|
|
11717
12256
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
|
|
11718
12257
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
|
|
@@ -11804,8 +12343,8 @@ import chalk6 from "chalk";
|
|
|
11804
12343
|
import { program } from "commander";
|
|
11805
12344
|
|
|
11806
12345
|
// pkgs/@akanjs/devkit/commandDecorators/dependencyBuilder.ts
|
|
11807
|
-
var
|
|
11808
|
-
var createDependencyKey = (refName, kind) => `${refName}${
|
|
12346
|
+
var capitalize7 = (value) => value.slice(0, 1).toUpperCase() + value.slice(1);
|
|
12347
|
+
var createDependencyKey = (refName, kind) => `${refName}${capitalize7(kind)}`;
|
|
11809
12348
|
|
|
11810
12349
|
class CommandContainer {
|
|
11811
12350
|
static #instances = new Map;
|
|
@@ -12285,13 +12824,13 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
|
|
|
12285
12824
|
...libNames.map((name2) => ({ name: name2, value: { type: "lib", name: name2 } }))
|
|
12286
12825
|
]
|
|
12287
12826
|
});
|
|
12288
|
-
const
|
|
12289
|
-
const modules = await
|
|
12827
|
+
const executor2 = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
|
|
12828
|
+
const modules = await executor2.getModules();
|
|
12290
12829
|
const moduleName = await select3({
|
|
12291
12830
|
message: `Select the module name`,
|
|
12292
|
-
choices: modules.map((name2) => ({ name: `${
|
|
12831
|
+
choices: modules.map((name2) => ({ name: `${executor2.name}:${name2}`, value: name2 }))
|
|
12293
12832
|
});
|
|
12294
|
-
return ModuleExecutor.from(
|
|
12833
|
+
return ModuleExecutor.from(executor2, moduleName);
|
|
12295
12834
|
} else
|
|
12296
12835
|
throw new Error(`Invalid system type: ${argMeta.type}`);
|
|
12297
12836
|
};
|
|
@@ -12543,6 +13082,8 @@ var NODE_NATIVE_MODULE_SET = new Set([
|
|
|
12543
13082
|
]);
|
|
12544
13083
|
// pkgs/@akanjs/devkit/getCredentials.ts
|
|
12545
13084
|
import yaml from "js-yaml";
|
|
13085
|
+
// pkgs/@akanjs/devkit/getModelFileData.ts
|
|
13086
|
+
import { capitalize as capitalize8 } from "akanjs/common";
|
|
12546
13087
|
// pkgs/@akanjs/devkit/getRelatedCnsts.ts
|
|
12547
13088
|
import { readFileSync as readFileSync4, realpathSync } from "fs";
|
|
12548
13089
|
import ora2 from "ora";
|
|
@@ -12550,18 +13091,18 @@ import * as ts7 from "typescript";
|
|
|
12550
13091
|
var tsTranspiler = new Bun.Transpiler({ loader: "ts" });
|
|
12551
13092
|
var tsxTranspiler = new Bun.Transpiler({ loader: "tsx" });
|
|
12552
13093
|
var getTranspiler = (filePath) => filePath.endsWith(".tsx") ? tsxTranspiler : tsTranspiler;
|
|
12553
|
-
var scanModuleSpecifiers = (
|
|
12554
|
-
const scannedImports = getTranspiler(filePath).scanImports(
|
|
13094
|
+
var scanModuleSpecifiers = (source2, filePath, includeExports) => {
|
|
13095
|
+
const scannedImports = getTranspiler(filePath).scanImports(source2).map((imp) => imp.path).filter(Boolean);
|
|
12555
13096
|
if (includeExports) {
|
|
12556
13097
|
const specifiers = new Set(scannedImports);
|
|
12557
13098
|
const typeOnlyModuleRegex = /\b(?:import|export)\s+type\s+[\s\S]*?\s+from\s*["']([^"']+)["']/g;
|
|
12558
|
-
for (const match of
|
|
13099
|
+
for (const match of source2.matchAll(typeOnlyModuleRegex)) {
|
|
12559
13100
|
const importPath = match[1];
|
|
12560
13101
|
if (importPath)
|
|
12561
13102
|
specifiers.add(importPath);
|
|
12562
13103
|
}
|
|
12563
13104
|
const exportFromRegex = /\bexport\s+(?:type\s+)?(?:\*|{[\s\S]*?})\s+from\s*["']([^"']+)["']/g;
|
|
12564
|
-
for (const match of
|
|
13105
|
+
for (const match of source2.matchAll(exportFromRegex)) {
|
|
12565
13106
|
const importPath = match[1];
|
|
12566
13107
|
if (importPath)
|
|
12567
13108
|
specifiers.add(importPath);
|
|
@@ -12570,7 +13111,7 @@ var scanModuleSpecifiers = (source, filePath, includeExports) => {
|
|
|
12570
13111
|
}
|
|
12571
13112
|
const importSpecifiers = new Set;
|
|
12572
13113
|
const importDeclarationRegex = /\bimport\s+(?:type\s+)?(?:["']([^"']+)["']|[\s\S]*?\s+from\s*["']([^"']+)["'])/g;
|
|
12573
|
-
for (const match of
|
|
13114
|
+
for (const match of source2.matchAll(importDeclarationRegex)) {
|
|
12574
13115
|
const importPath = match[1] ?? match[2];
|
|
12575
13116
|
if (importPath && (scannedImports.includes(importPath) || match[0].startsWith("import type"))) {
|
|
12576
13117
|
importSpecifiers.add(importPath);
|
|
@@ -12593,8 +13134,8 @@ var collectImportedFiles = (constantFilePath, parsedConfig) => {
|
|
|
12593
13134
|
if (analyzedFiles.has(filePath))
|
|
12594
13135
|
return;
|
|
12595
13136
|
analyzedFiles.add(filePath);
|
|
12596
|
-
const
|
|
12597
|
-
for (const importPath of scanModuleSpecifiers(
|
|
13137
|
+
const source2 = readFileSync4(filePath, "utf-8");
|
|
13138
|
+
for (const importPath of scanModuleSpecifiers(source2, filePath, false)) {
|
|
12598
13139
|
if (!importPath.startsWith("."))
|
|
12599
13140
|
continue;
|
|
12600
13141
|
const resolved = ts7.resolveModuleName(importPath, filePath, parsedConfig.options, ts7.sys).resolvedModule?.resolvedFileName;
|
|
@@ -12643,21 +13184,21 @@ var analyzeProperties = (filesToAnalyze, program2, checker) => {
|
|
|
12643
13184
|
if (analyzedFiles.has(filePath))
|
|
12644
13185
|
return;
|
|
12645
13186
|
analyzedFiles.add(filePath);
|
|
12646
|
-
const
|
|
12647
|
-
if (!
|
|
13187
|
+
const source2 = program2.getSourceFile(filePath);
|
|
13188
|
+
if (!source2)
|
|
12648
13189
|
return;
|
|
12649
13190
|
if (!sourceLineCache.has(filePath)) {
|
|
12650
|
-
sourceLineCache.set(filePath,
|
|
13191
|
+
sourceLineCache.set(filePath, source2.getFullText().split(`
|
|
12651
13192
|
`));
|
|
12652
13193
|
}
|
|
12653
13194
|
const sourceLines = sourceLineCache.get(filePath);
|
|
12654
13195
|
function visit(node) {
|
|
12655
|
-
if (!
|
|
13196
|
+
if (!source2)
|
|
12656
13197
|
return;
|
|
12657
13198
|
if (ts7.isPropertyAccessExpression(node)) {
|
|
12658
13199
|
const left = node.expression;
|
|
12659
13200
|
const right = node.name;
|
|
12660
|
-
const { line } = ts7.getLineAndCharacterOfPosition(
|
|
13201
|
+
const { line } = ts7.getLineAndCharacterOfPosition(source2, node.getStart());
|
|
12661
13202
|
if (ts7.isIdentifier(left) && sourceLines && sourceLines.length > line && sourceLines[line] && (sourceLines[line]?.includes(`@Field.Prop(() => ${left.text}.${right.text}`) || sourceLines[line].includes(`base.Filter(${left.text}.${right.text},`))) {
|
|
12662
13203
|
const symbol = getCachedSymbol(right);
|
|
12663
13204
|
if (symbol?.declarations && symbol.declarations.length > 0) {
|
|
@@ -12708,7 +13249,7 @@ var analyzeProperties = (filesToAnalyze, program2, checker) => {
|
|
|
12708
13249
|
}
|
|
12709
13250
|
ts7.forEachChild(node, visit);
|
|
12710
13251
|
}
|
|
12711
|
-
visit(
|
|
13252
|
+
visit(source2);
|
|
12712
13253
|
}
|
|
12713
13254
|
for (const filePath of filesToAnalyze) {
|
|
12714
13255
|
analyzeFileProperties(filePath);
|
|
@@ -14112,8 +14653,8 @@ class CloudRunner extends runner("cloud") {
|
|
|
14112
14653
|
#getSshTarget(config) {
|
|
14113
14654
|
return `${config.username ? `${config.username}@` : ""}${config.host}`;
|
|
14114
14655
|
}
|
|
14115
|
-
#getScpArgs(config,
|
|
14116
|
-
return [...config.port ? ["-P", config.port.toString()] : [],
|
|
14656
|
+
#getScpArgs(config, source2, target) {
|
|
14657
|
+
return [...config.port ? ["-P", config.port.toString()] : [], source2, target];
|
|
14117
14658
|
}
|
|
14118
14659
|
#getSshArgs(config, command3) {
|
|
14119
14660
|
return [...config.port ? ["-p", config.port.toString()] : [], this.#getSshTarget(config), command3];
|
|
@@ -14493,7 +15034,7 @@ import path43 from "path";
|
|
|
14493
15034
|
|
|
14494
15035
|
// pkgs/@akanjs/cli/module/module.script.ts
|
|
14495
15036
|
import { input as input6 } from "@inquirer/prompts";
|
|
14496
|
-
import { capitalize as
|
|
15037
|
+
import { capitalize as capitalize11, randomPicks } from "akanjs/common";
|
|
14497
15038
|
|
|
14498
15039
|
// pkgs/@akanjs/cli/page/page.runner.ts
|
|
14499
15040
|
class PageRunner extends runner("page") {
|
|
@@ -14523,7 +15064,7 @@ function pluralizeName(name) {
|
|
|
14523
15064
|
}
|
|
14524
15065
|
|
|
14525
15066
|
// pkgs/@akanjs/cli/module/module.request.ts
|
|
14526
|
-
import { capitalize as
|
|
15067
|
+
import { capitalize as capitalize9 } from "akanjs/common";
|
|
14527
15068
|
|
|
14528
15069
|
// pkgs/@akanjs/cli/module/module.prompt.ts
|
|
14529
15070
|
var componentDefaultDescription = ({
|
|
@@ -14648,7 +15189,7 @@ var requestTemplate = ({
|
|
|
14648
15189
|
${componentDefaultDescription({
|
|
14649
15190
|
sysName,
|
|
14650
15191
|
modelName,
|
|
14651
|
-
ModelName: ModelName ??
|
|
15192
|
+
ModelName: ModelName ?? capitalize9(modelName),
|
|
14652
15193
|
exampleFiles,
|
|
14653
15194
|
constant,
|
|
14654
15195
|
properties
|
|
@@ -14705,7 +15246,7 @@ var requestView = ({
|
|
|
14705
15246
|
${componentDefaultDescription({
|
|
14706
15247
|
sysName,
|
|
14707
15248
|
modelName,
|
|
14708
|
-
ModelName: ModelName ??
|
|
15249
|
+
ModelName: ModelName ?? capitalize9(modelName),
|
|
14709
15250
|
exampleFiles,
|
|
14710
15251
|
constant,
|
|
14711
15252
|
properties
|
|
@@ -14766,7 +15307,7 @@ var requestUnit = ({
|
|
|
14766
15307
|
${componentDefaultDescription({
|
|
14767
15308
|
sysName,
|
|
14768
15309
|
modelName,
|
|
14769
|
-
ModelName: ModelName ??
|
|
15310
|
+
ModelName: ModelName ?? capitalize9(modelName),
|
|
14770
15311
|
exampleFiles,
|
|
14771
15312
|
constant,
|
|
14772
15313
|
properties
|
|
@@ -14815,7 +15356,47 @@ var requestUnit = ({
|
|
|
14815
15356
|
`;
|
|
14816
15357
|
|
|
14817
15358
|
// pkgs/@akanjs/cli/module/module.runner.ts
|
|
14818
|
-
import { capitalize as
|
|
15359
|
+
import { capitalize as capitalize10 } from "akanjs/common";
|
|
15360
|
+
var purposeByModule = {
|
|
15361
|
+
budget: "Budget represents planned or actual money allocated inside the app.",
|
|
15362
|
+
project: "Project represents a project workspace or business initiative managed by the app.",
|
|
15363
|
+
task: "Task represents work items that move through the app workflow."
|
|
15364
|
+
};
|
|
15365
|
+
var moduleAbstractContent = (moduleName) => {
|
|
15366
|
+
const title = capitalize10(moduleName);
|
|
15367
|
+
const label = bilingualLabelForField(moduleName);
|
|
15368
|
+
const purpose = purposeByModule[moduleName] ?? `${title} represents ${label.en.toLowerCase()} records managed by the app.`;
|
|
15369
|
+
return `# ${title} Module Abstract
|
|
15370
|
+
|
|
15371
|
+
## Purpose
|
|
15372
|
+
|
|
15373
|
+
${purpose}
|
|
15374
|
+
|
|
15375
|
+
## Domain Rules
|
|
15376
|
+
|
|
15377
|
+
- Keep ${label.en.toLowerCase()} data consistent with user-facing dictionary labels.
|
|
15378
|
+
|
|
15379
|
+
## Data Meaning
|
|
15380
|
+
|
|
15381
|
+
${label.en} (${label.ko}) is the primary business concept for this module.
|
|
15382
|
+
|
|
15383
|
+
## Workflows
|
|
15384
|
+
|
|
15385
|
+
No lifecycle workflow yet.
|
|
15386
|
+
|
|
15387
|
+
## Agent Notes
|
|
15388
|
+
|
|
15389
|
+
- Read this abstract before changing the module.
|
|
15390
|
+
- Update this file when business invariants, workflows, or public behavior change.
|
|
15391
|
+
- Do not update this file for formatting-only, import-only, or style-only changes.
|
|
15392
|
+
|
|
15393
|
+
## Related Modules
|
|
15394
|
+
|
|
15395
|
+
- None yet.
|
|
15396
|
+
`;
|
|
15397
|
+
};
|
|
15398
|
+
var localModuleFilename = (moduleName, pathKey2) => moduleSourcePaths(moduleName)[pathKey2].replace(`lib/${moduleName}/`, "");
|
|
15399
|
+
|
|
14819
15400
|
class ModuleRunner extends runner("module") {
|
|
14820
15401
|
async createService(module) {
|
|
14821
15402
|
const serviceName = module.name.replace(/^_+/, "");
|
|
@@ -14845,23 +15426,47 @@ class ModuleRunner extends runner("module") {
|
|
|
14845
15426
|
async createComponentTemplate(module, type) {
|
|
14846
15427
|
await module.sys.applyTemplate({
|
|
14847
15428
|
basePath: `./lib/${module.name}`,
|
|
14848
|
-
template: `module/__Model__.${
|
|
15429
|
+
template: `module/__Model__.${capitalize10(type)}.tsx`,
|
|
14849
15430
|
dict: { model: module.name, appName: module.sys.name }
|
|
14850
15431
|
});
|
|
14851
15432
|
return {
|
|
14852
15433
|
component: {
|
|
14853
|
-
filename: `${module.name}.${
|
|
14854
|
-
content: await module.sys.readFile(`lib/${module.name}/${
|
|
15434
|
+
filename: `${capitalize10(module.name)}.${capitalize10(type)}.tsx`,
|
|
15435
|
+
content: await module.sys.readFile(`lib/${module.name}/${capitalize10(module.name)}.${capitalize10(type)}.tsx`)
|
|
14855
15436
|
}
|
|
14856
15437
|
};
|
|
14857
15438
|
}
|
|
14858
15439
|
async createModuleTemplate(module) {
|
|
14859
15440
|
const names = pluralizeName(module.name);
|
|
15441
|
+
const modelLabel = bilingualLabelForField(module.name);
|
|
15442
|
+
const modelDescription = bilingualDescriptionForField(module.name);
|
|
15443
|
+
const filenames = {
|
|
15444
|
+
abstract: localModuleFilename(module.name, "abstract"),
|
|
15445
|
+
constant: localModuleFilename(module.name, "constant"),
|
|
15446
|
+
dictionary: localModuleFilename(module.name, "dictionary"),
|
|
15447
|
+
service: localModuleFilename(module.name, "service"),
|
|
15448
|
+
store: localModuleFilename(module.name, "store"),
|
|
15449
|
+
signal: localModuleFilename(module.name, "signal"),
|
|
15450
|
+
unit: localModuleFilename(module.name, "unit"),
|
|
15451
|
+
view: localModuleFilename(module.name, "view"),
|
|
15452
|
+
template: localModuleFilename(module.name, "template"),
|
|
15453
|
+
zone: localModuleFilename(module.name, "zone"),
|
|
15454
|
+
util: localModuleFilename(module.name, "util")
|
|
15455
|
+
};
|
|
14860
15456
|
await module.applyTemplate({
|
|
14861
15457
|
basePath: `.`,
|
|
14862
15458
|
template: "module",
|
|
14863
|
-
dict: {
|
|
15459
|
+
dict: {
|
|
15460
|
+
model: module.name,
|
|
15461
|
+
models: names,
|
|
15462
|
+
sysName: module.sys.name,
|
|
15463
|
+
modelLabelEn: modelLabel.en,
|
|
15464
|
+
modelLabelKo: modelLabel.ko,
|
|
15465
|
+
modelDescEn: modelDescription.en,
|
|
15466
|
+
modelDescKo: modelDescription.ko
|
|
15467
|
+
}
|
|
14864
15468
|
});
|
|
15469
|
+
await module.writeFile(filenames.abstract, moduleAbstractContent(module.name));
|
|
14865
15470
|
const [
|
|
14866
15471
|
abstractContent,
|
|
14867
15472
|
constantContent,
|
|
@@ -14875,30 +15480,30 @@ class ModuleRunner extends runner("module") {
|
|
|
14875
15480
|
zoneContent,
|
|
14876
15481
|
utilContent
|
|
14877
15482
|
] = await Promise.all([
|
|
14878
|
-
module.readFile(
|
|
14879
|
-
module.readFile(
|
|
14880
|
-
module.readFile(
|
|
14881
|
-
module.readFile(
|
|
14882
|
-
module.readFile(
|
|
14883
|
-
module.readFile(
|
|
14884
|
-
module.readFile(
|
|
14885
|
-
module.readFile(
|
|
14886
|
-
module.readFile(
|
|
14887
|
-
module.readFile(
|
|
14888
|
-
module.readFile(
|
|
15483
|
+
module.readFile(filenames.abstract),
|
|
15484
|
+
module.readFile(filenames.constant),
|
|
15485
|
+
module.readFile(filenames.dictionary),
|
|
15486
|
+
module.readFile(filenames.service),
|
|
15487
|
+
module.readFile(filenames.store),
|
|
15488
|
+
module.readFile(filenames.signal),
|
|
15489
|
+
module.readFile(filenames.unit),
|
|
15490
|
+
module.readFile(filenames.view),
|
|
15491
|
+
module.readFile(filenames.template),
|
|
15492
|
+
module.readFile(filenames.zone),
|
|
15493
|
+
module.readFile(filenames.util)
|
|
14889
15494
|
]);
|
|
14890
15495
|
return {
|
|
14891
|
-
abstract: { filename:
|
|
14892
|
-
constant: { filename:
|
|
14893
|
-
dictionary: { filename:
|
|
14894
|
-
service: { filename:
|
|
14895
|
-
store: { filename:
|
|
14896
|
-
signal: { filename:
|
|
14897
|
-
unit: { filename:
|
|
14898
|
-
view: { filename:
|
|
14899
|
-
template: { filename:
|
|
14900
|
-
zone: { filename:
|
|
14901
|
-
util: { filename:
|
|
15496
|
+
abstract: { filename: filenames.abstract, content: abstractContent },
|
|
15497
|
+
constant: { filename: filenames.constant, content: constantContent },
|
|
15498
|
+
dictionary: { filename: filenames.dictionary, content: dictionaryContent },
|
|
15499
|
+
service: { filename: filenames.service, content: serviceContent },
|
|
15500
|
+
store: { filename: filenames.store, content: storeContent },
|
|
15501
|
+
signal: { filename: filenames.signal, content: signalContent },
|
|
15502
|
+
unit: { filename: filenames.unit, content: unitContent },
|
|
15503
|
+
view: { filename: filenames.view, content: viewContent },
|
|
15504
|
+
template: { filename: filenames.template, content: templateContent },
|
|
15505
|
+
zone: { filename: filenames.zone, content: zoneContent },
|
|
15506
|
+
util: { filename: filenames.util, content: utilContent }
|
|
14902
15507
|
};
|
|
14903
15508
|
}
|
|
14904
15509
|
}
|
|
@@ -14928,11 +15533,11 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
14928
15533
|
const isContinued = await sys3.exists(`lib/${name}/${name}.constant.ts`);
|
|
14929
15534
|
const session = new AiSession("createModule", { workspace: sys3.workspace, cacheKey: name, isContinued });
|
|
14930
15535
|
const moduleConstantExampleFiles = await sys3.workspace.getConstantFiles();
|
|
14931
|
-
const
|
|
14932
|
-
const files = await this.moduleRunner.createModuleTemplate(
|
|
15536
|
+
const executor2 = ModuleExecutor.from(sys3, name);
|
|
15537
|
+
const files = await this.moduleRunner.createModuleTemplate(executor2);
|
|
14933
15538
|
const { constant, dictionary } = files;
|
|
14934
15539
|
if (page && sys3.type === "app")
|
|
14935
|
-
await this.pageScript.createCrudPage(
|
|
15540
|
+
await this.pageScript.createCrudPage(executor2, { app: sys3, basePath: null, single: false });
|
|
14936
15541
|
const modelDesc = description ?? await input6({ message: "description of module" });
|
|
14937
15542
|
const modelSchemaDesign = schemaDescription ?? await input6({ message: "schema description of module" });
|
|
14938
15543
|
const config = await sys3.getConfig();
|
|
@@ -15000,8 +15605,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15000
15605
|
async createTest(workspace, name) {}
|
|
15001
15606
|
async createTemplate(mod) {
|
|
15002
15607
|
const { component: template } = await this.moduleRunner.createComponentTemplate(mod, "template");
|
|
15003
|
-
const
|
|
15004
|
-
const
|
|
15608
|
+
const paths = moduleSourcePaths(mod.name);
|
|
15609
|
+
const templateExampleFiles = (await mod.sys.getTemplatesSourceCode()).filter((f) => !f.filePath.includes(paths.template));
|
|
15610
|
+
const Name = capitalize11(mod.name);
|
|
15005
15611
|
const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15006
15612
|
const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15007
15613
|
const session = new AiSession("createTemplate", { workspace: mod.sys.workspace, cacheKey: mod.name });
|
|
@@ -15027,8 +15633,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15027
15633
|
}
|
|
15028
15634
|
async createUnit(mod) {
|
|
15029
15635
|
const { component: unit } = await this.moduleRunner.createComponentTemplate(mod, "unit");
|
|
15030
|
-
const
|
|
15031
|
-
const
|
|
15636
|
+
const paths = moduleSourcePaths(mod.name);
|
|
15637
|
+
const Name = capitalize11(mod.name);
|
|
15638
|
+
const unitExampleFiles = (await mod.sys.getUnitsSourceCode()).filter((f) => !f.filePath.includes(paths.unit));
|
|
15032
15639
|
const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15033
15640
|
const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15034
15641
|
const session = new AiSession("createUnit", { workspace: mod.sys.workspace, cacheKey: mod.name });
|
|
@@ -15052,8 +15659,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15052
15659
|
}
|
|
15053
15660
|
async createView(mod) {
|
|
15054
15661
|
const { component: view } = await this.moduleRunner.createComponentTemplate(mod, "view");
|
|
15055
|
-
const
|
|
15056
|
-
const
|
|
15662
|
+
const paths = moduleSourcePaths(mod.name);
|
|
15663
|
+
const viewExampleFiles = (await mod.sys.getViewsSourceCode()).filter((f) => !f.filePath.includes(paths.view));
|
|
15664
|
+
const Name = capitalize11(mod.name);
|
|
15057
15665
|
const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15058
15666
|
const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15059
15667
|
const session = new AiSession("createView", { workspace: mod.sys.workspace, cacheKey: mod.name });
|
|
@@ -15078,7 +15686,7 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15078
15686
|
}
|
|
15079
15687
|
|
|
15080
15688
|
// pkgs/@akanjs/cli/primitive/primitive.script.ts
|
|
15081
|
-
import { capitalize as
|
|
15689
|
+
import { capitalize as capitalize12 } from "akanjs/common";
|
|
15082
15690
|
class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
15083
15691
|
async resolveSys(workspace, target) {
|
|
15084
15692
|
if (!target)
|
|
@@ -15126,7 +15734,7 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15126
15734
|
}
|
|
15127
15735
|
async addEnumField(workspace, input7) {
|
|
15128
15736
|
const values = parseValues(input7.values);
|
|
15129
|
-
return await this.addFieldToSources(workspace, { ...input7, type: `${
|
|
15737
|
+
return await this.addFieldToSources(workspace, { ...input7, type: `${capitalize12(input7.field ?? "")}` }, { enumValues: values });
|
|
15130
15738
|
}
|
|
15131
15739
|
async addFieldToSources(workspace, input7, { enumValues }) {
|
|
15132
15740
|
const sys3 = await this.resolveSys(workspace, input7.app);
|
|
@@ -15170,10 +15778,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15170
15778
|
nextActions: []
|
|
15171
15779
|
});
|
|
15172
15780
|
}
|
|
15173
|
-
const moduleClassName =
|
|
15781
|
+
const moduleClassName = capitalize12(input7.module);
|
|
15174
15782
|
const inputClassName = `${moduleClassName}Input`;
|
|
15175
|
-
const
|
|
15176
|
-
const
|
|
15783
|
+
const paths = moduleSourcePaths(input7.module);
|
|
15784
|
+
const constantPath = paths.constant;
|
|
15785
|
+
const dictionaryPath = paths.dictionary;
|
|
15786
|
+
const templatePath = paths.template;
|
|
15177
15787
|
const changedFiles = [];
|
|
15178
15788
|
const generatedFiles2 = generatedFilesForSync(sys3);
|
|
15179
15789
|
const [hasConstant, hasDictionary] = await Promise.all([sys3.exists(constantPath), sys3.exists(dictionaryPath)]);
|
|
@@ -15212,8 +15822,8 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15212
15822
|
});
|
|
15213
15823
|
}
|
|
15214
15824
|
if (enumValues) {
|
|
15215
|
-
const enumClassName = `${moduleClassName}${
|
|
15216
|
-
const enumName = `${lowerlize(moduleClassName)}${
|
|
15825
|
+
const enumClassName = `${moduleClassName}${capitalize12(input7.field)}`;
|
|
15826
|
+
const enumName = `${lowerlize(moduleClassName)}${capitalize12(input7.field)}`;
|
|
15217
15827
|
constantContent = insertEnumClass(ensureEnumImport(constantContent), enumClassName, enumName, enumValues);
|
|
15218
15828
|
dictionaryContent = ensureConstantTypeImport(dictionaryContent, `./${input7.module}.constant`, enumClassName);
|
|
15219
15829
|
input7.type = enumClassName;
|
|
@@ -15230,10 +15840,36 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15230
15840
|
}
|
|
15231
15841
|
if (!enumValues && normalizedType) {
|
|
15232
15842
|
input7.type = normalizedType;
|
|
15843
|
+
const defaultCoercion = coerceFieldDefault(input7.type, input7.defaultValue);
|
|
15844
|
+
if (defaultCoercion.diagnostic)
|
|
15845
|
+
diagnostics.push(defaultCoercion.diagnostic);
|
|
15233
15846
|
constantContent = ensureBaseTypeImport(constantContent, input7.type);
|
|
15234
15847
|
}
|
|
15235
|
-
|
|
15848
|
+
if (enumValues) {
|
|
15849
|
+
const defaultCoercion = coerceFieldDefault("enum", input7.defaultValue, { enumValues });
|
|
15850
|
+
if (defaultCoercion.diagnostic)
|
|
15851
|
+
diagnostics.push(defaultCoercion.diagnostic);
|
|
15852
|
+
}
|
|
15853
|
+
const nextConstantContent = insertIntoObject(constantContent, inputClassName, `${input7.field}: ${fieldExpression(input7.type, input7.defaultValue, { enumValues })},`);
|
|
15854
|
+
const nextConstantContentWithLight = nextConstantContent && input7.includeInLight ? insertLightProjectionField(nextConstantContent, moduleClassName, input7.field) : nextConstantContent;
|
|
15236
15855
|
const nextDictionaryContent = insertDictionaryModelField(dictionaryContent, moduleClassName, input7.field);
|
|
15856
|
+
if (nextConstantContentWithLight && (input7.type === "Int" || input7.type === "Float") && new RegExp(`\\b${input7.field}\\s*:\\s*field\\(${input7.type}, \\{ default: "`, "m").test(nextConstantContentWithLight)) {
|
|
15857
|
+
diagnostics.push({
|
|
15858
|
+
severity: "error",
|
|
15859
|
+
code: "primitive-default-value-invalid",
|
|
15860
|
+
input: "default",
|
|
15861
|
+
failureScope: "source-change",
|
|
15862
|
+
message: `Generated ${input7.type} default for "${input7.field}" would be a string literal; refusing to write source.`
|
|
15863
|
+
});
|
|
15864
|
+
}
|
|
15865
|
+
if (nextConstantContentWithLight && (input7.type === "Int" || input7.type === "Float") && !new RegExp(`import \\{[^}]*\\b${input7.type}\\b[^}]*\\} from "akanjs/base";`).test(nextConstantContentWithLight)) {
|
|
15866
|
+
diagnostics.push({
|
|
15867
|
+
severity: "error",
|
|
15868
|
+
code: "primitive-base-type-import-missing",
|
|
15869
|
+
failureScope: "source-change",
|
|
15870
|
+
message: `Generated source for ${input7.field} requires ${input7.type} import from "akanjs/base".`
|
|
15871
|
+
});
|
|
15872
|
+
}
|
|
15237
15873
|
if (!nextConstantContent) {
|
|
15238
15874
|
diagnostics.push({
|
|
15239
15875
|
severity: "error",
|
|
@@ -15241,6 +15877,14 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15241
15877
|
message: `Could not find ${inputClassName} object shape in ${constantPath}.`
|
|
15242
15878
|
});
|
|
15243
15879
|
}
|
|
15880
|
+
if (nextConstantContent && input7.includeInLight && !nextConstantContentWithLight) {
|
|
15881
|
+
diagnostics.push({
|
|
15882
|
+
severity: "warning",
|
|
15883
|
+
code: "primitive-light-projection-shape-unsupported",
|
|
15884
|
+
failureScope: "source-change",
|
|
15885
|
+
message: `Could not find a safe Light${moduleClassName} projection insertion point in ${constantPath}.`
|
|
15886
|
+
});
|
|
15887
|
+
}
|
|
15244
15888
|
if (!nextDictionaryContent) {
|
|
15245
15889
|
diagnostics.push({
|
|
15246
15890
|
severity: "error",
|
|
@@ -15248,10 +15892,51 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15248
15892
|
message: `Could not find ${moduleClassName} dictionary model shape in ${dictionaryPath}.`
|
|
15249
15893
|
});
|
|
15250
15894
|
}
|
|
15251
|
-
|
|
15252
|
-
|
|
15895
|
+
let nextTemplateContent;
|
|
15896
|
+
if (input7.surfaces?.includes("template")) {
|
|
15897
|
+
const policy = addFieldUiPolicyForType(enumValues ? "enum" : input7.type);
|
|
15898
|
+
const hasTemplate = await sys3.exists(templatePath);
|
|
15899
|
+
if (!hasTemplate) {
|
|
15900
|
+
diagnostics.push({
|
|
15901
|
+
severity: "warning",
|
|
15902
|
+
code: "primitive-template-missing",
|
|
15903
|
+
failureScope: "source-change",
|
|
15904
|
+
message: `Template source file was not found: ${templatePath}. Auto-edit skipped; add the field to the Template form manually if this module renders one.`
|
|
15905
|
+
});
|
|
15906
|
+
} else if (!policy.autoTemplateSupported || policy.component === "Field.ToggleSelect") {
|
|
15907
|
+
diagnostics.push({
|
|
15908
|
+
severity: "warning",
|
|
15909
|
+
code: "primitive-template-component-manual",
|
|
15910
|
+
failureScope: "source-change",
|
|
15911
|
+
message: `Template auto insertion for ${policy.component} is not supported because option binding must be confirmed. Candidate position: inside Layout.Template near existing Field components in ${templatePath}.`
|
|
15912
|
+
});
|
|
15913
|
+
} else {
|
|
15914
|
+
const templateContent = await sys3.readFile(templatePath);
|
|
15915
|
+
nextTemplateContent = insertTemplateField({
|
|
15916
|
+
content: templateContent,
|
|
15917
|
+
moduleName: input7.module,
|
|
15918
|
+
moduleClassName,
|
|
15919
|
+
fieldName: input7.field,
|
|
15920
|
+
component: policy.component
|
|
15921
|
+
});
|
|
15922
|
+
if (!nextTemplateContent) {
|
|
15923
|
+
diagnostics.push({
|
|
15924
|
+
severity: "warning",
|
|
15925
|
+
code: "primitive-template-shape-unsupported",
|
|
15926
|
+
failureScope: "source-change",
|
|
15927
|
+
message: `Could not find a safe Template insertion point in ${templatePath}. Expected generated ${input7.module}Form hook and Layout.Template closing tag; candidate position is the existing field list before </Layout.Template>.`
|
|
15928
|
+
});
|
|
15929
|
+
}
|
|
15930
|
+
}
|
|
15931
|
+
}
|
|
15932
|
+
if (!diagnostics.some((diagnostic) => diagnostic.severity === "error") && nextConstantContentWithLight && nextDictionaryContent) {
|
|
15933
|
+
await sys3.writeFile(constantPath, nextConstantContentWithLight);
|
|
15253
15934
|
await sys3.writeFile(dictionaryPath, nextDictionaryContent);
|
|
15254
15935
|
changedFiles.push(sourceFile(sys3, constantPath, "modify", "Field source shape was updated."), sourceFile(sys3, dictionaryPath, "modify", "Field dictionary labels were updated."));
|
|
15936
|
+
if (nextTemplateContent !== undefined && nextTemplateContent !== null) {
|
|
15937
|
+
await sys3.writeFile(templatePath, nextTemplateContent);
|
|
15938
|
+
changedFiles.push(sourceFile(sys3, templatePath, "modify", "Template field surface was updated."));
|
|
15939
|
+
}
|
|
15255
15940
|
}
|
|
15256
15941
|
return createPrimitiveWriteReport({
|
|
15257
15942
|
command: enumValues ? "add-enum-field" : "add-field",
|
|
@@ -15312,8 +15997,8 @@ class RepairRunner extends runner("repair") {
|
|
|
15312
15997
|
format = "markdown",
|
|
15313
15998
|
execute
|
|
15314
15999
|
}) {
|
|
15315
|
-
const
|
|
15316
|
-
const report = await this.createRepairReport(kind, { workspace, app, module, target, execute:
|
|
16000
|
+
const executor2 = execute ?? defaultExecutor(workspace);
|
|
16001
|
+
const report = await this.createRepairReport(kind, { workspace, app, module, target, execute: executor2 });
|
|
15317
16002
|
const { artifact: artifact2, runId } = await writeWorkflowRunArtifact(workspace, report);
|
|
15318
16003
|
const reportWithArtifact = artifact2;
|
|
15319
16004
|
if (kind === "generated" && reportWithArtifact.status === "passed" && reportWithArtifact.target) {
|
|
@@ -15620,6 +16305,7 @@ class ScalarScript extends script("scalar", [ScalarRunner]) {
|
|
|
15620
16305
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
15621
16306
|
import { mkdir as mkdir12, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
15622
16307
|
import path42 from "path";
|
|
16308
|
+
import { capitalize as capitalize13 } from "akanjs/common";
|
|
15623
16309
|
|
|
15624
16310
|
// pkgs/@akanjs/cli/workflows/shared.ts
|
|
15625
16311
|
var sysInputs = {
|
|
@@ -15733,7 +16419,18 @@ var addFieldWorkflowSpec = {
|
|
|
15733
16419
|
description: "Field type or scalar name. Use Int for integer fields or Float for decimal fields; do not use Number."
|
|
15734
16420
|
},
|
|
15735
16421
|
values: { type: "string-list", description: "Comma-separated enum values when type is enum." },
|
|
15736
|
-
default: {
|
|
16422
|
+
default: {
|
|
16423
|
+
type: "string",
|
|
16424
|
+
description: "Optional default value. plan/apply coerce by field type: Int/Float to numeric literals, Boolean to true/false, Date to new Date(...), String/scalar to string literals, and enum only when the value is in values."
|
|
16425
|
+
},
|
|
16426
|
+
surfaces: {
|
|
16427
|
+
type: "string-list",
|
|
16428
|
+
description: "Optional UI surfaces to update, comma-separated. Use template to auto-update simple generated Template forms; View/Unit stay planned as manual review."
|
|
16429
|
+
},
|
|
16430
|
+
includeInLight: {
|
|
16431
|
+
type: "boolean",
|
|
16432
|
+
description: "Whether to add the field to the Light<Model> projection used by list/card displays."
|
|
16433
|
+
}
|
|
15737
16434
|
},
|
|
15738
16435
|
optionalSurfaces: {
|
|
15739
16436
|
dictionary: "include",
|
|
@@ -16180,7 +16877,7 @@ var failedPlan = (workflow2, diagnostics) => ({
|
|
|
16180
16877
|
recommendations: [],
|
|
16181
16878
|
requiresApproval: true
|
|
16182
16879
|
});
|
|
16183
|
-
var failedApplyReport = (workflow2, diagnostics,
|
|
16880
|
+
var failedApplyReport = (workflow2, diagnostics, plan2) => createWorkflowApplyReport({
|
|
16184
16881
|
workflow: workflow2,
|
|
16185
16882
|
mode: "apply",
|
|
16186
16883
|
changedFiles: [],
|
|
@@ -16188,7 +16885,7 @@ var failedApplyReport = (workflow2, diagnostics, plan) => createWorkflowApplyRep
|
|
|
16188
16885
|
commands: [],
|
|
16189
16886
|
diagnostics,
|
|
16190
16887
|
nextActions: [],
|
|
16191
|
-
plan:
|
|
16888
|
+
plan: plan2 ?? failedPlan(workflow2, diagnostics)
|
|
16192
16889
|
});
|
|
16193
16890
|
var commandForShell2 = (command3) => command3.startsWith("akan ") ? `bun run ${command3}` : command3;
|
|
16194
16891
|
var inferValidationKind = (command3) => {
|
|
@@ -16248,6 +16945,61 @@ var defaultValidationExecutor = (workspace) => async (command3) => {
|
|
|
16248
16945
|
}
|
|
16249
16946
|
};
|
|
16250
16947
|
var readJsonFile = async (filePath) => JSON.parse(await readFile2(resolvePath(filePath), "utf8"));
|
|
16948
|
+
var planInputString2 = (plan2, key) => {
|
|
16949
|
+
const value = plan2.inputs[key];
|
|
16950
|
+
return typeof value === "string" ? value : "";
|
|
16951
|
+
};
|
|
16952
|
+
var workflowPathsForPlanLike = (plan2) => {
|
|
16953
|
+
const app = planInputString2(plan2, "app");
|
|
16954
|
+
const module = planInputString2(plan2, "module");
|
|
16955
|
+
const moduleClass = module ? capitalize13(module) : "<Module>";
|
|
16956
|
+
return plan2.predictedChanges.map((change) => change.target.replace(/^\*\//, app ? `apps/${app}/` : "").replaceAll("<module>", module || "<module>").replaceAll("<Module>", moduleClass));
|
|
16957
|
+
};
|
|
16958
|
+
var workflowDiagnosticFromDoctor = (diagnostic, fallbackScope) => ({
|
|
16959
|
+
severity: diagnostic.severity,
|
|
16960
|
+
code: diagnostic.code,
|
|
16961
|
+
message: diagnostic.message,
|
|
16962
|
+
scope: diagnostic.scope ?? fallbackScope,
|
|
16963
|
+
failureScope: (diagnostic.scope ?? fallbackScope) === "baseline" ? "workspace-config" : (diagnostic.scope ?? fallbackScope) === "workflow" ? "source-change" : "unknown",
|
|
16964
|
+
context: diagnostic.context
|
|
16965
|
+
});
|
|
16966
|
+
var baselineBlockerCachePath = (workflow2) => `.akan/workflows/baseline/${workflow2}.json`;
|
|
16967
|
+
var blockerFingerprint = (blocker) => [blocker.failureScope, blocker.code, blocker.command ?? "", blocker.kind ?? "", blocker.message].join("|");
|
|
16968
|
+
var applyBaselineBlockerCache = async (workspace, report) => {
|
|
16969
|
+
const cacheableBlockers = report.knownBlockers.filter((blocker) => blocker.failureScope === "workspace-config" || blocker.failureScope === "environment");
|
|
16970
|
+
if (cacheableBlockers.length === 0)
|
|
16971
|
+
return report;
|
|
16972
|
+
const cachePath = baselineBlockerCachePath(report.workflow);
|
|
16973
|
+
const cached = (await workspace.exists(cachePath) ? await workspace.readJson(cachePath) : null) ?? { schemaVersion: 1, workflow: report.workflow, blockers: [] };
|
|
16974
|
+
const knownFingerprints = new Set(cached.blockers.map((blocker) => blocker.fingerprint));
|
|
16975
|
+
const now = new Date().toISOString();
|
|
16976
|
+
const blockers = new Map(cached.blockers.map((blocker) => [blocker.fingerprint, blocker]));
|
|
16977
|
+
for (const blocker of cacheableBlockers) {
|
|
16978
|
+
const fingerprint = blockerFingerprint(blocker);
|
|
16979
|
+
blockers.set(fingerprint, {
|
|
16980
|
+
fingerprint,
|
|
16981
|
+
code: blocker.code,
|
|
16982
|
+
message: blocker.message,
|
|
16983
|
+
failureScope: blocker.failureScope,
|
|
16984
|
+
command: blocker.command,
|
|
16985
|
+
kind: blocker.kind,
|
|
16986
|
+
lastSeenAt: now
|
|
16987
|
+
});
|
|
16988
|
+
}
|
|
16989
|
+
await workspace.writeFile(cachePath, jsonText({ schemaVersion: 1, workflow: report.workflow, blockers: [...blockers.values()] }), { silent: true });
|
|
16990
|
+
return {
|
|
16991
|
+
...report,
|
|
16992
|
+
knownBlockers: report.knownBlockers.map((blocker) => {
|
|
16993
|
+
if (!knownFingerprints.has(blockerFingerprint(blocker)))
|
|
16994
|
+
return blocker;
|
|
16995
|
+
return {
|
|
16996
|
+
...blocker,
|
|
16997
|
+
known: true,
|
|
16998
|
+
message: `Known baseline blocker, unrelated to this source change: ${blocker.message}`
|
|
16999
|
+
};
|
|
17000
|
+
})
|
|
17001
|
+
};
|
|
17002
|
+
};
|
|
16251
17003
|
|
|
16252
17004
|
class WorkflowRunner extends runner("workflow") {
|
|
16253
17005
|
list({ format = "markdown" } = {}) {
|
|
@@ -16269,13 +17021,13 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16269
17021
|
const spec = getWorkflowSpec(workflowSpecs, workflow2);
|
|
16270
17022
|
if (!spec)
|
|
16271
17023
|
throw new Error(`Unknown workflow: ${workflow2}. Run \`akan workflow list\` to see available workflows.`);
|
|
16272
|
-
const
|
|
17024
|
+
const plan2 = createWorkflowPlan(spec, compactWorkflowInputs(inputs));
|
|
16273
17025
|
if (out) {
|
|
16274
17026
|
const outPath = resolvePath(out);
|
|
16275
17027
|
await mkdir12(path42.dirname(outPath), { recursive: true });
|
|
16276
|
-
await writeFile2(outPath, jsonText(
|
|
17028
|
+
await writeFile2(outPath, jsonText(plan2));
|
|
16277
17029
|
}
|
|
16278
|
-
return format === "json" ? jsonText(
|
|
17030
|
+
return format === "json" ? jsonText(plan2) : renderWorkflowPlan(plan2);
|
|
16279
17031
|
}
|
|
16280
17032
|
async apply(planPath, {
|
|
16281
17033
|
dryRun = false,
|
|
@@ -16289,7 +17041,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16289
17041
|
const { artifact: artifact2 } = await writeWorkflowRunArtifact(workspace, report);
|
|
16290
17042
|
return renderWorkflowApply(artifact2, format);
|
|
16291
17043
|
};
|
|
16292
|
-
let
|
|
17044
|
+
let plan2;
|
|
16293
17045
|
try {
|
|
16294
17046
|
const parsed = JSON.parse(await readFile2(resolvePath(planPath), "utf8"));
|
|
16295
17047
|
if (!isWorkflowPlan2(parsed)) {
|
|
@@ -16302,7 +17054,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16302
17054
|
]);
|
|
16303
17055
|
return await renderApplyReport(report);
|
|
16304
17056
|
}
|
|
16305
|
-
|
|
17057
|
+
plan2 = parsed;
|
|
16306
17058
|
} catch (error) {
|
|
16307
17059
|
const report = failedApplyReport("unknown", [
|
|
16308
17060
|
{
|
|
@@ -16313,30 +17065,30 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16313
17065
|
]);
|
|
16314
17066
|
return await renderApplyReport(report);
|
|
16315
17067
|
}
|
|
16316
|
-
const spec = getWorkflowSpec(workflowSpecs,
|
|
17068
|
+
const spec = getWorkflowSpec(workflowSpecs, plan2.workflow);
|
|
16317
17069
|
if (!spec) {
|
|
16318
|
-
const report = failedApplyReport(
|
|
17070
|
+
const report = failedApplyReport(plan2.workflow, [
|
|
16319
17071
|
{
|
|
16320
17072
|
severity: "error",
|
|
16321
17073
|
code: "workflow-unknown",
|
|
16322
|
-
message: `Unknown workflow in plan: ${
|
|
17074
|
+
message: `Unknown workflow in plan: ${plan2.workflow}.`
|
|
16323
17075
|
}
|
|
16324
|
-
],
|
|
17076
|
+
], plan2);
|
|
16325
17077
|
return await renderApplyReport(report);
|
|
16326
17078
|
}
|
|
16327
17079
|
if (dryRun)
|
|
16328
|
-
return await renderApplyReport(createDryRunWorkflowApplyReport(
|
|
17080
|
+
return await renderApplyReport(createDryRunWorkflowApplyReport(plan2));
|
|
16329
17081
|
if (!registry) {
|
|
16330
|
-
const report = failedApplyReport(
|
|
17082
|
+
const report = failedApplyReport(plan2.workflow, [
|
|
16331
17083
|
{
|
|
16332
17084
|
severity: "error",
|
|
16333
17085
|
code: "workflow-registry-missing",
|
|
16334
17086
|
message: "Workflow apply requires a step runner registry."
|
|
16335
17087
|
}
|
|
16336
|
-
],
|
|
17088
|
+
], plan2);
|
|
16337
17089
|
return await renderApplyReport(report);
|
|
16338
17090
|
}
|
|
16339
|
-
return await renderApplyReport(await new WorkflowExecutor(registry).apply(
|
|
17091
|
+
return await renderApplyReport(await new WorkflowExecutor(registry).apply(plan2));
|
|
16340
17092
|
}
|
|
16341
17093
|
async validate(runIdOrPlan, {
|
|
16342
17094
|
format = "markdown",
|
|
@@ -16344,15 +17096,22 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16344
17096
|
execute
|
|
16345
17097
|
}) {
|
|
16346
17098
|
const loaded = await this.loadValidationTarget(runIdOrPlan, workspace);
|
|
16347
|
-
const
|
|
17099
|
+
const doctor = await AkanContextAnalyzer.doctor(workspace, {
|
|
17100
|
+
strict: true,
|
|
17101
|
+
runIdOrPlan,
|
|
17102
|
+
changedFiles: loaded.changedFiles
|
|
17103
|
+
});
|
|
17104
|
+
const report = await applyBaselineBlockerCache(workspace, await createWorkflowValidationRunReport({
|
|
16348
17105
|
workflow: loaded.plan?.workflow ?? loaded.workflow,
|
|
16349
17106
|
source: loaded.source,
|
|
16350
17107
|
plan: loaded.plan,
|
|
16351
17108
|
commands: loaded.commands,
|
|
16352
17109
|
execute: execute ?? defaultValidationExecutor(workspace),
|
|
16353
17110
|
diagnostics: loaded.diagnostics,
|
|
17111
|
+
baselineDiagnostics: (doctor.baselineDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "baseline")),
|
|
17112
|
+
workflowDiagnostics: (doctor.workflowDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "workflow")),
|
|
16354
17113
|
repairActions: loaded.repairActions
|
|
16355
|
-
});
|
|
17114
|
+
}));
|
|
16356
17115
|
await writeWorkflowRunArtifact(workspace, report);
|
|
16357
17116
|
return renderWorkflowValidation(report, format);
|
|
16358
17117
|
}
|
|
@@ -16391,6 +17150,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16391
17150
|
plan: artifact2.plan,
|
|
16392
17151
|
commands: artifact2.recommendedValidationCommands.length ? artifact2.recommendedValidationCommands : workflowCommandsForPlan(artifact2.plan),
|
|
16393
17152
|
diagnostics: artifact2.diagnostics,
|
|
17153
|
+
changedFiles: artifact2.changedFiles.map((file) => file.path),
|
|
16394
17154
|
repairActions: []
|
|
16395
17155
|
};
|
|
16396
17156
|
}
|
|
@@ -16401,6 +17161,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16401
17161
|
plan: artifact2.plan,
|
|
16402
17162
|
commands: artifact2.plan ? workflowCommandsForPlan(artifact2.plan) : [],
|
|
16403
17163
|
diagnostics: artifact2.diagnostics,
|
|
17164
|
+
changedFiles: [],
|
|
16404
17165
|
repairActions: artifact2.repairActions
|
|
16405
17166
|
};
|
|
16406
17167
|
}
|
|
@@ -16416,6 +17177,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16416
17177
|
message: `Workflow validation source is not supported: ${runIdOrPlan}.`
|
|
16417
17178
|
}
|
|
16418
17179
|
],
|
|
17180
|
+
changedFiles: [],
|
|
16419
17181
|
repairActions: []
|
|
16420
17182
|
};
|
|
16421
17183
|
};
|
|
@@ -16428,6 +17190,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16428
17190
|
plan: parsed,
|
|
16429
17191
|
commands: workflowCommandsForPlan(parsed),
|
|
16430
17192
|
diagnostics: parsed.diagnostics,
|
|
17193
|
+
changedFiles: workflowPathsForPlanLike(parsed),
|
|
16431
17194
|
repairActions: []
|
|
16432
17195
|
};
|
|
16433
17196
|
}
|
|
@@ -16450,6 +17213,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16450
17213
|
message: `Could not read workflow validation source: ${runIdOrPlan}. ${error instanceof Error ? error.message : ""}`.trim()
|
|
16451
17214
|
}
|
|
16452
17215
|
],
|
|
17216
|
+
changedFiles: [],
|
|
16453
17217
|
repairActions: []
|
|
16454
17218
|
};
|
|
16455
17219
|
}
|
|
@@ -16516,7 +17280,8 @@ var readonlyMcpTools = [
|
|
|
16516
17280
|
{ name: "list_modules", inputSchema: emptySchema },
|
|
16517
17281
|
{
|
|
16518
17282
|
name: "get_module_context",
|
|
16519
|
-
|
|
17283
|
+
description: "Return module context. Pass app in monorepos to disambiguate duplicate module names.",
|
|
17284
|
+
inputSchema: { type: "object", properties: { app: stringProperty, module: stringProperty }, required: ["module"] }
|
|
16520
17285
|
},
|
|
16521
17286
|
{
|
|
16522
17287
|
name: "get_guideline",
|
|
@@ -16651,6 +17416,7 @@ class ContextRunner extends runner("context") {
|
|
|
16651
17416
|
}
|
|
16652
17417
|
if (name === "get_workspace_summary" || name === "list_apps" || name === "list_modules" || name === "get_module_context") {
|
|
16653
17418
|
const context = await AkanContextAnalyzer.analyze(workspace, {
|
|
17419
|
+
app: args.app ?? null,
|
|
16654
17420
|
module: args.module ?? null,
|
|
16655
17421
|
includeAbstractContent: name === "get_module_context"
|
|
16656
17422
|
});
|
|
@@ -16660,7 +17426,28 @@ class ContextRunner extends runner("context") {
|
|
|
16660
17426
|
return context.apps;
|
|
16661
17427
|
if (name === "list_modules")
|
|
16662
17428
|
return AkanContextAnalyzer.findModules(context);
|
|
16663
|
-
|
|
17429
|
+
const modules = AkanContextAnalyzer.findModules(context, args.module, {
|
|
17430
|
+
app: args.app ?? null
|
|
17431
|
+
});
|
|
17432
|
+
if (name === "get_module_context" && !args.app && modules.length > 1) {
|
|
17433
|
+
return {
|
|
17434
|
+
diagnostics: [
|
|
17435
|
+
{
|
|
17436
|
+
severity: "error",
|
|
17437
|
+
code: "module-context-ambiguous",
|
|
17438
|
+
message: `Multiple modules match "${args.module}". Re-run get_module_context with an app argument.`,
|
|
17439
|
+
input: "app"
|
|
17440
|
+
}
|
|
17441
|
+
],
|
|
17442
|
+
candidates: modules.map((module) => ({
|
|
17443
|
+
app: module.sysName,
|
|
17444
|
+
sysType: module.sysType,
|
|
17445
|
+
module: module.name,
|
|
17446
|
+
path: module.path
|
|
17447
|
+
}))
|
|
17448
|
+
};
|
|
17449
|
+
}
|
|
17450
|
+
return modules;
|
|
16664
17451
|
}
|
|
16665
17452
|
if (name === "get_guideline")
|
|
16666
17453
|
return await Prompter.getInstruction(stringArg(args, "name"));
|
|
@@ -16689,6 +17476,16 @@ class ContextRunner extends runner("context") {
|
|
|
16689
17476
|
validationFailureScopes: ["workspace-config", "environment", "source-change", "unknown"],
|
|
16690
17477
|
artifactChainFields: ["planPath", "applyReportPath", "repairReportPath", "runId", "validationTarget", "next"],
|
|
16691
17478
|
diagnosticScopes: ["baseline", "workflow", "unknown"],
|
|
17479
|
+
validationStatuses: {
|
|
17480
|
+
sourceStatus: ["passed", "failed", "unknown"],
|
|
17481
|
+
workspaceStatus: ["passed", "failed", "unknown"],
|
|
17482
|
+
overallStatus: ["passed", "failed", "blocked-by-workspace-config", "blocked-by-environment"],
|
|
17483
|
+
knownBlockers: "Repeated workspace-config/environment failures are summarized before command output."
|
|
17484
|
+
},
|
|
17485
|
+
moduleContextInputs: {
|
|
17486
|
+
module: "required",
|
|
17487
|
+
app: "recommended in monorepos; required when module names are ambiguous"
|
|
17488
|
+
},
|
|
16692
17489
|
generatedFreshnessStatuses: ["fresh", "stale", "missing", "unknown"],
|
|
16693
17490
|
directEditFallbackPolicy: applyFirstPolicy,
|
|
16694
17491
|
applyReportFields: [
|
|
@@ -16714,12 +17511,12 @@ class ContextRunner extends runner("context") {
|
|
|
16714
17511
|
const workflow2 = stringArg(args, "workflow");
|
|
16715
17512
|
const inputs = workflowInputsArg(args);
|
|
16716
17513
|
const planPath = typeof args.out === "string" && args.out ? args.out : defaultWorkflowPlanPath(workflow2, inputs);
|
|
16717
|
-
const
|
|
17514
|
+
const plan2 = parseJsonOutput(await new WorkflowRunner().plan(workflow2, inputs, {
|
|
16718
17515
|
format: "json",
|
|
16719
17516
|
out: workspacePath(workspace, planPath)
|
|
16720
17517
|
}));
|
|
16721
17518
|
return {
|
|
16722
|
-
...
|
|
17519
|
+
...plan2,
|
|
16723
17520
|
planPath,
|
|
16724
17521
|
next: { tool: "apply_workflow", args: { planPath } },
|
|
16725
17522
|
policy: applyFirstPolicy
|