@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
4
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
5
|
-
import
|
|
5
|
+
import path44 from "path";
|
|
6
6
|
|
|
7
7
|
// pkgs/@akanjs/devkit/aiEditor.ts
|
|
8
8
|
import { input, select } from "@inquirer/prompts";
|
|
@@ -4799,6 +4799,11 @@ var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!d
|
|
|
4799
4799
|
|
|
4800
4800
|
// pkgs/@akanjs/devkit/workflow/artifacts.ts
|
|
4801
4801
|
var sourceChangeBlocked = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown"));
|
|
4802
|
+
var workflowPlanApproval = {
|
|
4803
|
+
required: true,
|
|
4804
|
+
meaning: "Review this read-only plan before apply_workflow mutates files.",
|
|
4805
|
+
applyTool: "apply_workflow"
|
|
4806
|
+
};
|
|
4802
4807
|
var inferNextActionCode = (action, diagnostics) => {
|
|
4803
4808
|
if (action.action)
|
|
4804
4809
|
return action.action;
|
|
@@ -4843,13 +4848,19 @@ var createWorkflowApplyReport = ({
|
|
|
4843
4848
|
});
|
|
4844
4849
|
}
|
|
4845
4850
|
const orderedNextActions = uniqueBy(nextActionsWithIntent, (action) => action.command).sort((left, right) => nextActionPriority(left, diagnostics) - nextActionPriority(right, diagnostics));
|
|
4851
|
+
const sourceFilesChanged = uniqueBy(changedFiles, (file) => `${file.action}:${file.path}:${file.reason}`);
|
|
4852
|
+
const generatedFilesSynced = uniqueBy(generatedFiles, (file) => `${file.action}:${file.path}:${file.reason}`);
|
|
4846
4853
|
return {
|
|
4847
4854
|
schemaVersion: 1,
|
|
4848
4855
|
workflow,
|
|
4849
4856
|
mode,
|
|
4850
4857
|
status: workflowStatus(diagnostics),
|
|
4851
|
-
|
|
4852
|
-
|
|
4858
|
+
summary: {
|
|
4859
|
+
sourceFilesChanged,
|
|
4860
|
+
generatedFilesSynced
|
|
4861
|
+
},
|
|
4862
|
+
changedFiles: sourceFilesChanged,
|
|
4863
|
+
generatedFiles: generatedFilesSynced,
|
|
4853
4864
|
appliedCommands: uniqueBy(appliedCommands, (command) => command.command),
|
|
4854
4865
|
recommendedValidationCommands: uniqueBy(validationCommands, (command) => command.command),
|
|
4855
4866
|
commands: uniqueBy(validationCommands, (command) => command.command),
|
|
@@ -4927,6 +4938,52 @@ var statusForValidationKind = (commands, kind) => {
|
|
|
4927
4938
|
return "unknown";
|
|
4928
4939
|
return matching.some((command) => command.status === "failed") ? "failed" : "passed";
|
|
4929
4940
|
};
|
|
4941
|
+
var statusForCommands = (commands) => {
|
|
4942
|
+
if (commands.length === 0)
|
|
4943
|
+
return "unknown";
|
|
4944
|
+
return commandStatus(commands) === "failed" ? "failed" : "passed";
|
|
4945
|
+
};
|
|
4946
|
+
var statusForDiagnostics = (diagnostics) => {
|
|
4947
|
+
if (diagnostics.length === 0)
|
|
4948
|
+
return "unknown";
|
|
4949
|
+
return workflowStatus(diagnostics) === "failed" ? "failed" : "passed";
|
|
4950
|
+
};
|
|
4951
|
+
var workflowDiagnosticContextPaths = (diagnostics) => uniqueBy(diagnostics.flatMap((diagnostic) => diagnostic.context?.paths ?? []), (filePath) => filePath);
|
|
4952
|
+
var createWorkflowBaselineSummary = (diagnostics, { detailsIncluded = true, knownBlockerCount = 0 } = {}) => {
|
|
4953
|
+
const grouped = new Map;
|
|
4954
|
+
let totalErrors = 0;
|
|
4955
|
+
let totalWarnings = 0;
|
|
4956
|
+
for (const diagnostic of diagnostics) {
|
|
4957
|
+
if (diagnostic.severity === "error")
|
|
4958
|
+
totalErrors += 1;
|
|
4959
|
+
else
|
|
4960
|
+
totalWarnings += 1;
|
|
4961
|
+
const existing = grouped.get(diagnostic.code);
|
|
4962
|
+
if (existing) {
|
|
4963
|
+
existing.count += 1;
|
|
4964
|
+
if (existing.severity !== diagnostic.severity)
|
|
4965
|
+
existing.severity = "mixed";
|
|
4966
|
+
} else {
|
|
4967
|
+
grouped.set(diagnostic.code, {
|
|
4968
|
+
code: diagnostic.code,
|
|
4969
|
+
severity: diagnostic.severity,
|
|
4970
|
+
count: 1,
|
|
4971
|
+
sampleMessage: diagnostic.message
|
|
4972
|
+
});
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
const contextPaths = workflowDiagnosticContextPaths(diagnostics);
|
|
4976
|
+
return {
|
|
4977
|
+
status: totalErrors > 0 ? "failed" : diagnostics.length > 0 ? "passed" : "unknown",
|
|
4978
|
+
total: diagnostics.length,
|
|
4979
|
+
totalErrors,
|
|
4980
|
+
totalWarnings,
|
|
4981
|
+
detailsIncluded,
|
|
4982
|
+
knownBlockerCount,
|
|
4983
|
+
byCode: [...grouped.values()],
|
|
4984
|
+
...contextPaths.length ? { contextPaths } : {}
|
|
4985
|
+
};
|
|
4986
|
+
};
|
|
4930
4987
|
var createKnownBlockers = (commands, diagnostics) => {
|
|
4931
4988
|
const commandBlockers = commands.filter((command) => command.status === "failed" && (command.failureScope === "workspace-config" || command.failureScope === "environment")).map((command) => ({
|
|
4932
4989
|
code: `workflow-validation-${command.failureScope}`,
|
|
@@ -4955,18 +5012,28 @@ var createKnownBlockers = (commands, diagnostics) => {
|
|
|
4955
5012
|
}
|
|
4956
5013
|
return [...grouped.values()];
|
|
4957
5014
|
};
|
|
4958
|
-
var createValidationStatuses = (commands,
|
|
5015
|
+
var createValidationStatuses = (commands, reportDiagnostics, baselineDiagnostics, workflowDiagnostics) => {
|
|
5016
|
+
const diagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
|
|
4959
5017
|
const scopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(diagnostics)];
|
|
4960
|
-
const
|
|
5018
|
+
const sourceDiagnostics = [...reportDiagnostics, ...workflowDiagnostics].filter((diagnostic) => diagnostic.failureScope === "source-change" || diagnostic.scope === "workflow" || !diagnostic.failureScope && diagnostic.scope !== "baseline");
|
|
5019
|
+
const sourceScopes = [...failedCommandScopes(commands), ...errorDiagnosticScopes(sourceDiagnostics)];
|
|
5020
|
+
const sourceStatus = statusForScope(commands, sourceDiagnostics, sourceScopes, "source-change");
|
|
5021
|
+
const validationCommandsStatus = statusForCommands(commands);
|
|
5022
|
+
const baselineStatus = statusForDiagnostics(baselineDiagnostics);
|
|
5023
|
+
const nonBaselineDiagnostics = [...reportDiagnostics, ...workflowDiagnostics];
|
|
4961
5024
|
const workspaceStatus = hasScopeFailure(scopes, "workspace-config", diagnostics) || hasScopeFailure(scopes, "environment", diagnostics) ? "failed" : commands.length || diagnostics.length ? "passed" : "unknown";
|
|
4962
|
-
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";
|
|
5025
|
+
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";
|
|
4963
5026
|
return {
|
|
4964
5027
|
sourceStatus,
|
|
4965
5028
|
workspaceStatus,
|
|
5029
|
+
validationCommandsStatus,
|
|
5030
|
+
baselineStatus,
|
|
4966
5031
|
overallStatus,
|
|
4967
5032
|
summary: {
|
|
4968
5033
|
sourceChange: sourceStatus,
|
|
4969
5034
|
generatedSync: statusForValidationKind(commands, "sync"),
|
|
5035
|
+
validationCommands: validationCommandsStatus,
|
|
5036
|
+
baseline: baselineStatus,
|
|
4970
5037
|
workspaceConfig: statusForScope(commands, diagnostics, scopes, "workspace-config"),
|
|
4971
5038
|
environment: statusForScope(commands, diagnostics, scopes, "environment")
|
|
4972
5039
|
}
|
|
@@ -5000,7 +5067,8 @@ var createWorkflowValidationRunReport = async ({
|
|
|
5000
5067
|
] : []);
|
|
5001
5068
|
const reportDiagnostics = [...diagnostics, ...commandDiagnostics];
|
|
5002
5069
|
const scopedDiagnostics = [...reportDiagnostics, ...baselineDiagnostics, ...workflowDiagnostics];
|
|
5003
|
-
const
|
|
5070
|
+
const knownBlockers = createKnownBlockers(results, scopedDiagnostics);
|
|
5071
|
+
const statuses = createValidationStatuses(results, reportDiagnostics, baselineDiagnostics, workflowDiagnostics);
|
|
5004
5072
|
return {
|
|
5005
5073
|
schemaVersion: 1,
|
|
5006
5074
|
runId,
|
|
@@ -5009,9 +5077,12 @@ var createWorkflowValidationRunReport = async ({
|
|
|
5009
5077
|
source,
|
|
5010
5078
|
status: statuses.overallStatus === "passed" ? "passed" : "failed",
|
|
5011
5079
|
...statuses,
|
|
5012
|
-
knownBlockers
|
|
5080
|
+
knownBlockers,
|
|
5013
5081
|
commands: results,
|
|
5014
5082
|
diagnostics: reportDiagnostics,
|
|
5083
|
+
baselineSummary: createWorkflowBaselineSummary(baselineDiagnostics, {
|
|
5084
|
+
knownBlockerCount: knownBlockers.filter((blocker) => blocker.failureScope === "workspace-config").length
|
|
5085
|
+
}),
|
|
5015
5086
|
baselineDiagnostics,
|
|
5016
5087
|
workflowDiagnostics,
|
|
5017
5088
|
repairActions: uniqueBy(repairActions, (action) => action.command),
|
|
@@ -6327,7 +6398,7 @@ var checkTypeScriptSyntax = async (workspace, filePath) => {
|
|
|
6327
6398
|
return null;
|
|
6328
6399
|
const content = await workspace.readFile(filePath);
|
|
6329
6400
|
const source = ts6.createSourceFile(filePath, content, ts6.ScriptTarget.Latest, true, scriptKind);
|
|
6330
|
-
const diagnostic = source.parseDiagnostics[0];
|
|
6401
|
+
const diagnostic = (source.parseDiagnostics ?? [])[0];
|
|
6331
6402
|
if (!diagnostic)
|
|
6332
6403
|
return null;
|
|
6333
6404
|
const position = diagnostic.file?.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
@@ -6525,9 +6596,9 @@ var addFieldUiSurfaceInspection = (plan) => {
|
|
|
6525
6596
|
code: "add-field-ui-surface-review",
|
|
6526
6597
|
kind: "manual-action",
|
|
6527
6598
|
target,
|
|
6528
|
-
action: templateRequested ? `Template was requested for ${field}. If no Template file changed,
|
|
6599
|
+
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.`,
|
|
6529
6600
|
confidence: "medium",
|
|
6530
|
-
message: `Review UI
|
|
6601
|
+
message: `Review user-visible UI for ${module}.${field}; recommended component is ${policy.component}.`
|
|
6531
6602
|
}
|
|
6532
6603
|
],
|
|
6533
6604
|
nextActions: [
|
|
@@ -6782,10 +6853,48 @@ var createAddFieldDefaultRecommendations = (inputs) => {
|
|
|
6782
6853
|
kind: "manual-action",
|
|
6783
6854
|
confidence: "high",
|
|
6784
6855
|
message: `Default for ${field} will be normalized to ${coercion.normalizedType} literal ${coercion.expression}.`,
|
|
6785
|
-
action: "Review the normalized default in the apply report
|
|
6856
|
+
action: "Review the normalized default in the apply report. To leave the field without a default, omit the default input."
|
|
6786
6857
|
}
|
|
6787
6858
|
];
|
|
6788
6859
|
};
|
|
6860
|
+
var moneyLikeFieldPattern = /(budget|price|amount|cost|rate|fee|salary|balance)/i;
|
|
6861
|
+
var wholeNumberFieldPattern = /(count|quantity|total|number|index|rank|order|age)/i;
|
|
6862
|
+
var createAddFieldInputRecommendations = (inputs) => {
|
|
6863
|
+
const field = typeof inputs.field === "string" ? inputs.field : "<field>";
|
|
6864
|
+
const typeName = typeof inputs.type === "string" ? inputs.type : null;
|
|
6865
|
+
const recommendations = [];
|
|
6866
|
+
if (!typeName)
|
|
6867
|
+
return recommendations;
|
|
6868
|
+
if (typeName.toLowerCase() === "number" || typeName.toLowerCase() === "numeric") {
|
|
6869
|
+
const recommendedType = moneyLikeFieldPattern.test(field) ? "Float" : wholeNumberFieldPattern.test(field) ? "Int" : null;
|
|
6870
|
+
recommendations.push({
|
|
6871
|
+
code: "add-field-type-choice",
|
|
6872
|
+
kind: "input-guidance",
|
|
6873
|
+
confidence: recommendedType ? "high" : "medium",
|
|
6874
|
+
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.`,
|
|
6875
|
+
action: recommendedType ? `Re-run plan_workflow with type="${recommendedType}".` : 'Re-run plan_workflow with type="Int" or type="Float".'
|
|
6876
|
+
});
|
|
6877
|
+
}
|
|
6878
|
+
if (typeName.toLowerCase() === "enum" && !Array.isArray(inputs.values)) {
|
|
6879
|
+
recommendations.push({
|
|
6880
|
+
code: "add-field-enum-values",
|
|
6881
|
+
kind: "input-guidance",
|
|
6882
|
+
confidence: "high",
|
|
6883
|
+
message: `Enum field ${field} needs values before apply can choose valid dictionary and default behavior.`,
|
|
6884
|
+
action: 'Pass values as an array or comma-separated string, for example values=["draft","done"].'
|
|
6885
|
+
});
|
|
6886
|
+
}
|
|
6887
|
+
if (inputs.default === undefined) {
|
|
6888
|
+
recommendations.push({
|
|
6889
|
+
code: "add-field-default-optional",
|
|
6890
|
+
kind: "input-guidance",
|
|
6891
|
+
confidence: "medium",
|
|
6892
|
+
message: `No default will be written for ${field} because default is omitted.`,
|
|
6893
|
+
action: "Keep default omitted when the field should have no default value."
|
|
6894
|
+
});
|
|
6895
|
+
}
|
|
6896
|
+
return recommendations;
|
|
6897
|
+
};
|
|
6789
6898
|
var createAddFieldRecommendations = (inputs) => {
|
|
6790
6899
|
const app = typeof inputs.app === "string" ? inputs.app : "<app-or-lib>";
|
|
6791
6900
|
const module = typeof inputs.module === "string" ? inputs.module : "<module>";
|
|
@@ -6846,9 +6955,9 @@ var createAddFieldRecommendations = (inputs) => {
|
|
|
6846
6955
|
code: "add-field-light-projection-choice",
|
|
6847
6956
|
kind: "manual-action",
|
|
6848
6957
|
target: paths.constant,
|
|
6849
|
-
action: `Pass includeInLight=true when ${field}
|
|
6958
|
+
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.`,
|
|
6850
6959
|
confidence: "medium",
|
|
6851
|
-
message:
|
|
6960
|
+
message: `${module}.${field} is not selected for list/card projection data.`
|
|
6852
6961
|
}
|
|
6853
6962
|
] : [],
|
|
6854
6963
|
...includeInLight ? [
|
|
@@ -6866,18 +6975,20 @@ var createAddFieldRecommendations = (inputs) => {
|
|
|
6866
6975
|
kind: "manual-action",
|
|
6867
6976
|
target: paths.template,
|
|
6868
6977
|
confidence: "medium",
|
|
6869
|
-
message: `Template
|
|
6978
|
+
message: `Template form auto-edit is skipped for ${module}.${field} because surfaces does not include "template".`,
|
|
6979
|
+
action: `Pass surfaces=["template"] when users should enter ${field} in the Template form.`
|
|
6870
6980
|
}
|
|
6871
6981
|
] : [],
|
|
6872
6982
|
{
|
|
6873
6983
|
code: "add-field-ui-manual-review",
|
|
6874
6984
|
kind: "manual-action",
|
|
6875
6985
|
target: paths.template,
|
|
6876
|
-
action: `
|
|
6986
|
+
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.`,
|
|
6877
6987
|
confidence: "medium",
|
|
6878
|
-
message:
|
|
6988
|
+
message: `${app}:${module}.${field} may need UI review for user-visible Template, Unit, and View behavior.`
|
|
6879
6989
|
},
|
|
6880
|
-
...createAddFieldDefaultRecommendations(inputs)
|
|
6990
|
+
...createAddFieldDefaultRecommendations(inputs),
|
|
6991
|
+
...createAddFieldInputRecommendations(inputs)
|
|
6881
6992
|
];
|
|
6882
6993
|
};
|
|
6883
6994
|
var createWorkflowPlanPredictedChanges = (spec, inputs) => {
|
|
@@ -7020,7 +7131,8 @@ var createWorkflowPlan = (spec, rawInputs) => {
|
|
|
7020
7131
|
validation: spec.validation,
|
|
7021
7132
|
diagnostics,
|
|
7022
7133
|
recommendations: createWorkflowPlanRecommendations(spec, inputs),
|
|
7023
|
-
requiresApproval: true
|
|
7134
|
+
requiresApproval: true,
|
|
7135
|
+
approval: workflowPlanApproval
|
|
7024
7136
|
};
|
|
7025
7137
|
};
|
|
7026
7138
|
// pkgs/@akanjs/devkit/workflow/render.ts
|
|
@@ -7058,6 +7170,7 @@ var renderWorkflowPlan = (plan) => [
|
|
|
7058
7170
|
"",
|
|
7059
7171
|
`- Mode: ${plan.mode}`,
|
|
7060
7172
|
`- Requires approval: ${plan.requiresApproval}`,
|
|
7173
|
+
`- Approval: ${plan.approval?.meaning ?? "Review this read-only plan before applying workflow mutations."}`,
|
|
7061
7174
|
"",
|
|
7062
7175
|
"## Inputs",
|
|
7063
7176
|
...Object.entries(plan.inputs).map(([name, value]) => `- \`${name}\`: ${Array.isArray(value) ? value.join(", ") : value}`),
|
|
@@ -7094,6 +7207,10 @@ var renderDiagnostic = (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.
|
|
|
7094
7207
|
var renderNextAction = (action) => `- \`${action.command}\`: ${action.reason}`;
|
|
7095
7208
|
var applySourceStatus = (report) => report.diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown")) ? "failed" : "passed";
|
|
7096
7209
|
var renderWorkflowApplyReport = (report) => {
|
|
7210
|
+
const applySummary = {
|
|
7211
|
+
sourceFilesChanged: report.summary?.sourceFilesChanged ?? report.changedFiles,
|
|
7212
|
+
generatedFilesSynced: report.summary?.generatedFilesSynced ?? report.generatedFiles
|
|
7213
|
+
};
|
|
7097
7214
|
const manualReviewItems = [
|
|
7098
7215
|
...report.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").map(renderDiagnostic),
|
|
7099
7216
|
...report.recommendations.filter((recommendation) => recommendation.kind === "manual-action").map(renderRecommendation)
|
|
@@ -7107,6 +7224,8 @@ var renderWorkflowApplyReport = (report) => {
|
|
|
7107
7224
|
`- Status: ${report.status}`,
|
|
7108
7225
|
`- Source-change status: ${applySourceStatus(report)}`,
|
|
7109
7226
|
`- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
|
|
7227
|
+
`- Source files changed: ${applySummary.sourceFilesChanged.length}`,
|
|
7228
|
+
`- Generated files queued for sync: ${applySummary.generatedFilesSynced.length}`,
|
|
7110
7229
|
...report.validationTarget ? [`- Validation target: ${report.validationTarget}`] : [],
|
|
7111
7230
|
"",
|
|
7112
7231
|
"## Apply Checks",
|
|
@@ -7116,10 +7235,10 @@ var renderWorkflowApplyReport = (report) => {
|
|
|
7116
7235
|
...sourceBlockers.length ? sourceBlockers : ["- none"],
|
|
7117
7236
|
"",
|
|
7118
7237
|
"## Automatically Modified",
|
|
7119
|
-
...
|
|
7238
|
+
...applySummary.sourceFilesChanged.length ? applySummary.sourceFilesChanged.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
7120
7239
|
"",
|
|
7121
7240
|
"## Generated Sync",
|
|
7122
|
-
...
|
|
7241
|
+
...applySummary.generatedFilesSynced.length ? applySummary.generatedFilesSynced.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
|
|
7123
7242
|
"",
|
|
7124
7243
|
"## Applied Commands",
|
|
7125
7244
|
...report.appliedCommands.length ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`) : ["- none"],
|
|
@@ -7146,52 +7265,76 @@ var renderWorkflowApplyReport = (report) => {
|
|
|
7146
7265
|
`);
|
|
7147
7266
|
};
|
|
7148
7267
|
var renderWorkflowApply = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowApplyReport(report);
|
|
7149
|
-
var renderWorkflowValidationRunReport = (report) =>
|
|
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
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7268
|
+
var renderWorkflowValidationRunReport = (report) => {
|
|
7269
|
+
const baselineSummary = report.baselineSummary ?? {
|
|
7270
|
+
status: report.baselineDiagnostics?.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "unknown",
|
|
7271
|
+
total: report.baselineDiagnostics?.length ?? 0,
|
|
7272
|
+
totalErrors: report.baselineDiagnostics?.filter((diagnostic) => diagnostic.severity === "error").length ?? 0,
|
|
7273
|
+
totalWarnings: report.baselineDiagnostics?.filter((diagnostic) => diagnostic.severity === "warning").length ?? 0,
|
|
7274
|
+
detailsIncluded: true,
|
|
7275
|
+
knownBlockerCount: report.knownBlockers.filter((blocker) => blocker.known).length,
|
|
7276
|
+
byCode: []
|
|
7277
|
+
};
|
|
7278
|
+
return [
|
|
7279
|
+
`# Workflow Validation: ${report.workflow}`,
|
|
7280
|
+
"",
|
|
7281
|
+
`- Run: ${report.runId}`,
|
|
7282
|
+
`- Status: ${report.status}`,
|
|
7283
|
+
`- Source status: ${report.sourceStatus}`,
|
|
7284
|
+
`- Workspace status: ${report.workspaceStatus}`,
|
|
7285
|
+
`- Validation commands status: ${report.validationCommandsStatus ?? "unknown"}`,
|
|
7286
|
+
`- Baseline status: ${report.baselineStatus ?? baselineSummary.status}`,
|
|
7287
|
+
`- Overall status: ${report.overallStatus}`,
|
|
7288
|
+
"",
|
|
7289
|
+
"## Status Summary",
|
|
7290
|
+
`- Source-change: ${report.summary.sourceChange}`,
|
|
7291
|
+
`- Generated sync: ${report.summary.generatedSync}`,
|
|
7292
|
+
`- Validation commands: ${report.summary.validationCommands ?? "unknown"}`,
|
|
7293
|
+
`- Baseline: ${report.summary.baseline ?? baselineSummary.status}`,
|
|
7294
|
+
`- Workspace config: ${report.summary.workspaceConfig}`,
|
|
7295
|
+
`- Environment: ${report.summary.environment}`,
|
|
7296
|
+
"",
|
|
7297
|
+
"## Source Change Diagnostics",
|
|
7298
|
+
...report.workflowDiagnostics?.length ? report.workflowDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
7299
|
+
"",
|
|
7300
|
+
"## Existing Workspace Blockers",
|
|
7301
|
+
`- Status: ${baselineSummary.status}`,
|
|
7302
|
+
`- Errors: ${baselineSummary.totalErrors}`,
|
|
7303
|
+
`- Warnings: ${baselineSummary.totalWarnings}`,
|
|
7304
|
+
...baselineSummary.byCode.length ? baselineSummary.byCode.map((item) => `- [${item.severity}] ${item.code} (${item.count}x): ${item.sampleMessage}`) : ["- none"],
|
|
7305
|
+
...baselineSummary.detailsIncluded ? [] : [
|
|
7306
|
+
"- Baseline details are summarized by default. Re-run validation with includeBaselineDetails=true for full baselineDiagnostics."
|
|
7307
|
+
],
|
|
7308
|
+
"",
|
|
7309
|
+
"## Existing Workspace Blocker Details",
|
|
7310
|
+
...report.baselineDiagnostics?.length ? report.baselineDiagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : baselineSummary.detailsIncluded ? ["- none"] : ["- omitted"],
|
|
7311
|
+
"",
|
|
7312
|
+
"## Known Blockers",
|
|
7313
|
+
...report.knownBlockers.length ? report.knownBlockers.map((blocker) => {
|
|
7314
|
+
const command = blocker.command ? ` \`${blocker.command}\`` : "";
|
|
7315
|
+
const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
|
|
7316
|
+
const known = blocker.known ? " known" : "";
|
|
7317
|
+
return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
|
|
7318
|
+
}) : ["- none"],
|
|
7319
|
+
"",
|
|
7320
|
+
"## Commands",
|
|
7321
|
+
...report.commands.length ? report.commands.map((command) => {
|
|
7322
|
+
const scope = command.failureScope ? ` (${command.failureScope})` : "";
|
|
7323
|
+
return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
|
|
7324
|
+
}) : ["- none"],
|
|
7325
|
+
"",
|
|
7326
|
+
"## Diagnostics",
|
|
7327
|
+
...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
|
|
7328
|
+
"",
|
|
7329
|
+
"## Repair Actions",
|
|
7330
|
+
...report.repairActions.length ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
7331
|
+
"",
|
|
7332
|
+
"## Next Actions",
|
|
7333
|
+
...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
|
|
7334
|
+
""
|
|
7335
|
+
].join(`
|
|
7194
7336
|
`);
|
|
7337
|
+
};
|
|
7195
7338
|
var renderWorkflowValidation = (report, format = "markdown") => format === "json" ? jsonText(report) : renderWorkflowValidationRunReport(report);
|
|
7196
7339
|
var renderRepairReportMarkdown = (report) => [
|
|
7197
7340
|
`# Akan Repair: ${report.kind}`,
|
|
@@ -7280,7 +7423,11 @@ var toolingRolloutGate = {
|
|
|
7280
7423
|
candidates: toolingRolloutCandidates
|
|
7281
7424
|
};
|
|
7282
7425
|
var isRestrictedCandidate = (candidate) => candidate.status !== "allowed";
|
|
7283
|
-
var restrictedCandidates = new Map
|
|
7426
|
+
var restrictedCandidates = new Map;
|
|
7427
|
+
for (const candidate of toolingRolloutCandidates) {
|
|
7428
|
+
if (isRestrictedCandidate(candidate))
|
|
7429
|
+
restrictedCandidates.set(candidate.packageName, candidate);
|
|
7430
|
+
}
|
|
7284
7431
|
// pkgs/@akanjs/devkit/akanContext.ts
|
|
7285
7432
|
var resourceList = [
|
|
7286
7433
|
{ uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
|
|
@@ -7849,6 +7996,7 @@ var applyFirstPolicy = {
|
|
|
7849
7996
|
mode: "apply-first",
|
|
7850
7997
|
directSourceEdits: "fallback-only",
|
|
7851
7998
|
applyRequiredWhen: ["plan_workflow returns planPath", "plan_workflow returns next.tool=apply_workflow"],
|
|
7999
|
+
approvalMeaning: "requiresApproval is an agent/user review signal before apply_workflow mutates files; it is not a separate MCP permission gate.",
|
|
7852
8000
|
validationRequiredAfterApply: ["validationTarget", "applyReportPath"],
|
|
7853
8001
|
fallbackAllowedWhen: [
|
|
7854
8002
|
"list_workflows and explain_workflow show no matching workflow",
|
|
@@ -7984,7 +8132,12 @@ var readonlyMcpTools = [
|
|
|
7984
8132
|
description: "Report workspace diagnostics, optionally split into baseline and workflow scopes.",
|
|
7985
8133
|
inputSchema: {
|
|
7986
8134
|
type: "object",
|
|
7987
|
-
properties: {
|
|
8135
|
+
properties: {
|
|
8136
|
+
strict: booleanProperty,
|
|
8137
|
+
runIdOrPlan: stringProperty,
|
|
8138
|
+
changedFiles: stringArrayProperty,
|
|
8139
|
+
includeBaselineDetails: booleanProperty
|
|
8140
|
+
}
|
|
7988
8141
|
}
|
|
7989
8142
|
},
|
|
7990
8143
|
{
|
|
@@ -8029,7 +8182,7 @@ var applyMcpTools = [
|
|
|
8029
8182
|
description: "Validate a plan, apply report, validationTarget, or run artifact.",
|
|
8030
8183
|
inputSchema: {
|
|
8031
8184
|
type: "object",
|
|
8032
|
-
properties: { runIdOrPlan: stringProperty },
|
|
8185
|
+
properties: { runIdOrPlan: stringProperty, includeBaselineDetails: booleanProperty },
|
|
8033
8186
|
required: ["runIdOrPlan"]
|
|
8034
8187
|
}
|
|
8035
8188
|
},
|
|
@@ -8079,7 +8232,16 @@ var createAkanValidationContract = (listTools = listAkanMcpTools) => ({
|
|
|
8079
8232
|
validationStatuses: {
|
|
8080
8233
|
sourceStatus: ["passed", "failed", "unknown"],
|
|
8081
8234
|
workspaceStatus: ["passed", "failed", "unknown"],
|
|
8082
|
-
|
|
8235
|
+
validationCommandsStatus: ["passed", "failed", "unknown"],
|
|
8236
|
+
baselineStatus: ["passed", "failed", "unknown"],
|
|
8237
|
+
overallStatus: [
|
|
8238
|
+
"passed",
|
|
8239
|
+
"failed",
|
|
8240
|
+
"passed-with-baseline-blockers",
|
|
8241
|
+
"blocked-by-workspace-config",
|
|
8242
|
+
"blocked-by-environment"
|
|
8243
|
+
],
|
|
8244
|
+
baselineSummary: "Default MCP validation output summarizes baseline diagnostics; pass includeBaselineDetails=true for full baselineDiagnostics.",
|
|
8083
8245
|
knownBlockers: "Repeated workspace-config/environment failures are summarized before command output."
|
|
8084
8246
|
},
|
|
8085
8247
|
moduleContextInputs: {
|
|
@@ -8090,6 +8252,7 @@ var createAkanValidationContract = (listTools = listAkanMcpTools) => ({
|
|
|
8090
8252
|
toolingRolloutGate,
|
|
8091
8253
|
directEditFallbackPolicy: applyFirstPolicy,
|
|
8092
8254
|
applyReportFields: [
|
|
8255
|
+
"summary",
|
|
8093
8256
|
"appliedCommands",
|
|
8094
8257
|
"recommendedValidationCommands",
|
|
8095
8258
|
"commands",
|
|
@@ -8131,7 +8294,14 @@ var inspectAkanContext = async (workspace, args) => {
|
|
|
8131
8294
|
strict: true,
|
|
8132
8295
|
runIdOrPlan: workspacePath(workspace, props.request.runIdOrPlan)
|
|
8133
8296
|
});
|
|
8134
|
-
|
|
8297
|
+
const baselineSummary = createWorkflowBaselineSummary((doctor.baselineDiagnostics ?? []).map((diagnostic) => ({
|
|
8298
|
+
severity: diagnostic.severity,
|
|
8299
|
+
code: diagnostic.code,
|
|
8300
|
+
message: diagnostic.message,
|
|
8301
|
+
scope: diagnostic.scope,
|
|
8302
|
+
context: diagnostic.context
|
|
8303
|
+
})), { detailsIncluded: false });
|
|
8304
|
+
diagnostics.push(...(doctor.workflowDiagnostics ?? doctor.diagnostics).map((diagnostic) => ({
|
|
8135
8305
|
severity: diagnostic.severity,
|
|
8136
8306
|
code: diagnostic.code,
|
|
8137
8307
|
message: diagnostic.message,
|
|
@@ -8152,7 +8322,8 @@ var inspectAkanContext = async (workspace, args) => {
|
|
|
8152
8322
|
reason: doctor.status === "passed" ? "No strict workspace diagnostics were found for the workflow context." : "Review diagnostics before applying or manually editing source files."
|
|
8153
8323
|
}, {
|
|
8154
8324
|
status: doctor.status,
|
|
8155
|
-
|
|
8325
|
+
baselineSummary,
|
|
8326
|
+
baselineDiagnostics: 0,
|
|
8156
8327
|
workflowDiagnostics: doctor.workflowDiagnostics?.length ?? 0
|
|
8157
8328
|
});
|
|
8158
8329
|
}
|
|
@@ -12333,6 +12504,7 @@ void run().catch((error) => {
|
|
|
12333
12504
|
}
|
|
12334
12505
|
// pkgs/@akanjs/devkit/applicationReleasePackager.ts
|
|
12335
12506
|
import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
|
|
12507
|
+
import path38 from "path";
|
|
12336
12508
|
|
|
12337
12509
|
// pkgs/@akanjs/devkit/uploadRelease.ts
|
|
12338
12510
|
import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
|
|
@@ -12448,13 +12620,9 @@ class ApplicationReleasePackager {
|
|
|
12448
12620
|
await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
|
|
12449
12621
|
await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
|
|
12450
12622
|
await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
"-C",
|
|
12455
|
-
buildRoot,
|
|
12456
|
-
"./"
|
|
12457
|
-
]);
|
|
12623
|
+
const releaseRoot = this.#app.workspace.workspaceRoot;
|
|
12624
|
+
const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
|
|
12625
|
+
await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
|
|
12458
12626
|
await this.#writeCsrZipIfPresent();
|
|
12459
12627
|
return { platformVersion };
|
|
12460
12628
|
}
|
|
@@ -12475,22 +12643,18 @@ class ApplicationReleasePackager {
|
|
|
12475
12643
|
await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
|
|
12476
12644
|
const libDeps = ["social", "shared", "platform", "util"];
|
|
12477
12645
|
await Promise.all(libDeps.map((lib) => cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true })));
|
|
12478
|
-
await Promise.all([".next", "ios", "android", "public/libs"].map(async (
|
|
12479
|
-
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${
|
|
12646
|
+
await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
|
|
12647
|
+
const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
|
|
12480
12648
|
if (await FileSys.dirExists(targetPath))
|
|
12481
12649
|
await rm4(targetPath, { recursive: true, force: true });
|
|
12482
12650
|
}));
|
|
12483
12651
|
const syncPaths = [".husky", ".gitignore", "package.json"];
|
|
12484
|
-
await Promise.all(syncPaths.map((
|
|
12652
|
+
await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
|
|
12485
12653
|
await this.#writeSourceTsconfig(sourceRoot, libDeps);
|
|
12486
12654
|
await Bun.write(`${sourceRoot}/README.md`, readme);
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
"-C",
|
|
12491
|
-
sourceRoot,
|
|
12492
|
-
"./"
|
|
12493
|
-
]);
|
|
12655
|
+
const sourceCwd = this.#app.workspace.cwdPath;
|
|
12656
|
+
const sourceArchivePath = path38.relative(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`).split(path38.sep).join("/");
|
|
12657
|
+
await this.#app.workspace.spawn("tar", ["-zcf", sourceArchivePath, "-C", sourceRoot, "./"], { cwd: sourceCwd });
|
|
12494
12658
|
}
|
|
12495
12659
|
async#resetSourceRoot(sourceRoot) {
|
|
12496
12660
|
if (await FileSys.dirExists(sourceRoot)) {
|
|
@@ -12572,20 +12736,20 @@ class ApplicationReleasePackager {
|
|
|
12572
12736
|
}
|
|
12573
12737
|
}
|
|
12574
12738
|
// pkgs/@akanjs/devkit/applicationTestPreload.ts
|
|
12575
|
-
import
|
|
12739
|
+
import path39 from "path";
|
|
12576
12740
|
var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
|
|
12577
12741
|
async function resolveSignalTestPreloadPath(target) {
|
|
12578
12742
|
const candidates = [];
|
|
12579
12743
|
const addResolvedPackageCandidate = (basePath2) => {
|
|
12580
12744
|
try {
|
|
12581
|
-
candidates.push(
|
|
12745
|
+
candidates.push(path39.join(path39.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
|
|
12582
12746
|
} catch {}
|
|
12583
12747
|
};
|
|
12584
12748
|
addResolvedPackageCandidate(target.cwdPath);
|
|
12585
12749
|
addResolvedPackageCandidate(process.cwd());
|
|
12586
|
-
addResolvedPackageCandidate(
|
|
12750
|
+
addResolvedPackageCandidate(path39.dirname(Bun.main));
|
|
12587
12751
|
addResolvedPackageCandidate(import.meta.dir);
|
|
12588
|
-
candidates.push(
|
|
12752
|
+
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));
|
|
12589
12753
|
for (const candidate of [...new Set(candidates)]) {
|
|
12590
12754
|
if (await Bun.file(candidate).exists())
|
|
12591
12755
|
return candidate;
|
|
@@ -12595,7 +12759,7 @@ async function resolveSignalTestPreloadPath(target) {
|
|
|
12595
12759
|
// pkgs/@akanjs/devkit/builder.ts
|
|
12596
12760
|
import { existsSync as existsSync2 } from "fs";
|
|
12597
12761
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
12598
|
-
import
|
|
12762
|
+
import path40 from "path";
|
|
12599
12763
|
var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
|
|
12600
12764
|
var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
|
|
12601
12765
|
var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
|
|
@@ -12612,14 +12776,14 @@ class Builder {
|
|
|
12612
12776
|
#globEntrypoints(cwd, pattern) {
|
|
12613
12777
|
const glob = new Bun.Glob(pattern);
|
|
12614
12778
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12615
|
-
const segments = relativePath.split(
|
|
12779
|
+
const segments = relativePath.split(path40.sep);
|
|
12616
12780
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12617
|
-
}).map((rel) =>
|
|
12781
|
+
}).map((rel) => path40.join(cwd, rel));
|
|
12618
12782
|
}
|
|
12619
12783
|
#globFiles(cwd, pattern = "**/*.*") {
|
|
12620
12784
|
const glob = new Bun.Glob(pattern);
|
|
12621
12785
|
return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
|
|
12622
|
-
const segments = relativePath.split(
|
|
12786
|
+
const segments = relativePath.split(path40.sep);
|
|
12623
12787
|
return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
|
|
12624
12788
|
});
|
|
12625
12789
|
}
|
|
@@ -12627,17 +12791,17 @@ class Builder {
|
|
|
12627
12791
|
const out = [];
|
|
12628
12792
|
for (const p of additionalEntryPoints) {
|
|
12629
12793
|
if (p.includes("*")) {
|
|
12630
|
-
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${
|
|
12794
|
+
const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path40.sep}`) ? p.slice(cwd.length + 1) : p;
|
|
12631
12795
|
out.push(...this.#globEntrypoints(cwd, rel));
|
|
12632
12796
|
} else
|
|
12633
|
-
out.push(
|
|
12797
|
+
out.push(path40.isAbsolute(p) ? p : path40.join(cwd, p));
|
|
12634
12798
|
}
|
|
12635
12799
|
return out;
|
|
12636
12800
|
}
|
|
12637
12801
|
#getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
|
|
12638
12802
|
const cwd = this.#executor.cwdPath;
|
|
12639
12803
|
const entrypoints = [
|
|
12640
|
-
...bundle ? [
|
|
12804
|
+
...bundle ? [path40.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
|
|
12641
12805
|
...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
|
|
12642
12806
|
];
|
|
12643
12807
|
return {
|
|
@@ -12658,9 +12822,9 @@ class Builder {
|
|
|
12658
12822
|
for (const relativePath of this.#globFiles(cwd)) {
|
|
12659
12823
|
if (relativePath === "package.json")
|
|
12660
12824
|
continue;
|
|
12661
|
-
const sourcePath =
|
|
12662
|
-
const targetPath =
|
|
12663
|
-
await mkdir10(
|
|
12825
|
+
const sourcePath = path40.join(cwd, relativePath);
|
|
12826
|
+
const targetPath = path40.join(this.#distExecutor.cwdPath, relativePath);
|
|
12827
|
+
await mkdir10(path40.dirname(targetPath), { recursive: true });
|
|
12664
12828
|
await Bun.write(targetPath, Bun.file(sourcePath));
|
|
12665
12829
|
}
|
|
12666
12830
|
}
|
|
@@ -12671,13 +12835,13 @@ class Builder {
|
|
|
12671
12835
|
return withoutFormatDir;
|
|
12672
12836
|
if (!hasDotSlash && withoutFormatDir === publishedPath)
|
|
12673
12837
|
return publishedPath;
|
|
12674
|
-
const parsed =
|
|
12838
|
+
const parsed = path40.posix.parse(withoutFormatDir);
|
|
12675
12839
|
if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
|
|
12676
12840
|
return withoutFormatDir;
|
|
12677
|
-
const withoutExt =
|
|
12841
|
+
const withoutExt = path40.posix.join(parsed.dir, parsed.name);
|
|
12678
12842
|
const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
|
|
12679
12843
|
const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
|
|
12680
|
-
const matchedSource = sourceCandidates.find((candidate) => existsSync2(
|
|
12844
|
+
const matchedSource = sourceCandidates.find((candidate) => existsSync2(path40.join(this.#executor.cwdPath, candidate)));
|
|
12681
12845
|
if (!matchedSource)
|
|
12682
12846
|
return withoutFormatDir;
|
|
12683
12847
|
return hasDotSlash ? `./${matchedSource}` : matchedSource;
|
|
@@ -12728,7 +12892,7 @@ class Builder {
|
|
|
12728
12892
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
12729
12893
|
import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
|
|
12730
12894
|
import os from "os";
|
|
12731
|
-
import
|
|
12895
|
+
import path41 from "path";
|
|
12732
12896
|
import { select as select2 } from "@inquirer/prompts";
|
|
12733
12897
|
import { MobileProject } from "@trapezedev/project";
|
|
12734
12898
|
import { capitalize as capitalize5 } from "akanjs/common";
|
|
@@ -12935,13 +13099,13 @@ var rootCapacitorConfigFilenames = [
|
|
|
12935
13099
|
"capacitor.config.js",
|
|
12936
13100
|
"capacitor.config.json"
|
|
12937
13101
|
];
|
|
12938
|
-
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) =>
|
|
13102
|
+
var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
|
|
12939
13103
|
async function clearRootCapacitorConfigs(appRoot) {
|
|
12940
13104
|
await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
|
|
12941
13105
|
}
|
|
12942
13106
|
async function writeRootCapacitorConfig(appRoot, content) {
|
|
12943
13107
|
await clearRootCapacitorConfigs(appRoot);
|
|
12944
|
-
await Bun.write(
|
|
13108
|
+
await Bun.write(path41.join(appRoot, "capacitor.config.json"), content);
|
|
12945
13109
|
}
|
|
12946
13110
|
var getLocalIP = () => {
|
|
12947
13111
|
const interfaces = os.networkInterfaces();
|
|
@@ -13050,13 +13214,13 @@ function buildIosNativeRunCommand({
|
|
|
13050
13214
|
scheme = "App",
|
|
13051
13215
|
configuration = "Debug"
|
|
13052
13216
|
}) {
|
|
13053
|
-
const derivedDataPath =
|
|
13217
|
+
const derivedDataPath = path41.join(appRoot, "ios/DerivedData", device.id);
|
|
13054
13218
|
const productPlatform = device.kind === "device" ? "iphoneos" : "iphonesimulator";
|
|
13055
13219
|
const destination = device.kind === "device" ? `id=${device.xcodebuildId ?? device.id}` : `platform=iOS Simulator,id=${device.id}`;
|
|
13056
13220
|
return {
|
|
13057
13221
|
configuration,
|
|
13058
13222
|
derivedDataPath,
|
|
13059
|
-
appPath:
|
|
13223
|
+
appPath: path41.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
|
|
13060
13224
|
xcodebuildArgs: [
|
|
13061
13225
|
"-project",
|
|
13062
13226
|
"App.xcodeproj",
|
|
@@ -13266,7 +13430,7 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13266
13430
|
...capacitorConfig,
|
|
13267
13431
|
appId,
|
|
13268
13432
|
appName,
|
|
13269
|
-
webDir:
|
|
13433
|
+
webDir: path41.posix.join(".akan", "mobile", name, "www"),
|
|
13270
13434
|
plugins: {
|
|
13271
13435
|
CapacitorCookies: { enabled: true },
|
|
13272
13436
|
...pluginsConfig,
|
|
@@ -13323,10 +13487,10 @@ class CapacitorApp {
|
|
|
13323
13487
|
constructor(app, target) {
|
|
13324
13488
|
this.app = app;
|
|
13325
13489
|
this.target = target;
|
|
13326
|
-
this.targetRootPath =
|
|
13327
|
-
this.targetRoot =
|
|
13328
|
-
this.targetWebRoot =
|
|
13329
|
-
this.targetAssetRoot =
|
|
13490
|
+
this.targetRootPath = path41.posix.join(".akan", "mobile", this.target.name);
|
|
13491
|
+
this.targetRoot = path41.join(this.app.cwdPath, this.targetRootPath);
|
|
13492
|
+
this.targetWebRoot = path41.join(this.targetRoot, "www");
|
|
13493
|
+
this.targetAssetRoot = path41.join(this.targetRoot, "assets");
|
|
13330
13494
|
this.project = new MobileProject(this.app.cwdPath, {
|
|
13331
13495
|
android: { path: this.androidRootPath },
|
|
13332
13496
|
ios: { path: this.iosProjectPath }
|
|
@@ -13341,9 +13505,9 @@ class CapacitorApp {
|
|
|
13341
13505
|
await mkdir11(this.targetRoot, { recursive: true });
|
|
13342
13506
|
if (regenerate) {
|
|
13343
13507
|
if (!platform || platform === "ios")
|
|
13344
|
-
await rm5(
|
|
13508
|
+
await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
|
|
13345
13509
|
if (!platform || platform === "android")
|
|
13346
|
-
await rm5(
|
|
13510
|
+
await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
|
|
13347
13511
|
}
|
|
13348
13512
|
const project = this.project;
|
|
13349
13513
|
await this.project.load();
|
|
@@ -13466,7 +13630,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13466
13630
|
const xcodebuildArgs = noAllowProvisioningUpdates ? command.xcodebuildArgs : [...command.xcodebuildArgs.slice(0, -1), "-allowProvisioningUpdates", ...command.xcodebuildArgs.slice(-1)];
|
|
13467
13631
|
try {
|
|
13468
13632
|
await this.#spawn("xcodebuild", xcodebuildArgs, {
|
|
13469
|
-
cwd:
|
|
13633
|
+
cwd: path41.join(this.app.cwdPath, this.iosProjectPath),
|
|
13470
13634
|
env: mobileEnv
|
|
13471
13635
|
});
|
|
13472
13636
|
const devicectlId = runTarget.devicectlId ?? runTarget.id;
|
|
@@ -13491,7 +13655,7 @@ ${textError instanceof Error ? textError.message : ""}`);
|
|
|
13491
13655
|
return isRecord2(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
|
|
13492
13656
|
}
|
|
13493
13657
|
async#getIosDevelopmentTeam() {
|
|
13494
|
-
const pbxprojPath =
|
|
13658
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
13495
13659
|
if (!await Bun.file(pbxprojPath).exists())
|
|
13496
13660
|
return;
|
|
13497
13661
|
return (await Bun.file(pbxprojPath).text()).match(/DEVELOPMENT_TEAM = ([^;]+);/)?.[1]?.trim();
|
|
@@ -13517,7 +13681,7 @@ ${error.message}`;
|
|
|
13517
13681
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
13518
13682
|
}
|
|
13519
13683
|
async#updateAndroidBuildTypes() {
|
|
13520
|
-
const appGradle = await FileEditor.create(
|
|
13684
|
+
const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
13521
13685
|
const buildTypesBlock = `
|
|
13522
13686
|
debug {
|
|
13523
13687
|
applicationIdSuffix ".debug"
|
|
@@ -13561,7 +13725,7 @@ ${error.message}`;
|
|
|
13561
13725
|
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
13562
13726
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
13563
13727
|
stdio: "inherit",
|
|
13564
|
-
cwd:
|
|
13728
|
+
cwd: path41.join(this.app.cwdPath, this.androidRootPath),
|
|
13565
13729
|
env: await this.#commandEnv("release", env)
|
|
13566
13730
|
});
|
|
13567
13731
|
}
|
|
@@ -13569,10 +13733,10 @@ ${error.message}`;
|
|
|
13569
13733
|
await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
|
|
13570
13734
|
}
|
|
13571
13735
|
async#ensureAndroidAssetsDir() {
|
|
13572
|
-
await mkdir11(
|
|
13736
|
+
await mkdir11(path41.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
13573
13737
|
}
|
|
13574
13738
|
async#ensureAndroidDebugKeystore() {
|
|
13575
|
-
const keystorePath =
|
|
13739
|
+
const keystorePath = path41.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
|
|
13576
13740
|
if (await Bun.file(keystorePath).exists())
|
|
13577
13741
|
return;
|
|
13578
13742
|
await this.#spawn("keytool", [
|
|
@@ -13611,7 +13775,7 @@ ${error.message}`;
|
|
|
13611
13775
|
await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
|
|
13612
13776
|
}
|
|
13613
13777
|
async#assertAndroidReleaseSigningConfig() {
|
|
13614
|
-
const gradlePropertiesPath =
|
|
13778
|
+
const gradlePropertiesPath = path41.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
|
|
13615
13779
|
const gradleProperties = await Bun.file(gradlePropertiesPath).exists() ? await Bun.file(gradlePropertiesPath).text() : "";
|
|
13616
13780
|
const missingKeys = getMissingAndroidReleaseSigningKeys({ gradleProperties });
|
|
13617
13781
|
if (missingKeys.length > 0)
|
|
@@ -13638,12 +13802,12 @@ ${error.message}`;
|
|
|
13638
13802
|
await this.#prepareAndroid({ operation: "release", env: "main" });
|
|
13639
13803
|
}
|
|
13640
13804
|
async prepareWww() {
|
|
13641
|
-
const htmlSource =
|
|
13805
|
+
const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
|
|
13642
13806
|
if (!await Bun.file(htmlSource).exists())
|
|
13643
13807
|
throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
|
|
13644
13808
|
await rm5(this.targetWebRoot, { recursive: true, force: true });
|
|
13645
13809
|
await mkdir11(this.targetWebRoot, { recursive: true });
|
|
13646
|
-
await Bun.write(
|
|
13810
|
+
await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
|
|
13647
13811
|
}
|
|
13648
13812
|
#injectMobileTargetMeta(html) {
|
|
13649
13813
|
const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
@@ -13663,7 +13827,7 @@ ${error.message}`;
|
|
|
13663
13827
|
});
|
|
13664
13828
|
const content = `${JSON.stringify(config, null, 2)}
|
|
13665
13829
|
`;
|
|
13666
|
-
await Bun.write(
|
|
13830
|
+
await Bun.write(path41.join(this.targetRoot, "capacitor.config.json"), content);
|
|
13667
13831
|
return content;
|
|
13668
13832
|
}
|
|
13669
13833
|
async#prepareTargetAssets() {
|
|
@@ -13671,11 +13835,11 @@ ${error.message}`;
|
|
|
13671
13835
|
return;
|
|
13672
13836
|
await mkdir11(this.targetAssetRoot, { recursive: true });
|
|
13673
13837
|
if (this.target.assets.icon)
|
|
13674
|
-
await cp2(
|
|
13838
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.icon), path41.join(this.targetAssetRoot, "icon.png"), {
|
|
13675
13839
|
force: true
|
|
13676
13840
|
});
|
|
13677
13841
|
if (this.target.assets.splash)
|
|
13678
|
-
await cp2(
|
|
13842
|
+
await cp2(path41.join(this.app.cwdPath, this.target.assets.splash), path41.join(this.targetAssetRoot, "splash.png"), {
|
|
13679
13843
|
force: true
|
|
13680
13844
|
});
|
|
13681
13845
|
}
|
|
@@ -13683,11 +13847,11 @@ ${error.message}`;
|
|
|
13683
13847
|
const files = this.target.files?.[platform];
|
|
13684
13848
|
if (!files)
|
|
13685
13849
|
return;
|
|
13686
|
-
const platformRoot =
|
|
13850
|
+
const platformRoot = path41.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
|
|
13687
13851
|
await Promise.all(Object.entries(files).map(async ([to, from]) => {
|
|
13688
|
-
const targetPath =
|
|
13689
|
-
await mkdir11(
|
|
13690
|
-
await cp2(
|
|
13852
|
+
const targetPath = path41.join(platformRoot, to);
|
|
13853
|
+
await mkdir11(path41.dirname(targetPath), { recursive: true });
|
|
13854
|
+
await cp2(path41.join(this.app.cwdPath, from), targetPath, { force: true });
|
|
13691
13855
|
}));
|
|
13692
13856
|
}
|
|
13693
13857
|
async#generateAssets({ operation, env }) {
|
|
@@ -13697,7 +13861,7 @@ ${error.message}`;
|
|
|
13697
13861
|
"@capacitor/assets",
|
|
13698
13862
|
"generate",
|
|
13699
13863
|
"--assetPath",
|
|
13700
|
-
|
|
13864
|
+
path41.posix.join(this.targetRootPath, "assets"),
|
|
13701
13865
|
"--iosProject",
|
|
13702
13866
|
this.iosProjectPath,
|
|
13703
13867
|
"--androidProject",
|
|
@@ -13907,7 +14071,7 @@ var Pkg = createInternalArgToken("Pkg");
|
|
|
13907
14071
|
var Module = createInternalArgToken("Module");
|
|
13908
14072
|
var Workspace = createInternalArgToken("Workspace");
|
|
13909
14073
|
// pkgs/@akanjs/devkit/commandDecorators/command.ts
|
|
13910
|
-
import
|
|
14074
|
+
import path42 from "path";
|
|
13911
14075
|
import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
|
|
13912
14076
|
import { Logger as Logger10 } from "akanjs/common";
|
|
13913
14077
|
import chalk6 from "chalk";
|
|
@@ -14411,7 +14575,7 @@ var runCommands = async (...commands) => {
|
|
|
14411
14575
|
process.exit(1);
|
|
14412
14576
|
});
|
|
14413
14577
|
const __dirname2 = getDirname(import.meta.url);
|
|
14414
|
-
const packageJsonCandidates = [`${
|
|
14578
|
+
const packageJsonCandidates = [`${path42.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
|
|
14415
14579
|
let cliPackageJson = null;
|
|
14416
14580
|
for (const packageJsonPath of packageJsonCandidates) {
|
|
14417
14581
|
if (!await FileSys.fileExists(packageJsonPath))
|
|
@@ -14691,8 +14855,8 @@ var scanModuleSpecifiers = (source2, filePath, includeExports) => {
|
|
|
14691
14855
|
return importSpecifiers;
|
|
14692
14856
|
};
|
|
14693
14857
|
var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
|
|
14694
|
-
const configFile = ts10.readConfigFile(tsConfigPath, (
|
|
14695
|
-
return ts10.sys.readFile(
|
|
14858
|
+
const configFile = ts10.readConfigFile(tsConfigPath, (path43) => {
|
|
14859
|
+
return ts10.sys.readFile(path43);
|
|
14696
14860
|
});
|
|
14697
14861
|
return ts10.parseJsonConfigFileContent(configFile.config, ts10.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
|
|
14698
14862
|
};
|
|
@@ -14908,6 +15072,571 @@ ${document}
|
|
|
14908
15072
|
`;
|
|
14909
15073
|
}
|
|
14910
15074
|
}
|
|
15075
|
+
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
15076
|
+
import { createHash } from "crypto";
|
|
15077
|
+
import { readdir as readdir3, readFile as readFile2, stat as stat4 } from "fs/promises";
|
|
15078
|
+
import path43 from "path";
|
|
15079
|
+
import ignore2 from "ignore";
|
|
15080
|
+
import ts11 from "typescript";
|
|
15081
|
+
var MAX_FILE_LINES = 2000;
|
|
15082
|
+
var PLACEHOLDER_EXPORT_NAMES = new Set([
|
|
15083
|
+
"aa",
|
|
15084
|
+
"dumb",
|
|
15085
|
+
"dumb2",
|
|
15086
|
+
"someBaseLogic",
|
|
15087
|
+
"someCommonLogic",
|
|
15088
|
+
"someFrontendLogic",
|
|
15089
|
+
"someBackendLogic"
|
|
15090
|
+
]);
|
|
15091
|
+
var SUGGESTED_RULES = [
|
|
15092
|
+
"Keep generated scanSync index files out of hand-written changes; generated indexes should only contain one-depth export statements.",
|
|
15093
|
+
"Generated Akan index files should not contain placeholder exports such as aa, dumb, dumb2, or some*Logic.",
|
|
15094
|
+
"Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
|
|
15095
|
+
"Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
|
|
15096
|
+
"Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
|
|
15097
|
+
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
|
|
15098
|
+
"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.",
|
|
15099
|
+
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
15100
|
+
"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.",
|
|
15101
|
+
"Move shared app utilities to apps/*/common instead of creating apps/*/base.",
|
|
15102
|
+
"Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
|
|
15103
|
+
];
|
|
15104
|
+
var APP_ROOT_FILES = new Set([
|
|
15105
|
+
"akan.app.json",
|
|
15106
|
+
"akan.config.ts",
|
|
15107
|
+
"capacitor.config.ts",
|
|
15108
|
+
"client.ts",
|
|
15109
|
+
"main.ts",
|
|
15110
|
+
"package.json",
|
|
15111
|
+
"server.ts",
|
|
15112
|
+
"tsconfig.json"
|
|
15113
|
+
]);
|
|
15114
|
+
var LIB_ROOT_FILES = new Set([
|
|
15115
|
+
"cnst.ts",
|
|
15116
|
+
"db.ts",
|
|
15117
|
+
"dict.ts",
|
|
15118
|
+
"option.ts",
|
|
15119
|
+
"sig.ts",
|
|
15120
|
+
"srv.ts",
|
|
15121
|
+
"st.ts",
|
|
15122
|
+
"useClient.ts",
|
|
15123
|
+
"useServer.ts"
|
|
15124
|
+
]);
|
|
15125
|
+
var CONVENTION_SUFFIXES = [
|
|
15126
|
+
".constant.ts",
|
|
15127
|
+
".dictionary.ts",
|
|
15128
|
+
".document.ts",
|
|
15129
|
+
".service.ts",
|
|
15130
|
+
".signal.ts",
|
|
15131
|
+
".store.ts"
|
|
15132
|
+
];
|
|
15133
|
+
|
|
15134
|
+
class AkanQualityScanner {
|
|
15135
|
+
async scan(workspaceRoot) {
|
|
15136
|
+
const targetFiles = await this.#collectTargetFiles(workspaceRoot);
|
|
15137
|
+
const sourceFiles = await Promise.all(targetFiles.map((file) => this.#readSourceFile(workspaceRoot, file)));
|
|
15138
|
+
const warnings = [
|
|
15139
|
+
...this.#scanGlobalQuality(sourceFiles),
|
|
15140
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanSingleFileQuality(sourceFile2)),
|
|
15141
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanConventionQuality(sourceFile2)),
|
|
15142
|
+
...sourceFiles.flatMap((sourceFile2) => this.#scanLayoutQuality(sourceFile2))
|
|
15143
|
+
];
|
|
15144
|
+
return {
|
|
15145
|
+
workspaceRoot,
|
|
15146
|
+
scannedFiles: sourceFiles.length,
|
|
15147
|
+
warnings: warnings.sort(compareWarnings),
|
|
15148
|
+
suggestedRules: SUGGESTED_RULES
|
|
15149
|
+
};
|
|
15150
|
+
}
|
|
15151
|
+
async#collectTargetFiles(workspaceRoot) {
|
|
15152
|
+
const ignoreFilter = ignore2().add(await this.#readGitIgnore(workspaceRoot));
|
|
15153
|
+
const files = [];
|
|
15154
|
+
for (const targetRoot of ["apps", "libs"]) {
|
|
15155
|
+
const absoluteTargetRoot = path43.join(workspaceRoot, targetRoot);
|
|
15156
|
+
if (!await isDirectory(absoluteTargetRoot))
|
|
15157
|
+
continue;
|
|
15158
|
+
await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
|
|
15159
|
+
}
|
|
15160
|
+
return files.sort();
|
|
15161
|
+
}
|
|
15162
|
+
async#readGitIgnore(workspaceRoot) {
|
|
15163
|
+
const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
|
|
15164
|
+
if (!await Bun.file(gitIgnorePath).exists())
|
|
15165
|
+
return [];
|
|
15166
|
+
return (await readFile2(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
15167
|
+
}
|
|
15168
|
+
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
15169
|
+
const entries = await readdir3(currentPath, { withFileTypes: true });
|
|
15170
|
+
for (const entry of entries) {
|
|
15171
|
+
const absolutePath = path43.join(currentPath, entry.name);
|
|
15172
|
+
const relativePath = toPosix(path43.relative(workspaceRoot, absolutePath));
|
|
15173
|
+
if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
|
|
15174
|
+
continue;
|
|
15175
|
+
if (entry.isDirectory()) {
|
|
15176
|
+
await this.#walkTargetFiles(workspaceRoot, absolutePath, ignoreFilter, files);
|
|
15177
|
+
continue;
|
|
15178
|
+
}
|
|
15179
|
+
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
15180
|
+
files.push(relativePath);
|
|
15181
|
+
}
|
|
15182
|
+
}
|
|
15183
|
+
}
|
|
15184
|
+
async#readSourceFile(workspaceRoot, file) {
|
|
15185
|
+
const absolutePath = path43.join(workspaceRoot, file);
|
|
15186
|
+
const content = await readFile2(absolutePath, "utf8");
|
|
15187
|
+
return {
|
|
15188
|
+
file,
|
|
15189
|
+
absolutePath,
|
|
15190
|
+
content,
|
|
15191
|
+
sourceFile: ts11.createSourceFile(file, content, ts11.ScriptTarget.Latest, true, getScriptKind(file))
|
|
15192
|
+
};
|
|
15193
|
+
}
|
|
15194
|
+
#scanGlobalQuality(sourceFiles) {
|
|
15195
|
+
const exportedFunctionLikes = sourceFiles.flatMap((sourceFile2) => getExportedFunctionLikes(sourceFile2));
|
|
15196
|
+
const warnings = [];
|
|
15197
|
+
for (const [name, declarations] of groupBy(exportedFunctionLikes, (declaration) => declaration.name)) {
|
|
15198
|
+
if (declarations.length < 2)
|
|
15199
|
+
continue;
|
|
15200
|
+
warnings.push({
|
|
15201
|
+
rule: "akan.global.duplicate-exported-function-name",
|
|
15202
|
+
scope: "global",
|
|
15203
|
+
severity: "warning",
|
|
15204
|
+
message: `Exported function or class name "${name}" is declared in ${declarations.length} files.`,
|
|
15205
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15206
|
+
});
|
|
15207
|
+
}
|
|
15208
|
+
const declarationsWithBody = exportedFunctionLikes.filter((declaration) => declaration.bodyFingerprint);
|
|
15209
|
+
for (const [fingerprint, declarations] of groupBy(declarationsWithBody, (declaration) => declaration.bodyFingerprint ?? "")) {
|
|
15210
|
+
const uniqueNames = new Set(declarations.map((declaration) => declaration.name));
|
|
15211
|
+
if (declarations.length < 2 || uniqueNames.size < 2 || fingerprint === "")
|
|
15212
|
+
continue;
|
|
15213
|
+
warnings.push({
|
|
15214
|
+
rule: "akan.global.duplicate-exported-function-body",
|
|
15215
|
+
scope: "global",
|
|
15216
|
+
severity: "warning",
|
|
15217
|
+
message: `Exported functions/classes share the same implementation body: ${declarations.map((declaration) => declaration.name).join(", ")}.`,
|
|
15218
|
+
locations: declarations.map(({ file, line }) => ({ file, line }))
|
|
15219
|
+
});
|
|
15220
|
+
}
|
|
15221
|
+
return warnings;
|
|
15222
|
+
}
|
|
15223
|
+
#scanSingleFileQuality(sourceFile2) {
|
|
15224
|
+
const warnings = [];
|
|
15225
|
+
const lineCount = sourceFile2.content.split(/\r?\n/).length;
|
|
15226
|
+
const recommendedLineLimit = getRecommendedLineLimit(sourceFile2.file);
|
|
15227
|
+
if (recommendedLineLimit && lineCount > recommendedLineLimit) {
|
|
15228
|
+
warnings.push({
|
|
15229
|
+
rule: "akan.file.recommended-max-lines",
|
|
15230
|
+
scope: "file",
|
|
15231
|
+
severity: "warning",
|
|
15232
|
+
file: sourceFile2.file,
|
|
15233
|
+
message: `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`
|
|
15234
|
+
});
|
|
15235
|
+
}
|
|
15236
|
+
if (lineCount > MAX_FILE_LINES) {
|
|
15237
|
+
warnings.push({
|
|
15238
|
+
rule: "akan.file.max-lines",
|
|
15239
|
+
scope: "file",
|
|
15240
|
+
severity: "warning",
|
|
15241
|
+
file: sourceFile2.file,
|
|
15242
|
+
message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
|
|
15243
|
+
});
|
|
15244
|
+
}
|
|
15245
|
+
warnings.push(...getPlaceholderExportWarnings(sourceFile2));
|
|
15246
|
+
warnings.push(...getDictionaryTextWarnings(sourceFile2));
|
|
15247
|
+
warnings.push(...getGlobalMutationWarnings(sourceFile2));
|
|
15248
|
+
const exportedClassNames = getExportedClassNames(sourceFile2.sourceFile);
|
|
15249
|
+
if (exportedClassNames.length === 0)
|
|
15250
|
+
return warnings;
|
|
15251
|
+
const allowedInterfaceNames = new Set(exportedClassNames.map((name) => `${name}Options`));
|
|
15252
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15253
|
+
if (declaration.kind === "class" && exportedClassNames.includes(declaration.name))
|
|
15254
|
+
continue;
|
|
15255
|
+
if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name))
|
|
15256
|
+
continue;
|
|
15257
|
+
warnings.push({
|
|
15258
|
+
rule: "akan.file.class-export-global-declaration",
|
|
15259
|
+
scope: "file",
|
|
15260
|
+
severity: "warning",
|
|
15261
|
+
file: sourceFile2.file,
|
|
15262
|
+
line: declaration.line,
|
|
15263
|
+
message: `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`
|
|
15264
|
+
});
|
|
15265
|
+
}
|
|
15266
|
+
return warnings;
|
|
15267
|
+
}
|
|
15268
|
+
#scanConventionQuality(sourceFile2) {
|
|
15269
|
+
const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile2.file.endsWith(candidate));
|
|
15270
|
+
if (!suffix)
|
|
15271
|
+
return [];
|
|
15272
|
+
const modelName = toPascalCase(path43.basename(sourceFile2.file, suffix));
|
|
15273
|
+
const warnings = [];
|
|
15274
|
+
for (const declaration of getTopLevelDeclarations(sourceFile2)) {
|
|
15275
|
+
if (isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
15276
|
+
continue;
|
|
15277
|
+
warnings.push({
|
|
15278
|
+
rule: `akan.convention${suffix.replace(".ts", "")}`,
|
|
15279
|
+
scope: "convention",
|
|
15280
|
+
severity: "warning",
|
|
15281
|
+
file: sourceFile2.file,
|
|
15282
|
+
line: declaration.line,
|
|
15283
|
+
message: `${path43.basename(sourceFile2.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
|
|
15284
|
+
});
|
|
15285
|
+
}
|
|
15286
|
+
return warnings;
|
|
15287
|
+
}
|
|
15288
|
+
#scanLayoutQuality(sourceFile2) {
|
|
15289
|
+
const segments = sourceFile2.file.split("/");
|
|
15290
|
+
const warnings = [];
|
|
15291
|
+
if (segments[0] === "apps" && segments.length === 3 && !APP_ROOT_FILES.has(segments[2])) {
|
|
15292
|
+
warnings.push({
|
|
15293
|
+
rule: "akan.layout.app-root-file",
|
|
15294
|
+
scope: "layout",
|
|
15295
|
+
severity: "warning",
|
|
15296
|
+
file: sourceFile2.file,
|
|
15297
|
+
message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
|
|
15298
|
+
});
|
|
15299
|
+
}
|
|
15300
|
+
const libRootFile = getLibRootFile(sourceFile2.file);
|
|
15301
|
+
if (libRootFile && !LIB_ROOT_FILES.has(libRootFile)) {
|
|
15302
|
+
warnings.push({
|
|
15303
|
+
rule: "akan.layout.lib-root-file",
|
|
15304
|
+
scope: "layout",
|
|
15305
|
+
severity: "warning",
|
|
15306
|
+
file: sourceFile2.file,
|
|
15307
|
+
message: `Unexpected lib root file "${libRootFile}". Keep direct lib root files limited to generated support facets.`
|
|
15308
|
+
});
|
|
15309
|
+
}
|
|
15310
|
+
const moduleUiWarning = getModuleUiWarning(sourceFile2.file);
|
|
15311
|
+
if (moduleUiWarning)
|
|
15312
|
+
warnings.push(moduleUiWarning);
|
|
15313
|
+
return warnings;
|
|
15314
|
+
}
|
|
15315
|
+
}
|
|
15316
|
+
function formatQualityScanResult(result) {
|
|
15317
|
+
const sections = [
|
|
15318
|
+
"Akan Code Quality Scan",
|
|
15319
|
+
`workspace: ${result.workspaceRoot}`,
|
|
15320
|
+
`scanned files: ${result.scannedFiles}`,
|
|
15321
|
+
`warnings: ${result.warnings.length}`,
|
|
15322
|
+
"",
|
|
15323
|
+
"Warnings:",
|
|
15324
|
+
"",
|
|
15325
|
+
...formatQualityWarnings(result.warnings),
|
|
15326
|
+
"",
|
|
15327
|
+
"Suggested quality rules:",
|
|
15328
|
+
"",
|
|
15329
|
+
...result.suggestedRules.map((rule) => ` - ${rule}`)
|
|
15330
|
+
];
|
|
15331
|
+
return sections.join(`
|
|
15332
|
+
`);
|
|
15333
|
+
}
|
|
15334
|
+
function formatQualityWarnings(warnings) {
|
|
15335
|
+
if (warnings.length === 0)
|
|
15336
|
+
return ["No warnings found."];
|
|
15337
|
+
return warnings.flatMap((warning) => {
|
|
15338
|
+
const location = formatQualityLocation(warning.file, warning.line);
|
|
15339
|
+
const lines = [`${location} - warning ${warning.rule}: ${warning.message}`];
|
|
15340
|
+
if (warning.locations?.length) {
|
|
15341
|
+
lines.push(...warning.locations.map(({ file, line }) => ` note: related location ${formatQualityLocation(file, line)}`));
|
|
15342
|
+
}
|
|
15343
|
+
return lines;
|
|
15344
|
+
});
|
|
15345
|
+
}
|
|
15346
|
+
function formatQualityLocation(file, line) {
|
|
15347
|
+
return `${file ?? "<global>"}:${line ?? 1}:1`;
|
|
15348
|
+
}
|
|
15349
|
+
function getExportedFunctionLikes(sourceFile2) {
|
|
15350
|
+
const declarations = [];
|
|
15351
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15352
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15353
|
+
declarations.push({
|
|
15354
|
+
name: statement.name.text,
|
|
15355
|
+
kind: "function",
|
|
15356
|
+
file: sourceFile2.file,
|
|
15357
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15358
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement.body)
|
|
15359
|
+
});
|
|
15360
|
+
}
|
|
15361
|
+
if (ts11.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
15362
|
+
declarations.push({
|
|
15363
|
+
name: statement.name.text,
|
|
15364
|
+
kind: "class",
|
|
15365
|
+
file: sourceFile2.file,
|
|
15366
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15367
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, statement)
|
|
15368
|
+
});
|
|
15369
|
+
}
|
|
15370
|
+
if (ts11.isVariableStatement(statement) && isExported(statement)) {
|
|
15371
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
15372
|
+
if (!ts11.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
|
|
15373
|
+
continue;
|
|
15374
|
+
declarations.push({
|
|
15375
|
+
name: declaration.name.text,
|
|
15376
|
+
kind: "function-variable",
|
|
15377
|
+
file: sourceFile2.file,
|
|
15378
|
+
line: getLine(sourceFile2.sourceFile, declaration),
|
|
15379
|
+
bodyFingerprint: getBodyFingerprint(sourceFile2.sourceFile, declaration.initializer)
|
|
15380
|
+
});
|
|
15381
|
+
}
|
|
15382
|
+
}
|
|
15383
|
+
}
|
|
15384
|
+
return declarations;
|
|
15385
|
+
}
|
|
15386
|
+
function getExportedClassNames(sourceFile2) {
|
|
15387
|
+
return sourceFile2.statements.filter((statement) => ts11.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
|
|
15388
|
+
}
|
|
15389
|
+
function getPlaceholderExportWarnings(sourceFile2) {
|
|
15390
|
+
if (!sourceFile2.file.endsWith("/index.ts") && !sourceFile2.file.endsWith("/index.tsx"))
|
|
15391
|
+
return [];
|
|
15392
|
+
return getTopLevelDeclarations(sourceFile2).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => ({
|
|
15393
|
+
rule: "akan.file.placeholder-export",
|
|
15394
|
+
scope: "file",
|
|
15395
|
+
severity: "warning",
|
|
15396
|
+
file: sourceFile2.file,
|
|
15397
|
+
line: declaration.line,
|
|
15398
|
+
message: `Generated or barrel index file should not export placeholder "${declaration.name}".`
|
|
15399
|
+
}));
|
|
15400
|
+
}
|
|
15401
|
+
function getDictionaryTextWarnings(sourceFile2) {
|
|
15402
|
+
if (!sourceFile2.file.endsWith(".dictionary.ts"))
|
|
15403
|
+
return [];
|
|
15404
|
+
const stalePatterns = [
|
|
15405
|
+
{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" },
|
|
15406
|
+
{ pattern: /settting/, label: "misspelling: settting" },
|
|
15407
|
+
{ pattern: /\uBC30\uB108 \uC218/, label: "stale copied Korean domain noun: \uBC30\uB108 \uC218" }
|
|
15408
|
+
];
|
|
15409
|
+
return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile2.content, pattern).map((line) => ({
|
|
15410
|
+
rule: "akan.file.dictionary-stale-text",
|
|
15411
|
+
scope: "file",
|
|
15412
|
+
severity: "warning",
|
|
15413
|
+
file: sourceFile2.file,
|
|
15414
|
+
line,
|
|
15415
|
+
message: `Dictionary text appears to contain ${label}.`
|
|
15416
|
+
})));
|
|
15417
|
+
}
|
|
15418
|
+
function getGlobalMutationWarnings(sourceFile2) {
|
|
15419
|
+
const warnings = [];
|
|
15420
|
+
for (const statement of sourceFile2.sourceFile.statements) {
|
|
15421
|
+
if (ts11.isModuleDeclaration(statement) && statement.name.getText(sourceFile2.sourceFile) === "global") {
|
|
15422
|
+
warnings.push({
|
|
15423
|
+
rule: "akan.file.global-declaration",
|
|
15424
|
+
scope: "file",
|
|
15425
|
+
severity: "warning",
|
|
15426
|
+
file: sourceFile2.file,
|
|
15427
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15428
|
+
message: "Global declarations require an explicit low-level integration allowlist."
|
|
15429
|
+
});
|
|
15430
|
+
}
|
|
15431
|
+
if (ts11.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
15432
|
+
warnings.push({
|
|
15433
|
+
rule: "akan.file.window-augmentation",
|
|
15434
|
+
scope: "file",
|
|
15435
|
+
severity: "warning",
|
|
15436
|
+
file: sourceFile2.file,
|
|
15437
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15438
|
+
message: "Window augmentation should be isolated to approved browser integration files."
|
|
15439
|
+
});
|
|
15440
|
+
}
|
|
15441
|
+
if (ts11.isExpressionStatement(statement) && statement.expression.getText(sourceFile2.sourceFile).includes(".prototype.")) {
|
|
15442
|
+
warnings.push({
|
|
15443
|
+
rule: "akan.file.prototype-mutation",
|
|
15444
|
+
scope: "file",
|
|
15445
|
+
severity: "warning",
|
|
15446
|
+
file: sourceFile2.file,
|
|
15447
|
+
line: getLine(sourceFile2.sourceFile, statement),
|
|
15448
|
+
message: "Prototype mutation should be avoided or isolated to approved low-level integration files."
|
|
15449
|
+
});
|
|
15450
|
+
}
|
|
15451
|
+
}
|
|
15452
|
+
return warnings;
|
|
15453
|
+
}
|
|
15454
|
+
function getTopLevelDeclarations(sourceFile2) {
|
|
15455
|
+
return sourceFile2.sourceFile.statements.flatMap((statement) => getTopLevelDeclaration(sourceFile2.sourceFile, statement));
|
|
15456
|
+
}
|
|
15457
|
+
function getTopLevelDeclaration(sourceFile2, statement) {
|
|
15458
|
+
const line = getLine(sourceFile2, statement);
|
|
15459
|
+
if (ts11.isClassDeclaration(statement) && statement.name) {
|
|
15460
|
+
return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
|
|
15461
|
+
}
|
|
15462
|
+
if (ts11.isFunctionDeclaration(statement) && statement.name) {
|
|
15463
|
+
return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
|
|
15464
|
+
}
|
|
15465
|
+
if (ts11.isInterfaceDeclaration(statement)) {
|
|
15466
|
+
return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
|
|
15467
|
+
}
|
|
15468
|
+
if (ts11.isTypeAliasDeclaration(statement)) {
|
|
15469
|
+
return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
|
|
15470
|
+
}
|
|
15471
|
+
if (ts11.isEnumDeclaration(statement)) {
|
|
15472
|
+
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
15473
|
+
}
|
|
15474
|
+
if (ts11.isVariableStatement(statement)) {
|
|
15475
|
+
return statement.declarationList.declarations.filter((declaration) => ts11.isIdentifier(declaration.name)).map((declaration) => ({
|
|
15476
|
+
name: declaration.name.text,
|
|
15477
|
+
kind: "variable",
|
|
15478
|
+
line: getLine(sourceFile2, declaration),
|
|
15479
|
+
exported: isExported(statement),
|
|
15480
|
+
node: statement
|
|
15481
|
+
}));
|
|
15482
|
+
}
|
|
15483
|
+
if (ts11.isExportDeclaration(statement)) {
|
|
15484
|
+
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
15485
|
+
}
|
|
15486
|
+
return [];
|
|
15487
|
+
}
|
|
15488
|
+
function isAllowedConventionDeclaration(suffix, modelName, declaration) {
|
|
15489
|
+
if (suffix === ".dictionary.ts")
|
|
15490
|
+
return isExportedConst(declaration) && declaration.name === "dictionary";
|
|
15491
|
+
if (suffix === ".constant.ts")
|
|
15492
|
+
return isAllowedConstantDeclaration(modelName, declaration);
|
|
15493
|
+
if (suffix === ".document.ts")
|
|
15494
|
+
return declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name);
|
|
15495
|
+
if (suffix === ".service.ts")
|
|
15496
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Service`;
|
|
15497
|
+
if (suffix === ".signal.ts")
|
|
15498
|
+
return declaration.kind === "class" && [`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name);
|
|
15499
|
+
if (suffix === ".store.ts")
|
|
15500
|
+
return declaration.kind === "class" && declaration.name === `${modelName}Store`;
|
|
15501
|
+
return false;
|
|
15502
|
+
}
|
|
15503
|
+
function isAllowedConstantDeclaration(modelName, declaration) {
|
|
15504
|
+
if (declaration.kind !== "class")
|
|
15505
|
+
return false;
|
|
15506
|
+
if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
|
|
15507
|
+
return true;
|
|
15508
|
+
if (!ts11.isClassDeclaration(declaration.node))
|
|
15509
|
+
return false;
|
|
15510
|
+
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts11.SyntaxKind.ExtendsKeyword);
|
|
15511
|
+
const expression = heritageClause?.types[0]?.expression;
|
|
15512
|
+
return !!expression && expression.getText().startsWith("enumOf(");
|
|
15513
|
+
}
|
|
15514
|
+
function getConventionDescription(suffix, modelName) {
|
|
15515
|
+
if (suffix === ".dictionary.ts")
|
|
15516
|
+
return "export const dictionary";
|
|
15517
|
+
if (suffix === ".constant.ts")
|
|
15518
|
+
return `${modelName}Input, ${modelName}Object, ${modelName}, Light${modelName}, ${modelName}Insight, or enumOf classes`;
|
|
15519
|
+
if (suffix === ".document.ts")
|
|
15520
|
+
return `${modelName}Filter, ${modelName}, or ${modelName}Model`;
|
|
15521
|
+
if (suffix === ".service.ts")
|
|
15522
|
+
return `${modelName}Service`;
|
|
15523
|
+
if (suffix === ".signal.ts")
|
|
15524
|
+
return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
|
|
15525
|
+
return `${modelName}Store`;
|
|
15526
|
+
}
|
|
15527
|
+
function getLibRootFile(file) {
|
|
15528
|
+
const segments = file.split("/");
|
|
15529
|
+
if (segments[0] === "libs" && segments.length === 4 && segments[2] === "lib")
|
|
15530
|
+
return segments[3];
|
|
15531
|
+
if (segments[0] === "apps" && segments.length === 5 && segments[2] === "lib")
|
|
15532
|
+
return segments[4];
|
|
15533
|
+
return null;
|
|
15534
|
+
}
|
|
15535
|
+
function getModuleUiWarning(file) {
|
|
15536
|
+
if (!file.endsWith(".tsx"))
|
|
15537
|
+
return null;
|
|
15538
|
+
const moduleInfo = getModuleInfo(file);
|
|
15539
|
+
if (!moduleInfo)
|
|
15540
|
+
return null;
|
|
15541
|
+
const { moduleName, fileName, kind } = moduleInfo;
|
|
15542
|
+
const pascalName = toPascalCase(moduleName.replace(/^_+/, ""));
|
|
15543
|
+
const allowedSuffixes = kind === "database" ? ["Template", "Unit", "Util", "View", "Zone"] : kind === "service" ? ["Util", "Zone"] : ["Template", "Unit"];
|
|
15544
|
+
const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
|
|
15545
|
+
if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx"))
|
|
15546
|
+
return null;
|
|
15547
|
+
return {
|
|
15548
|
+
rule: "akan.layout.module-ui-file",
|
|
15549
|
+
scope: "layout",
|
|
15550
|
+
severity: "warning",
|
|
15551
|
+
file,
|
|
15552
|
+
message: `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`
|
|
15553
|
+
};
|
|
15554
|
+
}
|
|
15555
|
+
function getModuleInfo(file) {
|
|
15556
|
+
const segments = file.split("/");
|
|
15557
|
+
const libIndex = segments.indexOf("lib");
|
|
15558
|
+
if (libIndex < 0)
|
|
15559
|
+
return null;
|
|
15560
|
+
const moduleName = segments[libIndex + 1];
|
|
15561
|
+
if (moduleName === "__scalar") {
|
|
15562
|
+
if (segments.length !== libIndex + 4)
|
|
15563
|
+
return null;
|
|
15564
|
+
return { moduleName: segments[libIndex + 2], fileName: segments[libIndex + 3], kind: "scalar" };
|
|
15565
|
+
}
|
|
15566
|
+
if (segments.length !== libIndex + 3)
|
|
15567
|
+
return null;
|
|
15568
|
+
const fileName = segments[libIndex + 2];
|
|
15569
|
+
if (moduleName.startsWith("__")) {
|
|
15570
|
+
const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
|
|
15571
|
+
return { moduleName: scalarName, fileName, kind: "scalar" };
|
|
15572
|
+
}
|
|
15573
|
+
if (moduleName.startsWith("_"))
|
|
15574
|
+
return { moduleName, fileName, kind: "service" };
|
|
15575
|
+
return { moduleName, fileName, kind: "database" };
|
|
15576
|
+
}
|
|
15577
|
+
function isExportedConst(declaration) {
|
|
15578
|
+
return declaration.exported && ts11.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts11.NodeFlags.Const) !== 0;
|
|
15579
|
+
}
|
|
15580
|
+
function isExported(node) {
|
|
15581
|
+
return !!ts11.getCombinedModifierFlags(node) && !!(ts11.getCombinedModifierFlags(node) & ts11.ModifierFlags.Export);
|
|
15582
|
+
}
|
|
15583
|
+
function isFunctionLikeInitializer(node) {
|
|
15584
|
+
return !!node && (ts11.isArrowFunction(node) || ts11.isFunctionExpression(node));
|
|
15585
|
+
}
|
|
15586
|
+
function getBodyFingerprint(sourceFile2, node) {
|
|
15587
|
+
if (!node)
|
|
15588
|
+
return;
|
|
15589
|
+
const normalizedBody = node.getText(sourceFile2).replace(/\s+/g, " ").trim();
|
|
15590
|
+
if (normalizedBody.length < 80)
|
|
15591
|
+
return;
|
|
15592
|
+
return createHash("sha256").update(normalizedBody).digest("hex");
|
|
15593
|
+
}
|
|
15594
|
+
function shouldSkipPath(relativePath, isDirectory, ignoreFilter) {
|
|
15595
|
+
const ignorePath = isDirectory ? `${relativePath}/` : relativePath;
|
|
15596
|
+
return relativePath === ".git" || relativePath.includes("/.git/") || relativePath.includes("/node_modules/") || ignoreFilter.ignores(relativePath) || ignoreFilter.ignores(ignorePath);
|
|
15597
|
+
}
|
|
15598
|
+
async function isDirectory(absolutePath) {
|
|
15599
|
+
try {
|
|
15600
|
+
return (await stat4(absolutePath)).isDirectory();
|
|
15601
|
+
} catch {
|
|
15602
|
+
return false;
|
|
15603
|
+
}
|
|
15604
|
+
}
|
|
15605
|
+
function getScriptKind(file) {
|
|
15606
|
+
return file.endsWith(".tsx") ? ts11.ScriptKind.TSX : ts11.ScriptKind.TS;
|
|
15607
|
+
}
|
|
15608
|
+
function getLine(sourceFile2, node) {
|
|
15609
|
+
return sourceFile2.getLineAndCharacterOfPosition(node.getStart(sourceFile2)).line + 1;
|
|
15610
|
+
}
|
|
15611
|
+
function getRecommendedLineLimit(file) {
|
|
15612
|
+
if (file.endsWith(".service.ts"))
|
|
15613
|
+
return 500;
|
|
15614
|
+
if (file.endsWith(".Template.tsx") || file.endsWith(".Zone.tsx"))
|
|
15615
|
+
return 800;
|
|
15616
|
+
if (file.endsWith(".Util.tsx"))
|
|
15617
|
+
return 1000;
|
|
15618
|
+
return null;
|
|
15619
|
+
}
|
|
15620
|
+
function findPatternLines(content, pattern) {
|
|
15621
|
+
return content.split(/\r?\n/).flatMap((line, index) => pattern.test(line) ? [index + 1] : []);
|
|
15622
|
+
}
|
|
15623
|
+
function toPascalCase(value) {
|
|
15624
|
+
return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
|
|
15625
|
+
}
|
|
15626
|
+
function toPosix(value) {
|
|
15627
|
+
return value.split(path43.sep).join("/");
|
|
15628
|
+
}
|
|
15629
|
+
function groupBy(items, getKey) {
|
|
15630
|
+
const grouped = new Map;
|
|
15631
|
+
for (const item of items) {
|
|
15632
|
+
const key = getKey(item);
|
|
15633
|
+
grouped.set(key, [...grouped.get(key) ?? [], item]);
|
|
15634
|
+
}
|
|
15635
|
+
return grouped;
|
|
15636
|
+
}
|
|
15637
|
+
function compareWarnings(a, b) {
|
|
15638
|
+
return a.scope.localeCompare(b.scope) || (a.file ?? "").localeCompare(b.file ?? "") || (a.line ?? 0) - (b.line ?? 0) || a.rule.localeCompare(b.rule);
|
|
15639
|
+
}
|
|
14911
15640
|
// pkgs/@akanjs/devkit/selectModel.ts
|
|
14912
15641
|
import { select as select5 } from "@inquirer/prompts";
|
|
14913
15642
|
// pkgs/@akanjs/devkit/streamAi.ts
|
|
@@ -15025,10 +15754,10 @@ class IncrementalBuilder {
|
|
|
15025
15754
|
}
|
|
15026
15755
|
}
|
|
15027
15756
|
batchTouchesPagesTree(appDir, batch) {
|
|
15028
|
-
const absAppDir =
|
|
15757
|
+
const absAppDir = path44.resolve(appDir);
|
|
15029
15758
|
for (const f of batch.files) {
|
|
15030
|
-
const abs =
|
|
15031
|
-
if (!abs.startsWith(`${absAppDir}${
|
|
15759
|
+
const abs = path44.resolve(f);
|
|
15760
|
+
if (!abs.startsWith(`${absAppDir}${path44.sep}`) && abs !== absAppDir)
|
|
15032
15761
|
continue;
|
|
15033
15762
|
if (/\.(tsx|ts|jsx|js)$/.test(abs))
|
|
15034
15763
|
return true;
|
|
@@ -15036,15 +15765,15 @@ class IncrementalBuilder {
|
|
|
15036
15765
|
return false;
|
|
15037
15766
|
}
|
|
15038
15767
|
async batchMayChangePageKeys(appDir, batch) {
|
|
15039
|
-
const absAppDir =
|
|
15040
|
-
const pageKeys = new Set((await this.#app.getPageKeys()).map((key) =>
|
|
15768
|
+
const absAppDir = path44.resolve(appDir);
|
|
15769
|
+
const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path44.normalize(key)));
|
|
15041
15770
|
for (const f of batch.files) {
|
|
15042
|
-
const abs =
|
|
15043
|
-
if (!abs.startsWith(`${absAppDir}${
|
|
15771
|
+
const abs = path44.resolve(f);
|
|
15772
|
+
if (!abs.startsWith(`${absAppDir}${path44.sep}`) && abs !== absAppDir)
|
|
15044
15773
|
continue;
|
|
15045
15774
|
if (!/\.(tsx|ts|jsx|js)$/.test(abs))
|
|
15046
15775
|
continue;
|
|
15047
|
-
const rel =
|
|
15776
|
+
const rel = path44.normalize(path44.relative(absAppDir, abs));
|
|
15048
15777
|
if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
|
|
15049
15778
|
return true;
|
|
15050
15779
|
}
|
|
@@ -15072,7 +15801,7 @@ class IncrementalBuilder {
|
|
|
15072
15801
|
${cssText}`).toString(36);
|
|
15073
15802
|
const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
|
|
15074
15803
|
const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
|
|
15075
|
-
await Bun.write(
|
|
15804
|
+
await Bun.write(path44.join(artifactDir, cssRelPath), cssText);
|
|
15076
15805
|
cssAssetEntries.push([basePath2, { cssUrl, cssRelPath }]);
|
|
15077
15806
|
cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
|
|
15078
15807
|
})()
|
|
@@ -15140,10 +15869,10 @@ ${cssText}`).toString(36);
|
|
|
15140
15869
|
if (changedFiles.length === 0)
|
|
15141
15870
|
return false;
|
|
15142
15871
|
return changedFiles.some((file) => {
|
|
15143
|
-
const normalized =
|
|
15872
|
+
const normalized = path44.resolve(file);
|
|
15144
15873
|
if (/\.(woff2?|ttf|otf)$/i.test(normalized))
|
|
15145
15874
|
return true;
|
|
15146
|
-
return this.#optimizedFonts.files.some((fontFile) =>
|
|
15875
|
+
return this.#optimizedFonts.files.some((fontFile) => path44.resolve(fontFile) === normalized);
|
|
15147
15876
|
});
|
|
15148
15877
|
}
|
|
15149
15878
|
async installWatcher() {
|