@akanjs/cli 2.3.9-rc.4 → 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 +736 -313
- package/index.js +937 -378
- package/package.json +2 -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();
|
|
@@ -4887,6 +4887,12 @@ var statusForScope = (commands, diagnostics, scopes, expectedScope) => {
|
|
|
4887
4887
|
return "unknown";
|
|
4888
4888
|
return hasScopeFailure(scopes, expectedScope, diagnostics) ? "failed" : "passed";
|
|
4889
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
|
+
};
|
|
4890
4896
|
var createKnownBlockers = (commands, diagnostics) => {
|
|
4891
4897
|
const commandBlockers = commands.filter((command) => command.status === "failed" && (command.failureScope === "workspace-config" || command.failureScope === "environment")).map((command) => ({
|
|
4892
4898
|
code: `workflow-validation-${command.failureScope}`,
|
|
@@ -4920,7 +4926,17 @@ var createValidationStatuses = (commands, diagnostics) => {
|
|
|
4920
4926
|
const sourceStatus = statusForScope(commands, diagnostics, scopes, "source-change");
|
|
4921
4927
|
const workspaceStatus = hasScopeFailure(scopes, "workspace-config", diagnostics) || hasScopeFailure(scopes, "environment", diagnostics) ? "failed" : commands.length || diagnostics.length ? "passed" : "unknown";
|
|
4922
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";
|
|
4923
|
-
return {
|
|
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
|
+
};
|
|
4924
4940
|
};
|
|
4925
4941
|
var createWorkflowValidationRunReport = async ({
|
|
4926
4942
|
runId = createWorkflowRunId("validation"),
|
|
@@ -5030,221 +5046,6 @@ var createRepairReport = ({
|
|
|
5030
5046
|
});
|
|
5031
5047
|
// pkgs/@akanjs/devkit/workflow/executor.ts
|
|
5032
5048
|
import { capitalize as capitalize2 } from "akanjs/common";
|
|
5033
|
-
var workflowStepKey = (workflow, stepId) => `${workflow}:${stepId}`;
|
|
5034
|
-
var primitiveReportToWorkflowStepResult = (report) => ({
|
|
5035
|
-
changedFiles: report.changedFiles,
|
|
5036
|
-
generatedFiles: report.generatedFiles,
|
|
5037
|
-
commands: report.validationCommands,
|
|
5038
|
-
diagnostics: report.diagnostics,
|
|
5039
|
-
recommendations: [],
|
|
5040
|
-
nextActions: report.nextActions
|
|
5041
|
-
});
|
|
5042
|
-
var workflowStringInput = (value) => typeof value === "string" ? value : null;
|
|
5043
|
-
var workflowStringListInput = (value) => Array.isArray(value) ? value.join(",") : null;
|
|
5044
|
-
var resolveWorkflowSys = async (workspace, target) => {
|
|
5045
|
-
if (!target)
|
|
5046
|
-
return null;
|
|
5047
|
-
const [apps, libs] = await workspace.getSyss();
|
|
5048
|
-
if (apps.includes(target))
|
|
5049
|
-
return AppExecutor.from(workspace, target);
|
|
5050
|
-
if (libs.includes(target))
|
|
5051
|
-
return LibExecutor.from(workspace, target);
|
|
5052
|
-
return null;
|
|
5053
|
-
};
|
|
5054
|
-
var targetMissing = (input2 = "app") => ({
|
|
5055
|
-
severity: "error",
|
|
5056
|
-
code: "workflow-target-missing",
|
|
5057
|
-
input: input2,
|
|
5058
|
-
message: "Workflow target app or library was not found."
|
|
5059
|
-
});
|
|
5060
|
-
var inputMissing = (input2) => ({
|
|
5061
|
-
severity: "error",
|
|
5062
|
-
code: "workflow-input-missing",
|
|
5063
|
-
input: input2,
|
|
5064
|
-
message: `Workflow input "${input2}" is required for apply.`
|
|
5065
|
-
});
|
|
5066
|
-
var unsupportedInput = (input2, message) => ({
|
|
5067
|
-
severity: "error",
|
|
5068
|
-
code: "workflow-input-unsupported",
|
|
5069
|
-
input: input2,
|
|
5070
|
-
message
|
|
5071
|
-
});
|
|
5072
|
-
var addFieldUiSurfaceInspection = (plan) => {
|
|
5073
|
-
const app = workflowStringInput(plan.inputs.app);
|
|
5074
|
-
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
5075
|
-
const field = workflowStringInput(plan.inputs.field) ?? "<field>";
|
|
5076
|
-
const typeName = workflowStringInput(plan.inputs.type);
|
|
5077
|
-
const isNumeric = typeName === "Int" || typeName === "Float" || typeName === "integer" || typeName === "float";
|
|
5078
|
-
const moduleClassName = capitalize2(module);
|
|
5079
|
-
const target = `${app ? `apps/${app}` : "*"}/lib/${module}/${moduleClassName}.Template.tsx`;
|
|
5080
|
-
return {
|
|
5081
|
-
recommendations: [
|
|
5082
|
-
{
|
|
5083
|
-
code: "add-field-ui-surface-review",
|
|
5084
|
-
kind: "manual-action",
|
|
5085
|
-
target,
|
|
5086
|
-
action: isNumeric ? `Review ${moduleClassName} Template/Unit/View/Store surfaces before adding ${field}; confirm the local Field.Text numeric parser/formatter, validation rule, and dictionary label pattern.` : `Add ${field} to ${moduleClassName} UI surfaces only when the local Field.* pattern is clear.`,
|
|
5087
|
-
confidence: "medium",
|
|
5088
|
-
message: isNumeric ? `No UI file was modified automatically because a safe numeric input component pattern is not yet detected for ${module}.${field}.` : `Review Template/Unit/View/Store surfaces for ${module}.${field}; no UI file was modified automatically.`
|
|
5089
|
-
}
|
|
5090
|
-
],
|
|
5091
|
-
nextActions: [
|
|
5092
|
-
{
|
|
5093
|
-
command: `akan workflow explain ${plan.workflow}`,
|
|
5094
|
-
reason: "Review UI surface guidance before manually editing ambiguous UI files."
|
|
5095
|
-
}
|
|
5096
|
-
]
|
|
5097
|
-
};
|
|
5098
|
-
};
|
|
5099
|
-
var createWorkflowStepRegistry = ({
|
|
5100
|
-
workspace,
|
|
5101
|
-
createModule,
|
|
5102
|
-
createScalar,
|
|
5103
|
-
createUi,
|
|
5104
|
-
addField,
|
|
5105
|
-
addEnumField
|
|
5106
|
-
}) => {
|
|
5107
|
-
const inspect = async () => {
|
|
5108
|
-
return;
|
|
5109
|
-
};
|
|
5110
|
-
const commandOnly = async () => {
|
|
5111
|
-
return;
|
|
5112
|
-
};
|
|
5113
|
-
return {
|
|
5114
|
-
inspectSystem: inspect,
|
|
5115
|
-
inspectModule: inspect,
|
|
5116
|
-
syncTarget: commandOnly,
|
|
5117
|
-
lintTarget: commandOnly,
|
|
5118
|
-
[workflowStepKey("create-module", "create-module")]: async (_step, plan) => {
|
|
5119
|
-
const app = workflowStringInput(plan.inputs.app);
|
|
5120
|
-
const module = workflowStringInput(plan.inputs.module);
|
|
5121
|
-
const sys2 = await resolveWorkflowSys(workspace, app);
|
|
5122
|
-
if (!sys2 || !module)
|
|
5123
|
-
return { diagnostics: [!sys2 ? targetMissing() : inputMissing("module")] };
|
|
5124
|
-
return primitiveReportToWorkflowStepResult(await createModule(sys2, module));
|
|
5125
|
-
},
|
|
5126
|
-
[workflowStepKey("create-scalar", "create-scalar")]: async (_step, plan) => {
|
|
5127
|
-
const app = workflowStringInput(plan.inputs.app);
|
|
5128
|
-
const scalar = workflowStringInput(plan.inputs.scalar);
|
|
5129
|
-
const sys2 = await resolveWorkflowSys(workspace, app);
|
|
5130
|
-
if (!sys2 || !scalar)
|
|
5131
|
-
return { diagnostics: [!sys2 ? targetMissing() : inputMissing("scalar")] };
|
|
5132
|
-
return primitiveReportToWorkflowStepResult(await createScalar(sys2, scalar));
|
|
5133
|
-
},
|
|
5134
|
-
[workflowStepKey("create-ui", "create-ui")]: async (_step, plan) => {
|
|
5135
|
-
const surface = workflowStringInput(plan.inputs.surface);
|
|
5136
|
-
if (surface !== "view" && surface !== "unit" && surface !== "template") {
|
|
5137
|
-
return {
|
|
5138
|
-
diagnostics: [
|
|
5139
|
-
unsupportedInput("surface", "Workflow apply currently supports create-ui surfaces: view, unit, template.")
|
|
5140
|
-
]
|
|
5141
|
-
};
|
|
5142
|
-
}
|
|
5143
|
-
return primitiveReportToWorkflowStepResult(await createUi({
|
|
5144
|
-
app: workflowStringInput(plan.inputs.app),
|
|
5145
|
-
module: workflowStringInput(plan.inputs.module),
|
|
5146
|
-
surface
|
|
5147
|
-
}));
|
|
5148
|
-
},
|
|
5149
|
-
[workflowStepKey("add-field", "update-constant")]: async (_step, plan) => {
|
|
5150
|
-
if (workflowStringInput(plan.inputs.type)?.toLowerCase() === "enum") {
|
|
5151
|
-
return primitiveReportToWorkflowStepResult(await addEnumField({
|
|
5152
|
-
app: workflowStringInput(plan.inputs.app),
|
|
5153
|
-
module: workflowStringInput(plan.inputs.module),
|
|
5154
|
-
field: workflowStringInput(plan.inputs.field),
|
|
5155
|
-
values: workflowStringListInput(plan.inputs.values),
|
|
5156
|
-
defaultValue: workflowStringInput(plan.inputs.default)
|
|
5157
|
-
}));
|
|
5158
|
-
}
|
|
5159
|
-
return primitiveReportToWorkflowStepResult(await addField({
|
|
5160
|
-
app: workflowStringInput(plan.inputs.app),
|
|
5161
|
-
module: workflowStringInput(plan.inputs.module),
|
|
5162
|
-
field: workflowStringInput(plan.inputs.field),
|
|
5163
|
-
type: workflowStringInput(plan.inputs.type),
|
|
5164
|
-
defaultValue: workflowStringInput(plan.inputs.default)
|
|
5165
|
-
}));
|
|
5166
|
-
},
|
|
5167
|
-
[workflowStepKey("add-field", "update-dictionary")]: inspect,
|
|
5168
|
-
[workflowStepKey("add-field", "update-ui-surfaces")]: async (_step, plan) => addFieldUiSurfaceInspection(plan),
|
|
5169
|
-
[workflowStepKey("add-enum-field", "update-constant")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addEnumField({
|
|
5170
|
-
app: workflowStringInput(plan.inputs.app),
|
|
5171
|
-
module: workflowStringInput(plan.inputs.module),
|
|
5172
|
-
field: workflowStringInput(plan.inputs.field),
|
|
5173
|
-
values: workflowStringListInput(plan.inputs.values),
|
|
5174
|
-
defaultValue: workflowStringInput(plan.inputs.default)
|
|
5175
|
-
})),
|
|
5176
|
-
[workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
|
|
5177
|
-
[workflowStepKey("add-enum-field", "update-option")]: inspect
|
|
5178
|
-
};
|
|
5179
|
-
};
|
|
5180
|
-
class WorkflowExecutor {
|
|
5181
|
-
registry;
|
|
5182
|
-
constructor(registry) {
|
|
5183
|
-
this.registry = registry;
|
|
5184
|
-
}
|
|
5185
|
-
async apply(plan) {
|
|
5186
|
-
const changedFiles = [];
|
|
5187
|
-
const generatedFiles = [];
|
|
5188
|
-
const recommendedValidationCommands = [];
|
|
5189
|
-
const diagnostics = [...plan.diagnostics];
|
|
5190
|
-
const recommendations = [...plan.recommendations];
|
|
5191
|
-
const nextActions = [];
|
|
5192
|
-
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
5193
|
-
return createWorkflowApplyReport({
|
|
5194
|
-
workflow: plan.workflow,
|
|
5195
|
-
mode: "apply",
|
|
5196
|
-
changedFiles,
|
|
5197
|
-
generatedFiles,
|
|
5198
|
-
recommendedValidationCommands,
|
|
5199
|
-
diagnostics,
|
|
5200
|
-
recommendations,
|
|
5201
|
-
nextActions,
|
|
5202
|
-
plan
|
|
5203
|
-
});
|
|
5204
|
-
}
|
|
5205
|
-
recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
|
|
5206
|
-
nextActions.push(...workflowCommandsForPlan(plan));
|
|
5207
|
-
for (const step of plan.steps) {
|
|
5208
|
-
const runner = this.registry[workflowStepKey(plan.workflow, step.id)] ?? this.registry[step.tool] ?? this.registry[step.id];
|
|
5209
|
-
if (!runner) {
|
|
5210
|
-
diagnostics.push({
|
|
5211
|
-
severity: "error",
|
|
5212
|
-
code: "workflow-step-unsupported",
|
|
5213
|
-
message: `Workflow ${plan.workflow} step "${step.id}" is not supported by workflow apply yet.`
|
|
5214
|
-
});
|
|
5215
|
-
nextActions.push({
|
|
5216
|
-
command: `akan workflow explain ${plan.workflow}`,
|
|
5217
|
-
reason: "Review the unsupported workflow step before retrying apply."
|
|
5218
|
-
});
|
|
5219
|
-
break;
|
|
5220
|
-
}
|
|
5221
|
-
const result = await runner(step, plan);
|
|
5222
|
-
if (!result)
|
|
5223
|
-
continue;
|
|
5224
|
-
changedFiles.push(...result.changedFiles ?? []);
|
|
5225
|
-
generatedFiles.push(...result.generatedFiles ?? []);
|
|
5226
|
-
recommendedValidationCommands.push(...result.commands ?? []);
|
|
5227
|
-
diagnostics.push(...result.diagnostics ?? []);
|
|
5228
|
-
recommendations.push(...result.recommendations ?? []);
|
|
5229
|
-
nextActions.push(...result.nextActions ?? []);
|
|
5230
|
-
if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
|
|
5231
|
-
break;
|
|
5232
|
-
}
|
|
5233
|
-
return createWorkflowApplyReport({
|
|
5234
|
-
workflow: plan.workflow,
|
|
5235
|
-
mode: "apply",
|
|
5236
|
-
changedFiles,
|
|
5237
|
-
generatedFiles,
|
|
5238
|
-
recommendedValidationCommands,
|
|
5239
|
-
diagnostics,
|
|
5240
|
-
recommendations,
|
|
5241
|
-
nextActions,
|
|
5242
|
-
plan
|
|
5243
|
-
});
|
|
5244
|
-
}
|
|
5245
|
-
}
|
|
5246
|
-
// pkgs/@akanjs/devkit/workflow/plan.ts
|
|
5247
|
-
import { capitalize as capitalize3 } from "akanjs/common";
|
|
5248
5049
|
|
|
5249
5050
|
// pkgs/@akanjs/devkit/workflow/primitive.ts
|
|
5250
5051
|
var createPrimitiveWriteReport = ({
|
|
@@ -5296,6 +5097,23 @@ var sourceFile = (sys2, path10, action, reason) => ({
|
|
|
5296
5097
|
action,
|
|
5297
5098
|
reason
|
|
5298
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
|
+
};
|
|
5299
5117
|
var generatedFilesForSync = (sys2, reason = "Generated files may change after sync.") => generatedFilePathsForTarget(getSysRoot(sys2), reason);
|
|
5300
5118
|
var validationCommandsForTarget = (target) => [
|
|
5301
5119
|
{ command: `akan sync ${target}`, reason: "Refresh generated Akan files from source conventions." },
|
|
@@ -5322,6 +5140,50 @@ var createPassedPrimitiveReport = ({
|
|
|
5322
5140
|
var scalarChangedFiles = (sys2, scalarName, files) => Object.values(files).map((file) => sourceFile(sys2, `lib/__scalar/${scalarName}/${file.filename}`, "create", "Scalar source file was created."));
|
|
5323
5141
|
var titleize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
5324
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
|
+
};
|
|
5325
5187
|
var normalizeFieldType = (typeName) => {
|
|
5326
5188
|
const normalizedTypes = {
|
|
5327
5189
|
string: "String",
|
|
@@ -5349,6 +5211,15 @@ var ensureBaseTypeImport = (content, typeName) => {
|
|
|
5349
5211
|
${content}`;
|
|
5350
5212
|
};
|
|
5351
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;
|
|
5352
5223
|
const trimmed = rawDefault.trim();
|
|
5353
5224
|
if (!trimmed || !Number.isFinite(Number(trimmed)))
|
|
5354
5225
|
return null;
|
|
@@ -5356,16 +5227,41 @@ var numericDefault = (typeName, rawDefault) => {
|
|
|
5356
5227
|
return null;
|
|
5357
5228
|
return trimmed;
|
|
5358
5229
|
};
|
|
5359
|
-
var
|
|
5360
|
-
if (
|
|
5361
|
-
return
|
|
5362
|
-
|
|
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
|
+
}
|
|
5363
5257
|
if (normalizedType === "Int" || normalizedType === "Float") {
|
|
5364
5258
|
const expression = numericDefault(normalizedType, defaultValue);
|
|
5365
5259
|
if (expression !== null)
|
|
5366
|
-
return { expression };
|
|
5260
|
+
return { expression, normalized: typeof defaultValue === "string", normalizedType };
|
|
5367
5261
|
return {
|
|
5368
5262
|
expression: null,
|
|
5263
|
+
normalized: false,
|
|
5264
|
+
normalizedType,
|
|
5369
5265
|
diagnostic: {
|
|
5370
5266
|
severity: "error",
|
|
5371
5267
|
code: "primitive-default-value-invalid",
|
|
@@ -5376,11 +5272,13 @@ var coerceFieldDefault = (typeName, defaultValue) => {
|
|
|
5376
5272
|
};
|
|
5377
5273
|
}
|
|
5378
5274
|
if (normalizedType === "Boolean") {
|
|
5379
|
-
const
|
|
5380
|
-
if (
|
|
5381
|
-
return { expression:
|
|
5275
|
+
const expression = booleanDefault(defaultValue);
|
|
5276
|
+
if (expression !== null)
|
|
5277
|
+
return { expression, normalized: typeof defaultValue === "string", normalizedType };
|
|
5382
5278
|
return {
|
|
5383
5279
|
expression: null,
|
|
5280
|
+
normalized: false,
|
|
5281
|
+
normalizedType,
|
|
5384
5282
|
diagnostic: {
|
|
5385
5283
|
severity: "error",
|
|
5386
5284
|
code: "primitive-default-value-invalid",
|
|
@@ -5390,11 +5288,49 @@ var coerceFieldDefault = (typeName, defaultValue) => {
|
|
|
5390
5288
|
}
|
|
5391
5289
|
};
|
|
5392
5290
|
}
|
|
5393
|
-
|
|
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
|
+
};
|
|
5394
5330
|
};
|
|
5395
|
-
var fieldExpression = (typeName, defaultValue) => {
|
|
5331
|
+
var fieldExpression = (typeName, defaultValue, options = {}) => {
|
|
5396
5332
|
const typeExpression = normalizeFieldType(typeName);
|
|
5397
|
-
const defaultExpression = coerceFieldDefault(typeExpression, defaultValue).expression;
|
|
5333
|
+
const defaultExpression = coerceFieldDefault(options.enumValues ? "enum" : typeExpression, defaultValue, options).expression;
|
|
5398
5334
|
const defaultOption = defaultExpression ? `, { default: ${defaultExpression} }` : "";
|
|
5399
5335
|
return `field(${typeExpression}${defaultOption})`;
|
|
5400
5336
|
};
|
|
@@ -5414,6 +5350,50 @@ var insertIntoObject = (content, className, line) => {
|
|
|
5414
5350
|
`;
|
|
5415
5351
|
return `${prefix}${insertion}${suffix}`;
|
|
5416
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
|
+
};
|
|
5417
5397
|
var ensureEnumImport = (content) => {
|
|
5418
5398
|
if (content.includes("enumOf"))
|
|
5419
5399
|
return content;
|
|
@@ -5443,45 +5423,312 @@ ${enumClass}`;
|
|
|
5443
5423
|
var insertDictionaryModelField = (content, moduleClassName, fieldName) => {
|
|
5444
5424
|
if (new RegExp(`\\b${fieldName}\\s*:`).test(content))
|
|
5445
5425
|
return content;
|
|
5446
|
-
const label =
|
|
5426
|
+
const label = bilingualLabelForField(fieldName);
|
|
5427
|
+
const desc = bilingualDescriptionForField(fieldName);
|
|
5447
5428
|
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
|
|
5448
5429
|
if (modelIndex < 0)
|
|
5449
5430
|
return null;
|
|
5450
5431
|
const objectEndIndex = content.indexOf(" }))", modelIndex);
|
|
5451
5432
|
if (objectEndIndex < 0)
|
|
5452
5433
|
return null;
|
|
5453
|
-
return `${content.slice(0, objectEndIndex)} ${fieldName}: t([${JSON.stringify(label)}, ${JSON.stringify(label)}]).desc([${JSON.stringify(
|
|
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)}]),
|
|
5454
5435
|
${content.slice(objectEndIndex)}`;
|
|
5455
5436
|
};
|
|
5456
|
-
var ensureConstantTypeImport = (content, constantPath, typeName) => {
|
|
5457
|
-
if (new RegExp(`import type \\{[^}]*\\b${typeName}\\b[^}]*\\} from "${constantPath}";`).test(content))
|
|
5458
|
-
return content;
|
|
5459
|
-
const importPattern = new RegExp(`import type \\{ ([^}]+) \\} from "${constantPath}";`);
|
|
5460
|
-
const existingImport = content.match(importPattern);
|
|
5461
|
-
if (existingImport !== null) {
|
|
5462
|
-
const names = existingImport[1]?.split(",").map((name) => name.trim()) ?? [];
|
|
5463
|
-
return content.replace(existingImport[0], `import type { ${[...names, typeName].sort().join(", ")} } from "${constantPath}";`);
|
|
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);
|
|
5530
|
+
return null;
|
|
5531
|
+
};
|
|
5532
|
+
var targetMissing = (input2 = "app") => ({
|
|
5533
|
+
severity: "error",
|
|
5534
|
+
code: "workflow-target-missing",
|
|
5535
|
+
input: input2,
|
|
5536
|
+
message: "Workflow target app or library was not found."
|
|
5537
|
+
});
|
|
5538
|
+
var inputMissing = (input2) => ({
|
|
5539
|
+
severity: "error",
|
|
5540
|
+
code: "workflow-input-missing",
|
|
5541
|
+
input: input2,
|
|
5542
|
+
message: `Workflow input "${input2}" is required for apply.`
|
|
5543
|
+
});
|
|
5544
|
+
var unsupportedInput = (input2, message) => ({
|
|
5545
|
+
severity: "error",
|
|
5546
|
+
code: "workflow-input-unsupported",
|
|
5547
|
+
input: input2,
|
|
5548
|
+
message
|
|
5549
|
+
});
|
|
5550
|
+
var addFieldUiSurfaceInspection = (plan) => {
|
|
5551
|
+
const app = workflowStringInput(plan.inputs.app);
|
|
5552
|
+
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
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;
|
|
5558
|
+
const moduleClassName = capitalize2(module);
|
|
5559
|
+
const target = `${app ? `apps/${app}` : "*"}/${moduleSourcePaths(module).template}`;
|
|
5560
|
+
return {
|
|
5561
|
+
recommendations: [
|
|
5562
|
+
{
|
|
5563
|
+
code: "add-field-ui-surface-review",
|
|
5564
|
+
kind: "manual-action",
|
|
5565
|
+
target,
|
|
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.`,
|
|
5567
|
+
confidence: "medium",
|
|
5568
|
+
message: `Review UI surfaces for ${module}.${field}; recommended component is ${policy.component}, with manual reasons and candidate positions in action.`
|
|
5569
|
+
}
|
|
5570
|
+
],
|
|
5571
|
+
nextActions: [
|
|
5572
|
+
{
|
|
5573
|
+
command: `akan workflow explain ${plan.workflow}`,
|
|
5574
|
+
reason: "Review UI surface guidance before manually editing ambiguous UI files."
|
|
5575
|
+
}
|
|
5576
|
+
]
|
|
5577
|
+
};
|
|
5578
|
+
};
|
|
5579
|
+
var createWorkflowStepRegistry = ({
|
|
5580
|
+
workspace,
|
|
5581
|
+
createModule,
|
|
5582
|
+
createScalar,
|
|
5583
|
+
createUi,
|
|
5584
|
+
addField,
|
|
5585
|
+
addEnumField
|
|
5586
|
+
}) => {
|
|
5587
|
+
const inspect = async () => {
|
|
5588
|
+
return;
|
|
5589
|
+
};
|
|
5590
|
+
const commandOnly = async () => {
|
|
5591
|
+
return;
|
|
5592
|
+
};
|
|
5593
|
+
return {
|
|
5594
|
+
inspectSystem: inspect,
|
|
5595
|
+
inspectModule: inspect,
|
|
5596
|
+
syncTarget: commandOnly,
|
|
5597
|
+
lintTarget: commandOnly,
|
|
5598
|
+
[workflowStepKey("create-module", "create-module")]: async (_step, plan) => {
|
|
5599
|
+
const app = workflowStringInput(plan.inputs.app);
|
|
5600
|
+
const module = workflowStringInput(plan.inputs.module);
|
|
5601
|
+
const sys2 = await resolveWorkflowSys(workspace, app);
|
|
5602
|
+
if (!sys2 || !module)
|
|
5603
|
+
return { diagnostics: [!sys2 ? targetMissing() : inputMissing("module")] };
|
|
5604
|
+
return primitiveReportToWorkflowStepResult(await createModule(sys2, module));
|
|
5605
|
+
},
|
|
5606
|
+
[workflowStepKey("create-scalar", "create-scalar")]: async (_step, plan) => {
|
|
5607
|
+
const app = workflowStringInput(plan.inputs.app);
|
|
5608
|
+
const scalar = workflowStringInput(plan.inputs.scalar);
|
|
5609
|
+
const sys2 = await resolveWorkflowSys(workspace, app);
|
|
5610
|
+
if (!sys2 || !scalar)
|
|
5611
|
+
return { diagnostics: [!sys2 ? targetMissing() : inputMissing("scalar")] };
|
|
5612
|
+
return primitiveReportToWorkflowStepResult(await createScalar(sys2, scalar));
|
|
5613
|
+
},
|
|
5614
|
+
[workflowStepKey("create-ui", "create-ui")]: async (_step, plan) => {
|
|
5615
|
+
const surface = workflowStringInput(plan.inputs.surface);
|
|
5616
|
+
if (surface !== "view" && surface !== "unit" && surface !== "template") {
|
|
5617
|
+
return {
|
|
5618
|
+
diagnostics: [
|
|
5619
|
+
unsupportedInput("surface", "Workflow apply currently supports create-ui surfaces: view, unit, template.")
|
|
5620
|
+
]
|
|
5621
|
+
};
|
|
5622
|
+
}
|
|
5623
|
+
return primitiveReportToWorkflowStepResult(await createUi({
|
|
5624
|
+
app: workflowStringInput(plan.inputs.app),
|
|
5625
|
+
module: workflowStringInput(plan.inputs.module),
|
|
5626
|
+
surface
|
|
5627
|
+
}));
|
|
5628
|
+
},
|
|
5629
|
+
[workflowStepKey("add-field", "update-constant")]: async (_step, plan) => {
|
|
5630
|
+
if (workflowStringInput(plan.inputs.type)?.toLowerCase() === "enum") {
|
|
5631
|
+
return primitiveReportToWorkflowStepResult(await addEnumField({
|
|
5632
|
+
app: workflowStringInput(plan.inputs.app),
|
|
5633
|
+
module: workflowStringInput(plan.inputs.module),
|
|
5634
|
+
field: workflowStringInput(plan.inputs.field),
|
|
5635
|
+
values: workflowStringListInput(plan.inputs.values),
|
|
5636
|
+
defaultValue: workflowStringInput(plan.inputs.default),
|
|
5637
|
+
surfaces: workflowStringArrayInput(plan.inputs.surfaces),
|
|
5638
|
+
includeInLight: workflowBooleanInput(plan.inputs.includeInLight)
|
|
5639
|
+
}));
|
|
5640
|
+
}
|
|
5641
|
+
return primitiveReportToWorkflowStepResult(await addField({
|
|
5642
|
+
app: workflowStringInput(plan.inputs.app),
|
|
5643
|
+
module: workflowStringInput(plan.inputs.module),
|
|
5644
|
+
field: workflowStringInput(plan.inputs.field),
|
|
5645
|
+
type: workflowStringInput(plan.inputs.type),
|
|
5646
|
+
defaultValue: workflowStringInput(plan.inputs.default),
|
|
5647
|
+
surfaces: workflowStringArrayInput(plan.inputs.surfaces),
|
|
5648
|
+
includeInLight: workflowBooleanInput(plan.inputs.includeInLight)
|
|
5649
|
+
}));
|
|
5650
|
+
},
|
|
5651
|
+
[workflowStepKey("add-field", "update-dictionary")]: inspect,
|
|
5652
|
+
[workflowStepKey("add-field", "update-ui-surfaces")]: async (_step, plan) => addFieldUiSurfaceInspection(plan),
|
|
5653
|
+
[workflowStepKey("add-enum-field", "update-constant")]: async (_step, plan) => primitiveReportToWorkflowStepResult(await addEnumField({
|
|
5654
|
+
app: workflowStringInput(plan.inputs.app),
|
|
5655
|
+
module: workflowStringInput(plan.inputs.module),
|
|
5656
|
+
field: workflowStringInput(plan.inputs.field),
|
|
5657
|
+
values: workflowStringListInput(plan.inputs.values),
|
|
5658
|
+
defaultValue: workflowStringInput(plan.inputs.default)
|
|
5659
|
+
})),
|
|
5660
|
+
[workflowStepKey("add-enum-field", "update-dictionary")]: inspect,
|
|
5661
|
+
[workflowStepKey("add-enum-field", "update-option")]: inspect
|
|
5662
|
+
};
|
|
5663
|
+
};
|
|
5664
|
+
class WorkflowExecutor {
|
|
5665
|
+
registry;
|
|
5666
|
+
constructor(registry) {
|
|
5667
|
+
this.registry = registry;
|
|
5464
5668
|
}
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
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
|
+
}
|
|
5484
5730
|
// pkgs/@akanjs/devkit/workflow/plan.ts
|
|
5731
|
+
import { capitalize as capitalize3 } from "akanjs/common";
|
|
5485
5732
|
var surfaceModes = new Set(["infer", "include", "skip"]);
|
|
5486
5733
|
var parseStringList = (value) => {
|
|
5487
5734
|
if (Array.isArray(value)) {
|
|
@@ -5499,6 +5746,18 @@ var normalizeInputValue = (name, spec, value) => {
|
|
|
5499
5746
|
const values = parseStringList(value);
|
|
5500
5747
|
return values && values.length > 0 ? values : null;
|
|
5501
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
|
+
}
|
|
5502
5761
|
if (typeof value === "string" && surfaceModes.has(value))
|
|
5503
5762
|
return value;
|
|
5504
5763
|
throw new Error(`Unsupported workflow input value for ${name}`);
|
|
@@ -5506,17 +5765,45 @@ var normalizeInputValue = (name, spec, value) => {
|
|
|
5506
5765
|
var listWorkflowSpecs = (specs) => [...specs].sort((a, b) => a.name.localeCompare(b.name));
|
|
5507
5766
|
var getWorkflowSpec = (specs, name) => specs.find((spec) => spec.name === name) ?? null;
|
|
5508
5767
|
var compactWorkflowInputs = (inputs) => Object.fromEntries(Object.entries(inputs).filter(([, value]) => value !== null && value !== ""));
|
|
5509
|
-
var
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
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
|
+
];
|
|
5520
5807
|
};
|
|
5521
5808
|
var createAddFieldRecommendations = (inputs) => {
|
|
5522
5809
|
const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
|
|
@@ -5525,16 +5812,18 @@ var createAddFieldRecommendations = (inputs) => {
|
|
|
5525
5812
|
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
5526
5813
|
if (!typeName)
|
|
5527
5814
|
return [];
|
|
5528
|
-
const
|
|
5529
|
-
const
|
|
5530
|
-
const
|
|
5531
|
-
const
|
|
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;
|
|
5532
5821
|
return [
|
|
5533
5822
|
...normalizedType === "Int" || normalizedType === "Float" ? [
|
|
5534
5823
|
{
|
|
5535
5824
|
code: "add-field-import",
|
|
5536
5825
|
kind: "import",
|
|
5537
|
-
target:
|
|
5826
|
+
target: paths.constant,
|
|
5538
5827
|
confidence: "high",
|
|
5539
5828
|
message: `Import ${normalizedType} from "akanjs/base" before writing field(${normalizedType}).`
|
|
5540
5829
|
}
|
|
@@ -5542,35 +5831,122 @@ var createAddFieldRecommendations = (inputs) => {
|
|
|
5542
5831
|
{
|
|
5543
5832
|
code: "add-field-placement-constant",
|
|
5544
5833
|
kind: "placement",
|
|
5545
|
-
target:
|
|
5834
|
+
target: paths.constant,
|
|
5546
5835
|
confidence: "high",
|
|
5547
5836
|
message: `Insert ${field}: field(${normalizedType}) in ${capitalize3(module)}Input.`
|
|
5548
5837
|
},
|
|
5549
5838
|
{
|
|
5550
5839
|
code: "add-field-placement-dictionary",
|
|
5551
5840
|
kind: "placement",
|
|
5552
|
-
target:
|
|
5841
|
+
target: paths.dictionary,
|
|
5553
5842
|
confidence: "high",
|
|
5554
5843
|
message: `Add dictionary labels for ${module}.${field}.`
|
|
5555
5844
|
},
|
|
5556
5845
|
{
|
|
5557
5846
|
code: "add-field-component",
|
|
5558
5847
|
kind: "ui-component",
|
|
5559
|
-
target:
|
|
5560
|
-
confidence:
|
|
5561
|
-
action:
|
|
5562
|
-
message:
|
|
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}.`
|
|
5563
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
|
+
] : [],
|
|
5564
5891
|
{
|
|
5565
5892
|
code: "add-field-ui-manual-review",
|
|
5566
5893
|
kind: "manual-action",
|
|
5567
|
-
target:
|
|
5568
|
-
action: `Review ${app}:${module} Template
|
|
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.`,
|
|
5569
5896
|
confidence: "medium",
|
|
5570
|
-
message:
|
|
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."
|
|
5571
5940
|
}
|
|
5572
5941
|
];
|
|
5573
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
|
+
};
|
|
5574
5950
|
var createWorkflowPlanRecommendations = (spec, inputs) => [
|
|
5575
5951
|
{
|
|
5576
5952
|
code: "workflow-apply-first",
|
|
@@ -5634,14 +6010,32 @@ var createWorkflowPlan = (spec, rawInputs) => {
|
|
|
5634
6010
|
message: `Field type "${fieldType}" is ambiguous in Akan. Use Int for integer fields or Float for decimal fields.`
|
|
5635
6011
|
});
|
|
5636
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
|
+
}
|
|
5637
6031
|
return {
|
|
5638
6032
|
schemaVersion: 1,
|
|
5639
6033
|
workflow: spec.name,
|
|
5640
6034
|
mode: "plan",
|
|
5641
6035
|
inputs,
|
|
5642
|
-
optionalSurfaces: spec
|
|
6036
|
+
optionalSurfaces: createWorkflowPlanOptionalSurfaces(spec, inputs),
|
|
5643
6037
|
steps: spec.steps,
|
|
5644
|
-
predictedChanges: spec
|
|
6038
|
+
predictedChanges: createWorkflowPlanPredictedChanges(spec, inputs),
|
|
5645
6039
|
validation: spec.validation,
|
|
5646
6040
|
diagnostics,
|
|
5647
6041
|
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
|
@@ -5710,35 +6104,55 @@ var renderWorkflowPlan = (plan) => [
|
|
|
5710
6104
|
""
|
|
5711
6105
|
].join(`
|
|
5712
6106
|
`);
|
|
5713
|
-
var
|
|
5714
|
-
|
|
5715
|
-
""
|
|
5716
|
-
`-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
""
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
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}`;
|
|
6111
|
+
};
|
|
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(`
|
|
5741
6154
|
`);
|
|
6155
|
+
};
|
|
5742
6156
|
var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
5743
6157
|
var renderWorkflowValidationRunReport = (report) => [
|
|
5744
6158
|
`# Workflow Validation: ${report.workflow}`,
|
|
@@ -5749,11 +6163,18 @@ var renderWorkflowValidationRunReport = (report) => [
|
|
|
5749
6163
|
`- Workspace status: ${report.workspaceStatus}`,
|
|
5750
6164
|
`- Overall status: ${report.overallStatus}`,
|
|
5751
6165
|
"",
|
|
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}`,
|
|
6171
|
+
"",
|
|
5752
6172
|
"## Known Blockers",
|
|
5753
6173
|
...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
|
|
5754
6174
|
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
5755
6175
|
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
5756
|
-
|
|
6176
|
+
const known = blocker.known ? " known" : "";
|
|
6177
|
+
return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
|
|
5757
6178
|
}) : ["- none"],
|
|
5758
6179
|
"",
|
|
5759
6180
|
"## Commands",
|
|
@@ -12661,6 +13082,8 @@ var NODE_NATIVE_MODULE_SET = new Set([
|
|
|
12661
13082
|
]);
|
|
12662
13083
|
// pkgs/@akanjs/devkit/getCredentials.ts
|
|
12663
13084
|
import yaml from "js-yaml";
|
|
13085
|
+
// pkgs/@akanjs/devkit/getModelFileData.ts
|
|
13086
|
+
import { capitalize as capitalize8 } from "akanjs/common";
|
|
12664
13087
|
// pkgs/@akanjs/devkit/getRelatedCnsts.ts
|
|
12665
13088
|
import { readFileSync as readFileSync4, realpathSync } from "fs";
|
|
12666
13089
|
import ora2 from "ora";
|
|
@@ -14611,7 +15034,7 @@ import path43 from "path";
|
|
|
14611
15034
|
|
|
14612
15035
|
// pkgs/@akanjs/cli/module/module.script.ts
|
|
14613
15036
|
import { input as input6 } from "@inquirer/prompts";
|
|
14614
|
-
import { capitalize as
|
|
15037
|
+
import { capitalize as capitalize11, randomPicks } from "akanjs/common";
|
|
14615
15038
|
|
|
14616
15039
|
// pkgs/@akanjs/cli/page/page.runner.ts
|
|
14617
15040
|
class PageRunner extends runner("page") {
|
|
@@ -14641,7 +15064,7 @@ function pluralizeName(name) {
|
|
|
14641
15064
|
}
|
|
14642
15065
|
|
|
14643
15066
|
// pkgs/@akanjs/cli/module/module.request.ts
|
|
14644
|
-
import { capitalize as
|
|
15067
|
+
import { capitalize as capitalize9 } from "akanjs/common";
|
|
14645
15068
|
|
|
14646
15069
|
// pkgs/@akanjs/cli/module/module.prompt.ts
|
|
14647
15070
|
var componentDefaultDescription = ({
|
|
@@ -14766,7 +15189,7 @@ var requestTemplate = ({
|
|
|
14766
15189
|
${componentDefaultDescription({
|
|
14767
15190
|
sysName,
|
|
14768
15191
|
modelName,
|
|
14769
|
-
ModelName: ModelName ??
|
|
15192
|
+
ModelName: ModelName ?? capitalize9(modelName),
|
|
14770
15193
|
exampleFiles,
|
|
14771
15194
|
constant,
|
|
14772
15195
|
properties
|
|
@@ -14823,7 +15246,7 @@ var requestView = ({
|
|
|
14823
15246
|
${componentDefaultDescription({
|
|
14824
15247
|
sysName,
|
|
14825
15248
|
modelName,
|
|
14826
|
-
ModelName: ModelName ??
|
|
15249
|
+
ModelName: ModelName ?? capitalize9(modelName),
|
|
14827
15250
|
exampleFiles,
|
|
14828
15251
|
constant,
|
|
14829
15252
|
properties
|
|
@@ -14884,7 +15307,7 @@ var requestUnit = ({
|
|
|
14884
15307
|
${componentDefaultDescription({
|
|
14885
15308
|
sysName,
|
|
14886
15309
|
modelName,
|
|
14887
|
-
ModelName: ModelName ??
|
|
15310
|
+
ModelName: ModelName ?? capitalize9(modelName),
|
|
14888
15311
|
exampleFiles,
|
|
14889
15312
|
constant,
|
|
14890
15313
|
properties
|
|
@@ -14933,22 +15356,29 @@ var requestUnit = ({
|
|
|
14933
15356
|
`;
|
|
14934
15357
|
|
|
14935
15358
|
// pkgs/@akanjs/cli/module/module.runner.ts
|
|
14936
|
-
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
|
+
};
|
|
14937
15365
|
var moduleAbstractContent = (moduleName) => {
|
|
14938
|
-
const title =
|
|
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.`;
|
|
14939
15369
|
return `# ${title} Module Abstract
|
|
14940
15370
|
|
|
14941
15371
|
## Purpose
|
|
14942
15372
|
|
|
14943
|
-
${
|
|
15373
|
+
${purpose}
|
|
14944
15374
|
|
|
14945
15375
|
## Domain Rules
|
|
14946
15376
|
|
|
14947
|
-
-
|
|
15377
|
+
- Keep ${label.en.toLowerCase()} data consistent with user-facing dictionary labels.
|
|
14948
15378
|
|
|
14949
15379
|
## Data Meaning
|
|
14950
15380
|
|
|
14951
|
-
|
|
15381
|
+
${label.en} (${label.ko}) is the primary business concept for this module.
|
|
14952
15382
|
|
|
14953
15383
|
## Workflows
|
|
14954
15384
|
|
|
@@ -14965,6 +15395,7 @@ No lifecycle workflow yet.
|
|
|
14965
15395
|
- None yet.
|
|
14966
15396
|
`;
|
|
14967
15397
|
};
|
|
15398
|
+
var localModuleFilename = (moduleName, pathKey2) => moduleSourcePaths(moduleName)[pathKey2].replace(`lib/${moduleName}/`, "");
|
|
14968
15399
|
|
|
14969
15400
|
class ModuleRunner extends runner("module") {
|
|
14970
15401
|
async createService(module) {
|
|
@@ -14995,24 +15426,47 @@ class ModuleRunner extends runner("module") {
|
|
|
14995
15426
|
async createComponentTemplate(module, type) {
|
|
14996
15427
|
await module.sys.applyTemplate({
|
|
14997
15428
|
basePath: `./lib/${module.name}`,
|
|
14998
|
-
template: `module/__Model__.${
|
|
15429
|
+
template: `module/__Model__.${capitalize10(type)}.tsx`,
|
|
14999
15430
|
dict: { model: module.name, appName: module.sys.name }
|
|
15000
15431
|
});
|
|
15001
15432
|
return {
|
|
15002
15433
|
component: {
|
|
15003
|
-
filename: `${module.name}.${
|
|
15004
|
-
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`)
|
|
15005
15436
|
}
|
|
15006
15437
|
};
|
|
15007
15438
|
}
|
|
15008
15439
|
async createModuleTemplate(module) {
|
|
15009
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
|
+
};
|
|
15010
15456
|
await module.applyTemplate({
|
|
15011
15457
|
basePath: `.`,
|
|
15012
15458
|
template: "module",
|
|
15013
|
-
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
|
+
}
|
|
15014
15468
|
});
|
|
15015
|
-
await module.writeFile(
|
|
15469
|
+
await module.writeFile(filenames.abstract, moduleAbstractContent(module.name));
|
|
15016
15470
|
const [
|
|
15017
15471
|
abstractContent,
|
|
15018
15472
|
constantContent,
|
|
@@ -15026,30 +15480,30 @@ class ModuleRunner extends runner("module") {
|
|
|
15026
15480
|
zoneContent,
|
|
15027
15481
|
utilContent
|
|
15028
15482
|
] = await Promise.all([
|
|
15029
|
-
module.readFile(
|
|
15030
|
-
module.readFile(
|
|
15031
|
-
module.readFile(
|
|
15032
|
-
module.readFile(
|
|
15033
|
-
module.readFile(
|
|
15034
|
-
module.readFile(
|
|
15035
|
-
module.readFile(
|
|
15036
|
-
module.readFile(
|
|
15037
|
-
module.readFile(
|
|
15038
|
-
module.readFile(
|
|
15039
|
-
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)
|
|
15040
15494
|
]);
|
|
15041
15495
|
return {
|
|
15042
|
-
abstract: { filename:
|
|
15043
|
-
constant: { filename:
|
|
15044
|
-
dictionary: { filename:
|
|
15045
|
-
service: { filename:
|
|
15046
|
-
store: { filename:
|
|
15047
|
-
signal: { filename:
|
|
15048
|
-
unit: { filename:
|
|
15049
|
-
view: { filename:
|
|
15050
|
-
template: { filename:
|
|
15051
|
-
zone: { filename:
|
|
15052
|
-
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 }
|
|
15053
15507
|
};
|
|
15054
15508
|
}
|
|
15055
15509
|
}
|
|
@@ -15151,8 +15605,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15151
15605
|
async createTest(workspace, name) {}
|
|
15152
15606
|
async createTemplate(mod) {
|
|
15153
15607
|
const { component: template } = await this.moduleRunner.createComponentTemplate(mod, "template");
|
|
15154
|
-
const
|
|
15155
|
-
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);
|
|
15156
15611
|
const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15157
15612
|
const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15158
15613
|
const session = new AiSession("createTemplate", { workspace: mod.sys.workspace, cacheKey: mod.name });
|
|
@@ -15178,8 +15633,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15178
15633
|
}
|
|
15179
15634
|
async createUnit(mod) {
|
|
15180
15635
|
const { component: unit } = await this.moduleRunner.createComponentTemplate(mod, "unit");
|
|
15181
|
-
const
|
|
15182
|
-
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));
|
|
15183
15639
|
const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15184
15640
|
const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15185
15641
|
const session = new AiSession("createUnit", { workspace: mod.sys.workspace, cacheKey: mod.name });
|
|
@@ -15203,8 +15659,9 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15203
15659
|
}
|
|
15204
15660
|
async createView(mod) {
|
|
15205
15661
|
const { component: view } = await this.moduleRunner.createComponentTemplate(mod, "view");
|
|
15206
|
-
const
|
|
15207
|
-
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);
|
|
15208
15665
|
const relatedCnsts = getRelatedCnsts(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15209
15666
|
const constant = await FileSys.readText(`${mod.sys.cwdPath}/lib/${mod.name}/${mod.name}.constant.ts`);
|
|
15210
15667
|
const session = new AiSession("createView", { workspace: mod.sys.workspace, cacheKey: mod.name });
|
|
@@ -15229,7 +15686,7 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
|
|
|
15229
15686
|
}
|
|
15230
15687
|
|
|
15231
15688
|
// pkgs/@akanjs/cli/primitive/primitive.script.ts
|
|
15232
|
-
import { capitalize as
|
|
15689
|
+
import { capitalize as capitalize12 } from "akanjs/common";
|
|
15233
15690
|
class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
15234
15691
|
async resolveSys(workspace, target) {
|
|
15235
15692
|
if (!target)
|
|
@@ -15277,7 +15734,7 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15277
15734
|
}
|
|
15278
15735
|
async addEnumField(workspace, input7) {
|
|
15279
15736
|
const values = parseValues(input7.values);
|
|
15280
|
-
return await this.addFieldToSources(workspace, { ...input7, type: `${
|
|
15737
|
+
return await this.addFieldToSources(workspace, { ...input7, type: `${capitalize12(input7.field ?? "")}` }, { enumValues: values });
|
|
15281
15738
|
}
|
|
15282
15739
|
async addFieldToSources(workspace, input7, { enumValues }) {
|
|
15283
15740
|
const sys3 = await this.resolveSys(workspace, input7.app);
|
|
@@ -15321,10 +15778,12 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15321
15778
|
nextActions: []
|
|
15322
15779
|
});
|
|
15323
15780
|
}
|
|
15324
|
-
const moduleClassName =
|
|
15781
|
+
const moduleClassName = capitalize12(input7.module);
|
|
15325
15782
|
const inputClassName = `${moduleClassName}Input`;
|
|
15326
|
-
const
|
|
15327
|
-
const
|
|
15783
|
+
const paths = moduleSourcePaths(input7.module);
|
|
15784
|
+
const constantPath = paths.constant;
|
|
15785
|
+
const dictionaryPath = paths.dictionary;
|
|
15786
|
+
const templatePath = paths.template;
|
|
15328
15787
|
const changedFiles = [];
|
|
15329
15788
|
const generatedFiles2 = generatedFilesForSync(sys3);
|
|
15330
15789
|
const [hasConstant, hasDictionary] = await Promise.all([sys3.exists(constantPath), sys3.exists(dictionaryPath)]);
|
|
@@ -15363,8 +15822,8 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15363
15822
|
});
|
|
15364
15823
|
}
|
|
15365
15824
|
if (enumValues) {
|
|
15366
|
-
const enumClassName = `${moduleClassName}${
|
|
15367
|
-
const enumName = `${lowerlize(moduleClassName)}${
|
|
15825
|
+
const enumClassName = `${moduleClassName}${capitalize12(input7.field)}`;
|
|
15826
|
+
const enumName = `${lowerlize(moduleClassName)}${capitalize12(input7.field)}`;
|
|
15368
15827
|
constantContent = insertEnumClass(ensureEnumImport(constantContent), enumClassName, enumName, enumValues);
|
|
15369
15828
|
dictionaryContent = ensureConstantTypeImport(dictionaryContent, `./${input7.module}.constant`, enumClassName);
|
|
15370
15829
|
input7.type = enumClassName;
|
|
@@ -15386,9 +15845,15 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15386
15845
|
diagnostics.push(defaultCoercion.diagnostic);
|
|
15387
15846
|
constantContent = ensureBaseTypeImport(constantContent, input7.type);
|
|
15388
15847
|
}
|
|
15389
|
-
|
|
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;
|
|
15390
15855
|
const nextDictionaryContent = insertDictionaryModelField(dictionaryContent, moduleClassName, input7.field);
|
|
15391
|
-
if (
|
|
15856
|
+
if (nextConstantContentWithLight && (input7.type === "Int" || input7.type === "Float") && new RegExp(`\\b${input7.field}\\s*:\\s*field\\(${input7.type}, \\{ default: "`, "m").test(nextConstantContentWithLight)) {
|
|
15392
15857
|
diagnostics.push({
|
|
15393
15858
|
severity: "error",
|
|
15394
15859
|
code: "primitive-default-value-invalid",
|
|
@@ -15397,7 +15862,7 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15397
15862
|
message: `Generated ${input7.type} default for "${input7.field}" would be a string literal; refusing to write source.`
|
|
15398
15863
|
});
|
|
15399
15864
|
}
|
|
15400
|
-
if (
|
|
15865
|
+
if (nextConstantContentWithLight && (input7.type === "Int" || input7.type === "Float") && !new RegExp(`import \\{[^}]*\\b${input7.type}\\b[^}]*\\} from "akanjs/base";`).test(nextConstantContentWithLight)) {
|
|
15401
15866
|
diagnostics.push({
|
|
15402
15867
|
severity: "error",
|
|
15403
15868
|
code: "primitive-base-type-import-missing",
|
|
@@ -15412,6 +15877,14 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15412
15877
|
message: `Could not find ${inputClassName} object shape in ${constantPath}.`
|
|
15413
15878
|
});
|
|
15414
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
|
+
}
|
|
15415
15888
|
if (!nextDictionaryContent) {
|
|
15416
15889
|
diagnostics.push({
|
|
15417
15890
|
severity: "error",
|
|
@@ -15419,10 +15892,51 @@ class PrimitiveScript extends script("primitive", [ModuleScript]) {
|
|
|
15419
15892
|
message: `Could not find ${moduleClassName} dictionary model shape in ${dictionaryPath}.`
|
|
15420
15893
|
});
|
|
15421
15894
|
}
|
|
15422
|
-
|
|
15423
|
-
|
|
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);
|
|
15424
15934
|
await sys3.writeFile(dictionaryPath, nextDictionaryContent);
|
|
15425
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
|
+
}
|
|
15426
15940
|
}
|
|
15427
15941
|
return createPrimitiveWriteReport({
|
|
15428
15942
|
command: enumValues ? "add-enum-field" : "add-field",
|
|
@@ -15791,7 +16305,7 @@ class ScalarScript extends script("scalar", [ScalarRunner]) {
|
|
|
15791
16305
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
15792
16306
|
import { mkdir as mkdir12, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
15793
16307
|
import path42 from "path";
|
|
15794
|
-
import { capitalize as
|
|
16308
|
+
import { capitalize as capitalize13 } from "akanjs/common";
|
|
15795
16309
|
|
|
15796
16310
|
// pkgs/@akanjs/cli/workflows/shared.ts
|
|
15797
16311
|
var sysInputs = {
|
|
@@ -15907,7 +16421,15 @@ var addFieldWorkflowSpec = {
|
|
|
15907
16421
|
values: { type: "string-list", description: "Comma-separated enum values when type is enum." },
|
|
15908
16422
|
default: {
|
|
15909
16423
|
type: "string",
|
|
15910
|
-
description: "Optional default value.
|
|
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."
|
|
15911
16433
|
}
|
|
15912
16434
|
},
|
|
15913
16435
|
optionalSurfaces: {
|
|
@@ -16430,7 +16952,7 @@ var planInputString2 = (plan2, key) => {
|
|
|
16430
16952
|
var workflowPathsForPlanLike = (plan2) => {
|
|
16431
16953
|
const app = planInputString2(plan2, "app");
|
|
16432
16954
|
const module = planInputString2(plan2, "module");
|
|
16433
|
-
const moduleClass = module ?
|
|
16955
|
+
const moduleClass = module ? capitalize13(module) : "<Module>";
|
|
16434
16956
|
return plan2.predictedChanges.map((change) => change.target.replace(/^\*\//, app ? `apps/${app}/` : "").replaceAll("<module>", module || "<module>").replaceAll("<Module>", moduleClass));
|
|
16435
16957
|
};
|
|
16436
16958
|
var workflowDiagnosticFromDoctor = (diagnostic, fallbackScope) => ({
|
|
@@ -16441,6 +16963,43 @@ var workflowDiagnosticFromDoctor = (diagnostic, fallbackScope) => ({
|
|
|
16441
16963
|
failureScope: (diagnostic.scope ?? fallbackScope) === "baseline" ? "workspace-config" : (diagnostic.scope ?? fallbackScope) === "workflow" ? "source-change" : "unknown",
|
|
16442
16964
|
context: diagnostic.context
|
|
16443
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
|
+
};
|
|
16444
17003
|
|
|
16445
17004
|
class WorkflowRunner extends runner("workflow") {
|
|
16446
17005
|
list({ format = "markdown" } = {}) {
|
|
@@ -16542,7 +17101,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16542
17101
|
runIdOrPlan,
|
|
16543
17102
|
changedFiles: loaded.changedFiles
|
|
16544
17103
|
});
|
|
16545
|
-
const report = await createWorkflowValidationRunReport({
|
|
17104
|
+
const report = await applyBaselineBlockerCache(workspace, await createWorkflowValidationRunReport({
|
|
16546
17105
|
workflow: loaded.plan?.workflow ?? loaded.workflow,
|
|
16547
17106
|
source: loaded.source,
|
|
16548
17107
|
plan: loaded.plan,
|
|
@@ -16552,7 +17111,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
16552
17111
|
baselineDiagnostics: (doctor.baselineDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "baseline")),
|
|
16553
17112
|
workflowDiagnostics: (doctor.workflowDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "workflow")),
|
|
16554
17113
|
repairActions: loaded.repairActions
|
|
16555
|
-
});
|
|
17114
|
+
}));
|
|
16556
17115
|
await writeWorkflowRunArtifact(workspace, report);
|
|
16557
17116
|
return renderWorkflowValidation(report, format);
|
|
16558
17117
|
}
|