@akanjs/devkit 3.0.0-beta.19 → 3.0.0-beta.20
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/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +1 -4
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +1 -3
- package/akanApp/akanApp.host.ts +64 -250
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +30 -39
- package/akanConfig/akanConfig.ts +40 -136
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.ts +25 -59
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +3 -19
- package/artifact/implicitRootLayout.ts +34 -91
- package/artifact/routeSeedIndex.ts +11 -44
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +4 -14
- package/capacitorApp.ts +86 -235
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +33 -57
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +3 -2
- package/cloud/globalConfig.ts +64 -31
- package/codeAgent/agent/AkanCodeServices.ts +15 -57
- package/codeAgent/agent/AkanContextFiles.ts +5 -28
- package/codeAgent/agent/CodeAgent.ts +33 -108
- package/codeAgent/agent/CodeAgentAsks.ts +7 -30
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
- package/codeAgent/agent/CodeAgentGate.ts +8 -34
- package/codeAgent/agent/CodeAgentProxy.ts +4 -13
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
- package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
- package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
- package/codeAgent/agent/CodeAgentUi.ts +2 -7
- package/codeAgent/agent/CodeMailbox.ts +7 -34
- package/codeAgent/agent/CodeSessionFork.ts +4 -19
- package/codeAgent/agent/CodeSessionIndex.ts +5 -31
- package/codeAgent/agent/SubagentPool.ts +48 -58
- package/codeAgent/agent/akanCodeModel.ts +5 -39
- package/codeAgent/agent/akanCodePaths.ts +4 -24
- package/codeAgent/agent/akanContextFiles.test.ts +0 -2
- package/codeAgent/agent/akanSystemPrompt.ts +1 -9
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +5 -28
- package/codeAgent/agent/codeMailbox.test.ts +0 -4
- package/codeAgent/agent/codeSessionFork.test.ts +0 -2
- package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
- package/codeAgent/agent/consoleToStderr.ts +2 -12
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +3 -18
- package/codeAgent/feedback/PreviewChrome.ts +5 -29
- package/codeAgent/feedback/PreviewFeedback.ts +2 -9
- package/codeAgent/feedback/PreviewView.ts +3 -15
- package/codeAgent/feedback/TurnFeedback.ts +2 -17
- package/codeAgent/feedback/VerifyFeedback.ts +1 -7
- package/codeAgent/feedback/previewChrome.test.ts +1 -1
- package/codeAgent/index.ts +2 -10
- package/codeAgent/skills/akan-code/SKILL.md +12 -4
- package/codeAgent/tools/AkanCli.ts +4 -16
- package/codeAgent/tools/AkanCodePlugins.ts +21 -43
- package/codeAgent/tools/AkanEditScope.ts +2 -12
- package/codeAgent/tools/AkanEnvKeys.ts +3 -11
- package/codeAgent/tools/AkanToolPack.ts +6 -28
- package/codeAgent/tools/AkanVerifier.ts +1 -8
- package/codeAgent/tools/AskToolPack.ts +3 -15
- package/codeAgent/tools/MailToolPack.ts +0 -9
- package/codeAgent/tools/McpClient.ts +3 -24
- package/codeAgent/tools/McpOAuth.ts +6 -41
- package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
- package/codeAgent/tools/McpServerConfig.ts +7 -26
- package/codeAgent/tools/McpSignIn.ts +46 -49
- package/codeAgent/tools/McpTokenStore.ts +6 -17
- package/codeAgent/tools/McpToolPack.ts +107 -35
- package/codeAgent/tools/SessionToolPack.ts +1 -11
- package/codeAgent/tools/WebToolPack.ts +1 -7
- package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
- package/codeAgent/tools/askToolPack.test.ts +0 -3
- package/codeAgent/tools/mcpOAuth.test.ts +315 -27
- package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +44 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +1 -9
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +66 -144
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +27 -78
- package/devkitUtils.test.ts +47 -19
- package/executors.test.ts +114 -60
- package/executors.ts +165 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +1 -1
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +14 -38
- package/frontendBuild/autoImportSync.ts +15 -67
- package/frontendBuild/buildRouteClient.test.ts +10 -36
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +5 -18
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +5 -13
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +27 -52
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +7 -31
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +7 -47
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/devWatchBatch.test.ts +14 -18
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
- package/index.ts +3 -15
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +9 -29
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/gritRules.test.ts +6 -23
- package/linter.test.ts +1 -2
- package/linter.ts +22 -115
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +2 -2
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +3 -14
- package/platformTest/IdleWatchedProcess.test.ts +5 -13
- package/platformTest/IdleWatchedProcess.ts +1 -5
- package/platformTest/LinuxDockerTarget.ts +20 -35
- package/platformTest/PlatformTestRun.test.ts +5 -9
- package/platformTest/PlatformTestTarget.ts +24 -2
- package/platformTest/TestSnapshot.test.ts +6 -14
- package/platformTest/TestSnapshot.ts +1 -5
- package/platformTest/WindowsSshTarget.ts +6 -22
- package/prompter.ts +3 -6
- package/qualityScanner.test.ts +6 -24
- package/qualityScanner.ts +196 -284
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.ts +7 -30
- package/scanInfo.ts +82 -214
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/subspace.test.ts +6 -25
- package/subspace.ts +31 -109
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +49 -34
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +85 -146
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/source.ts +33 -56
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
package/typeChecker.ts
CHANGED
|
@@ -38,31 +38,15 @@ export class TypeChecker {
|
|
|
38
38
|
}
|
|
39
39
|
this.config = parsedConfig;
|
|
40
40
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Find tsconfig.json by walking up the directory tree
|
|
43
|
-
*/
|
|
44
41
|
#findConfigFile(searchPath: string): string | undefined {
|
|
45
42
|
return ts.findConfigFile(searchPath, (fileName) => ts.sys.fileExists(fileName), "tsconfig.json");
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
* Type-check a single TypeScript file
|
|
50
|
-
* @param filePath - Path to the TypeScript file to check
|
|
51
|
-
* @returns Array of diagnostic messages
|
|
52
|
-
*/
|
|
53
|
-
check(filePath: string): {
|
|
54
|
-
diagnostics: ts.Diagnostic[];
|
|
55
|
-
errors: ts.Diagnostic[];
|
|
56
|
-
warnings: ts.Diagnostic[];
|
|
57
|
-
fileDiagnostics: ts.Diagnostic[];
|
|
58
|
-
fileErrors: ts.Diagnostic[];
|
|
59
|
-
fileWarnings: ts.Diagnostic[];
|
|
60
|
-
} {
|
|
45
|
+
check(filePath: string) {
|
|
61
46
|
const program = ts.createProgram([filePath], this.config.options);
|
|
62
47
|
const diagnostics = [
|
|
63
48
|
...program.getSemanticDiagnostics(),
|
|
64
49
|
...program.getSyntacticDiagnostics(),
|
|
65
|
-
// Only check declaration diagnostics when declaration emit is enabled
|
|
66
50
|
...(this.config.options.declaration ? program.getDeclarationDiagnostics() : []),
|
|
67
51
|
];
|
|
68
52
|
const errors = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
@@ -73,11 +57,6 @@ export class TypeChecker {
|
|
|
73
57
|
return { diagnostics, errors, warnings, fileDiagnostics, fileErrors, fileWarnings };
|
|
74
58
|
}
|
|
75
59
|
|
|
76
|
-
/**
|
|
77
|
-
* Format diagnostics for console output
|
|
78
|
-
* @param diagnostics - Array of TypeScript diagnostics
|
|
79
|
-
* @returns Formatted string
|
|
80
|
-
*/
|
|
81
60
|
formatDiagnostics(diagnostics: ts.Diagnostic[]): string {
|
|
82
61
|
if (diagnostics.length === 0) return chalk.bold("✅ No type errors found");
|
|
83
62
|
|
|
@@ -86,48 +65,24 @@ export class TypeChecker {
|
|
|
86
65
|
let warningCount = 0;
|
|
87
66
|
let suggestionCount = 0;
|
|
88
67
|
|
|
89
|
-
// Group diagnostics by file
|
|
90
68
|
const diagnosticsByFile = new Map<string, ts.Diagnostic[]>();
|
|
91
69
|
diagnostics.forEach((diagnostic) => {
|
|
92
70
|
if (diagnostic.category === ts.DiagnosticCategory.Error) errorCount++;
|
|
93
71
|
else if (diagnostic.category === ts.DiagnosticCategory.Warning) warningCount++;
|
|
94
72
|
else if (diagnostic.category === ts.DiagnosticCategory.Suggestion) suggestionCount++;
|
|
95
|
-
|
|
96
|
-
if (diagnostic.file) {
|
|
97
|
-
const fileName = diagnostic.file.fileName;
|
|
98
|
-
if (!diagnosticsByFile.has(fileName)) diagnosticsByFile.set(fileName, []);
|
|
99
|
-
const fileDiagnostics = diagnosticsByFile.get(fileName);
|
|
100
|
-
if (fileDiagnostics) fileDiagnostics.push(diagnostic);
|
|
101
|
-
} else {
|
|
102
|
-
if (!diagnosticsByFile.has("")) diagnosticsByFile.set("", []);
|
|
103
|
-
const fileDiagnostics = diagnosticsByFile.get("");
|
|
104
|
-
if (fileDiagnostics) fileDiagnostics.push(diagnostic);
|
|
105
|
-
}
|
|
73
|
+
diagnosticsByFile.getOrInsert(diagnostic.file?.fileName ?? "", []).push(diagnostic);
|
|
106
74
|
});
|
|
107
75
|
|
|
108
|
-
// Format diagnostics by file
|
|
109
76
|
diagnosticsByFile.forEach((fileDiagnostics, fileName) => {
|
|
110
77
|
if (fileName) output.push(`\n${chalk.cyan(fileName)}`);
|
|
111
78
|
|
|
112
79
|
fileDiagnostics.forEach((diagnostic) => {
|
|
113
|
-
const categoryText =
|
|
114
|
-
diagnostic.category === ts.DiagnosticCategory.Error
|
|
115
|
-
? "error"
|
|
116
|
-
: diagnostic.category === ts.DiagnosticCategory.Warning
|
|
117
|
-
? "warning"
|
|
118
|
-
: "suggestion";
|
|
119
|
-
const categoryColor =
|
|
120
|
-
diagnostic.category === ts.DiagnosticCategory.Error
|
|
121
|
-
? chalk.red
|
|
122
|
-
: diagnostic.category === ts.DiagnosticCategory.Warning
|
|
123
|
-
? chalk.yellow
|
|
124
|
-
: chalk.blue;
|
|
125
|
-
const icon =
|
|
80
|
+
const [categoryText, categoryColor, icon] =
|
|
126
81
|
diagnostic.category === ts.DiagnosticCategory.Error
|
|
127
|
-
? "❌"
|
|
82
|
+
? (["error", chalk.red, "❌"] as const)
|
|
128
83
|
: diagnostic.category === ts.DiagnosticCategory.Warning
|
|
129
|
-
? "⚠️"
|
|
130
|
-
: "💡";
|
|
84
|
+
? (["warning", chalk.yellow, "⚠️"] as const)
|
|
85
|
+
: (["suggestion", chalk.blue, "💡"] as const);
|
|
131
86
|
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
132
87
|
const tsCode = chalk.dim(`(TS${diagnostic.code})`);
|
|
133
88
|
|
|
@@ -137,7 +92,6 @@ export class TypeChecker {
|
|
|
137
92
|
output.push(`\n ${icon} ${categoryColor(categoryText)}: ${message} ${tsCode}`);
|
|
138
93
|
output.push(` ${chalk.gray("at")} ${fileName}:${chalk.bold(`${line + 1}:${character + 1}`)}`);
|
|
139
94
|
|
|
140
|
-
// Show source line with underline
|
|
141
95
|
const sourceLines = diagnostic.file.text.split("\n");
|
|
142
96
|
if (line < sourceLines.length) {
|
|
143
97
|
const sourceLine = sourceLines[line];
|
|
@@ -145,7 +99,6 @@ export class TypeChecker {
|
|
|
145
99
|
|
|
146
100
|
output.push(`\n${chalk.dim(`${lineNumber} |`)} ${sourceLine}`);
|
|
147
101
|
|
|
148
|
-
// Create underline with squiggly for TypeScript
|
|
149
102
|
const underlinePrefix = " ".repeat(character);
|
|
150
103
|
const length = diagnostic.length ?? 1;
|
|
151
104
|
const underline = "~".repeat(Math.max(1, length));
|
|
@@ -166,11 +119,6 @@ export class TypeChecker {
|
|
|
166
119
|
return `\n${summary.join(", ")} found${output.join("\n")}`;
|
|
167
120
|
}
|
|
168
121
|
|
|
169
|
-
/**
|
|
170
|
-
* Get detailed diagnostic information with code snippet
|
|
171
|
-
* @param filePath - Path to the TypeScript file to check
|
|
172
|
-
* @returns Object containing diagnostics and detailed information
|
|
173
|
-
*/
|
|
174
122
|
getDetailedDiagnostics(filePath: string): {
|
|
175
123
|
diagnostics: ts.Diagnostic[];
|
|
176
124
|
details: { line: number; column: number; message: string; code: number; codeSnippet?: string }[];
|
|
@@ -199,11 +147,7 @@ export class TypeChecker {
|
|
|
199
147
|
return { diagnostics, details };
|
|
200
148
|
}
|
|
201
149
|
|
|
202
|
-
/**
|
|
203
|
-
* Check if a file has type errors
|
|
204
|
-
* @param filePath - Path to the TypeScript file to check
|
|
205
|
-
* @returns true if there are no type errors, false otherwise
|
|
206
|
-
*/
|
|
150
|
+
/** Also false when the check itself throws. */
|
|
207
151
|
hasNoTypeErrors(filePath: string): boolean {
|
|
208
152
|
try {
|
|
209
153
|
const { diagnostics } = this.check(filePath);
|
package/uploadRelease.ts
CHANGED
|
@@ -2,10 +2,8 @@ import { Logger, RestClient } from "akanjs/common";
|
|
|
2
2
|
|
|
3
3
|
import { Spinner } from "./spinner";
|
|
4
4
|
|
|
5
|
-
const spinning = (message: string) => {
|
|
6
|
-
|
|
7
|
-
return spinner;
|
|
8
|
-
};
|
|
5
|
+
const spinning = (message: string) => new Spinner(message, { prefix: message, enableSpin: true }).start();
|
|
6
|
+
|
|
9
7
|
export const uploadRelease = async (
|
|
10
8
|
appName: string,
|
|
11
9
|
{
|
|
@@ -27,33 +25,20 @@ export const uploadRelease = async (
|
|
|
27
25
|
const logger = new Logger("uploadRelease");
|
|
28
26
|
const basePath = local ? "http://localhost:8282/backend" : "https://cloud.akanjs.com/backend";
|
|
29
27
|
const httpClient = new RestClient(basePath);
|
|
30
|
-
const buildPath = `${workspaceRoot}/releases/builds/${appName}-release.tar.gz`;
|
|
31
|
-
const appBuildPath = `${workspaceRoot}/releases/builds/${appName}-appBuild.zip`;
|
|
32
|
-
const sourcePath = `${workspaceRoot}/releases/sources/${appName}-source.tar.gz`;
|
|
33
|
-
|
|
34
28
|
const readingFilesSpinner = spinning("Reading files...");
|
|
35
29
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
30
|
+
const uploads = [
|
|
31
|
+
[`builds/${appName}-release.tar.gz`, `${appName}-release.tar.gz`],
|
|
32
|
+
[`sources/${appName}-source.tar.gz`, `${appName}-source.tar.gz`],
|
|
33
|
+
[`builds/${appName}-appBuild.zip`, `${appName}-appBuild.zip`],
|
|
34
|
+
].map(([relativePath, name]) => ({ file: Bun.file(`${workspaceRoot}/releases/${relativePath}`), name }));
|
|
35
|
+
const metas = uploads.map(({ file }) => ({ lastModifiedAt: new Date(file.lastModified), size: file.size }));
|
|
42
36
|
readingFilesSpinner.succeed("Reading files... done");
|
|
43
37
|
|
|
44
38
|
const preparingFormSpinner = spinning("Preparing form data...");
|
|
45
39
|
const formData = new FormData();
|
|
46
|
-
formData.append("files",
|
|
47
|
-
formData.append("
|
|
48
|
-
formData.append("files", appBuildFile, `${appName}-appBuild.zip`);
|
|
49
|
-
formData.append(
|
|
50
|
-
"metas",
|
|
51
|
-
JSON.stringify([
|
|
52
|
-
{ lastModifiedAt: buildStat.mtime, size: buildStat.size },
|
|
53
|
-
{ lastModifiedAt: sourceStat.mtime, size: sourceStat.size },
|
|
54
|
-
{ lastModifiedAt: appBuildStat.mtime, size: appBuildStat.size },
|
|
55
|
-
]),
|
|
56
|
-
);
|
|
40
|
+
for (const { file, name } of uploads) formData.append("files", file, name);
|
|
41
|
+
formData.append("metas", JSON.stringify(metas));
|
|
57
42
|
formData.append("type", "release");
|
|
58
43
|
preparingFormSpinner.succeed("Preparing form data... done");
|
|
59
44
|
|
package/workflow/artifacts.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capitalize } from "akanjs/common";
|
|
1
2
|
import type { Workspace } from "../commandDecorators";
|
|
2
3
|
import type {
|
|
3
4
|
GeneratedSyncState,
|
|
@@ -146,6 +147,23 @@ export const workflowCommandsForPlan = (plan: WorkflowPlan) =>
|
|
|
146
147
|
kind: validation.kind,
|
|
147
148
|
})) satisfies WorkflowApplyCommand[];
|
|
148
149
|
|
|
150
|
+
const planInputString = (plan: WorkflowPlan, key: string) => {
|
|
151
|
+
const value = plan.inputs[key];
|
|
152
|
+
return typeof value === "string" ? value : "";
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const workflowPathsForPlan = (plan: WorkflowPlan) => {
|
|
156
|
+
const app = planInputString(plan, "app");
|
|
157
|
+
const module = planInputString(plan, "module");
|
|
158
|
+
const moduleClass = module ? capitalize(module) : "<Module>";
|
|
159
|
+
return plan.predictedChanges.map((change) =>
|
|
160
|
+
change.target
|
|
161
|
+
.replace(/^\*\//, app ? `apps/${app}/` : "")
|
|
162
|
+
.replaceAll("<module>", module || "<module>")
|
|
163
|
+
.replaceAll("<Module>", moduleClass),
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
149
167
|
export const workflowRunsDir = ".akan/workflows/runs";
|
|
150
168
|
|
|
151
169
|
export const createWorkflowRunId = (prefix = "run") =>
|
|
@@ -9,11 +9,6 @@ import type {
|
|
|
9
9
|
WorkflowStepResult,
|
|
10
10
|
} from "./types";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* `WorkflowExecutor` takes its registry by constructor injection and its plan as data, so `apply` runs with
|
|
14
|
-
* no filesystem and no workspace — the `workspace` argument is what gates every post-apply check. These
|
|
15
|
-
* cover the ordering rules that `cli/workflow/workflow.test.ts` can only reach through a real scaffold.
|
|
16
|
-
*/
|
|
17
12
|
const step = (id: string, tool = `tool:${id}`): WorkflowStep => ({
|
|
18
13
|
id,
|
|
19
14
|
tool,
|
|
@@ -21,8 +16,7 @@ const step = (id: string, tool = `tool:${id}`): WorkflowStep => ({
|
|
|
21
16
|
description: id,
|
|
22
17
|
});
|
|
23
18
|
|
|
24
|
-
//
|
|
25
|
-
// step runs, so a partial literal fails at runtime instead of at the type level.
|
|
19
|
+
// Not a cast partial: `apply` reads `validation` before the first step, so a missing field fails only at runtime.
|
|
26
20
|
const planOf = (steps: WorkflowStep[], diagnostics: WorkflowDiagnostic[] = []): WorkflowPlan => ({
|
|
27
21
|
schemaVersion: 1,
|
|
28
22
|
workflow: "add-field",
|
|
@@ -37,10 +31,6 @@ const planOf = (steps: WorkflowStep[], diagnostics: WorkflowDiagnostic[] = []):
|
|
|
37
31
|
requiresApproval: true,
|
|
38
32
|
});
|
|
39
33
|
|
|
40
|
-
/**
|
|
41
|
-
* A registry that records **which key** each call resolved through, not just that a step ran — the
|
|
42
|
-
* resolution order is the thing under test, and every candidate key would otherwise record the same step id.
|
|
43
|
-
*/
|
|
44
34
|
const changed = (path: string): PrimitiveChangedFile => ({ path, action: "modify", reason: "test" });
|
|
45
35
|
|
|
46
36
|
const recordingRegistry = (results: Record<string, WorkflowStepResult | undefined> = {}) => {
|
|
@@ -79,7 +69,6 @@ describe("WorkflowExecutor.apply", () => {
|
|
|
79
69
|
|
|
80
70
|
test("resolves a runner by workflow-scoped key first, then tool, then bare step id", async () => {
|
|
81
71
|
const { calls, registry, on } = recordingRegistry();
|
|
82
|
-
// All three keys are registered for the same step, so only the winner records a call.
|
|
83
72
|
on(workflowStepKey("add-field", "scoped"));
|
|
84
73
|
on("tool:scoped");
|
|
85
74
|
on("scoped");
|
package/workflow/executor.ts
CHANGED
|
@@ -48,16 +48,13 @@ const workflowStringListInput = (value: WorkflowInputValue | undefined) =>
|
|
|
48
48
|
const workflowStringArrayInput = (value: WorkflowInputValue | undefined) => (Array.isArray(value) ? value : null);
|
|
49
49
|
const workflowBooleanInput = (value: WorkflowInputValue | undefined) => (typeof value === "boolean" ? value : null);
|
|
50
50
|
|
|
51
|
-
const postApplyDiagnostic = (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const postApplyWarning = (code: string, message: string, target: string): WorkflowDiagnostic => ({
|
|
60
|
-
severity: "warning",
|
|
51
|
+
const postApplyDiagnostic = (
|
|
52
|
+
code: string,
|
|
53
|
+
message: string,
|
|
54
|
+
target: string,
|
|
55
|
+
severity: WorkflowDiagnostic["severity"] = "error",
|
|
56
|
+
): WorkflowDiagnostic => ({
|
|
57
|
+
severity,
|
|
61
58
|
code,
|
|
62
59
|
message,
|
|
63
60
|
failureScope: "source-change",
|
|
@@ -114,27 +111,23 @@ const checkTypeScriptSyntax = async (workspace: Workspace, filePath: string) =>
|
|
|
114
111
|
|
|
115
112
|
const checkChangedFile = async (workspace: Workspace, file: PrimitiveChangedFile): Promise<WorkflowStepResult> => {
|
|
116
113
|
if (file.action === "remove") return { postApplyChecks: [] };
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return { diagnostics, postApplyChecks: checks };
|
|
124
|
-
}
|
|
125
|
-
const syntaxIssue = await checkTypeScriptSyntax(workspace, file.path);
|
|
126
|
-
if (syntaxIssue) {
|
|
127
|
-
diagnostics.push(postApplyDiagnostic(syntaxIssue.code, syntaxIssue.message, file.path));
|
|
128
|
-
checks.push({ ...syntaxIssue, target: file.path, status: "failed" });
|
|
129
|
-
return { diagnostics, postApplyChecks: checks };
|
|
114
|
+
const issue = (await checkPathCasing(workspace, file.path)) ?? (await checkTypeScriptSyntax(workspace, file.path));
|
|
115
|
+
if (issue) {
|
|
116
|
+
return {
|
|
117
|
+
diagnostics: [postApplyDiagnostic(issue.code, issue.message, file.path)],
|
|
118
|
+
postApplyChecks: [{ ...issue, target: file.path, status: "failed" }],
|
|
119
|
+
};
|
|
130
120
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
121
|
+
return {
|
|
122
|
+
postApplyChecks: [
|
|
123
|
+
{
|
|
124
|
+
code: "workflow-post-apply-file-valid",
|
|
125
|
+
target: file.path,
|
|
126
|
+
status: "passed",
|
|
127
|
+
message: "Changed file exists with exact casing and parses as source when applicable.",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
};
|
|
138
131
|
};
|
|
139
132
|
|
|
140
133
|
const checkRecommendationPath = async (
|
|
@@ -199,18 +192,6 @@ const workflowModuleContext = async (workspace: Workspace, plan: WorkflowPlan):
|
|
|
199
192
|
};
|
|
200
193
|
};
|
|
201
194
|
|
|
202
|
-
const structureCheck = (
|
|
203
|
-
code: string,
|
|
204
|
-
target: string,
|
|
205
|
-
status: WorkflowPostApplyCheck["status"],
|
|
206
|
-
message: string,
|
|
207
|
-
): WorkflowPostApplyCheck => ({
|
|
208
|
-
code,
|
|
209
|
-
target,
|
|
210
|
-
status,
|
|
211
|
-
message,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
195
|
const checkAddFieldStructure = async (workspace: Workspace, plan: WorkflowPlan): Promise<WorkflowStepResult> => {
|
|
215
196
|
if (plan.workflow !== "add-field" && plan.workflow !== "add-enum-field") return {};
|
|
216
197
|
const app = workflowStringInput(plan.inputs.app);
|
|
@@ -260,16 +241,14 @@ const checkAddFieldStructure = async (workspace: Workspace, plan: WorkflowPlan):
|
|
|
260
241
|
.map((diagnostic) => diagnostic.message),
|
|
261
242
|
].filter((failure): failure is string => failure !== null);
|
|
262
243
|
const constantValid = constantFailures.length === 0;
|
|
263
|
-
postApplyChecks.push(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
),
|
|
272
|
-
);
|
|
244
|
+
postApplyChecks.push({
|
|
245
|
+
code: constantValid ? "workflow-post-apply-constant-shape-valid" : "workflow-post-apply-structure-invalid",
|
|
246
|
+
target: constantPath,
|
|
247
|
+
status: constantValid ? "passed" : "failed",
|
|
248
|
+
message: constantValid
|
|
249
|
+
? `${inputClassName} keeps via structure, builder usage, imports, and requested field presence.`
|
|
250
|
+
: constantFailures.join(" "),
|
|
251
|
+
});
|
|
273
252
|
if (!constantValid) {
|
|
274
253
|
diagnostics.push(
|
|
275
254
|
postApplyDiagnostic("workflow-post-apply-structure-invalid", constantFailures.join(" "), constantPath),
|
|
@@ -294,16 +273,14 @@ const checkAddFieldStructure = async (workspace: Workspace, plan: WorkflowPlan):
|
|
|
294
273
|
.map((diagnostic) => diagnostic.message),
|
|
295
274
|
].filter((failure): failure is string => failure !== null);
|
|
296
275
|
const dictionaryValid = dictionaryFailures.length === 0;
|
|
297
|
-
postApplyChecks.push(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
),
|
|
306
|
-
);
|
|
276
|
+
postApplyChecks.push({
|
|
277
|
+
code: dictionaryValid ? "workflow-post-apply-dictionary-shape-valid" : "workflow-post-apply-structure-invalid",
|
|
278
|
+
target: dictionaryPath,
|
|
279
|
+
status: dictionaryValid ? "passed" : "failed",
|
|
280
|
+
message: dictionaryValid
|
|
281
|
+
? `.model<${moduleClassName}> keeps the requested field inside the model object and preserves dictionary chain order.`
|
|
282
|
+
: dictionaryFailures.join(" "),
|
|
283
|
+
});
|
|
307
284
|
if (!dictionaryValid) {
|
|
308
285
|
diagnostics.push(
|
|
309
286
|
postApplyDiagnostic("workflow-post-apply-structure-invalid", dictionaryFailures.join(" "), dictionaryPath),
|
|
@@ -313,22 +290,21 @@ const checkAddFieldStructure = async (workspace: Workspace, plan: WorkflowPlan):
|
|
|
313
290
|
const constantOrderValid = fieldOrderValid(constantNames, fieldName);
|
|
314
291
|
const dictionaryOrderValid = fieldOrderValid(dictionaryStructure.fields, fieldName);
|
|
315
292
|
const orderValid = constantOrderValid && dictionaryOrderValid;
|
|
316
|
-
postApplyChecks.push(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
),
|
|
325
|
-
);
|
|
293
|
+
postApplyChecks.push({
|
|
294
|
+
code: "workflow-post-apply-field-order-valid",
|
|
295
|
+
target: `${constantPath}, ${dictionaryPath}`,
|
|
296
|
+
status: orderValid ? "passed" : "failed",
|
|
297
|
+
message: orderValid
|
|
298
|
+
? `Field "${fieldName}" follows the shared priority ordering policy.`
|
|
299
|
+
: `Field "${fieldName}" is present but does not match the shared priority ordering policy.`,
|
|
300
|
+
});
|
|
326
301
|
if (!orderValid) {
|
|
327
302
|
diagnostics.push(
|
|
328
|
-
|
|
303
|
+
postApplyDiagnostic(
|
|
329
304
|
"workflow-post-apply-field-order-mismatch",
|
|
330
305
|
`Field "${fieldName}" is present but does not match the shared priority ordering policy.`,
|
|
331
306
|
`${constantPath}, ${dictionaryPath}`,
|
|
307
|
+
"warning",
|
|
332
308
|
),
|
|
333
309
|
);
|
|
334
310
|
}
|
|
@@ -365,6 +341,14 @@ const unsupportedInput = (input: string, message: string): WorkflowDiagnostic =>
|
|
|
365
341
|
message,
|
|
366
342
|
});
|
|
367
343
|
|
|
344
|
+
const surfaceReview = (
|
|
345
|
+
plan: WorkflowPlan,
|
|
346
|
+
{ code, target, action, message, reason }: Record<"code" | "target" | "action" | "message" | "reason", string>,
|
|
347
|
+
): WorkflowStepResult => ({
|
|
348
|
+
recommendations: [{ code, kind: "manual-action", target, action, confidence: "medium", message }],
|
|
349
|
+
nextActions: [{ command: `akan workflow explain ${plan.workflow}`, reason }],
|
|
350
|
+
});
|
|
351
|
+
|
|
368
352
|
const addFieldUiSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult => {
|
|
369
353
|
const app = workflowStringInput(plan.inputs.app);
|
|
370
354
|
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
@@ -374,75 +358,41 @@ const addFieldUiSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult =>
|
|
|
374
358
|
const surfaces = workflowStringArrayInput(plan.inputs.surfaces);
|
|
375
359
|
const templateRequested = surfaces?.includes("template") ?? false;
|
|
376
360
|
const moduleClassName = moduleComponentName(module);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
: `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.`,
|
|
387
|
-
confidence: "medium",
|
|
388
|
-
message: `Review user-visible UI for ${module}.${field}; recommended component is ${policy.component}.`,
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
nextActions: [
|
|
392
|
-
{
|
|
393
|
-
command: `akan workflow explain ${plan.workflow}`,
|
|
394
|
-
reason: "Review UI surface guidance before manually editing ambiguous UI files.",
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
};
|
|
361
|
+
return surfaceReview(plan, {
|
|
362
|
+
code: "add-field-ui-surface-review",
|
|
363
|
+
target: `${app ? `apps/${app}` : "*"}/${moduleSourcePaths(module).template}`,
|
|
364
|
+
action: templateRequested
|
|
365
|
+
? `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.`
|
|
366
|
+
: `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.`,
|
|
367
|
+
message: `Review user-visible UI for ${module}.${field}; recommended component is ${policy.component}.`,
|
|
368
|
+
reason: "Review UI surface guidance before manually editing ambiguous UI files.",
|
|
369
|
+
});
|
|
398
370
|
};
|
|
399
371
|
|
|
400
372
|
const addMutationActionSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult => {
|
|
401
373
|
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
402
374
|
const mutation = workflowStringInput(plan.inputs.mutation) ?? "<mutation>";
|
|
403
375
|
const moduleClassName = moduleComponentName(module);
|
|
404
|
-
return {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
confidence: "medium",
|
|
412
|
-
message: `Workflow apply does not write store or UI code for ${module}.${mutation}; review whether a screen should call it.`,
|
|
413
|
-
},
|
|
414
|
-
],
|
|
415
|
-
nextActions: [
|
|
416
|
-
{
|
|
417
|
-
command: `akan workflow explain ${plan.workflow}`,
|
|
418
|
-
reason: "Review action surface guidance before manually editing store or UI files.",
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
};
|
|
376
|
+
return surfaceReview(plan, {
|
|
377
|
+
code: "add-mutation-action-surface-review",
|
|
378
|
+
target: `*/${moduleSourcePaths(module).store}`,
|
|
379
|
+
action: `Name the endpoint's guards and add its dictionary .endpoint() entry with a .desc(). After sync, fetch.${mutation} exists; write a store action only for a toast, an optimistic update, or a multi-field write, and put the control that calls it in ${moduleClassName}.Util.tsx.`,
|
|
380
|
+
message: `Workflow apply does not write store or UI code for ${module}.${mutation}; review whether a screen should call it.`,
|
|
381
|
+
reason: "Review action surface guidance before manually editing store or UI files.",
|
|
382
|
+
});
|
|
422
383
|
};
|
|
423
384
|
|
|
424
385
|
const addSliceViewSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult => {
|
|
425
386
|
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
426
387
|
const slice = workflowStringInput(plan.inputs.slice) ?? "<slice>";
|
|
427
388
|
const moduleClassName = moduleComponentName(module);
|
|
428
|
-
return {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
confidence: "medium",
|
|
436
|
-
message: `Workflow apply does not write page or Zone code for ${module}.${slice}; review where the list should render.`,
|
|
437
|
-
},
|
|
438
|
-
],
|
|
439
|
-
nextActions: [
|
|
440
|
-
{
|
|
441
|
-
command: `akan workflow explain ${plan.workflow}`,
|
|
442
|
-
reason: "Review view surface guidance before manually editing Zone or page files.",
|
|
443
|
-
},
|
|
444
|
-
],
|
|
445
|
-
};
|
|
389
|
+
return surfaceReview(plan, {
|
|
390
|
+
code: "add-slice-view-surface-review",
|
|
391
|
+
target: `*/${moduleSourcePaths(module).zone}`,
|
|
392
|
+
action: `Fill in the service query stub and add the dictionary .slice() entry. After sync, load fetch.init${moduleClassName}${capitalize(slice)}() in the page and pass the result to a ${moduleClassName}.Zone as an init prop.`,
|
|
393
|
+
message: `Workflow apply does not write page or Zone code for ${module}.${slice}; review where the list should render.`,
|
|
394
|
+
reason: "Review view surface guidance before manually editing Zone or page files.",
|
|
395
|
+
});
|
|
446
396
|
};
|
|
447
397
|
|
|
448
398
|
export const createWorkflowStepRegistry = ({
|
|
@@ -581,9 +531,8 @@ export class WorkflowExecutor {
|
|
|
581
531
|
const postApplyChecks: WorkflowPostApplyCheck[] = [];
|
|
582
532
|
const recommendations = [...plan.recommendations];
|
|
583
533
|
const nextActions: PrimitiveNextAction[] = [];
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
return createWorkflowApplyReport({
|
|
534
|
+
const report = () =>
|
|
535
|
+
createWorkflowApplyReport({
|
|
587
536
|
workflow: plan.workflow,
|
|
588
537
|
mode: "apply",
|
|
589
538
|
changedFiles,
|
|
@@ -595,7 +544,8 @@ export class WorkflowExecutor {
|
|
|
595
544
|
nextActions,
|
|
596
545
|
plan,
|
|
597
546
|
});
|
|
598
|
-
|
|
547
|
+
|
|
548
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) return report();
|
|
599
549
|
|
|
600
550
|
recommendedValidationCommands.push(...workflowCommandsForPlan(plan));
|
|
601
551
|
nextActions.push(...workflowCommandsForPlan(plan));
|
|
@@ -646,17 +596,6 @@ export class WorkflowExecutor {
|
|
|
646
596
|
);
|
|
647
597
|
}
|
|
648
598
|
|
|
649
|
-
return
|
|
650
|
-
workflow: plan.workflow,
|
|
651
|
-
mode: "apply",
|
|
652
|
-
changedFiles,
|
|
653
|
-
generatedFiles,
|
|
654
|
-
recommendedValidationCommands,
|
|
655
|
-
diagnostics,
|
|
656
|
-
postApplyChecks,
|
|
657
|
-
recommendations,
|
|
658
|
-
nextActions,
|
|
659
|
-
plan,
|
|
660
|
-
});
|
|
599
|
+
return report();
|
|
661
600
|
}
|
|
662
601
|
}
|
package/workflow/index.ts
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
4
2
|
import path from "node:path";
|
|
5
3
|
import type { AkanModuleContext } from "../akanContext";
|
|
6
4
|
import type { Workspace } from "../commandDecorators";
|
|
5
|
+
import { tempDirs, writeText } from "../testHelpers";
|
|
7
6
|
import { buildAkanModuleContextIndex } from "./moduleIndex";
|
|
8
7
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
afterEach(async () => {
|
|
12
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
13
|
-
});
|
|
8
|
+
const tempRoot = tempDirs("akan-module-index-");
|
|
14
9
|
|
|
15
10
|
const makeWorkspace = async () => {
|
|
16
|
-
const root = await
|
|
17
|
-
tempRoots.push(root);
|
|
11
|
+
const root = await tempRoot();
|
|
18
12
|
return {
|
|
19
13
|
root,
|
|
20
14
|
workspace: { workspaceRoot: root } as Workspace,
|
|
21
15
|
};
|
|
22
16
|
};
|
|
23
17
|
|
|
24
|
-
const writeText = async (filePath: string, content: string) => {
|
|
25
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
26
|
-
await writeFile(filePath, content);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
18
|
const moduleContext = (files: string[], name = "post"): AkanModuleContext => ({
|
|
30
19
|
kind: "domain",
|
|
31
20
|
name,
|