@akanjs/cli 2.3.9-rc.8 → 2.3.9
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 +879 -150
- package/index.js +1025 -211
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -4797,6 +4797,11 @@ var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!d
|
|
|
4797
4797
|
|
|
4798
4798
|
// pkgs/@akanjs/devkit/workflow/artifacts.ts
|
|
4799
4799
|
var sourceChangeBlocked = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown"));
|
|
4800
|
+
var workflowPlanApproval = {
|
|
4801
|
+
required: true,
|
|
4802
|
+
meaning: "Review this read-only plan before apply_workflow mutates files.",
|
|
4803
|
+
applyTool: "apply_workflow"
|
|
4804
|
+
};
|
|
4800
4805
|
var inferNextActionCode = (action, diagnostics) => {
|
|
4801
4806
|
if (action.action)
|
|
4802
4807
|
return action.action;
|
|
@@ -4841,13 +4846,19 @@ var createWorkflowApplyReport = ({
|
|
|
4841
4846
|
});
|
|
4842
4847
|
}
|
|
4843
4848
|
const orderedNextActions = uniqueBy(nextActionsWithIntent, (action) => action.command).sort((left, right) => nextActionPriority(left, diagnostics) - nextActionPriority(right, diagnostics));
|
|
4849
|
+
const sourceFilesChanged = uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`);
|
|
4850
|
+
const generatedFilesSynced = uniqueBy(generatedFiles, (file) => `${file.action}:${file.path}:${file.reason}`);
|
|
4844
4851
|
return {
|
|
4845
4852
|
schemaVersion: 1,
|
|
4846
4853
|
workflow,
|
|
4847
4854
|
mode,
|
|
4848
4855
|
status: workflowStatus(diagnostics),
|
|
4849
|
-
|
|
4850
|
-
|
|
4856
|
+
summary: {
|
|
4857
|
+
sourceFilesChanged,
|
|
4858
|
+
generatedFilesSynced
|
|
4859
|
+
},
|
|
4860
|
+
changedFiles: sourceFilesChanged,
|
|
4861
|
+
generatedFiles: generatedFilesSynced,
|
|
4851
4862
|
appliedCommands: uniqueBy(appliedCommands, (command) => command.command),
|
|
4852
4863
|
recommendedValidationCommands: uniqueBy(validationCommands, (command) => command.command),
|
|
4853
4864
|
commands: uniqueBy(validationCommands, (command) => command.command),
|
|
@@ -4925,6 +4936,52 @@ var statusForValidationKind = (commands, kind) => {
|
|
|
4925
4936
|
return "unknown";
|
|
4926
4937
|
return matching.some((command) => command.status === "failed") ? "failed" : "passed";
|
|
4927
4938
|
};
|
|
4939
|
+
var statusForCommands = (commands) => {
|
|
4940
|
+
if (commands.length === 0)
|
|
4941
|
+
return "unknown";
|
|
4942
|
+
return commandStatus(commands) === "failed" ? "failed" : "passed";
|
|
4943
|
+
};
|
|
4944
|
+
var statusForDiagnostics = (diagnostics) => {
|
|
4945
|
+
if (diagnostics.length === 0)
|
|
4946
|
+
return "unknown";
|
|
4947
|
+
return workflowStatus(diagnostics) === "failed" ? "failed" : "passed";
|
|
4948
|
+
};
|
|
4949
|
+
var workflowDiagnosticContextPaths = (diagnostics) => uniqueBy(diagnostics.flatMap((diagnostic) => diagnostic.context?.paths ?? []), (filePath) => filePath);
|
|
4950
|
+
var createWorkflowBaselineSummary = (diagnostics, { detailsIncluded = true, knownBlockerCount = 0 } = {}) => {
|
|
4951
|
+
const grouped = new Map;
|
|
4952
|
+
let totalErrors = 0;
|
|
4953
|
+
let totalWarnings = 0;
|
|
4954
|
+
for (const diagnostic of diagnostics) {
|
|
4955
|
+
if (diagnostic.severity === "error")
|
|
4956
|
+
totalErrors += 1;
|
|
4957
|
+
else
|
|
4958
|
+
totalWarnings += 1;
|
|
4959
|
+
const existing = grouped.get(diagnostic.code);
|
|
4960
|
+
if (existing) {
|
|
4961
|
+
existing.count += 1;
|
|
4962
|
+
if (existing.severity !== diagnostic.severity)
|
|
4963
|
+
existing.severity = "mixed";
|
|
4964
|
+
} else {
|
|
4965
|
+
grouped.set(diagnostic.code, {
|
|
4966
|
+
code: diagnostic.code,
|
|
4967
|
+
severity: diagnostic.severity,
|
|
4968
|
+
count: 1,
|
|
4969
|
+
sampleMessage: diagnostic.message
|
|
4970
|
+
});
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
const contextPaths = workflowDiagnosticContextPaths(diagnostics);
|
|
4974
|
+
return {
|
|
4975
|
+
status: totalErrors > 0 ? "failed" : diagnostics.length > 0 ? "passed" : "unknown",
|
|
4976
|
+
total: diagnostics.length,
|
|
4977
|
+
totalErrors,
|
|
4978
|
+
totalWarnings,
|
|
4979
|
+
detailsIncluded,
|
|
4980
|
+
knownBlockerCount,
|
|
4981
|
+
byCode: [...grouped.values()],
|
|
4982
|
+
...contextPaths.length ? { contextPaths } : {}
|
|
4983
|
+
};
|
|
4984
|
+
};
|
|
4928
4985
|
var createKnownBlockers = (commands, diagnostics) => {
|
|
4929
4986
|
const commandBlockers = commands.filter((command) => command.status === "failed" && (command.failureScope === "workspace-config" || command.failureScope === "environment")).map((command) => ({
|
|
4930
4987
|
code: `workflow-validation-${command.failureScope}`,
|
|
@@ -4953,18 +5010,28 @@ var createKnownBlockers = (commands, diagnostics) => {
|
|
|
4953
5010
|
}
|
|
4954
5011
|
return [...grouped.values()];
|
|
4955
5012
|
};
|
|
4956
|
-
var createValidationStatuses = (commands,
|
|
5013
|
+
var createValidationStatuses = (commands, reportDiagnostics, baselineDiagnostics, workflowDiagnostics) => {
|
|
5014
|
+
const diagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
|
|
4957
5015
|
const scopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(diagnostics)];
|
|
4958
|
-
const
|
|
5016
|
+
const sourceDiagnostics = [...reportDiagnostics, ...workflowDiagnostics].filter((diagnostic) => diagnostic.failureScope === "source-change" || diagnostic.scope === "workflow" || !diagnostic.failureScope && diagnostic.scope !== "baseline");
|
|
5017
|
+
const sourceScopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(sourceDiagnostics)];
|
|
5018
|
+
const sourceStatus = statusForScope(commands, sourceDiagnostics, sourceScopes, "source-change");
|
|
5019
|
+
const validationCommandsStatus = statusForCommands(commands);
|
|
5020
|
+
const baselineStatus = statusForDiagnostics(baselineDiagnostics);
|
|
5021
|
+
const nonBaselineDiagnostics = [...reportDiagnostics, ...workflowDiagnostics];
|
|
4959
5022
|
const workspaceStatus = hasScopeFailure(scopes, "workspace-config", diagnostics) || hasScopeFailure(scopes, "environment", diagnostics) ? "failed" : commands.length || diagnostics.length ? "passed" : "unknown";
|
|
4960
|
-
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";
|
|
5023
|
+
const overallStatus = hasScopeFailure(scopes, "source-change", diagnostics) ? "failed" : validationCommandsStatus === "passed" && baselineStatus === "failed" && workflowStatus(nonBaselineDiagnostics) !== "failed" ? "passed-with-baseline-blockers" : hasScopeFailure(scopes, "workspace-config", diagnostics) ? "blocked-by-workspace-config" : hasScopeFailure(scopes, "environment", diagnostics) ? "blocked-by-environment" : workflowStatus(diagnostics) === "failed" || commandStatus(commands) === "failed" ? "failed" : "passed";
|
|
4961
5024
|
return {
|
|
4962
5025
|
sourceStatus,
|
|
4963
5026
|
workspaceStatus,
|
|
5027
|
+
validationCommandsStatus,
|
|
5028
|
+
baselineStatus,
|
|
4964
5029
|
overallStatus,
|
|
4965
5030
|
summary: {
|
|
4966
5031
|
sourceChange: sourceStatus,
|
|
4967
5032
|
generatedSync: statusForValidationKind(commands, "sync"),
|
|
5033
|
+
validationCommands: validationCommandsStatus,
|
|
5034
|
+
baseline: baselineStatus,
|
|
4968
5035
|
workspaceConfig: statusForScope(commands, diagnostics, scopes, "workspace-config"),
|
|
4969
5036
|
environment: statusForScope(commands, diagnostics, scopes, "environment")
|
|
4970
5037
|
}
|
|
@@ -4998,7 +5065,8 @@ var createWorkflowValidationRunReport = async ({
|
|
|
4998
5065
|
] : []);
|
|
4999
5066
|
const reportDiagnostics = [...diagnostics, ...commandDiagnostics];
|
|
5000
5067
|
const scopedDiagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
|
|
5001
|
-
const
|
|
5068
|
+
const knownBlockers = createKnownBlockers(results, scopedDiagnostics);
|
|
5069
|
+
const statuses = createValidationStatuses(results, reportDiagnostics, baselineDiagnostics, workflowDiagnostics);
|
|
5002
5070
|
return {
|
|
5003
5071
|
schemaVersion: 1,
|
|
5004
5072
|
runId,
|
|
@@ -5007,9 +5075,12 @@ var createWorkflowValidationRunReport = async ({
|
|
|
5007
5075
|
source,
|
|
5008
5076
|
status: statuses.overallStatus === "passed" ? "passed" : "failed",
|
|
5009
5077
|
...statuses,
|
|
5010
|
-
knownBlockers
|
|
5078
|
+
knownBlockers,
|
|
5011
5079
|
commands: results,
|
|
5012
5080
|
diagnostics: reportDiagnostics,
|
|
5081
|
+
baselineSummary: createWorkflowBaselineSummary(baselineDiagnostics, {
|
|
5082
|
+
knownBlockerCount: knownBlockers.filter((blocker) => blocker.failureScope === "workspace-config").length
|
|
5083
|
+
}),
|
|
5013
5084
|
baselineDiagnostics,
|
|
5014
5085
|
workflowDiagnostics,
|
|
5015
5086
|
repairActions: uniqueBy(repairActions, (action) => action.command),
|
|
@@ -6325,7 +6396,7 @@ var checkTypeScriptSyntax = async (workspace, filePath) => {
|
|
|
6325
6396
|
return null;
|
|
6326
6397
|
const content = await workspace.readFile(filePath);
|
|
6327
6398
|
const source = ts6.createSourceFile(filePath, content, ts6.ScriptTarget.Latest, true, scriptKind);
|
|
6328
|
-
const diagnostic = source.parseDiagnostics[0];
|
|
6399
|
+
const diagnostic = (source.parseDiagnostics ?? [])[0];
|
|
6329
6400
|
if (!diagnostic)
|
|
6330
6401
|
return null;
|
|
6331
6402
|
const position = diagnostic.file?.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
@@ -6523,9 +6594,9 @@ var addFieldUiSurfaceInspection = (plan) => {
|
|
|
6523
6594
|
code: "add-field-ui-surface-review",
|
|
6524
6595
|
kind: "manual-action",
|
|
6525
6596
|
target,
|
|
6526
|
-
action: templateRequested ? `Template was requested for ${field}. If no Template file changed,
|
|
6597
|
+
action: templateRequested ? `Template was requested for ${field}. If no Template file changed, users will not see the field in the form yet because the file was missing, the generated ${module}Form/Layout.Template pattern was not found, or ${policy.component} needs option binding. Add it inside Layout.Template near existing Field components.` : `Template was not selected, so users will not see ${field} in the form from this apply. If list/card display is needed, include ${field} in Light${moduleClassName} projection data and place it in the local Unit/View card layout.`,
|
|
6527
6598
|
confidence: "medium",
|
|
6528
|
-
message: `Review UI
|
|
6599
|
+
message: `Review user-visible UI for ${module}.${field}; recommended component is ${policy.component}.`
|
|
6529
6600
|
}
|
|
6530
6601
|
],
|
|
6531
6602
|
nextActions: [
|
|
@@ -6780,10 +6851,48 @@ var createAddFieldDefaultRecommendations = (inputs) => {
|
|
|
6780
6851
|
kind: "manual-action",
|
|
6781
6852
|
confidence: "high",
|
|
6782
6853
|
message: `Default for ${field} will be normalized to ${coercion.normalizedType} literal ${coercion.expression}.`,
|
|
6783
|
-
action: "Review the normalized default in the apply report
|
|
6854
|
+
action: "Review the normalized default in the apply report. To leave the field without a default, omit the default input."
|
|
6784
6855
|
}
|
|
6785
6856
|
];
|
|
6786
6857
|
};
|
|
6858
|
+
var moneyLikeFieldPattern = /(budget|price|amount|cost|rate|fee|salary|balance)/i;
|
|
6859
|
+
var wholeNumberFieldPattern = /(count|quantity|total|number|index|rank|order|age)/i;
|
|
6860
|
+
var createAddFieldInputRecommendations = (inputs) => {
|
|
6861
|
+
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
6862
|
+
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
6863
|
+
const recommendations = [];
|
|
6864
|
+
if (!typeName)
|
|
6865
|
+
return recommendations;
|
|
6866
|
+
if (typeName.toLowerCase() === "number" || typeName.toLowerCase() === "numeric") {
|
|
6867
|
+
const recommendedType = moneyLikeFieldPattern.test(field) ? "Float" : wholeNumberFieldPattern.test(field) ? "Int" : null;
|
|
6868
|
+
recommendations.push({
|
|
6869
|
+
code: "add-field-type-choice",
|
|
6870
|
+
kind: "input-guidance",
|
|
6871
|
+
confidence: recommendedType ? "high" : "medium",
|
|
6872
|
+
message: recommendedType ? `Use ${recommendedType} for ${field}; Float is recommended for money-like decimal values and Int for whole-number values.` : `Choose Int for whole-number ${field} values or Float for decimal ${field} values.`,
|
|
6873
|
+
action: recommendedType ? `Re-run plan_workflow with type="${recommendedType}".` : 'Re-run plan_workflow with type="Int" or type="Float".'
|
|
6874
|
+
});
|
|
6875
|
+
}
|
|
6876
|
+
if (typeName.toLowerCase() === "enum" && !Array.isArray(inputs.values)) {
|
|
6877
|
+
recommendations.push({
|
|
6878
|
+
code: "add-field-enum-values",
|
|
6879
|
+
kind: "input-guidance",
|
|
6880
|
+
confidence: "high",
|
|
6881
|
+
message: `Enum field ${field} needs values before apply can choose valid dictionary and default behavior.`,
|
|
6882
|
+
action: 'Pass values as an array or comma-separated string, for example values=["draft","done"].'
|
|
6883
|
+
});
|
|
6884
|
+
}
|
|
6885
|
+
if (inputs.default === undefined) {
|
|
6886
|
+
recommendations.push({
|
|
6887
|
+
code: "add-field-default-optional",
|
|
6888
|
+
kind: "input-guidance",
|
|
6889
|
+
confidence: "medium",
|
|
6890
|
+
message: `No default will be written for ${field} because default is omitted.`,
|
|
6891
|
+
action: "Keep default omitted when the field should have no default value."
|
|
6892
|
+
});
|
|
6893
|
+
}
|
|
6894
|
+
return recommendations;
|
|
6895
|
+
};
|
|
6787
6896
|
var createAddFieldRecommendations = (inputs) => {
|
|
6788
6897
|
const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
|
|
6789
6898
|
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
@@ -6844,9 +6953,9 @@ var createAddFieldRecommendations = (inputs) => {
|
|
|
6844
6953
|
code: "add-field-light-projection-choice",
|
|
6845
6954
|
kind: "manual-action",
|
|
6846
6955
|
target: paths.constant,
|
|
6847
|
-
action: `Pass includeInLight=true when ${field}
|
|
6956
|
+
action: `Pass includeInLight=true when users should see ${field} in list/card data. Without it, the field can exist on ${capitalize2(module)}Input but stay absent from Light${capitalize2(module)} projections.`,
|
|
6848
6957
|
confidence: "medium",
|
|
6849
|
-
message:
|
|
6958
|
+
message: `${module}.${field} is not selected for list/card projection data.`
|
|
6850
6959
|
}
|
|
6851
6960
|
] : [],
|
|
6852
6961
|
...includeInLight ? [
|
|
@@ -6864,18 +6973,20 @@ var createAddFieldRecommendations = (inputs) => {
|
|
|
6864
6973
|
kind: "manual-action",
|
|
6865
6974
|
target: paths.template,
|
|
6866
6975
|
confidence: "medium",
|
|
6867
|
-
message: `Template
|
|
6976
|
+
message: `Template form auto-edit is skipped for ${module}.${field} because surfaces does not include "template".`,
|
|
6977
|
+
action: `Pass surfaces=["template"] when users should enter ${field} in the Template form.`
|
|
6868
6978
|
}
|
|
6869
6979
|
] : [],
|
|
6870
6980
|
{
|
|
6871
6981
|
code: "add-field-ui-manual-review",
|
|
6872
6982
|
kind: "manual-action",
|
|
6873
6983
|
target: paths.template,
|
|
6874
|
-
action: `
|
|
6984
|
+
action: `After apply, verify what users can see: Template form auto-edit only runs when a generated ${module}Form hook and Layout.Template field list are present. Unit/View cards are not auto-edited, so ${field} may be stored on ${capitalize2(module)} but not shown in list/card UI until you place it in the local card layout.`,
|
|
6875
6985
|
confidence: "medium",
|
|
6876
|
-
message:
|
|
6986
|
+
message: `${app}:${module}.${field} may need UI review for user-visible Template, Unit, and View behavior.`
|
|
6877
6987
|
},
|
|
6878
|
-
...createAddFieldDefaultRecommendations(inputs)
|
|
6988
|
+
...createAddFieldDefaultRecommendations(inputs),
|
|
6989
|
+
...createAddFieldInputRecommendations(inputs)
|
|
6879
6990
|
];
|
|
6880
6991
|
};
|
|
6881
6992
|
var createWorkflowPlanPredictedChanges = (spec, inputs) => {
|
|
@@ -7018,7 +7129,8 @@ var createWorkflowPlan = (spec, rawInputs) => {
|
|
|
7018
7129
|
validation: spec.validation,
|
|
7019
7130
|
diagnostics,
|
|
7020
7131
|
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
|
7021
|
-
requiresApproval: true
|
|
7132
|
+
requiresApproval: true,
|
|
7133
|
+
approval: workflowPlanApproval
|
|
7022
7134
|
};
|
|
7023
7135
|
};
|
|
7024
7136
|
// pkgs/@akanjs/devkit/workflow/render.ts
|
|
@@ -7056,6 +7168,7 @@ var renderWorkflowPlan = (plan) => [
|
|
|
7056
7168
|
"",
|
|
7057
7169
|
`- Mode: ${plan.mode}`,
|
|
7058
7170
|
`- Requires approval: ${plan.requiresApproval}`,
|
|
7171
|
+
`- Approval: ${plan.approval?.meaning ?? "Review this read-only plan before applying workflow mutations."}`,
|
|
7059
7172
|
"",
|
|
7060
7173
|
"## Inputs",
|
|
7061
7174
|
...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
|
|
@@ -7092,6 +7205,10 @@ var renderDiagnostic = (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.
|
|
|
7092
7205
|
var renderNextAction = (action) => `- \`${action.command}\`: ${action.reason}`;
|
|
7093
7206
|
var applySourceStatus = (report) => report.diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown")) ? "failed" : "passed";
|
|
7094
7207
|
var renderWorkflowApplyReport = (report) => {
|
|
7208
|
+
const applySummary = {
|
|
7209
|
+
sourceFilesChanged: report.summary?.sourceFilesChanged ?? report.changedFiles,
|
|
7210
|
+
generatedFilesSynced: report.summary?.generatedFilesSynced ?? report.generatedFiles
|
|
7211
|
+
};
|
|
7095
7212
|
const manualReviewItems = [
|
|
7096
7213
|
...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
|
|
7097
7214
|
...report.recommendations.filter((recommendation) => recommendation.kind === "manual-action").map(renderRecommendation)
|
|
@@ -7105,6 +7222,8 @@ var renderWorkflowApplyReport = (report) => {
|
|
|
7105
7222
|
`- Status: ${report.status}`,
|
|
7106
7223
|
`- Source-change status: ${applySourceStatus(report)}`,
|
|
7107
7224
|
`- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
|
|
7225
|
+
`- Source files changed: ${applySummary.sourceFilesChanged.length}`,
|
|
7226
|
+
`- Generated files queued for sync: ${applySummary.generatedFilesSynced.length}`,
|
|
7108
7227
|
...report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : [],
|
|
7109
7228
|
"",
|
|
7110
7229
|
"## Apply Checks",
|
|
@@ -7114,10 +7233,10 @@ var renderWorkflowApplyReport = (report) => {
|
|
|
7114
7233
|
...sourceBlockers.length ? sourceBlockers : ["- none"],
|
|
7115
7234
|
"",
|
|
7116
7235
|
"## Automatically Modified",
|
|
7117
|
-
...
|
|
7236
|
+
...applySummary.sourceFilesChanged.length ? applySummary.sourceFilesChanged.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
7118
7237
|
"",
|
|
7119
7238
|
"## Generated Sync",
|
|
7120
|
-
...
|
|
7239
|
+
...applySummary.generatedFilesSynced.length ? applySummary.generatedFilesSynced.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
7121
7240
|
"",
|
|
7122
7241
|
"## Applied Commands",
|
|
7123
7242
|
...report.appliedCommands.length ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
|
|
@@ -7144,52 +7263,76 @@ var renderWorkflowApplyReport = (report) => {
|
|
|
7144
7263
|
`);
|
|
7145
7264
|
};
|
|
7146
7265
|
var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
7147
|
-
var renderWorkflowValidationRunReport = (report) =>
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7266
|
+
var renderWorkflowValidationRunReport = (report) => {
|
|
7267
|
+
const baselineSummary = report.baselineSummary ?? {
|
|
7268
|
+
status: report.baselineDiagnostics?.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "unknown",
|
|
7269
|
+
total: report.baselineDiagnostics?.length ?? 0,
|
|
7270
|
+
totalErrors: report.baselineDiagnostics?.filter((diagnostic) => diagnostic.severity === "error").length ?? 0,
|
|
7271
|
+
totalWarnings: report.baselineDiagnostics?.filter((diagnostic) => diagnostic.severity === "warning").length ?? 0,
|
|
7272
|
+
detailsIncluded: true,
|
|
7273
|
+
knownBlockerCount: report.knownBlockers.filter((blocker) => blocker.known).length,
|
|
7274
|
+
byCode: []
|
|
7275
|
+
};
|
|
7276
|
+
return [
|
|
7277
|
+
`# Workflow Validation: ${report.workflow}`,
|
|
7278
|
+
"",
|
|
7279
|
+
`- Run: ${report.runId}`,
|
|
7280
|
+
`- Status: ${report.status}`,
|
|
7281
|
+
`- Source status: ${report.sourceStatus}`,
|
|
7282
|
+
`- Workspace status: ${report.workspaceStatus}`,
|
|
7283
|
+
`- Validation commands status: ${report.validationCommandsStatus ?? "unknown"}`,
|
|
7284
|
+
`- Baseline status: ${report.baselineStatus ?? baselineSummary.status}`,
|
|
7285
|
+
`- Overall status: ${report.overallStatus}`,
|
|
7286
|
+
"",
|
|
7287
|
+
"## Status Summary",
|
|
7288
|
+
`- Source-change: ${report.summary.sourceChange}`,
|
|
7289
|
+
`- Generated sync: ${report.summary.generatedSync}`,
|
|
7290
|
+
`- Validation commands: ${report.summary.validationCommands ?? "unknown"}`,
|
|
7291
|
+
`- Baseline: ${report.summary.baseline ?? baselineSummary.status}`,
|
|
7292
|
+
`- Workspace config: ${report.summary.workspaceConfig}`,
|
|
7293
|
+
`- Environment: ${report.summary.environment}`,
|
|
7294
|
+
"",
|
|
7295
|
+
"## Source Change Diagnostics",
|
|
7296
|
+
...report.workflowDiagnostics?.length ? report.workflowDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
7297
|
+
"",
|
|
7298
|
+
"## Existing Workspace Blockers",
|
|
7299
|
+
`- Status: ${baselineSummary.status}`,
|
|
7300
|
+
`- Errors: ${baselineSummary.totalErrors}`,
|
|
7301
|
+
`- Warnings: ${baselineSummary.totalWarnings}`,
|
|
7302
|
+
...baselineSummary.byCode.length ? baselineSummary.byCode.map((item) => `- [${item.severity}] ${item.code} (${item.count}x): ${item.sampleMessage}`) : ["- none"],
|
|
7303
|
+
...baselineSummary.detailsIncluded ? [] : [
|
|
7304
|
+
"- Baseline details are summarized by default. Re-run validation with includeBaselineDetails=true for full baselineDiagnostics."
|
|
7305
|
+
],
|
|
7306
|
+
"",
|
|
7307
|
+
"## Existing Workspace Blocker Details",
|
|
7308
|
+
...report.baselineDiagnostics?.length ? report.baselineDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : baselineSummary.detailsIncluded ? ["- none"] : ["- omitted"],
|
|
7309
|
+
"",
|
|
7310
|
+
"## Known Blockers",
|
|
7311
|
+
...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
|
|
7312
|
+
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
7313
|
+
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
7314
|
+
const known = blocker.known ? " known" : "";
|
|
7315
|
+
return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
|
|
7316
|
+
}) : ["- none"],
|
|
7317
|
+
"",
|
|
7318
|
+
"## Commands",
|
|
7319
|
+
...report.commands.length ? report.commands.map((command) => {
|
|
7320
|
+
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
7321
|
+
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
7322
|
+
}) : ["- none"],
|
|
7323
|
+
"",
|
|
7324
|
+
"## Diagnostics",
|
|
7325
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
7326
|
+
"",
|
|
7327
|
+
"## Repair Actions",
|
|
7328
|
+
...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
7329
|
+
"",
|
|
7330
|
+
"## Next Actions",
|
|
7331
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
7332
|
+
""
|
|
7333
|
+
].join(`
|
|
7192
7334
|
`);
|
|
7335
|
+
};
|
|
7193
7336
|
var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
|
|
7194
7337
|
var renderRepairReportMarkdown = (report) => [
|
|
7195
7338
|
`# Akan Repair: ${report.kind}`,
|
|
@@ -7278,7 +7421,11 @@ var toolingRolloutGate = {
|
|
|
7278
7421
|
candidates: toolingRolloutCandidates
|
|
7279
7422
|
};
|
|
7280
7423
|
var isRestrictedCandidate = (candidate) => candidate.status !== "allowed";
|
|
7281
|
-
var restrictedCandidates = new Map
|
|
7424
|
+
var restrictedCandidates = new Map;
|
|
7425
|
+
for (const candidate of toolingRolloutCandidates) {
|
|
7426
|
+
if (isRestrictedCandidate(candidate))
|
|
7427
|
+
restrictedCandidates.set(candidate.packageName, candidate);
|
|
7428
|
+
}
|
|
7282
7429
|
// pkgs/@akanjs/devkit/akanContext.ts
|
|
7283
7430
|
var resourceList = [
|
|
7284
7431
|
{ uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
|
|
@@ -7847,6 +7994,7 @@ var applyFirstPolicy = {
|
|
|
7847
7994
|
mode: "apply-first",
|
|
7848
7995
|
directSourceEdits: "fallback-only",
|
|
7849
7996
|
applyRequiredWhen: ["plan_workflow returns planPath", "plan_workflow returns next.tool=apply_workflow"],
|
|
7997
|
+
approvalMeaning: "requiresApproval is an agent/user review signal before apply_workflow mutates files; it is not a separate MCP permission gate.",
|
|
7850
7998
|
validationRequiredAfterApply: ["validationTarget", "applyReportPath"],
|
|
7851
7999
|
fallbackAllowedWhen: [
|
|
7852
8000
|
"list_workflows and explain_workflow show no matching workflow",
|
|
@@ -7982,7 +8130,12 @@ var readonlyMcpTools = [
|
|
|
7982
8130
|
description: "Report workspace diagnostics, optionally split into baseline and workflow scopes.",
|
|
7983
8131
|
inputSchema: {
|
|
7984
8132
|
type: "object",
|
|
7985
|
-
properties: {
|
|
8133
|
+
properties: {
|
|
8134
|
+
strict: booleanProperty,
|
|
8135
|
+
runIdOrPlan: stringProperty,
|
|
8136
|
+
changedFiles: stringArrayProperty,
|
|
8137
|
+
includeBaselineDetails: booleanProperty
|
|
8138
|
+
}
|
|
7986
8139
|
}
|
|
7987
8140
|
},
|
|
7988
8141
|
{
|
|
@@ -8027,7 +8180,7 @@ var applyMcpTools = [
|
|
|
8027
8180
|
description: "Validate a plan, apply report, validationTarget, or run artifact.",
|
|
8028
8181
|
inputSchema: {
|
|
8029
8182
|
type: "object",
|
|
8030
|
-
properties: { runIdOrPlan: stringProperty },
|
|
8183
|
+
properties: { runIdOrPlan: stringProperty, includeBaselineDetails: booleanProperty },
|
|
8031
8184
|
required: ["runIdOrPlan"]
|
|
8032
8185
|
}
|
|
8033
8186
|
},
|
|
@@ -8077,7 +8230,16 @@ var createAkanValidationContract = (listTools = listAkanMcpTools) => ({
|
|
|
8077
8230
|
validationStatuses: {
|
|
8078
8231
|
sourceStatus: ["passed", "failed", "unknown"],
|
|
8079
8232
|
workspaceStatus: ["passed", "failed", "unknown"],
|
|
8080
|
-
|
|
8233
|
+
validationCommandsStatus: ["passed", "failed", "unknown"],
|
|
8234
|
+
baselineStatus: ["passed", "failed", "unknown"],
|
|
8235
|
+
overallStatus: [
|
|
8236
|
+
"passed",
|
|
8237
|
+
"failed",
|
|
8238
|
+
"passed-with-baseline-blockers",
|
|
8239
|
+
"blocked-by-workspace-config",
|
|
8240
|
+
"blocked-by-environment"
|
|
8241
|
+
],
|
|
8242
|
+
baselineSummary: "Default MCP validation output summarizes baseline diagnostics; pass includeBaselineDetails=true for full baselineDiagnostics.",
|
|
8081
8243
|
knownBlockers: "Repeated workspace-config/environment failures are summarized before command output."
|
|
8082
8244
|
},
|
|
8083
8245
|
moduleContextInputs: {
|
|
@@ -8088,6 +8250,7 @@ var createAkanValidationContract = (listTools = listAkanMcpTools) => ({
|
|
|
8088
8250
|
toolingRolloutGate,
|
|
8089
8251
|
directEditFallbackPolicy: applyFirstPolicy,
|
|
8090
8252
|
applyReportFields: [
|
|
8253
|
+
"summary",
|
|
8091
8254
|
"appliedCommands",
|
|
8092
8255
|
"recommendedValidationCommands",
|
|
8093
8256
|
"commands",
|
|
@@ -8129,7 +8292,14 @@ var inspectAkanContext = async (workspace, args) => {
|
|
|
8129
8292
|
strict: true,
|
|
8130
8293
|
runIdOrPlan: workspacePath(workspace, props.request.runIdOrPlan)
|
|
8131
8294
|
});
|
|
8132
|
-
|
|
8295
|
+
const baselineSummary = createWorkflowBaselineSummary((doctor.baselineDiagnostics ?? []).map((diagnostic) => ({
|
|
8296
|
+
severity: diagnostic.severity,
|
|
8297
|
+
code: diagnostic.code,
|
|
8298
|
+
message: diagnostic.message,
|
|
8299
|
+
scope: diagnostic.scope,
|
|
8300
|
+
context: diagnostic.context
|
|
8301
|
+
})), { detailsIncluded: false });
|
|
8302
|
+
diagnostics.push(...(doctor.workflowDiagnostics ?? doctor.diagnostics).map((diagnostic) => ({
|
|
8133
8303
|
severity: diagnostic.severity,
|
|
8134
8304
|
code: diagnostic.code,
|
|
8135
8305
|
message: diagnostic.message,
|
|
@@ -8150,7 +8320,8 @@ var inspectAkanContext = async (workspace, args) => {
|
|
|
8150
8320
|
reason: doctor.status === "passed" ? "No strict workspace diagnostics were found for the workflow context." : "Review diagnostics before applying or manually editing source files."
|
|
8151
8321
|
}, {
|
|
8152
8322
|
status: doctor.status,
|
|
8153
|
-
|
|
8323
|
+
baselineSummary,
|
|
8324
|
+
baselineDiagnostics: 0,
|
|
8154
8325
|
workflowDiagnostics: doctor.workflowDiagnostics?.length ?? 0
|
|
8155
8326
|
});
|
|
8156
8327
|
}
|
|
@@ -12331,6 +12502,7 @@ void run().catch((error) => {
|
|
|
12331
12502
|
}
|
|
12332
12503
|
// pkgs/@akanjs/devkit/applicationReleasePackager.ts
|
|
12333
12504
|
import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
|
|
12505
|
+
import path38 from "path";
|
|
12334
12506
|
|
|
12335
12507
|
// pkgs/@akanjs/devkit/uploadRelease.ts
|
|
12336
12508
|
import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
|
|
@@ -12446,13 +12618,9 @@ class ApplicationReleasePackager {
|
|
|
12446
12618
|
await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
|
|
12447
12619
|
await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
|
|
12448
12620
|
await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
"-C",
|
|
12453
|
-
buildRoot,
|
|
12454
|
-
"./"
|
|
12455
|
-
]);
|
|
12621
|
+
const releaseRoot = this.#app.workspace.workspaceRoot;
|
|
12622
|
+
const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
|
|
12623
|
+
await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
|
|
12456
12624
|
await this.#writeCsrZipIfPresent();
|
|
12457
12625
|
return { platformVersion };
|
|
12458
12626
|
}
|
|
@@ -12473,22 +12641,18 @@ class ApplicationReleasePackager {
|
|
|
12473
12641
|
await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
|
|
12474
12642
|
const libDeps = ["social", "shared", "platform", "util"];
|
|
12475
12643
|
await Promise.all(libDeps.map((lib) => cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true })));
|
|
12476
|
-
await Promise.all([".next", "ios", "android", "public/libs"].map(async (
|
|
12477
|
-
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${
|
|
12644
|
+
await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
|
|
12645
|
+
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
|
|
12478
12646
|
if (await FileSys.dirExists(targetPath))
|
|
12479
12647
|
await rm4(targetPath, { recursive: true, force: true });
|
|
12480
12648
|
}));
|
|
12481
12649
|
const syncPaths = [".husky", ".gitignore", "package.json"];
|
|
12482
|
-
await Promise.all(syncPaths.map((
|
|
12650
|
+
await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
|
|
12483
12651
|
await this.#writeSourceTsconfig(sourceRoot, libDeps);
|
|
12484
12652
|
await Bun.write(`${sourceRoot}/README.md`, readme);
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
"-C",
|
|
12489
|
-
sourceRoot,
|
|
12490
|
-
"./"
|
|
12491
|
-
]);
|
|
12653
|
+
const sourceCwd = this.#app.workspace.cwdPath;
|
|
12654
|
+
const sourceArchivePath = path38.relative(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`).split(path38.sep).join("/");
|
|
12655
|
+
await this.#app.workspace.spawn("tar", ["-zcf", sourceArchivePath, "-C", sourceRoot, "./"], { cwd: sourceCwd });
|
|
12492
12656
|
}
|
|
12493
12657
|
async#resetSourceRoot(sourceRoot) {
|
|
12494
12658
|
if (await FileSys.dirExists(sourceRoot)) {
|
|
@@ -12570,20 +12734,20 @@ class ApplicationReleasePackager {
|
|
|
12570
12734
|
}
|
|
12571
12735
|
}
|
|
12572
12736
|
// pkgs/@akanjs/devkit/applicationTestPreload.ts
|
|
12573
|
-
import
|
|
12737
|
+
import path39 from "path";
|
|
12574
12738
|
var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
|
|
12575
12739
|
async function resolveSignalTestPreloadPath(target) {
|
|
12576
12740
|
const candidates = [];
|
|
12577
12741
|
const addResolvedPackageCandidate = (basePath2) => {
|
|
12578
12742
|
try {
|
|
12579
|
-
candidates.push(
|
|
12743
|
+
candidates.push(path39.join(path39.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
|
|
12580
12744
|
} catch {}
|
|
12581
12745
|
};
|
|
12582
12746
|
addResolvedPackageCandidate(target.cwdPath);
|
|
12583
12747
|
addResolvedPackageCandidate(process.cwd());
|
|
12584
|
-
addResolvedPackageCandidate(
|
|
12748
|
+
addResolvedPackageCandidate(path39.dirname(Bun.main));
|
|
12585
12749
|
addResolvedPackageCandidate(import.meta.dir);
|
|
12586
|
-
candidates.push(
|
|
12750
|
+
candidates.push(path39.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.join(path39.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path39.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
|
|
12587
12751
|
for (const candidate of [...new Set(candidates)]) {
|
|
12588
12752
|
if (await Bun.file(candidate).exists())
|
|
12589
12753
|
return candidate;
|
|
@@ -12593,7 +12757,7 @@ async function resolveSignalTestPreloadPath(target) {
|
|
|
12593
12757
|
// pkgs/@akanjs/devkit/builder.ts
|
|
12594
12758
|
import { existsSync as existsSync2 } from "fs";
|
|
12595
12759
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
12596
|
-
import
|
|
12760
|
+
import path40 from "path";
|
|
12597
12761
|
var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
|
|
12598
12762
|
var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
|
|
12599
12763
|
var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
|
|
@@ -12610,14 +12774,14 @@ class Builder {
|
|
|
12610
12774
|
#globEntrypoints(cwd, pattern) {
|
|
12611
12775
|
const glob = new Bun.Glob(pattern);
|
|
12612
12776
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12613
|
-
const segments = relativePath.split(
|
|
12777
|
+
const segments = relativePath.split(path40.sep);
|
|
12614
12778
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12615
|
-
}).map((rel) =>
|
|
12779
|
+
}).map((rel) => path40.join(cwd, rel));
|
|
12616
12780
|
}
|
|
12617
12781
|
#globFiles(cwd, pattern = "**/*.*") {
|
|
12618
12782
|
const glob = new Bun.Glob(pattern);
|
|
12619
12783
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12620
|
-
const segments = relativePath.split(
|
|
12784
|
+
const segments = relativePath.split(path40.sep);
|
|
12621
12785
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12622
12786
|
});
|
|
12623
12787
|
}
|
|
@@ -12625,17 +12789,17 @@ class Builder {
|
|
|
12625
12789
|
const out = [];
|
|
12626
12790
|
for (const p of additionalEntryPoints) {
|
|
12627
12791
|
if (p.includes("*")) {
|
|
12628
|
-
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${
|
|
12792
|
+
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path40.sep}`) ? p.slice(cwd.length + 1) : p;
|
|
12629
12793
|
out.push(...this.#globEntrypoints(cwd, rel));
|
|
12630
12794
|
} else
|
|
12631
|
-
out.push(
|
|
12795
|
+
out.push(path40.isAbsolute(p) ? p : path40.join(cwd, p));
|
|
12632
12796
|
}
|
|
12633
12797
|
return out;
|
|
12634
12798
|
}
|
|
12635
12799
|
#getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
|
|
12636
12800
|
const cwd = this.#executor.cwdPath;
|
|
12637
12801
|
const entrypoints = [
|
|
12638
|
-
...bundle ? [
|
|
12802
|
+
...bundle ? [path40.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
|
|
12639
12803
|
...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
|
|
12640
12804
|
];
|
|
12641
12805
|
return {
|
|
@@ -12656,9 +12820,9 @@ class Builder {
|
|
|
12656
12820
|
for (const relativePath of this.#globFiles(cwd)) {
|
|
12657
12821
|
if (relativePath === "package.json")
|
|
12658
12822
|
continue;
|
|
12659
|
-
const sourcePath =
|
|
12660
|
-
const targetPath =
|
|
12661
|
-
await mkdir10(
|
|
12823
|
+
const sourcePath = path40.join(cwd, relativePath);
|
|
12824
|
+
const targetPath = path40.join(this.#distExecutor.cwdPath, relativePath);
|
|
12825
|
+
await mkdir10(path40.dirname(targetPath), { recursive: true });
|
|
12662
12826
|
await Bun.write(targetPath, Bun.file(sourcePath));
|
|
12663
12827
|
}
|
|
12664
12828
|
}
|
|
@@ -12669,13 +12833,13 @@ class Builder {
|
|
|
12669
12833
|
return withoutFormatDir;
|
|
12670
12834
|
if (!hasDotSlash && withoutFormatDir === publishedPath)
|
|
12671
12835
|
return publishedPath;
|
|
12672
|
-
const parsed =
|
|
12836
|
+
const parsed = path40.posix.parse(withoutFormatDir);
|
|
12673
12837
|
if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
|
|
12674
12838
|
return withoutFormatDir;
|
|
12675
|
-
const withoutExt =
|
|
12839
|
+
const withoutExt = path40.posix.join(parsed.dir, parsed.name);
|
|
12676
12840
|
const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
|
|
12677
12841
|
const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
|
|
12678
|
-
const matchedSource = sourceCandidates.find((candidate) => existsSync2(
|
|
12842
|
+
const matchedSource = sourceCandidates.find((candidate) => existsSync2(path40.join(this.#executor.cwdPath, candidate)));
|
|
12679
12843
|
if (!matchedSource)
|
|
12680
12844
|
return withoutFormatDir;
|
|
12681
12845
|
return hasDotSlash ? `./${matchedSource}` : matchedSource;
|
|
@@ -12726,7 +12890,7 @@ class Builder {
|
|
|
12726
12890
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
12727
12891
|
import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
|
|
12728
12892
|
import os from "os";
|
|
12729
|
-
import
|
|
12893
|
+
import path41 from "path";
|
|
12730
12894
|
import { select as select2 } from "@inquirer/prompts";
|
|
12731
12895
|
import { MobileProject } from "@trapezedev/project";
|
|
12732
12896
|
import { capitalize as capitalize5 } from "akanjs/common";
|
|
@@ -12933,13 +13097,13 @@ var rootCapacitorConfigFilenames = [
|
|
|
12933
13097
|
"capacitor.config.js",
|
|
12934
13098
|
"capacitor.config.json"
|
|
12935
13099
|
];
|
|
12936
|
-
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) =>
|
|
13100
|
+
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
|
|
12937
13101
|
async function clearRootCapacitorConfigs(appRoot) {
|
|
12938
13102
|
await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
|
|
12939
13103
|
}
|
|
12940
13104
|
async function writeRootCapacitorConfig(appRoot, content) {
|
|
12941
13105
|
await clearRootCapacitorConfigs(appRoot);
|
|
12942
|
-
await Bun.write(
|
|
13106
|
+
await Bun.write(path41.join(appRoot, "capacitor.config.json"), content);
|
|
12943
13107
|
}
|
|
12944
13108
|
var getLocalIP = () => {
|
|
12945
13109
|
const interfaces = os.networkInterfaces();
|
|
@@ -13048,13 +13212,13 @@ function buildIosNativeRunCommand({
|
|
|
13048
13212
|
scheme = "App",
|
|
13049
13213
|
configuration = "Debug"
|
|
13050
13214
|
}) {
|
|
13051
|
-
const derivedDataPath =
|
|
13215
|
+
const derivedDataPath = path41.join(appRoot, "ios/DerivedData", device.id);
|
|
13052
13216
|
const productPlatform = device.kind === "device" ? "iphoneos" : "iphonesimulator";
|
|
13053
13217
|
const destination = device.kind === "device" ? `id=${device.xcodebuildId ?? device.id}` : `platform=iOS Simulator,id=${device.id}`;
|
|
13054
13218
|
return {
|
|
13055
13219
|
configuration,
|
|
13056
13220
|
derivedDataPath,
|
|
13057
|
-
appPath:
|
|
13221
|
+
appPath: path41.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
|
|
13058
13222
|
xcodebuildArgs: [
|
|
13059
13223
|
"-project",
|
|
13060
13224
|
"App.xcodeproj",
|
|
@@ -13264,7 +13428,7 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13264
13428
|
...capacitorConfig,
|
|
13265
13429
|
appId,
|
|
13266
13430
|
appName,
|
|
13267
|
-
webDir:
|
|
13431
|
+
webDir: path41.posix.join(".akan", "mobile", name, "www"),
|
|
13268
13432
|
plugins: {
|
|
13269
13433
|
CapacitorCookies: { enabled: true },
|
|
13270
13434
|
...pluginsConfig,
|
|
@@ -13321,10 +13485,10 @@ class CapacitorApp {
|
|
|
13321
13485
|
constructor(app, target) {
|
|
13322
13486
|
this.app = app;
|
|
13323
13487
|
this.target = target;
|
|
13324
|
-
this.targetRootPath =
|
|
13325
|
-
this.targetRoot =
|
|
13326
|
-
this.targetWebRoot =
|
|
13327
|
-
this.targetAssetRoot =
|
|
13488
|
+
this.targetRootPath = path41.posix.join(".akan", "mobile", this.target.name);
|
|
13489
|
+
this.targetRoot = path41.join(this.app.cwdPath, this.targetRootPath);
|
|
13490
|
+
this.targetWebRoot = path41.join(this.targetRoot, "www");
|
|
13491
|
+
this.targetAssetRoot = path41.join(this.targetRoot, "assets");
|
|
13328
13492
|
this.project = new MobileProject(this.app.cwdPath, {
|
|
13329
13493
|
android: { path: this.androidRootPath },
|
|
13330
13494
|
ios: { path: this.iosProjectPath }
|
|
@@ -13339,9 +13503,9 @@ class CapacitorApp {
|
|
|
13339
13503
|
await mkdir11(this.targetRoot, { recursive: true });
|
|
13340
13504
|
if (regenerate) {
|
|
13341
13505
|
if (!platform || platform === "ios")
|
|
13342
|
-
await rm5(
|
|
13506
|
+
await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
|
|
13343
13507
|
if (!platform || platform === "android")
|
|
13344
|
-
await rm5(
|
|
13508
|
+
await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
|
|
13345
13509
|
}
|
|
13346
13510
|
const project = this.project;
|
|
13347
13511
|
await this.project.load();
|
|
@@ -13464,7 +13628,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13464
13628
|
const xcodebuildArgs = noAllowProvisioningUpdates ? command.xcodebuildArgs : [...command.xcodebuildArgs.slice(0, -1), "-allowProvisioningUpdates", ...command.xcodebuildArgs.slice(-1)];
|
|
13465
13629
|
try {
|
|
13466
13630
|
await this.#spawn("xcodebuild", xcodebuildArgs, {
|
|
13467
|
-
cwd:
|
|
13631
|
+
cwd: path41.join(this.app.cwdPath, this.iosProjectPath),
|
|
13468
13632
|
env: mobileEnv
|
|
13469
13633
|
});
|
|
13470
13634
|
const devicectlId = runTarget.devicectlId ?? runTarget.id;
|
|
@@ -13489,7 +13653,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13489
13653
|
return isRecord2(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
|
|
13490
13654
|
}
|
|
13491
13655
|
async#getIosDevelopmentTeam() {
|
|
13492
|
-
const pbxprojPath =
|
|
13656
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
13493
13657
|
if (!await Bun.file(pbxprojPath).exists())
|
|
13494
13658
|
return;
|
|
13495
13659
|
return (await Bun.file(pbxprojPath).text()).match(/DEVELOPMENT_TEAM = ([^;]+);/)?.[1]?.trim();
|
|
@@ -13515,7 +13679,7 @@ ${error.message}`;
|
|
|
13515
13679
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
13516
13680
|
}
|
|
13517
13681
|
async#updateAndroidBuildTypes() {
|
|
13518
|
-
const appGradle = await FileEditor.create(
|
|
13682
|
+
const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
13519
13683
|
const buildTypesBlock = `
|
|
13520
13684
|
debug {
|
|
13521
13685
|
applicationIdSuffix ".debug"
|
|
@@ -13559,7 +13723,7 @@ ${error.message}`;
|
|
|
13559
13723
|
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
13560
13724
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
13561
13725
|
stdio: "inherit",
|
|
13562
|
-
cwd:
|
|
13726
|
+
cwd: path41.join(this.app.cwdPath, this.androidRootPath),
|
|
13563
13727
|
env: await this.#commandEnv("release", env)
|
|
13564
13728
|
});
|
|
13565
13729
|
}
|
|
@@ -13567,10 +13731,10 @@ ${error.message}`;
|
|
|
13567
13731
|
await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
|
|
13568
13732
|
}
|
|
13569
13733
|
async#ensureAndroidAssetsDir() {
|
|
13570
|
-
await mkdir11(
|
|
13734
|
+
await mkdir11(path41.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
13571
13735
|
}
|
|
13572
13736
|
async#ensureAndroidDebugKeystore() {
|
|
13573
|
-
const keystorePath =
|
|
13737
|
+
const keystorePath = path41.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
|
|
13574
13738
|
if (await Bun.file(keystorePath).exists())
|
|
13575
13739
|
return;
|
|
13576
13740
|
await this.#spawn("keytool", [
|
|
@@ -13609,7 +13773,7 @@ ${error.message}`;
|
|
|
13609
13773
|
await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
|
|
13610
13774
|
}
|
|
13611
13775
|
async#assertAndroidReleaseSigningConfig() {
|
|
13612
|
-
const gradlePropertiesPath =
|
|
13776
|
+
const gradlePropertiesPath = path41.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
|
|
13613
13777
|
const gradleProperties = await Bun.file(gradlePropertiesPath).exists() ? await Bun.file(gradlePropertiesPath).text() : "";
|
|
13614
13778
|
const missingKeys = getMissingAndroidReleaseSigningKeys({ gradleProperties });
|
|
13615
13779
|
if (missingKeys.length > 0)
|
|
@@ -13636,12 +13800,12 @@ ${error.message}`;
|
|
|
13636
13800
|
await this.#prepareAndroid({ operation: "release", env: "main" });
|
|
13637
13801
|
}
|
|
13638
13802
|
async prepareWww() {
|
|
13639
|
-
const htmlSource =
|
|
13803
|
+
const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
|
|
13640
13804
|
if (!await Bun.file(htmlSource).exists())
|
|
13641
13805
|
throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
|
|
13642
13806
|
await rm5(this.targetWebRoot, { recursive: true, force: true });
|
|
13643
13807
|
await mkdir11(this.targetWebRoot, { recursive: true });
|
|
13644
|
-
await Bun.write(
|
|
13808
|
+
await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
|
|
13645
13809
|
}
|
|
13646
13810
|
#injectMobileTargetMeta(html) {
|
|
13647
13811
|
const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
@@ -13661,7 +13825,7 @@ ${error.message}`;
|
|
|
13661
13825
|
});
|
|
13662
13826
|
const content = `${JSON.stringify(config, null, 2)}
|
|
13663
13827
|
`;
|
|
13664
|
-
await Bun.write(
|
|
13828
|
+
await Bun.write(path41.join(this.targetRoot, "capacitor.config.json"), content);
|
|
13665
13829
|
return content;
|
|
13666
13830
|
}
|
|
13667
13831
|
async#prepareTargetAssets() {
|
|
@@ -13669,11 +13833,11 @@ ${error.message}`;
|
|
|
13669
13833
|
return;
|
|
13670
13834
|
await mkdir11(this.targetAssetRoot, { recursive: true });
|
|
13671
13835
|
if (this.target.assets.icon)
|
|
13672
|
-
await cp2(
|
|
13836
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.icon), path41.join(this.targetAssetRoot, "icon.png"), {
|
|
13673
13837
|
force: true
|
|
13674
13838
|
});
|
|
13675
13839
|
if (this.target.assets.splash)
|
|
13676
|
-
await cp2(
|
|
13840
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.splash), path41.join(this.targetAssetRoot, "splash.png"), {
|
|
13677
13841
|
force: true
|
|
13678
13842
|
});
|
|
13679
13843
|
}
|
|
@@ -13681,11 +13845,11 @@ ${error.message}`;
|
|
|
13681
13845
|
const files = this.target.files?.[platform];
|
|
13682
13846
|
if (!files)
|
|
13683
13847
|
return;
|
|
13684
|
-
const platformRoot =
|
|
13848
|
+
const platformRoot = path41.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
|
|
13685
13849
|
await Promise.all(Object.entries(files).map(async ([to, from]) => {
|
|
13686
|
-
const targetPath =
|
|
13687
|
-
await mkdir11(
|
|
13688
|
-
await cp2(
|
|
13850
|
+
const targetPath = path41.join(platformRoot, to);
|
|
13851
|
+
await mkdir11(path41.dirname(targetPath), { recursive: true });
|
|
13852
|
+
await cp2(path41.join(this.app.cwdPath, from), targetPath, { force: true });
|
|
13689
13853
|
}));
|
|
13690
13854
|
}
|
|
13691
13855
|
async#generateAssets({ operation, env }) {
|
|
@@ -13695,7 +13859,7 @@ ${error.message}`;
|
|
|
13695
13859
|
"@capacitor/assets",
|
|
13696
13860
|
"generate",
|
|
13697
13861
|
"--assetPath",
|
|
13698
|
-
|
|
13862
|
+
path41.posix.join(this.targetRootPath, "assets"),
|
|
13699
13863
|
"--iosProject",
|
|
13700
13864
|
this.iosProjectPath,
|
|
13701
13865
|
"--androidProject",
|
|
@@ -13905,7 +14069,7 @@ var Pkg = createInternalArgToken("Pkg");
|
|
|
13905
14069
|
var Module = createInternalArgToken("Module");
|
|
13906
14070
|
var Workspace = createInternalArgToken("Workspace");
|
|
13907
14071
|
// pkgs/@akanjs/devkit/commandDecorators/command.ts
|
|
13908
|
-
import
|
|
14072
|
+
import path42 from "path";
|
|
13909
14073
|
import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
|
|
13910
14074
|
import { Logger as Logger10 } from "akanjs/common";
|
|
13911
14075
|
import chalk6 from "chalk";
|
|
@@ -14409,7 +14573,7 @@ var runCommands = async (...commands) => {
|
|
|
14409
14573
|
process.exit(1);
|
|
14410
14574
|
});
|
|
14411
14575
|
const __dirname2 = getDirname(import.meta.url);
|
|
14412
|
-
const packageJsonCandidates = [`${
|
|
14576
|
+
const packageJsonCandidates = [`${path42.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
|
|
14413
14577
|
let cliPackageJson = null;
|
|
14414
14578
|
for (const packageJsonPath of packageJsonCandidates) {
|
|
14415
14579
|
if (!await FileSys.fileExists(packageJsonPath))
|
|
@@ -14689,8 +14853,8 @@ var scanModuleSpecifiers = (source2, filePath, includeExports) => {
|
|
|
14689
14853
|
return importSpecifiers;
|
|
14690
14854
|
};
|
|
14691
14855
|
var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
|
|
14692
|
-
const configFile = ts10.readConfigFile(tsConfigPath, (
|
|
14693
|
-
return ts10.sys.readFile(
|
|
14856
|
+
const configFile = ts10.readConfigFile(tsConfigPath, (path43) => {
|
|
14857
|
+
return ts10.sys.readFile(path43);
|
|
14694
14858
|
});
|
|
14695
14859
|
return ts10.parseJsonConfigFileContent(configFile.config, ts10.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
|
|
14696
14860
|
};
|
|
@@ -14906,6 +15070,571 @@ ${document}
|
|
|
14906
15070
|
`;
|
|
14907
15071
|
}
|
|
14908
15072
|
}
|
|
15073
|
+
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
15074
|
+
import { createHash } from "crypto";
|
|
15075
|
+
import { readdir as readdir3, readFile as readFile2, stat as stat4 } from "fs/promises";
|
|
15076
|
+
import path43 from "path";
|
|
15077
|
+
import ignore2 from "ignore";
|
|
15078
|
+
import ts11 from "typescript";
|
|
15079
|
+
var MAX_FILE_LINES = 2000;
|
|
15080
|
+
var PLACEHOLDER_EXPORT_NAMES = new Set([
|
|
15081
|
+
"aa",
|
|
15082
|
+
"dumb",
|
|
15083
|
+
"dumb2",
|
|
15084
|
+
"someBaseLogic",
|
|
15085
|
+
"someCommonLogic",
|
|
15086
|
+
"someFrontendLogic",
|
|
15087
|
+
"someBackendLogic"
|
|
15088
|
+
]);
|
|
15089
|
+
var SUGGESTED_RULES = [
|
|
15090
|
+
"Keep generated scanSync index files out of hand-written changes; generated indexes should only contain one-depth export statements.",
|
|
15091
|
+
"Generated Akan index files should not contain placeholder exports such as aa, dumb, dumb2, or some*Logic.",
|
|
15092
|
+
"Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
|
|
15093
|
+
"Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
|
|
15094
|
+
"Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
|
|
15095
|
+
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
|
|
15096
|
+
"Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
|
|
15097
|
+
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
15098
|
+
"Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
|
|
15099
|
+
"Move shared app utilities to apps/*/common instead of creating apps/*/base.",
|
|
15100
|
+
"Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
|
|
15101
|
+
];
|
|
15102
|
+
var APP_ROOT_FILES = new Set([
|
|
15103
|
+
"akan.app.json",
|
|
15104
|
+
"akan.config.ts",
|
|
15105
|
+
"capacitor.config.ts",
|
|
15106
|
+
"client.ts",
|
|
15107
|
+
"main.ts",
|
|
15108
|
+
"package.json",
|
|
15109
|
+
"server.ts",
|
|
15110
|
+
"tsconfig.json"
|
|
15111
|
+
]);
|
|
15112
|
+
var LIB_ROOT_FILES = new Set([
|
|
15113
|
+
"cnst.ts",
|
|
15114
|
+
"db.ts",
|
|
15115
|
+
"dict.ts",
|
|
15116
|
+
"option.ts",
|
|
15117
|
+
"sig.ts",
|
|
15118
|
+
"srv.ts",
|
|
15119
|
+
"st.ts",
|
|
15120
|
+
"useClient.ts",
|
|
15121
|
+
"useServer.ts"
|
|
15122
|
+
]);
|
|
15123
|
+
var CONVENTION_SUFFIXES = [
|
|
15124
|
+
".constant.ts",
|
|
15125
|
+
".dictionary.ts",
|
|
15126
|
+
".document.ts",
|
|
15127
|
+
".service.ts",
|
|
15128
|
+
".signal.ts",
|
|
15129
|
+
".store.ts"
|
|
15130
|
+
];
|
|
15131
|
+
|
|
15132
|
+
class AkanQualityScanner {
|
|
15133
|
+
async scan(workspaceRoot) {
|
|
15134
|
+
const targetFiles = await this.#collectTargetFiles(workspaceRoot);
|
|
15135
|
+
const sourceFiles = await Promise.all(targetFiles.map((file) => this.#readSourceFile(workspaceRoot, file)));
|
|
15136
|
+
const warnings = [
|
|
15137
|
+
...this.#scanGlobalQuality(sourceFiles),
|
|
15138
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanSingleFileQuality(sourceFile2)),
|
|
15139
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanConventionQuality(sourceFile2)),
|
|
15140
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanLayoutQuality(sourceFile2))
|
|
15141
|
+
];
|
|
15142
|
+
return {
|
|
15143
|
+
workspaceRoot,
|
|
15144
|
+
scannedFiles: sourceFiles.length,
|
|
15145
|
+
warnings: warnings.sort(compareWarnings),
|
|
15146
|
+
suggestedRules: SUGGESTED_RULES
|
|
15147
|
+
};
|
|
15148
|
+
}
|
|
15149
|
+
async#collectTargetFiles(workspaceRoot) {
|
|
15150
|
+
const ignoreFilter = ignore2().add(await this.#readGitIgnore(workspaceRoot));
|
|
15151
|
+
const files = [];
|
|
15152
|
+
for (const targetRoot of ["apps", "libs"]) {
|
|
15153
|
+
const absoluteTargetRoot = path43.join(workspaceRoot, targetRoot);
|
|
15154
|
+
if (!await isDirectory(absoluteTargetRoot))
|
|
15155
|
+
continue;
|
|
15156
|
+
await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
|
|
15157
|
+
}
|
|
15158
|
+
return files.sort();
|
|
15159
|
+
}
|
|
15160
|
+
async#readGitIgnore(workspaceRoot) {
|
|
15161
|
+
const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
|
|
15162
|
+
if (!await Bun.file(gitIgnorePath).exists())
|
|
15163
|
+
return [];
|
|
15164
|
+
return (await readFile2(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
15165
|
+
}
|
|
15166
|
+
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
15167
|
+
const entries = await readdir3(currentPath, { withFileTypes: true });
|
|
15168
|
+
for (const entry of entries) {
|
|
15169
|
+
const absolutePath = path43.join(currentPath, entry.name);
|
|
15170
|
+
const relativePath = toPosix(path43.relative(workspaceRoot, absolutePath));
|
|
15171
|
+
if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
|
|
15172
|
+
continue;
|
|
15173
|
+
if (entry.isDirectory()) {
|
|
15174
|
+
await this.#walkTargetFiles(workspaceRoot, absolutePath, ignoreFilter, files);
|
|
15175
|
+
continue;
|
|
15176
|
+
}
|
|
15177
|
+
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
15178
|
+
files.push(relativePath);
|
|
15179
|
+
}
|
|
15180
|
+
}
|
|
15181
|
+
}
|
|
15182
|
+
async#readSourceFile(workspaceRoot, file) {
|
|
15183
|
+
const absolutePath = path43.join(workspaceRoot, file);
|
|
15184
|
+
const content = await readFile2(absolutePath, "utf8");
|
|
15185
|
+
return {
|
|
15186
|
+
file,
|
|
15187
|
+
absolutePath,
|
|
15188
|
+
content,
|
|
15189
|
+
sourceFile: ts11.createSourceFile(file, content, ts11.ScriptTarget.Latest, true, getScriptKind(file))
|
|
15190
|
+
};
|
|
15191
|
+
}
|
|
15192
|
+
#scanGlobalQuality(sourceFiles) {
|
|
15193
|
+
const exportedFunctionLikes = sourceFiles.flatMap((sourceFile2) => getExportedFunctionLikes(sourceFile2));
|
|
15194
|
+
const warnings = [];
|
|
15195
|
+
for (const [name, declarations] of groupBy(exportedFunctionLikes, (declaration) => declaration.name)) {
|
|
15196
|
+
if (declarations.length < 2)
|
|
15197
|
+
continue;
|
|
15198
|
+
warnings.push({
|
|
15199
|
+
rule: "akan.global.duplicate-exported-function-name",
|
|
15200
|
+
scope: "global",
|
|
15201
|
+
severity: "warning",
|
|
15202
|
+
message: `Exported function or class name "${name}" is declared in ${declarations.length} files.`,
|
|
15203
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15204
|
+
});
|
|
15205
|
+
}
|
|
15206
|
+
const declarationsWithBody = exportedFunctionLikes.filter((declaration) => declaration.bodyFingerprint);
|
|
15207
|
+
for (const [fingerprint, declarations] of groupBy(declarationsWithBody, (declaration) => declaration.bodyFingerprint ?? "")) {
|
|
15208
|
+
const uniqueNames = new Set(declarations.map((declaration) => declaration.name));
|
|
15209
|
+
if (declarations.length < 2 || uniqueNames.size < 2 || fingerprint === "")
|
|
15210
|
+
continue;
|
|
15211
|
+
warnings.push({
|
|
15212
|
+
rule: "akan.global.duplicate-exported-function-body",
|
|
15213
|
+
scope: "global",
|
|
15214
|
+
severity: "warning",
|
|
15215
|
+
message: `Exported functions/classes share the same implementation body: ${declarations.map((declaration) => declaration.name).join(", ")}.`,
|
|
15216
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15217
|
+
});
|
|
15218
|
+
}
|
|
15219
|
+
return warnings;
|
|
15220
|
+
}
|
|
15221
|
+
#scanSingleFileQuality(sourceFile2) {
|
|
15222
|
+
const warnings = [];
|
|
15223
|
+
const lineCount = sourceFile2.content.split(/\r?\n/).length;
|
|
15224
|
+
const recommendedLineLimit = getRecommendedLineLimit(sourceFile2.file);
|
|
15225
|
+
if (recommendedLineLimit && lineCount > recommendedLineLimit) {
|
|
15226
|
+
warnings.push({
|
|
15227
|
+
rule: "akan.file.recommended-max-lines",
|
|
15228
|
+
scope: "file",
|
|
15229
|
+
severity: "warning",
|
|
15230
|
+
file: sourceFile2.file,
|
|
15231
|
+
message: `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`
|
|
15232
|
+
});
|
|
15233
|
+
}
|
|
15234
|
+
if (lineCount > MAX_FILE_LINES) {
|
|
15235
|
+
warnings.push({
|
|
15236
|
+
rule: "akan.file.max-lines",
|
|
15237
|
+
scope: "file",
|
|
15238
|
+
severity: "warning",
|
|
15239
|
+
file: sourceFile2.file,
|
|
15240
|
+
message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
|
|
15241
|
+
});
|
|
15242
|
+
}
|
|
15243
|
+
warnings.push(...getPlaceholderExportWarnings(sourceFile2));
|
|
15244
|
+
warnings.push(...getDictionaryTextWarnings(sourceFile2));
|
|
15245
|
+
warnings.push(...getGlobalMutationWarnings(sourceFile2));
|
|
15246
|
+
const exportedClassNames = getExportedClassNames(sourceFile2.sourceFile);
|
|
15247
|
+
if (exportedClassNames.length === 0)
|
|
15248
|
+
return warnings;
|
|
15249
|
+
const allowedInterfaceNames = new Set(exportedClassNames.map((name) => `${name}Options`));
|
|
15250
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15251
|
+
if (declaration.kind === "class" && exportedClassNames.includes(declaration.name))
|
|
15252
|
+
continue;
|
|
15253
|
+
if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name))
|
|
15254
|
+
continue;
|
|
15255
|
+
warnings.push({
|
|
15256
|
+
rule: "akan.file.class-export-global-declaration",
|
|
15257
|
+
scope: "file",
|
|
15258
|
+
severity: "warning",
|
|
15259
|
+
file: sourceFile2.file,
|
|
15260
|
+
line: declaration.line,
|
|
15261
|
+
message: `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`
|
|
15262
|
+
});
|
|
15263
|
+
}
|
|
15264
|
+
return warnings;
|
|
15265
|
+
}
|
|
15266
|
+
#scanConventionQuality(sourceFile2) {
|
|
15267
|
+
const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile2.file.endsWith(candidate));
|
|
15268
|
+
if (!suffix)
|
|
15269
|
+
return [];
|
|
15270
|
+
const modelName = toPascalCase(path43.basename(sourceFile2.file, suffix));
|
|
15271
|
+
const warnings = [];
|
|
15272
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15273
|
+
if (isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
15274
|
+
continue;
|
|
15275
|
+
warnings.push({
|
|
15276
|
+
rule: `akan.convention${suffix.replace(".ts", "")}`,
|
|
15277
|
+
scope: "convention",
|
|
15278
|
+
severity: "warning",
|
|
15279
|
+
file: sourceFile2.file,
|
|
15280
|
+
line: declaration.line,
|
|
15281
|
+
message: `${path43.basename(sourceFile2.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
|
|
15282
|
+
});
|
|
15283
|
+
}
|
|
15284
|
+
return warnings;
|
|
15285
|
+
}
|
|
15286
|
+
#scanLayoutQuality(sourceFile2) {
|
|
15287
|
+
const segments = sourceFile2.file.split("/");
|
|
15288
|
+
const warnings = [];
|
|
15289
|
+
if (segments[0] === "apps" && segments.length === 3 && !APP_ROOT_FILES.has(segments[2])) {
|
|
15290
|
+
warnings.push({
|
|
15291
|
+
rule: "akan.layout.app-root-file",
|
|
15292
|
+
scope: "layout",
|
|
15293
|
+
severity: "warning",
|
|
15294
|
+
file: sourceFile2.file,
|
|
15295
|
+
message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
|
|
15296
|
+
});
|
|
15297
|
+
}
|
|
15298
|
+
const libRootFile = getLibRootFile(sourceFile2.file);
|
|
15299
|
+
if (libRootFile && !LIB_ROOT_FILES.has(libRootFile)) {
|
|
15300
|
+
warnings.push({
|
|
15301
|
+
rule: "akan.layout.lib-root-file",
|
|
15302
|
+
scope: "layout",
|
|
15303
|
+
severity: "warning",
|
|
15304
|
+
file: sourceFile2.file,
|
|
15305
|
+
message: `Unexpected lib root file "${libRootFile}". Keep direct lib root files limited to generated support facets.`
|
|
15306
|
+
});
|
|
15307
|
+
}
|
|
15308
|
+
const moduleUiWarning = getModuleUiWarning(sourceFile2.file);
|
|
15309
|
+
if (moduleUiWarning)
|
|
15310
|
+
warnings.push(moduleUiWarning);
|
|
15311
|
+
return warnings;
|
|
15312
|
+
}
|
|
15313
|
+
}
|
|
15314
|
+
function formatQualityScanResult(result) {
|
|
15315
|
+
const sections = [
|
|
15316
|
+
"Akan Code Quality Scan",
|
|
15317
|
+
`workspace: ${result.workspaceRoot}`,
|
|
15318
|
+
`scanned files: ${result.scannedFiles}`,
|
|
15319
|
+
`warnings: ${result.warnings.length}`,
|
|
15320
|
+
"",
|
|
15321
|
+
"Warnings:",
|
|
15322
|
+
"",
|
|
15323
|
+
...formatQualityWarnings(result.warnings),
|
|
15324
|
+
"",
|
|
15325
|
+
"Suggested quality rules:",
|
|
15326
|
+
"",
|
|
15327
|
+
...result.suggestedRules.map((rule) => ` - ${rule}`)
|
|
15328
|
+
];
|
|
15329
|
+
return sections.join(`
|
|
15330
|
+
`);
|
|
15331
|
+
}
|
|
15332
|
+
function formatQualityWarnings(warnings) {
|
|
15333
|
+
if (warnings.length === 0)
|
|
15334
|
+
return ["No warnings found."];
|
|
15335
|
+
return warnings.flatMap((warning) => {
|
|
15336
|
+
const location = formatQualityLocation(warning.file, warning.line);
|
|
15337
|
+
const lines = [`${location} - warning ${warning.rule}: ${warning.message}`];
|
|
15338
|
+
if (warning.locations?.length) {
|
|
15339
|
+
lines.push(...warning.locations.map(({ file, line }) => ` note: related location ${formatQualityLocation(file, line)}`));
|
|
15340
|
+
}
|
|
15341
|
+
return lines;
|
|
15342
|
+
});
|
|
15343
|
+
}
|
|
15344
|
+
function formatQualityLocation(file, line) {
|
|
15345
|
+
return `${file ?? "<global>"}:${line ?? 1}:1`;
|
|
15346
|
+
}
|
|
15347
|
+
function getExportedFunctionLikes(sourceFile2) {
|
|
15348
|
+
const declarations = [];
|
|
15349
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15350
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15351
|
+
declarations.push({
|
|
15352
|
+
name: statement.name.text,
|
|
15353
|
+
kind: "function",
|
|
15354
|
+
file: sourceFile2.file,
|
|
15355
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15356
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement.body)
|
|
15357
|
+
});
|
|
15358
|
+
}
|
|
15359
|
+
if (ts11.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15360
|
+
declarations.push({
|
|
15361
|
+
name: statement.name.text,
|
|
15362
|
+
kind: "class",
|
|
15363
|
+
file: sourceFile2.file,
|
|
15364
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15365
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement)
|
|
15366
|
+
});
|
|
15367
|
+
}
|
|
15368
|
+
if (ts11.isVariableStatement(statement) && isExported(statement)) {
|
|
15369
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
15370
|
+
if (!ts11.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
|
|
15371
|
+
continue;
|
|
15372
|
+
declarations.push({
|
|
15373
|
+
name: declaration.name.text,
|
|
15374
|
+
kind: "function-variable",
|
|
15375
|
+
file: sourceFile2.file,
|
|
15376
|
+
line: getLine(sourceFile2.sourceFile, declaration),
|
|
15377
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, declaration.initializer)
|
|
15378
|
+
});
|
|
15379
|
+
}
|
|
15380
|
+
}
|
|
15381
|
+
}
|
|
15382
|
+
return declarations;
|
|
15383
|
+
}
|
|
15384
|
+
function getExportedClassNames(sourceFile2) {
|
|
15385
|
+
return sourceFile2.statements.filter((statement) => ts11.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
|
|
15386
|
+
}
|
|
15387
|
+
function getPlaceholderExportWarnings(sourceFile2) {
|
|
15388
|
+
if (!sourceFile2.file.endsWith("/index.ts") && !sourceFile2.file.endsWith("/index.tsx"))
|
|
15389
|
+
return [];
|
|
15390
|
+
return getTopLevelDeclarations(sourceFile2).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => ({
|
|
15391
|
+
rule: "akan.file.placeholder-export",
|
|
15392
|
+
scope: "file",
|
|
15393
|
+
severity: "warning",
|
|
15394
|
+
file: sourceFile2.file,
|
|
15395
|
+
line: declaration.line,
|
|
15396
|
+
message: `Generated or barrel index file should not export placeholder "${declaration.name}".`
|
|
15397
|
+
}));
|
|
15398
|
+
}
|
|
15399
|
+
function getDictionaryTextWarnings(sourceFile2) {
|
|
15400
|
+
if (!sourceFile2.file.endsWith(".dictionary.ts"))
|
|
15401
|
+
return [];
|
|
15402
|
+
const stalePatterns = [
|
|
15403
|
+
{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" },
|
|
15404
|
+
{ pattern: /settting/, label: "misspelling: settting" },
|
|
15405
|
+
{ pattern: /\uBC30\uB108 \uC218/, label: "stale copied Korean domain noun: \uBC30\uB108 \uC218" }
|
|
15406
|
+
];
|
|
15407
|
+
return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile2.content, pattern).map((line) => ({
|
|
15408
|
+
rule: "akan.file.dictionary-stale-text",
|
|
15409
|
+
scope: "file",
|
|
15410
|
+
severity: "warning",
|
|
15411
|
+
file: sourceFile2.file,
|
|
15412
|
+
line,
|
|
15413
|
+
message: `Dictionary text appears to contain ${label}.`
|
|
15414
|
+
})));
|
|
15415
|
+
}
|
|
15416
|
+
function getGlobalMutationWarnings(sourceFile2) {
|
|
15417
|
+
const warnings = [];
|
|
15418
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15419
|
+
if (ts11.isModuleDeclaration(statement) && statement.name.getText(sourceFile2.sourceFile) === "global") {
|
|
15420
|
+
warnings.push({
|
|
15421
|
+
rule: "akan.file.global-declaration",
|
|
15422
|
+
scope: "file",
|
|
15423
|
+
severity: "warning",
|
|
15424
|
+
file: sourceFile2.file,
|
|
15425
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15426
|
+
message: "Global declarations require an explicit low-level integration allowlist."
|
|
15427
|
+
});
|
|
15428
|
+
}
|
|
15429
|
+
if (ts11.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
15430
|
+
warnings.push({
|
|
15431
|
+
rule: "akan.file.window-augmentation",
|
|
15432
|
+
scope: "file",
|
|
15433
|
+
severity: "warning",
|
|
15434
|
+
file: sourceFile2.file,
|
|
15435
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15436
|
+
message: "Window augmentation should be isolated to approved browser integration files."
|
|
15437
|
+
});
|
|
15438
|
+
}
|
|
15439
|
+
if (ts11.isExpressionStatement(statement) && statement.expression.getText(sourceFile2.sourceFile).includes(".prototype.")) {
|
|
15440
|
+
warnings.push({
|
|
15441
|
+
rule: "akan.file.prototype-mutation",
|
|
15442
|
+
scope: "file",
|
|
15443
|
+
severity: "warning",
|
|
15444
|
+
file: sourceFile2.file,
|
|
15445
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15446
|
+
message: "Prototype mutation should be avoided or isolated to approved low-level integration files."
|
|
15447
|
+
});
|
|
15448
|
+
}
|
|
15449
|
+
}
|
|
15450
|
+
return warnings;
|
|
15451
|
+
}
|
|
15452
|
+
function getTopLevelDeclarations(sourceFile2) {
|
|
15453
|
+
return sourceFile2.sourceFile.statements.flatMap((statement) => getTopLevelDeclaration(sourceFile2.sourceFile, statement));
|
|
15454
|
+
}
|
|
15455
|
+
function getTopLevelDeclaration(sourceFile2, statement) {
|
|
15456
|
+
const line = getLine(sourceFile2, statement);
|
|
15457
|
+
if (ts11.isClassDeclaration(statement) && statement.name) {
|
|
15458
|
+
return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
|
|
15459
|
+
}
|
|
15460
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name) {
|
|
15461
|
+
return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
|
|
15462
|
+
}
|
|
15463
|
+
if (ts11.isInterfaceDeclaration(statement)) {
|
|
15464
|
+
return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
|
|
15465
|
+
}
|
|
15466
|
+
if (ts11.isTypeAliasDeclaration(statement)) {
|
|
15467
|
+
return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
|
|
15468
|
+
}
|
|
15469
|
+
if (ts11.isEnumDeclaration(statement)) {
|
|
15470
|
+
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
15471
|
+
}
|
|
15472
|
+
if (ts11.isVariableStatement(statement)) {
|
|
15473
|
+
return statement.declarationList.declarations.filter((declaration) => ts11.isIdentifier(declaration.name)).map((declaration) => ({
|
|
15474
|
+
name: declaration.name.text,
|
|
15475
|
+
kind: "variable",
|
|
15476
|
+
line: getLine(sourceFile2, declaration),
|
|
15477
|
+
exported: isExported(statement),
|
|
15478
|
+
node: statement
|
|
15479
|
+
}));
|
|
15480
|
+
}
|
|
15481
|
+
if (ts11.isExportDeclaration(statement)) {
|
|
15482
|
+
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
15483
|
+
}
|
|
15484
|
+
return [];
|
|
15485
|
+
}
|
|
15486
|
+
function isAllowedConventionDeclaration(suffix, modelName, declaration) {
|
|
15487
|
+
if (suffix === ".dictionary.ts")
|
|
15488
|
+
return isExportedConst(declaration) && declaration.name === "dictionary";
|
|
15489
|
+
if (suffix === ".constant.ts")
|
|
15490
|
+
return isAllowedConstantDeclaration(modelName, declaration);
|
|
15491
|
+
if (suffix === ".document.ts")
|
|
15492
|
+
return declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name);
|
|
15493
|
+
if (suffix === ".service.ts")
|
|
15494
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Service`;
|
|
15495
|
+
if (suffix === ".signal.ts")
|
|
15496
|
+
return declaration.kind === "class" && [`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name);
|
|
15497
|
+
if (suffix === ".store.ts")
|
|
15498
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Store`;
|
|
15499
|
+
return false;
|
|
15500
|
+
}
|
|
15501
|
+
function isAllowedConstantDeclaration(modelName, declaration) {
|
|
15502
|
+
if (declaration.kind !== "class")
|
|
15503
|
+
return false;
|
|
15504
|
+
if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
|
|
15505
|
+
return true;
|
|
15506
|
+
if (!ts11.isClassDeclaration(declaration.node))
|
|
15507
|
+
return false;
|
|
15508
|
+
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts11.SyntaxKind.ExtendsKeyword);
|
|
15509
|
+
const expression = heritageClause?.types[0]?.expression;
|
|
15510
|
+
return !!expression && expression.getText().startsWith("enumOf(");
|
|
15511
|
+
}
|
|
15512
|
+
function getConventionDescription(suffix, modelName) {
|
|
15513
|
+
if (suffix === ".dictionary.ts")
|
|
15514
|
+
return "export const dictionary";
|
|
15515
|
+
if (suffix === ".constant.ts")
|
|
15516
|
+
return `${modelName}Input, ${modelName}Object, ${modelName}, Light${modelName}, ${modelName}Insight, or enumOf classes`;
|
|
15517
|
+
if (suffix === ".document.ts")
|
|
15518
|
+
return `${modelName}Filter, ${modelName}, or ${modelName}Model`;
|
|
15519
|
+
if (suffix === ".service.ts")
|
|
15520
|
+
return `${modelName}Service`;
|
|
15521
|
+
if (suffix === ".signal.ts")
|
|
15522
|
+
return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
|
|
15523
|
+
return `${modelName}Store`;
|
|
15524
|
+
}
|
|
15525
|
+
function getLibRootFile(file) {
|
|
15526
|
+
const segments = file.split("/");
|
|
15527
|
+
if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
|
|
15528
|
+
return segments[3];
|
|
15529
|
+
if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
|
|
15530
|
+
return segments[4];
|
|
15531
|
+
return null;
|
|
15532
|
+
}
|
|
15533
|
+
function getModuleUiWarning(file) {
|
|
15534
|
+
if (!file.endsWith(".tsx"))
|
|
15535
|
+
return null;
|
|
15536
|
+
const moduleInfo = getModuleInfo(file);
|
|
15537
|
+
if (!moduleInfo)
|
|
15538
|
+
return null;
|
|
15539
|
+
const { moduleName, fileName, kind } = moduleInfo;
|
|
15540
|
+
const pascalName = toPascalCase(moduleName.replace(/^_+/, ""));
|
|
15541
|
+
const allowedSuffixes = kind === "database" ? ["Template", "Unit", "Util", "View", "Zone"] : kind === "service" ? ["Util", "Zone"] : ["Template", "Unit"];
|
|
15542
|
+
const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
|
|
15543
|
+
if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx"))
|
|
15544
|
+
return null;
|
|
15545
|
+
return {
|
|
15546
|
+
rule: "akan.layout.module-ui-file",
|
|
15547
|
+
scope: "layout",
|
|
15548
|
+
severity: "warning",
|
|
15549
|
+
file,
|
|
15550
|
+
message: `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`
|
|
15551
|
+
};
|
|
15552
|
+
}
|
|
15553
|
+
function getModuleInfo(file) {
|
|
15554
|
+
const segments = file.split("/");
|
|
15555
|
+
const libIndex = segments.indexOf("lib");
|
|
15556
|
+
if (libIndex < 0)
|
|
15557
|
+
return null;
|
|
15558
|
+
const moduleName = segments[libIndex + 1];
|
|
15559
|
+
if (moduleName === "__scalar") {
|
|
15560
|
+
if (segments.length !== libIndex + 4)
|
|
15561
|
+
return null;
|
|
15562
|
+
return { moduleName: segments[libIndex + 2], fileName: segments[libIndex + 3], kind: "scalar" };
|
|
15563
|
+
}
|
|
15564
|
+
if (segments.length !== libIndex + 3)
|
|
15565
|
+
return null;
|
|
15566
|
+
const fileName = segments[libIndex + 2];
|
|
15567
|
+
if (moduleName.startsWith("__")) {
|
|
15568
|
+
const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
|
|
15569
|
+
return { moduleName: scalarName, fileName, kind: "scalar" };
|
|
15570
|
+
}
|
|
15571
|
+
if (moduleName.startsWith("_"))
|
|
15572
|
+
return { moduleName, fileName, kind: "service" };
|
|
15573
|
+
return { moduleName, fileName, kind: "database" };
|
|
15574
|
+
}
|
|
15575
|
+
function isExportedConst(declaration) {
|
|
15576
|
+
return declaration.exported && ts11.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts11.NodeFlags.Const) !== 0;
|
|
15577
|
+
}
|
|
15578
|
+
function isExported(node) {
|
|
15579
|
+
return !!ts11.getCombinedModifierFlags(node) && !!(ts11.getCombinedModifierFlags(node) & ts11.ModifierFlags.Export);
|
|
15580
|
+
}
|
|
15581
|
+
function isFunctionLikeInitializer(node) {
|
|
15582
|
+
return !!node && (ts11.isArrowFunction(node) || ts11.isFunctionExpression(node));
|
|
15583
|
+
}
|
|
15584
|
+
function getBodyFingerprint(sourceFile2, node) {
|
|
15585
|
+
if (!node)
|
|
15586
|
+
return;
|
|
15587
|
+
const normalizedBody = node.getText(sourceFile2).replace(/\s+/g, " ").trim();
|
|
15588
|
+
if (normalizedBody.length < 80)
|
|
15589
|
+
return;
|
|
15590
|
+
return createHash("sha256").update(normalizedBody).digest("hex");
|
|
15591
|
+
}
|
|
15592
|
+
function shouldSkipPath(relativePath, isDirectory, ignoreFilter) {
|
|
15593
|
+
const ignorePath = isDirectory ? `${relativePath}/` : relativePath;
|
|
15594
|
+
return relativePath === ".git" || relativePath.includes("/.git/") || relativePath.includes("/node_modules/") || ignoreFilter.ignores(relativePath) || ignoreFilter.ignores(ignorePath);
|
|
15595
|
+
}
|
|
15596
|
+
async function isDirectory(absolutePath) {
|
|
15597
|
+
try {
|
|
15598
|
+
return (await stat4(absolutePath)).isDirectory();
|
|
15599
|
+
} catch {
|
|
15600
|
+
return false;
|
|
15601
|
+
}
|
|
15602
|
+
}
|
|
15603
|
+
function getScriptKind(file) {
|
|
15604
|
+
return file.endsWith(".tsx") ? ts11.ScriptKind.TSX : ts11.ScriptKind.TS;
|
|
15605
|
+
}
|
|
15606
|
+
function getLine(sourceFile2, node) {
|
|
15607
|
+
return sourceFile2.getLineAndCharacterOfPosition(node.getStart(sourceFile2)).line + 1;
|
|
15608
|
+
}
|
|
15609
|
+
function getRecommendedLineLimit(file) {
|
|
15610
|
+
if (file.endsWith(".service.ts"))
|
|
15611
|
+
return 500;
|
|
15612
|
+
if (file.endsWith(".Template.tsx") || file.endsWith(".Zone.tsx"))
|
|
15613
|
+
return 800;
|
|
15614
|
+
if (file.endsWith(".Util.tsx"))
|
|
15615
|
+
return 1000;
|
|
15616
|
+
return null;
|
|
15617
|
+
}
|
|
15618
|
+
function findPatternLines(content, pattern) {
|
|
15619
|
+
return content.split(/\r?\n/).flatMap((line, index) => pattern.test(line) ? [index + 1] : []);
|
|
15620
|
+
}
|
|
15621
|
+
function toPascalCase(value) {
|
|
15622
|
+
return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
|
|
15623
|
+
}
|
|
15624
|
+
function toPosix(value) {
|
|
15625
|
+
return value.split(path43.sep).join("/");
|
|
15626
|
+
}
|
|
15627
|
+
function groupBy(items, getKey) {
|
|
15628
|
+
const grouped = new Map;
|
|
15629
|
+
for (const item of items) {
|
|
15630
|
+
const key = getKey(item);
|
|
15631
|
+
grouped.set(key, [...grouped.get(key) ?? [], item]);
|
|
15632
|
+
}
|
|
15633
|
+
return grouped;
|
|
15634
|
+
}
|
|
15635
|
+
function compareWarnings(a, b) {
|
|
15636
|
+
return a.scope.localeCompare(b.scope) || (a.file ?? "").localeCompare(b.file ?? "") || (a.line ?? 0) - (b.line ?? 0) || a.rule.localeCompare(b.rule);
|
|
15637
|
+
}
|
|
14909
15638
|
// pkgs/@akanjs/devkit/selectModel.ts
|
|
14910
15639
|
import { select as select5 } from "@inquirer/prompts";
|
|
14911
15640
|
// pkgs/@akanjs/devkit/streamAi.ts
|
|
@@ -15867,7 +16596,7 @@ class ApplicationCommand extends command("application", [ApplicationScript], ({
|
|
|
15867
16596
|
import { Logger as Logger16 } from "akanjs/common";
|
|
15868
16597
|
|
|
15869
16598
|
// pkgs/@akanjs/cli/package/package.runner.ts
|
|
15870
|
-
import
|
|
16599
|
+
import path44 from "path";
|
|
15871
16600
|
import { Logger as Logger14 } from "akanjs/common";
|
|
15872
16601
|
var {$: $2 } = globalThis.Bun;
|
|
15873
16602
|
|
|
@@ -15887,11 +16616,11 @@ class PackageRunner extends runner("package") {
|
|
|
15887
16616
|
}
|
|
15888
16617
|
async#getInstalledPackageJson() {
|
|
15889
16618
|
const packageJsonCandidates = [
|
|
15890
|
-
`${
|
|
16619
|
+
`${path44.dirname(Bun.main)}/package.json`,
|
|
15891
16620
|
`${process.cwd()}/node_modules/akanjs/package.json`
|
|
15892
16621
|
];
|
|
15893
16622
|
try {
|
|
15894
|
-
packageJsonCandidates.unshift(Bun.resolveSync("akanjs/package.json",
|
|
16623
|
+
packageJsonCandidates.unshift(Bun.resolveSync("akanjs/package.json", path44.dirname(Bun.main)));
|
|
15895
16624
|
} catch {}
|
|
15896
16625
|
for (const packageJsonPath of packageJsonCandidates) {
|
|
15897
16626
|
if (!await Bun.file(packageJsonPath).exists())
|
|
@@ -15900,7 +16629,7 @@ class PackageRunner extends runner("package") {
|
|
|
15900
16629
|
if (packageJson.name === "akanjs" || packageJson.name === "@akanjs/cli")
|
|
15901
16630
|
return packageJson;
|
|
15902
16631
|
}
|
|
15903
|
-
throw new Error(`[package] failed to locate akanjs package.json from ${
|
|
16632
|
+
throw new Error(`[package] failed to locate akanjs package.json from ${path44.dirname(Bun.main)}`);
|
|
15904
16633
|
}
|
|
15905
16634
|
async createPackage(workspace, pkgName) {
|
|
15906
16635
|
await workspace.applyTemplate({ basePath: `pkgs/${pkgName}`, template: "pkgRoot", dict: { pkgName } });
|
|
@@ -16075,7 +16804,7 @@ class PackageScript extends script("package", [PackageRunner]) {
|
|
|
16075
16804
|
}
|
|
16076
16805
|
|
|
16077
16806
|
// pkgs/@akanjs/cli/cloud/cloud.runner.ts
|
|
16078
|
-
import
|
|
16807
|
+
import path45 from "path";
|
|
16079
16808
|
import { confirm as confirm4, input as input5, select as select8 } from "@inquirer/prompts";
|
|
16080
16809
|
import { Logger as Logger15, sleep } from "akanjs/common";
|
|
16081
16810
|
import chalk7 from "chalk";
|
|
@@ -16346,7 +17075,7 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
16346
17075
|
await Promise.all(akanPkgs.map(async (library) => {
|
|
16347
17076
|
Logger15.info(`Publishing ${library}@${nextVersion} to ${registry ?? "npm"}...`);
|
|
16348
17077
|
await workspace.spawn("npm", ["publish", "--tag", tag, ...this.#getRegistryArgs(registry), ...this.#getLocalRegistryAuthArgs(registry)], {
|
|
16349
|
-
cwd:
|
|
17078
|
+
cwd: path45.join(workspace.workspaceRoot, "dist/pkgs", library),
|
|
16350
17079
|
env: this.#getRegistryEnv(registry),
|
|
16351
17080
|
stdio: "inherit"
|
|
16352
17081
|
});
|
|
@@ -16400,11 +17129,12 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
16400
17129
|
async downloadEnv(cloudApi2, workspace, workspaceId) {
|
|
16401
17130
|
await workspace.mkdir("local");
|
|
16402
17131
|
const localPath = await cloudApi2.downloadEnv(workspaceId);
|
|
16403
|
-
|
|
17132
|
+
const relativePath = path45.relative(workspace.workspaceRoot, localPath).split(path45.sep).join("/");
|
|
17133
|
+
await workspace.spawn("tar", ["-xf", relativePath], { cwd: workspace.workspaceRoot });
|
|
16404
17134
|
await workspace.remove(localPath);
|
|
16405
17135
|
}
|
|
16406
17136
|
async uploadEnv(cloudApi2, workspaceId, filePath) {
|
|
16407
|
-
const file = new File([Bun.file(filePath)],
|
|
17137
|
+
const file = new File([Bun.file(filePath)], path45.basename(filePath));
|
|
16408
17138
|
await cloudApi2.uploadEnv(workspaceId, file);
|
|
16409
17139
|
}
|
|
16410
17140
|
async downloadEnvByScp(workspace) {
|
|
@@ -16496,14 +17226,14 @@ class CloudScript extends script("cloud", [CloudRunner, ApplicationScript, Packa
|
|
|
16496
17226
|
}
|
|
16497
17227
|
async uploadEnv(workspace) {
|
|
16498
17228
|
const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
|
|
16499
|
-
const { path:
|
|
17229
|
+
const { path: path46 } = await this.cloudRunner.gatherEnvFiles(workspace);
|
|
16500
17230
|
if (workspaceId) {
|
|
16501
17231
|
await this.login(workspace);
|
|
16502
17232
|
const cloudApi2 = await CloudApi.fromHost(workspace);
|
|
16503
|
-
await this.cloudRunner.uploadEnv(cloudApi2, workspaceId,
|
|
17233
|
+
await this.cloudRunner.uploadEnv(cloudApi2, workspaceId, path46);
|
|
16504
17234
|
return;
|
|
16505
17235
|
}
|
|
16506
|
-
await this.cloudRunner.uploadEnvByScp(workspace,
|
|
17236
|
+
await this.cloudRunner.uploadEnvByScp(workspace, path46);
|
|
16507
17237
|
}
|
|
16508
17238
|
async deployAkan(workspace, { test = true, registryUrl } = {}) {
|
|
16509
17239
|
const akanPkgs = await this.cloudRunner.getAkanPkgs(workspace);
|
|
@@ -16744,8 +17474,8 @@ class RepairRunner extends runner("repair") {
|
|
|
16744
17474
|
}
|
|
16745
17475
|
|
|
16746
17476
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
16747
|
-
import { mkdir as mkdir12, readFile as
|
|
16748
|
-
import
|
|
17477
|
+
import { mkdir as mkdir12, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
17478
|
+
import path46 from "path";
|
|
16749
17479
|
import { capitalize as capitalize8 } from "akanjs/common";
|
|
16750
17480
|
|
|
16751
17481
|
// pkgs/@akanjs/cli/workflows/shared.ts
|
|
@@ -17288,7 +18018,7 @@ var workflowSpecs = [
|
|
|
17288
18018
|
];
|
|
17289
18019
|
|
|
17290
18020
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
17291
|
-
var resolvePath = (filePath) =>
|
|
18021
|
+
var resolvePath = (filePath) => path46.isAbsolute(filePath) ? filePath : path46.join(process.cwd(), filePath);
|
|
17292
18022
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17293
18023
|
var isWorkflowPlan2 = (value) => {
|
|
17294
18024
|
if (!isRecord3(value))
|
|
@@ -17316,7 +18046,8 @@ var failedPlan = (workflow2, diagnostics) => ({
|
|
|
17316
18046
|
validation: [],
|
|
17317
18047
|
diagnostics,
|
|
17318
18048
|
recommendations: [],
|
|
17319
|
-
requiresApproval: true
|
|
18049
|
+
requiresApproval: true,
|
|
18050
|
+
approval: workflowPlanApproval
|
|
17320
18051
|
});
|
|
17321
18052
|
var failedApplyReport = (workflow2, diagnostics, plan2) => createWorkflowApplyReport({
|
|
17322
18053
|
workflow: workflow2,
|
|
@@ -17385,7 +18116,7 @@ var defaultValidationExecutor = (workspace) => async (command3) => {
|
|
|
17385
18116
|
};
|
|
17386
18117
|
}
|
|
17387
18118
|
};
|
|
17388
|
-
var readJsonFile = async (filePath) => JSON.parse(await
|
|
18119
|
+
var readJsonFile = async (filePath) => JSON.parse(await readFile3(resolvePath(filePath), "utf8"));
|
|
17389
18120
|
var planInputString2 = (plan2, key) => {
|
|
17390
18121
|
const value = plan2.inputs[key];
|
|
17391
18122
|
return typeof value === "string" ? value : "";
|
|
@@ -17428,17 +18159,33 @@ var applyBaselineBlockerCache = async (workspace, report) => {
|
|
|
17428
18159
|
});
|
|
17429
18160
|
}
|
|
17430
18161
|
await workspace.writeFile(cachePath, jsonText({ schemaVersion: 1, workflow: report.workflow, blockers: [...blockers.values()] }), { silent: true });
|
|
18162
|
+
const knownBlockers = report.knownBlockers.map((blocker) => {
|
|
18163
|
+
if (!knownFingerprints.has(blockerFingerprint(blocker)))
|
|
18164
|
+
return blocker;
|
|
18165
|
+
return {
|
|
18166
|
+
...blocker,
|
|
18167
|
+
known: true,
|
|
18168
|
+
message: `Known baseline blocker, unrelated to this source change: ${blocker.message}`
|
|
18169
|
+
};
|
|
18170
|
+
});
|
|
17431
18171
|
return {
|
|
17432
18172
|
...report,
|
|
17433
|
-
knownBlockers
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
18173
|
+
knownBlockers,
|
|
18174
|
+
baselineSummary: {
|
|
18175
|
+
...report.baselineSummary,
|
|
18176
|
+
knownBlockerCount: knownBlockers.filter((blocker) => blocker.known).length
|
|
18177
|
+
}
|
|
18178
|
+
};
|
|
18179
|
+
};
|
|
18180
|
+
var withBaselineDetailsPolicy = (report, includeBaselineDetails) => {
|
|
18181
|
+
const baselineDiagnostics = report.baselineDiagnostics ?? [];
|
|
18182
|
+
return {
|
|
18183
|
+
...report,
|
|
18184
|
+
baselineSummary: createWorkflowBaselineSummary(baselineDiagnostics, {
|
|
18185
|
+
detailsIncluded: includeBaselineDetails,
|
|
18186
|
+
knownBlockerCount: report.knownBlockers.filter((blocker) => blocker.known).length
|
|
18187
|
+
}),
|
|
18188
|
+
baselineDiagnostics: includeBaselineDetails ? baselineDiagnostics : []
|
|
17442
18189
|
};
|
|
17443
18190
|
};
|
|
17444
18191
|
|
|
@@ -17465,7 +18212,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17465
18212
|
const plan2 = createWorkflowPlan(spec, compactWorkflowInputs(inputs));
|
|
17466
18213
|
if (out) {
|
|
17467
18214
|
const outPath = resolvePath(out);
|
|
17468
|
-
await mkdir12(
|
|
18215
|
+
await mkdir12(path46.dirname(outPath), { recursive: true });
|
|
17469
18216
|
await writeFile2(outPath, jsonText(plan2));
|
|
17470
18217
|
}
|
|
17471
18218
|
return format === "json" ? jsonText(plan2) : renderWorkflowPlan(plan2);
|
|
@@ -17484,7 +18231,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17484
18231
|
};
|
|
17485
18232
|
let plan2;
|
|
17486
18233
|
try {
|
|
17487
|
-
const parsed = JSON.parse(await
|
|
18234
|
+
const parsed = JSON.parse(await readFile3(resolvePath(planPath), "utf8"));
|
|
17488
18235
|
if (!isWorkflowPlan2(parsed)) {
|
|
17489
18236
|
const report = failedApplyReport("unknown", [
|
|
17490
18237
|
{
|
|
@@ -17534,7 +18281,8 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17534
18281
|
async validate(runIdOrPlan, {
|
|
17535
18282
|
format = "markdown",
|
|
17536
18283
|
workspace,
|
|
17537
|
-
execute
|
|
18284
|
+
execute,
|
|
18285
|
+
includeBaselineDetails = false
|
|
17538
18286
|
}) {
|
|
17539
18287
|
const loaded = await this.loadValidationTarget(runIdOrPlan, workspace);
|
|
17540
18288
|
const doctor = await AkanContextAnalyzer.doctor(workspace, {
|
|
@@ -17542,7 +18290,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17542
18290
|
runIdOrPlan,
|
|
17543
18291
|
changedFiles: loaded.changedFiles
|
|
17544
18292
|
});
|
|
17545
|
-
const report = await applyBaselineBlockerCache(workspace, await createWorkflowValidationRunReport({
|
|
18293
|
+
const report = withBaselineDetailsPolicy(await applyBaselineBlockerCache(workspace, await createWorkflowValidationRunReport({
|
|
17546
18294
|
workflow: loaded.plan?.workflow ?? loaded.workflow,
|
|
17547
18295
|
source: loaded.source,
|
|
17548
18296
|
plan: loaded.plan,
|
|
@@ -17552,7 +18300,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
17552
18300
|
baselineDiagnostics: (doctor.baselineDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "baseline")),
|
|
17553
18301
|
workflowDiagnostics: (doctor.workflowDiagnostics ?? []).map((diagnostic) => workflowDiagnosticFromDoctor(diagnostic, "workflow")),
|
|
17554
18302
|
repairActions: loaded.repairActions
|
|
17555
|
-
}));
|
|
18303
|
+
})), includeBaselineDetails);
|
|
17556
18304
|
await writeWorkflowRunArtifact(workspace, report);
|
|
17557
18305
|
return renderWorkflowValidation(report, format);
|
|
17558
18306
|
}
|
|
@@ -18814,6 +19562,32 @@ var createCliWorkflowStepRegistry = (workspace) => createWorkflowStepRegistry({
|
|
|
18814
19562
|
});
|
|
18815
19563
|
|
|
18816
19564
|
// pkgs/@akanjs/cli/context/context.runner.ts
|
|
19565
|
+
var workflowDiagnosticFromContext = (diagnostic) => ({
|
|
19566
|
+
severity: diagnostic.severity,
|
|
19567
|
+
code: diagnostic.code,
|
|
19568
|
+
message: diagnostic.message,
|
|
19569
|
+
scope: diagnostic.scope,
|
|
19570
|
+
context: diagnostic.context
|
|
19571
|
+
});
|
|
19572
|
+
var compactDoctorWorkspaceResult = (result, includeBaselineDetails) => {
|
|
19573
|
+
const baselineDiagnostics = result.baselineDiagnostics ?? [];
|
|
19574
|
+
if (baselineDiagnostics.length === 0) {
|
|
19575
|
+
return {
|
|
19576
|
+
...result,
|
|
19577
|
+
baselineSummary: createWorkflowBaselineSummary([], { detailsIncluded: includeBaselineDetails })
|
|
19578
|
+
};
|
|
19579
|
+
}
|
|
19580
|
+
const baselineSummary = createWorkflowBaselineSummary(baselineDiagnostics.map(workflowDiagnosticFromContext), {
|
|
19581
|
+
detailsIncluded: includeBaselineDetails
|
|
19582
|
+
});
|
|
19583
|
+
return {
|
|
19584
|
+
...result,
|
|
19585
|
+
baselineSummary,
|
|
19586
|
+
diagnostics: includeBaselineDetails ? result.diagnostics : result.diagnostics.filter((diagnostic) => diagnostic.scope !== "baseline"),
|
|
19587
|
+
baselineDiagnostics: includeBaselineDetails ? baselineDiagnostics : []
|
|
19588
|
+
};
|
|
19589
|
+
};
|
|
19590
|
+
|
|
18817
19591
|
class ContextRunner extends runner("context") {
|
|
18818
19592
|
async getContext(workspace, {
|
|
18819
19593
|
format = "markdown",
|
|
@@ -18903,11 +19677,11 @@ class ContextRunner extends runner("context") {
|
|
|
18903
19677
|
if (name === "get_guideline")
|
|
18904
19678
|
return await Prompter.getInstruction(stringArg(args, "name"));
|
|
18905
19679
|
if (name === "doctor_workspace")
|
|
18906
|
-
return await AkanContextAnalyzer.doctor(workspace, {
|
|
19680
|
+
return compactDoctorWorkspaceResult(await AkanContextAnalyzer.doctor(workspace, {
|
|
18907
19681
|
strict: !!args.strict,
|
|
18908
19682
|
runIdOrPlan: typeof args.runIdOrPlan === "string" ? workspacePath(workspace, args.runIdOrPlan) : null,
|
|
18909
19683
|
changedFiles: Array.isArray(args.changedFiles) ? args.changedFiles.filter((file) => typeof file === "string") : []
|
|
18910
|
-
});
|
|
19684
|
+
}), !!args.includeBaselineDetails);
|
|
18911
19685
|
if (name === "explain_command")
|
|
18912
19686
|
return this.explainCommand(stringArg(args, "command"));
|
|
18913
19687
|
if (name === "get_validation_contract")
|
|
@@ -18927,6 +19701,7 @@ class ContextRunner extends runner("context") {
|
|
|
18927
19701
|
return {
|
|
18928
19702
|
...plan2,
|
|
18929
19703
|
planPath,
|
|
19704
|
+
approval: typeof plan2 === "object" && plan2 && "approval" in plan2 ? { ...plan2.approval, canApplyWith: { planPath } } : undefined,
|
|
18930
19705
|
next: { tool: "apply_workflow", args: { planPath } },
|
|
18931
19706
|
policy: applyFirstPolicy
|
|
18932
19707
|
};
|
|
@@ -18949,7 +19724,8 @@ class ContextRunner extends runner("context") {
|
|
|
18949
19724
|
if (name === "run_validation")
|
|
18950
19725
|
return parseJsonOutput(await new WorkflowRunner().validate(workspacePath(workspace, stringArg(args, "runIdOrPlan")), {
|
|
18951
19726
|
format: "json",
|
|
18952
|
-
workspace
|
|
19727
|
+
workspace,
|
|
19728
|
+
includeBaselineDetails: !!args.includeBaselineDetails
|
|
18953
19729
|
}));
|
|
18954
19730
|
if (name === "repair_generated") {
|
|
18955
19731
|
const report = parseJsonOutput(await new RepairRunner().repair("generated", { workspace, app: stringArg(args, "app"), format: "json" }));
|
|
@@ -19260,14 +20036,14 @@ class GuidelinePrompt extends Prompter {
|
|
|
19260
20036
|
async#getScanFilePaths(matchPattern, { avoidDirs = ["node_modules", ".next"], filterText } = {}) {
|
|
19261
20037
|
const glob = new Bun.Glob(matchPattern);
|
|
19262
20038
|
const paths = [];
|
|
19263
|
-
for await (const
|
|
19264
|
-
if (avoidDirs.some((dir) =>
|
|
20039
|
+
for await (const path47 of glob.scan({ cwd: this.workspace.workspaceRoot, absolute: true })) {
|
|
20040
|
+
if (avoidDirs.some((dir) => path47.includes(dir)))
|
|
19265
20041
|
continue;
|
|
19266
|
-
const fileContent = await FileSys.readText(
|
|
20042
|
+
const fileContent = await FileSys.readText(path47);
|
|
19267
20043
|
const textFilter = filterText ? new RegExp(filterText) : null;
|
|
19268
20044
|
if (filterText && !textFilter?.test(fileContent))
|
|
19269
20045
|
continue;
|
|
19270
|
-
paths.push(
|
|
20046
|
+
paths.push(path47);
|
|
19271
20047
|
}
|
|
19272
20048
|
return paths;
|
|
19273
20049
|
}
|
|
@@ -19587,7 +20363,7 @@ class LibraryCommand extends command("library", [LibraryScript], ({ public: targ
|
|
|
19587
20363
|
|
|
19588
20364
|
// pkgs/@akanjs/cli/localRegistry/localRegistry.runner.ts
|
|
19589
20365
|
import { mkdir as mkdir13, rm as rm6 } from "fs/promises";
|
|
19590
|
-
import
|
|
20366
|
+
import path47 from "path";
|
|
19591
20367
|
import { Logger as Logger19 } from "akanjs/common";
|
|
19592
20368
|
var defaultLocalRegistryUrl = "http://127.0.0.1:4873";
|
|
19593
20369
|
var containerName = "akan-verdaccio";
|
|
@@ -19605,8 +20381,8 @@ class LocalRegistryRunner extends runner("localRegistry") {
|
|
|
19605
20381
|
Logger19.info(`Local registry is already running at ${registry}`);
|
|
19606
20382
|
return registry;
|
|
19607
20383
|
} catch {}
|
|
19608
|
-
const configPath2 =
|
|
19609
|
-
const storagePath =
|
|
20384
|
+
const configPath2 = path47.join(workspace.workspaceRoot, "pkgs/@akanjs/cli/localRegistry/verdaccio.yaml");
|
|
20385
|
+
const storagePath = path47.join(workspace.workspaceRoot, ".akan/verdaccio/storage");
|
|
19610
20386
|
await mkdir13(storagePath, { recursive: true });
|
|
19611
20387
|
await workspace.spawn("docker", [
|
|
19612
20388
|
"run",
|
|
@@ -19629,13 +20405,13 @@ class LocalRegistryRunner extends runner("localRegistry") {
|
|
|
19629
20405
|
try {
|
|
19630
20406
|
await workspace.spawn("docker", ["rm", "-f", containerName], { stdio: "inherit" });
|
|
19631
20407
|
} catch {}
|
|
19632
|
-
await rm6(
|
|
20408
|
+
await rm6(path47.join(workspace.workspaceRoot, ".akan/verdaccio"), { recursive: true, force: true });
|
|
19633
20409
|
Logger19.info("Local registry storage has been reset");
|
|
19634
20410
|
}
|
|
19635
20411
|
async smoke(workspace, { registryUrl } = {}) {
|
|
19636
20412
|
const registry = this.getRegistryUrl(registryUrl);
|
|
19637
|
-
const smokeRoot =
|
|
19638
|
-
await rm6(
|
|
20413
|
+
const smokeRoot = path47.join(workspace.workspaceRoot, ".akan/e2e");
|
|
20414
|
+
await rm6(path47.join(smokeRoot, smokeRepoName), { recursive: true, force: true });
|
|
19639
20415
|
await workspace.spawn(process.execPath, [
|
|
19640
20416
|
"dist/pkgs/create-akan-workspace/index.js",
|
|
19641
20417
|
smokeRepoName,
|
|
@@ -19652,12 +20428,12 @@ class LocalRegistryRunner extends runner("localRegistry") {
|
|
|
19652
20428
|
stdio: "inherit"
|
|
19653
20429
|
});
|
|
19654
20430
|
await workspace.spawn("akan", ["typecheck", smokeAppName], {
|
|
19655
|
-
cwd:
|
|
20431
|
+
cwd: path47.join(smokeRoot, smokeRepoName),
|
|
19656
20432
|
env: { ...process.env, AKAN_NPM_REGISTRY: registry, NPM_CONFIG_REGISTRY: registry },
|
|
19657
20433
|
stdio: "inherit"
|
|
19658
20434
|
});
|
|
19659
20435
|
await workspace.spawn("akan", ["build", smokeAppName], {
|
|
19660
|
-
cwd:
|
|
20436
|
+
cwd: path47.join(smokeRoot, smokeRepoName),
|
|
19661
20437
|
env: { ...process.env, AKAN_NPM_REGISTRY: registry, NPM_CONFIG_REGISTRY: registry },
|
|
19662
20438
|
stdio: "inherit"
|
|
19663
20439
|
});
|
|
@@ -19836,8 +20612,46 @@ class PrimitiveCommand extends command("primitive", [PrimitiveScript], ({ public
|
|
|
19836
20612
|
})) {
|
|
19837
20613
|
}
|
|
19838
20614
|
|
|
19839
|
-
// pkgs/@akanjs/cli/
|
|
20615
|
+
// pkgs/@akanjs/cli/quality/quality.script.ts
|
|
19840
20616
|
import { Logger as Logger22 } from "akanjs/common";
|
|
20617
|
+
|
|
20618
|
+
// pkgs/@akanjs/cli/quality/quality.runner.ts
|
|
20619
|
+
class QualityRunner extends runner("quality") {
|
|
20620
|
+
async scan(workspace) {
|
|
20621
|
+
return await new AkanQualityScanner().scan(workspace.workspaceRoot);
|
|
20622
|
+
}
|
|
20623
|
+
}
|
|
20624
|
+
|
|
20625
|
+
// pkgs/@akanjs/cli/quality/quality.script.ts
|
|
20626
|
+
class QualityScript extends script("quality", [QualityRunner]) {
|
|
20627
|
+
async scan(workspace, format = "text") {
|
|
20628
|
+
const spinner2 = workspace.spinning("Scanning Akan code quality...");
|
|
20629
|
+
const result = await this.qualityRunner.scan(workspace);
|
|
20630
|
+
spinner2.succeed(`Quality scan completed (${result.scannedFiles} files, ${result.warnings.length} warnings)`);
|
|
20631
|
+
Logger22.rawLog(format === "json" ? JSON.stringify(result, null, 2) : formatQualityScanResult(result));
|
|
20632
|
+
}
|
|
20633
|
+
}
|
|
20634
|
+
|
|
20635
|
+
// pkgs/@akanjs/cli/quality/quality.command.ts
|
|
20636
|
+
class QualityCommand extends command("quality", [QualityScript], ({ public: target }) => ({
|
|
20637
|
+
quality: target({ desc: "Scan apps and libs for Akan code quality warnings" }).arg("action", String, {
|
|
20638
|
+
desc: "quality action",
|
|
20639
|
+
default: "scan",
|
|
20640
|
+
enum: ["scan"]
|
|
20641
|
+
}).option("format", String, {
|
|
20642
|
+
desc: "output format",
|
|
20643
|
+
default: "text",
|
|
20644
|
+
enum: ["text", "json"]
|
|
20645
|
+
}).with(Workspace).exec(async function(action, format, workspace) {
|
|
20646
|
+
if (action !== "scan")
|
|
20647
|
+
throw new Error(`Unknown quality action: ${action}. Use "scan".`);
|
|
20648
|
+
await this.qualityScript.scan(workspace, format);
|
|
20649
|
+
})
|
|
20650
|
+
})) {
|
|
20651
|
+
}
|
|
20652
|
+
|
|
20653
|
+
// pkgs/@akanjs/cli/repair/repair.script.ts
|
|
20654
|
+
import { Logger as Logger23 } from "akanjs/common";
|
|
19841
20655
|
class RepairScript extends script("repair", [RepairRunner]) {
|
|
19842
20656
|
async repair(kind, {
|
|
19843
20657
|
workspace,
|
|
@@ -19846,7 +20660,7 @@ class RepairScript extends script("repair", [RepairRunner]) {
|
|
|
19846
20660
|
target = null,
|
|
19847
20661
|
format = "markdown"
|
|
19848
20662
|
}) {
|
|
19849
|
-
|
|
20663
|
+
Logger23.rawLog(await this.repairRunner.repair(kind, {
|
|
19850
20664
|
workspace,
|
|
19851
20665
|
app,
|
|
19852
20666
|
module,
|
|
@@ -19886,12 +20700,12 @@ class RepairCommand extends command("repair", [RepairScript], ({ public: target
|
|
|
19886
20700
|
}
|
|
19887
20701
|
|
|
19888
20702
|
// pkgs/@akanjs/cli/scalar/scalar.command.ts
|
|
19889
|
-
import { Logger as
|
|
20703
|
+
import { Logger as Logger24, lowerlize as lowerlize3 } from "akanjs/common";
|
|
19890
20704
|
class ScalarCommand extends command("scalar", [ScalarScript], ({ public: target }) => ({
|
|
19891
20705
|
createScalar: target({ desc: "Create a new scalar type (simple data model without DB)" }).arg("scalarName", String, { desc: "name of scalar" }).with(Sys).option("ai", Boolean, { default: false, desc: "use ai to create scalar" }).option("format", String, { flag: "o", desc: "output format", default: "markdown", enum: ["markdown", "json"] }).exec(async function(scalarName, sys3, ai, format) {
|
|
19892
20706
|
const name = lowerlize3(scalarName.replace(/ /g, ""));
|
|
19893
20707
|
const report = ai ? await this.scalarScript.createScalarWithAi(sys3, name) : await this.scalarScript.createScalar(sys3, name);
|
|
19894
|
-
|
|
20708
|
+
Logger24.rawLog(renderPrimitiveReport(report, format));
|
|
19895
20709
|
}),
|
|
19896
20710
|
removeScalar: target({ desc: "Remove a scalar type from an app or library" }).arg("scalarName", String, { desc: "name of scalar" }).with(Sys).exec(async function(scalarName, sys3) {
|
|
19897
20711
|
await this.scalarScript.removeScalar(sys3, scalarName);
|
|
@@ -19900,7 +20714,7 @@ class ScalarCommand extends command("scalar", [ScalarScript], ({ public: target
|
|
|
19900
20714
|
}
|
|
19901
20715
|
|
|
19902
20716
|
// pkgs/@akanjs/cli/workflow/workflow.script.ts
|
|
19903
|
-
import { Logger as
|
|
20717
|
+
import { Logger as Logger25 } from "akanjs/common";
|
|
19904
20718
|
class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, ScalarScript, PrimitiveScript]) {
|
|
19905
20719
|
async workflow(action, workflow2, inputs, {
|
|
19906
20720
|
format = "markdown",
|
|
@@ -19909,23 +20723,23 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
|
|
|
19909
20723
|
workspace
|
|
19910
20724
|
} = {}) {
|
|
19911
20725
|
if (action === "list") {
|
|
19912
|
-
|
|
20726
|
+
Logger25.rawLog(this.workflowRunner.list({ format }));
|
|
19913
20727
|
return;
|
|
19914
20728
|
}
|
|
19915
20729
|
if (!workflow2)
|
|
19916
20730
|
throw new Error(`Workflow name is required for "${action}".`);
|
|
19917
20731
|
if (action === "explain") {
|
|
19918
|
-
|
|
20732
|
+
Logger25.rawLog(this.workflowRunner.explain(workflow2, { format }));
|
|
19919
20733
|
return;
|
|
19920
20734
|
}
|
|
19921
20735
|
if (action === "plan") {
|
|
19922
|
-
|
|
20736
|
+
Logger25.rawLog(await this.workflowRunner.plan(workflow2, inputs, { format, out }));
|
|
19923
20737
|
return;
|
|
19924
20738
|
}
|
|
19925
20739
|
if (action === "apply") {
|
|
19926
20740
|
if (!workspace)
|
|
19927
20741
|
throw new Error("Workspace is required for workflow apply.");
|
|
19928
|
-
|
|
20742
|
+
Logger25.rawLog(await this.workflowRunner.apply(workflow2, {
|
|
19929
20743
|
dryRun,
|
|
19930
20744
|
format,
|
|
19931
20745
|
workspace,
|
|
@@ -19943,13 +20757,13 @@ class WorkflowScript extends script("workflow", [WorkflowRunner, ModuleScript, S
|
|
|
19943
20757
|
if (action === "validate") {
|
|
19944
20758
|
if (!workspace)
|
|
19945
20759
|
throw new Error("Workspace is required for workflow validate.");
|
|
19946
|
-
|
|
20760
|
+
Logger25.rawLog(await this.workflowRunner.validate(workflow2, { format, workspace }));
|
|
19947
20761
|
return;
|
|
19948
20762
|
}
|
|
19949
20763
|
if (action === "report") {
|
|
19950
20764
|
if (!workspace)
|
|
19951
20765
|
throw new Error("Workspace is required for workflow report.");
|
|
19952
|
-
|
|
20766
|
+
Logger25.rawLog(await this.workflowRunner.report(workflow2, { format, workspace }));
|
|
19953
20767
|
return;
|
|
19954
20768
|
}
|
|
19955
20769
|
throw new Error(`Unknown workflow action: ${action}. Use list, explain, plan, apply, validate, or report.`);
|
|
@@ -19973,11 +20787,11 @@ class WorkflowCommand extends command("workflow", [WorkflowScript], ({ public: t
|
|
|
19973
20787
|
}
|
|
19974
20788
|
|
|
19975
20789
|
// pkgs/@akanjs/cli/workspace/workspace.script.ts
|
|
19976
|
-
import
|
|
19977
|
-
import { Logger as
|
|
20790
|
+
import path49 from "path";
|
|
20791
|
+
import { Logger as Logger26 } from "akanjs/common";
|
|
19978
20792
|
|
|
19979
20793
|
// pkgs/@akanjs/cli/workspace/workspace.runner.ts
|
|
19980
|
-
import
|
|
20794
|
+
import path48 from "path";
|
|
19981
20795
|
var defaultWorkspacePeerDependencies = new Set([
|
|
19982
20796
|
"@react-spring/web",
|
|
19983
20797
|
"@use-gesture/react",
|
|
@@ -20028,7 +20842,7 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20028
20842
|
owner = ""
|
|
20029
20843
|
}) {
|
|
20030
20844
|
const cwdPath = process.cwd();
|
|
20031
|
-
const workspaceRoot =
|
|
20845
|
+
const workspaceRoot = path48.join(cwdPath, dirname2, repoName);
|
|
20032
20846
|
const normalizedRegistryUrl = registryUrl ? getNpmRegistryUrl(registryUrl) : undefined;
|
|
20033
20847
|
const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot, repoName });
|
|
20034
20848
|
const templateSpinner = workspace.spinning(`Creating workspace template files in ${dirname2}/${repoName}...`);
|
|
@@ -20084,9 +20898,9 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20084
20898
|
}
|
|
20085
20899
|
async#getCliPackageJson() {
|
|
20086
20900
|
const packageJsonCandidates = [
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
|
|
20901
|
+
path48.join(import.meta.dir, "../package.json"),
|
|
20902
|
+
path48.join(import.meta.dir, "package.json"),
|
|
20903
|
+
path48.join(path48.dirname(Bun.main), "package.json")
|
|
20090
20904
|
];
|
|
20091
20905
|
try {
|
|
20092
20906
|
packageJsonCandidates.unshift(Bun.resolveSync("@akanjs/cli/package.json", import.meta.dir));
|
|
@@ -20102,9 +20916,9 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20102
20916
|
}
|
|
20103
20917
|
async#getAkanPackageJson() {
|
|
20104
20918
|
const packageJsonCandidates = [
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20919
|
+
path48.join(import.meta.dir, "../../../akanjs/package.json"),
|
|
20920
|
+
path48.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
20921
|
+
path48.join(path48.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
20108
20922
|
];
|
|
20109
20923
|
try {
|
|
20110
20924
|
packageJsonCandidates.unshift(Bun.resolveSync("akanjs/package.json", import.meta.dir));
|
|
@@ -20118,13 +20932,13 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20118
20932
|
}
|
|
20119
20933
|
let current = import.meta.dir;
|
|
20120
20934
|
for (let depth = 0;depth < 6; depth++) {
|
|
20121
|
-
const packageJsonPath =
|
|
20935
|
+
const packageJsonPath = path48.join(current, "package.json");
|
|
20122
20936
|
if (await Bun.file(packageJsonPath).exists()) {
|
|
20123
20937
|
const packageJson = await FileSys.readJson(packageJsonPath);
|
|
20124
20938
|
if (packageJson.name === "akanjs")
|
|
20125
20939
|
return packageJson;
|
|
20126
20940
|
}
|
|
20127
|
-
const parent =
|
|
20941
|
+
const parent = path48.dirname(current);
|
|
20128
20942
|
if (parent === current)
|
|
20129
20943
|
break;
|
|
20130
20944
|
current = parent;
|
|
@@ -20201,11 +21015,11 @@ class WorkspaceScript extends script("workspace", [
|
|
|
20201
21015
|
} catch (_) {
|
|
20202
21016
|
gitSpinner.fail("Git repository initialization failed. It's not fatal, you can commit manually");
|
|
20203
21017
|
}
|
|
20204
|
-
const workspacePath2 =
|
|
20205
|
-
|
|
21018
|
+
const workspacePath2 = path49.join(dirname2, repoName);
|
|
21019
|
+
Logger26.rawLog(`
|
|
20206
21020
|
\uD83C\uDF89 Welcome aboard! Workspace created in ${dirname2}/${repoName}`);
|
|
20207
|
-
|
|
20208
|
-
|
|
21021
|
+
Logger26.rawLog(`\uD83D\uDE80 Run \`cd ${workspacePath2} && akan start ${appName}\` to start the development server.`);
|
|
21022
|
+
Logger26.rawLog(`
|
|
20209
21023
|
\uD83D\uDC4B Happy coding!`);
|
|
20210
21024
|
}
|
|
20211
21025
|
async generateAgentRules(workspace, { overwrite = false, cursorRules = true } = {}) {
|
|
@@ -20321,4 +21135,4 @@ class WorkspaceCommand extends command("workspace", [WorkspaceScript], ({ public
|
|
|
20321
21135
|
}
|
|
20322
21136
|
|
|
20323
21137
|
// pkgs/@akanjs/cli/index.ts
|
|
20324
|
-
runCommands(WorkspaceCommand, AgentCommand, ApplicationCommand, LibraryCommand, LocalRegistryCommand, PackageCommand, ModuleCommand, PageCommand, ContextCommand, CloudCommand, GuidelineCommand, ScalarCommand, PrimitiveCommand, RepairCommand, WorkflowCommand);
|
|
21138
|
+
runCommands(WorkspaceCommand, AgentCommand, ApplicationCommand, LibraryCommand, LocalRegistryCommand, PackageCommand, ModuleCommand, PageCommand, ContextCommand, CloudCommand, GuidelineCommand, ScalarCommand, PrimitiveCommand, QualityCommand, RepairCommand, WorkflowCommand);
|