@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/abstractDoc.test.ts
CHANGED
|
@@ -1,29 +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 { AbstractDoc } from "./abstractDoc";
|
|
6
4
|
import { AppExecutor, WorkspaceExecutor } from "./executors";
|
|
5
|
+
import { tempDirs, writeText } from "./testHelpers";
|
|
7
6
|
|
|
8
|
-
const
|
|
7
|
+
const makeTempRoot = tempDirs("akan-abstract-doc-");
|
|
9
8
|
|
|
10
9
|
const makeApp = async (files: Record<string, string>) => {
|
|
11
|
-
const root = await
|
|
12
|
-
tempRoots.push(root);
|
|
10
|
+
const root = await makeTempRoot();
|
|
13
11
|
const appName = "abstractDemo";
|
|
14
|
-
for (const [filePath, content] of Object.entries(files))
|
|
15
|
-
|
|
16
|
-
await mkdir(path.dirname(absolutePath), { recursive: true });
|
|
17
|
-
await writeFile(absolutePath, content);
|
|
18
|
-
}
|
|
12
|
+
for (const [filePath, content] of Object.entries(files))
|
|
13
|
+
await writeText(path.join(root, "apps", appName, filePath), content);
|
|
19
14
|
const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot: root, repoName: "repo" });
|
|
20
15
|
return AppExecutor.from(workspace, appName);
|
|
21
16
|
};
|
|
22
17
|
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
18
|
describe("AbstractDoc.kindOf", () => {
|
|
28
19
|
test("reads the module kind off the sys-relative path", () => {
|
|
29
20
|
expect(AbstractDoc.kindOf("lib/user/user.abstract.md")).toBe("domain");
|
package/abstractDoc.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { SysExecutor } from "./executors";
|
|
|
3
3
|
|
|
4
4
|
export type AbstractKind = "domain" | "service" | "scalar" | "other";
|
|
5
5
|
|
|
6
|
-
/** One `*.abstract.md` file: the agent-facing summary a module carries next to its source. */
|
|
7
6
|
export class AbstractDoc {
|
|
8
7
|
static readonly suffix = ".abstract.md";
|
|
9
8
|
/** `akan quality` warns above this. */
|
|
@@ -17,7 +16,6 @@ export class AbstractDoc {
|
|
|
17
16
|
static lineCountOf(content: string) {
|
|
18
17
|
return content.split(/\r?\n/).length;
|
|
19
18
|
}
|
|
20
|
-
/** Module kind read off the sys-relative path; anything outside a `lib/` module folder is "other". */
|
|
21
19
|
static kindOf(filePath: string): AbstractKind {
|
|
22
20
|
const [root, folder, ...rest] = filePath.split("/");
|
|
23
21
|
if (root !== "lib" || !folder) return "other";
|
|
@@ -60,8 +58,7 @@ export class AbstractDoc {
|
|
|
60
58
|
get lineCount() {
|
|
61
59
|
return AbstractDoc.lineCountOf(this.content);
|
|
62
60
|
}
|
|
63
|
-
// An AI editor
|
|
64
|
-
// replace the abstract — so a candidate must look like a markdown file and be shorter than what it replaces.
|
|
61
|
+
// An AI editor that decides nothing needs changing answers in prose, which must not replace the abstract.
|
|
65
62
|
canReplaceWith(candidate: string) {
|
|
66
63
|
const next = candidate.trim();
|
|
67
64
|
if (!next.startsWith("#")) return false;
|
package/agentsIndex.test.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
2
|
import path from "node:path";
|
|
5
3
|
import {
|
|
6
4
|
AGENT_BLOCK_END,
|
|
@@ -14,6 +12,7 @@ import {
|
|
|
14
12
|
upsertAgentBlock,
|
|
15
13
|
} from "./agentsIndex";
|
|
16
14
|
import type { RecipeInfo } from "./recipeScanner";
|
|
15
|
+
import { tempDirs, writeText } from "./testHelpers";
|
|
17
16
|
|
|
18
17
|
const button: RecipeInfo = {
|
|
19
18
|
name: "buttonRecipe",
|
|
@@ -79,12 +78,10 @@ describe("upsertAgentBlock / extractAgentBlock", () => {
|
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
describe("collectScopeRecipeSources", () => {
|
|
81
|
+
const makeTempRoot = tempDirs("agents-index-");
|
|
82
82
|
test("collects own + dependency lib recipes, never the framework's", async () => {
|
|
83
|
-
const root = await
|
|
84
|
-
const write = async (rel: string, content: string) =>
|
|
85
|
-
await mkdir(path.dirname(path.join(root, rel)), { recursive: true });
|
|
86
|
-
await writeFile(path.join(root, rel), content);
|
|
87
|
-
};
|
|
83
|
+
const root = await makeTempRoot();
|
|
84
|
+
const write = async (rel: string, content: string) => await writeText(path.join(root, rel), content);
|
|
88
85
|
await write("apps/minimal/ui/Recipe/appCard.ts", `export const appCard = recipe(tv({ base: "x" }));`);
|
|
89
86
|
await write("libs/shared/ui/Recipe/panel.ts", `export const panelRecipe = recipe(tv({ base: "y" }));`);
|
|
90
87
|
await write("pkgs/akanjs/ui/recipe/buttonRecipe.ts", `export const buttonRecipe = recipe(tv({ base: "z" }));`);
|
package/agentsIndex.ts
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
1
|
import type { RecipeInfo, RecipeSource } from "./recipeScanner";
|
|
2
2
|
|
|
3
|
-
// recipeScanner
|
|
4
|
-
// 상주하므로 스캔 스택은 첫 사용 시점에 지연 로드한다 — 정적 import 로 되돌리면 entryModuleGraph 테스트가 깨진다.
|
|
3
|
+
// Lazy: recipeScanner loads `typescript` (~65MB) and this module sits in the CLI entry graph (entryModuleGraph test).
|
|
5
4
|
let recipeScannerLoad: Promise<typeof import("./recipeScanner")> | null = null;
|
|
6
5
|
const loadRecipeScanner = () => (recipeScannerLoad ??= import("./recipeScanner"));
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* 색인은 소유 경계로 쪼갠다: 루트 AGENTS.md 는 프레임워크(akanjs/ui) 레시피만 싣고, 각 앱/lib 은
|
|
12
|
-
* 자기 스코프에서 import 가능한 레시피(own + 의존 lib)를 자기 AGENTS.md 에 싣는다. 항상 로드되는
|
|
13
|
-
* 컨텍스트가 앱 수에 비례해 커지는 것과, import 불가능한 이웃 앱 레시피가 환상을 유발하는 것을 막는다.
|
|
14
|
-
*
|
|
15
|
-
* 신선도는 두 지점이 보장한다: `SysExecutor.scan(write)` 가 재생성하고(akan sync/build/start 가 전부
|
|
16
|
-
* 지나가는 길목), `akan lint` 가 스캔 결과와 커밋된 블록을 비교해 stale 이면 실패시킨다.
|
|
17
|
-
*/
|
|
7
|
+
//* Split by ownership (root: framework recipes; each app/lib: what it may import), so always-loaded context does not
|
|
8
|
+
//* grow with the app count and no unimportable neighbour recipe is listed.
|
|
18
9
|
|
|
19
10
|
export const AGENT_BLOCK_START = "<!-- akan:agent:start -->";
|
|
20
11
|
export const AGENT_BLOCK_END = "<!-- akan:agent:end -->";
|
|
21
12
|
|
|
22
13
|
const AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
|
|
23
14
|
|
|
24
|
-
/**
|
|
25
|
-
* The generating release, stamped into the workspace block. Nothing re-runs `akan agent install` on its own, so
|
|
26
|
-
* without a stamp a workspace carrying conventions from four releases ago is indistinguishable from a current one —
|
|
27
|
-
* `akan doctor` compares this against the installed devkit and says so.
|
|
28
|
-
*/
|
|
15
|
+
/** Nothing re-runs `akan agent install` on its own, so `akan doctor` compares this stamp against the installed devkit. */
|
|
29
16
|
export const stampBlockVersion = (block: string, version: string): string =>
|
|
30
17
|
`${AGENT_VERSION_PREFIX}${version} -->\n\n${block}`;
|
|
31
18
|
|
|
@@ -36,7 +23,7 @@ export const extractBlockVersion = (content: string): string | null =>
|
|
|
36
23
|
/** The running `@akanjs/devkit` version; null when its package.json is unreadable, which must not fail a doctor run. */
|
|
37
24
|
export const readDevkitVersion = async (): Promise<string | null> => {
|
|
38
25
|
const { readFile } = await import("node:fs/promises");
|
|
39
|
-
const { getDirname } = await import("./
|
|
26
|
+
const { getDirname } = await import("./fileSys");
|
|
40
27
|
try {
|
|
41
28
|
const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
|
|
42
29
|
return (JSON.parse(raw) as { version?: string }).version ?? null;
|
|
@@ -97,10 +84,7 @@ export const renderRecipeEntries = (recipes: RecipeInfo[]): string => {
|
|
|
97
84
|
.join("\n\n");
|
|
98
85
|
};
|
|
99
86
|
|
|
100
|
-
/**
|
|
101
|
-
* Every recipe source importable from the scope: its own `ui/Recipe/` plus each dependency lib's.
|
|
102
|
-
* Framework recipes are excluded on purpose — they live in the root AGENTS.md, valid for every scope.
|
|
103
|
-
*/
|
|
87
|
+
/** Own `ui/Recipe/` plus each dependency lib's; framework recipes live in the root AGENTS.md instead. */
|
|
104
88
|
export const collectScopeRecipeSources = async (
|
|
105
89
|
workspaceRoot: string,
|
|
106
90
|
scope: AgentsIndexScope,
|
|
@@ -1,34 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { realpath, rm, writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { Logger } from "akanjs/common";
|
|
6
5
|
import type { App } from "../commandDecorators";
|
|
6
|
+
import { tempDirs, writeText } from "../testHelpers";
|
|
7
7
|
import { BackendImportGraph } from "./BackendImportGraph";
|
|
8
8
|
import { filesChangedSince } from "./devHostPolicy";
|
|
9
9
|
|
|
10
10
|
describe("BackendImportGraph", () => {
|
|
11
|
-
const
|
|
11
|
+
const makeTempRoot = tempDirs("akan-devkit-graph-");
|
|
12
12
|
|
|
13
13
|
const makeGraph = async (files: Record<string, string>) => {
|
|
14
|
-
// Realpath
|
|
15
|
-
|
|
16
|
-
const workspaceRoot = await realpath(await mkdtemp(path.join(os.tmpdir(), "akan-devkit-graph-")));
|
|
17
|
-
tempRoots.push(workspaceRoot);
|
|
14
|
+
// Realpath: `Bun.resolveSync` returns real paths, and macOS `/var/folders` is a symlink.
|
|
15
|
+
const workspaceRoot = await realpath(await makeTempRoot());
|
|
18
16
|
const cwdPath = path.join(workspaceRoot, "apps/demo");
|
|
19
|
-
for (const [rel, source] of Object.entries(files))
|
|
20
|
-
const filePath = path.join(cwdPath, rel);
|
|
21
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
22
|
-
await writeFile(filePath, source);
|
|
23
|
-
}
|
|
17
|
+
for (const [rel, source] of Object.entries(files)) await writeText(path.join(cwdPath, rel), source);
|
|
24
18
|
const app = { cwdPath, workspace: { workspaceRoot } } as unknown as App;
|
|
25
19
|
return { graph: new BackendImportGraph(app, new Logger("test")), cwdPath };
|
|
26
20
|
};
|
|
27
21
|
|
|
28
|
-
afterEach(async () => {
|
|
29
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
22
|
test("walks the backend entrypoints' import graph", async () => {
|
|
33
23
|
const { graph, cwdPath } = await makeGraph({
|
|
34
24
|
"main.ts": 'import "./server";\n',
|
|
@@ -51,7 +41,6 @@ describe("BackendImportGraph", () => {
|
|
|
51
41
|
await graph.refresh();
|
|
52
42
|
expect(graph.has(path.join(cwdPath, "lib/added.ts"))).toBe(false);
|
|
53
43
|
|
|
54
|
-
// The scan cache is keyed on (mtimeMs, size), so the rewrite must invalidate it.
|
|
55
44
|
await writeFile(path.join(cwdPath, "server.ts"), 'import "./lib/added";\nexport default 1;\n');
|
|
56
45
|
|
|
57
46
|
await graph.refresh();
|
|
@@ -81,8 +70,7 @@ describe("BackendImportGraph", () => {
|
|
|
81
70
|
await graph.refresh();
|
|
82
71
|
const before = await graph.fingerprint();
|
|
83
72
|
|
|
84
|
-
//
|
|
85
|
-
// the stamps are taken. `mtimeMs` has a coarse clock on Linux, so the size has to move too.
|
|
73
|
+
// `mtimeMs` has a coarse clock on Linux, so the size has to move too.
|
|
86
74
|
await writeFile(path.join(cwdPath, "lib/handler.ts"), "export const handler = () => 'changed';\n");
|
|
87
75
|
|
|
88
76
|
expect(filesChangedSince(before, await graph.fingerprint())).toEqual([path.join(cwdPath, "lib/handler.ts")]);
|
|
@@ -96,11 +84,9 @@ describe("BackendImportGraph", () => {
|
|
|
96
84
|
});
|
|
97
85
|
await graph.refresh();
|
|
98
86
|
const before = await graph.fingerprint();
|
|
99
|
-
// A recycle with no edit in it is the common case, and it must not cost a backend restart.
|
|
100
87
|
expect(filesChangedSince(before, await graph.fingerprint())).toEqual([]);
|
|
101
88
|
|
|
102
89
|
await rm(path.join(cwdPath, "lib/handler.ts"));
|
|
103
|
-
// Deleted counts as changed: the backend is still running what used to be there.
|
|
104
90
|
expect(filesChangedSince(before, await graph.fingerprint())).toEqual([path.join(cwdPath, "lib/handler.ts")]);
|
|
105
91
|
});
|
|
106
92
|
|
|
@@ -22,14 +22,7 @@ export class BackendImportGraph {
|
|
|
22
22
|
readonly #jsTranspiler = new Bun.Transpiler({ loader: "js" });
|
|
23
23
|
readonly #jsxTranspiler = new Bun.Transpiler({ loader: "jsx" });
|
|
24
24
|
#files = new Set<string>();
|
|
25
|
-
|
|
26
|
-
* `refresh()` runs on every server-side save and on every dev-host recycle, and re-reading plus
|
|
27
|
-
* re-parsing files that did not change is the whole cost of it. Keyed on (mtimeMs, size).
|
|
28
|
-
*
|
|
29
|
-
* Specifiers are cached, not resolved paths: creating a file can change what an *unchanged* importer's
|
|
30
|
-
* specifier resolves to, and `Bun.resolveSync` is cheap next to a read plus a transpiler scan. Only
|
|
31
|
-
* the scan result is retained — never the source text.
|
|
32
|
-
*/
|
|
25
|
+
// Specifiers, not resolved paths: a new file can change what an unchanged importer's specifier resolves to.
|
|
33
26
|
#scanCache = new Map<string, { mtimeMs: number; size: number; specifiers: Bun.Import[] }>();
|
|
34
27
|
#ready = false;
|
|
35
28
|
#lastRefreshSucceeded = false;
|
|
@@ -51,17 +44,6 @@ export class BackendImportGraph {
|
|
|
51
44
|
return this.#files.has(path.resolve(file));
|
|
52
45
|
}
|
|
53
46
|
|
|
54
|
-
/**
|
|
55
|
-
* Stamp every file the backend runs, so a caller can ask later what moved.
|
|
56
|
-
*
|
|
57
|
-
* Taken when the builder goes away and compared when its replacement is up, because nothing watches
|
|
58
|
-
* the tree in between: the departing builder's watcher left with it, and the replacement's index
|
|
59
|
-
* primes from the disk it finds, so an edit that lands in the gap is *baseline* to it and is never
|
|
60
|
-
* reported at all. The client half of such an edit is rescued by the replacement's boot build; the
|
|
61
|
-
* backend half is a server left running code that no longer exists, with nothing on screen to say so.
|
|
62
|
-
*
|
|
63
|
-
* One `stat` per graph file, against a gap that costs a whole boot build anyway.
|
|
64
|
-
*/
|
|
65
47
|
async fingerprint(): Promise<SourceFingerprints> {
|
|
66
48
|
const stamps = await Promise.all(
|
|
67
49
|
[...this.#files].map(async (file) => {
|
|
@@ -117,7 +99,6 @@ export class BackendImportGraph {
|
|
|
117
99
|
return files;
|
|
118
100
|
}
|
|
119
101
|
|
|
120
|
-
/** Null when the file is gone, which is the existence check the walk used to make separately. */
|
|
121
102
|
async #importsOf(file: string): Promise<Bun.Import[] | null> {
|
|
122
103
|
const stats = await stat(file).catch(() => null);
|
|
123
104
|
if (!stats?.isFile()) return null;
|
|
@@ -5,13 +5,11 @@ describe("readProcessRssBytes", () => {
|
|
|
5
5
|
test("reads this process's own rss", async () => {
|
|
6
6
|
const rssBytes = await AkanAppHost.readProcessRssBytes(process.pid);
|
|
7
7
|
if (rssBytes === null) throw new Error("expected to read this process's own rss");
|
|
8
|
-
// Loose bounds on purpose: the point is that it read a real number from the OS, not which number.
|
|
9
8
|
expect(rssBytes).toBeGreaterThan(1024 * 1024);
|
|
10
9
|
expect(rssBytes).toBeLessThan(64 * 1024 * 1024 * 1024);
|
|
11
10
|
});
|
|
12
11
|
|
|
13
|
-
//
|
|
14
|
-
// would read as "settled below the ceiling" and cancel a recycle that should happen.
|
|
12
|
+
// A 0 would read as "settled below the ceiling" and cancel a recycle that should happen.
|
|
15
13
|
test("returns null for a pid that does not exist", async () => {
|
|
16
14
|
expect(await AkanAppHost.readProcessRssBytes(2_147_483_646)).toBeNull();
|
|
17
15
|
});
|