@akanjs/devkit 3.0.0-beta.9 → 3.0.0
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/DEV_RUNTIME_KNOBS.md +44 -26
- package/README.ko.md +21 -7
- package/README.md +17 -8
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- 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 +56 -3
- package/akanApp/akanApp.host.ts +218 -278
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +81 -35
- package/akanApp/devHostPolicy.ts +41 -102
- package/akanConfig/akanBin.test.ts +62 -0
- package/akanConfig/akanBin.ts +78 -0
- package/akanConfig/akanConfig.test.ts +439 -141
- package/akanConfig/akanConfig.ts +359 -275
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +22 -8
- package/akanContext.ts +110 -160
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +41 -5
- package/applicationBuildRunner.ts +71 -63
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +32 -30
- package/artifact/implicitRootLayout.ts +58 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +35 -2
- package/biomeBase.ts +2 -5
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +71 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- 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 +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/csrE2e/csrE2eHarness.fixture.ts +505 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +252 -0
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/registryVendor.e2e.test.ts +221 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +176 -106
- package/executors.ts +212 -498
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +22 -9
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +123 -37
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +16 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +122 -110
- package/frontendBuild/csrArtifactBuilder.ts +9 -31
- package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
- package/frontendBuild/csrDevArtifactWriter.ts +307 -0
- package/frontendBuild/csrDevBundler.test.ts +66 -0
- package/frontendBuild/csrDevBundler.ts +95 -0
- package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
- package/frontendBuild/csrDevFactoryCheck.ts +39 -0
- package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
- package/frontendBuild/csrDevModuleCompiler.ts +422 -0
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +145 -0
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +363 -0
- package/frontendBuild/csrDevTypes.ts +61 -0
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +31 -82
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +57 -70
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +59 -32
- package/frontendBuild/hmrWatcher.ts +31 -74
- package/frontendBuild/index.ts +4 -0
- package/frontendBuild/pagesBundleBuilder.ts +28 -30
- package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +100 -62
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
- package/frontendBuild/ssrDevBundler.ts +85 -0
- 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 +147 -63
- package/incrementalBuilder/buildBatchProtocol.ts +13 -38
- package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
- package/incrementalBuilder/buildBatchRunner.ts +26 -38
- 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/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
- package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
- package/index.ts +5 -19
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +105 -199
- package/integration/devStabilityHarness.test.ts +13 -28
- 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/__fixtures__/no-document-cookie/bad.tsx +4 -0
- package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
- package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
- package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-document-cookie.grit +29 -0
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/lint/no-web-only-api-outside-webkit.grit +44 -0
- package/lint/no-web-storage.grit +30 -0
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/desktopBin.test.ts +182 -0
- package/mobile/desktopBin.ts +144 -0
- package/mobile/desktopServerStage.test.ts +252 -0
- package/mobile/desktopServerStage.ts +178 -0
- package/mobile/index.ts +9 -0
- package/mobile/mobileTarget.test.ts +57 -8
- package/mobile/mobileTarget.ts +45 -26
- package/mobile/nativeApi.test.ts +30 -0
- package/mobile/nativeApi.ts +40 -0
- package/mobile/nativeApp.test.ts +184 -0
- package/mobile/nativeApp.ts +454 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +438 -0
- package/mobile/nativeConfig.ts +294 -0
- package/mobile/nativePluginFolders.test.ts +81 -0
- package/mobile/nativePluginFolders.ts +60 -0
- package/mobile/nativeWebDir.test.ts +80 -0
- package/mobile/nativeWebDir.ts +66 -0
- package/mobile/placeholderAppId.test.ts +13 -0
- package/mobile/placeholderAppId.ts +16 -0
- package/mobile/usageDescriptions.test.ts +27 -0
- package/mobile/usageDescriptions.ts +24 -0
- package/package.json +11 -11
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +200 -286
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +91 -225
- 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/stripAnsi.ts +3 -0
- package/subspace.test.ts +9 -25
- package/subspace.ts +48 -115
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +194 -0
- 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 +42 -20
- package/transforms/serverEnvPlugin.test.ts +103 -0
- package/transforms/serverEnvPlugin.ts +41 -0
- package/transforms/transforms.test.ts +166 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +6 -23
- package/typeChecker.ts +7 -63
- package/types.ts +2 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- 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/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.test.ts +18 -3
- package/workspaceLayout.ts +22 -23
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/applicationReleasePackager.ts +0 -209
- package/capacitorApp.test.ts +0 -372
- package/capacitorApp.ts +0 -1648
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- 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/recipeScanner.test.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { collectRecipeSources, findInlineRecipeDuplicates, type RecipeInfo, scanRecipes } from "./recipeScanner";
|
|
5
|
+
import { tempDirs } from "./testHelpers";
|
|
6
6
|
|
|
7
7
|
const byName = (recipes: RecipeInfo[], name: string) => recipes.find((recipe) => recipe.name === name);
|
|
8
8
|
|
|
9
|
-
// Mirrors pkgs/akanjs/ui/recipe/ (framework) — two recipes in one source, variant + size surfaces.
|
|
10
9
|
// The scanner is per-source, so a folder of one-recipe files and a legacy multi-recipe file both parse.
|
|
11
10
|
const FRAMEWORK = `
|
|
12
11
|
import { recipe, tv } from "./recipeFactory";
|
|
@@ -24,7 +23,6 @@ export type ButtonVariants = NonNullable<Parameters<typeof buttonRecipe>[0]>;
|
|
|
24
23
|
export const badgeRecipe = recipe(tv({ base: "rounded-full", variants: { variant: { default: "bg-muted", info: "bg-info" } } }));
|
|
25
24
|
`;
|
|
26
25
|
|
|
27
|
-
// Mirrors apps/minimal/ui/Recipe/ shapes — base-only (no variants) + single-variant, with per-export JSDoc.
|
|
28
26
|
const APP = `
|
|
29
27
|
import { recipe, tv } from "akanjs/ui";
|
|
30
28
|
/** 전체 화면 배경/전경. 페이지 루트 컨테이너. */
|
|
@@ -97,12 +95,7 @@ describe("scanRecipes", () => {
|
|
|
97
95
|
});
|
|
98
96
|
});
|
|
99
97
|
|
|
100
|
-
//
|
|
101
|
-
// index, the recipeGate lint, the MCP module context) go through collectRecipeSources, and every one of them
|
|
102
|
-
// degrades silently — empty list, no error — if it stops finding sources. These tests are that alarm.
|
|
103
|
-
// The advisory exists to catch a look being re-authored inline. Requiring every base token only matched a
|
|
104
|
-
// verbatim copy of the whole base — the one shape that never occurs in practice — so it reported nothing on
|
|
105
|
-
// the near-copies it was built for, and silently, being advisory. These tests pin the ratio behaviour.
|
|
98
|
+
// Requiring every base token matched only a verbatim copy, which never occurs; these tests pin the ratio instead.
|
|
106
99
|
describe("findInlineRecipeDuplicates", () => {
|
|
107
100
|
// 8 tokens → ceil(8 * 0.7) = 6 must be reproduced.
|
|
108
101
|
const EIGHT = `export const cardRecipe = recipe(tv({ base: "flex rounded-box border border-border bg-card p-4 text-card-foreground shadow-sm" }));`;
|
|
@@ -135,9 +128,11 @@ describe("findInlineRecipeDuplicates", () => {
|
|
|
135
128
|
});
|
|
136
129
|
});
|
|
137
130
|
|
|
131
|
+
// The AGENTS.md index, the recipeGate lint and the MCP module context all degrade silently if this finds nothing.
|
|
138
132
|
describe("collectRecipeSources", () => {
|
|
133
|
+
const makeTempRoot = tempDirs("akan-recipe-");
|
|
139
134
|
const seed = async (files: Record<string, string>) => {
|
|
140
|
-
const root = await
|
|
135
|
+
const root = await makeTempRoot();
|
|
141
136
|
for (const [rel, content] of Object.entries(files)) {
|
|
142
137
|
const abs = path.join(root, rel);
|
|
143
138
|
await Bun.write(abs, content);
|
|
@@ -176,7 +171,7 @@ describe("collectRecipeSources", () => {
|
|
|
176
171
|
});
|
|
177
172
|
|
|
178
173
|
test("returns nothing when neither shape exists, without throwing", async () => {
|
|
179
|
-
const root = await
|
|
174
|
+
const root = await makeTempRoot();
|
|
180
175
|
await writeFile(path.join(root, "placeholder"), "");
|
|
181
176
|
expect(await collectRecipeSources(path.join(root, "ui"), "@apps/x/ui")).toEqual([]);
|
|
182
177
|
});
|
package/recipeScanner.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* A single Akan UI recipe discovered by scanning source. `variants` maps each variant key to its allowed option
|
|
6
|
-
* names (e.g. `{ variant: ["primary", "ghost"], size: ["sm", "md"] }`); a base-only recipe has `variants: {}`.
|
|
7
|
-
* `importFrom` is the module a consumer imports the recipe from (e.g. `@apps/minimal/ui`).
|
|
8
|
-
*/
|
|
4
|
+
/** `variants` maps each key to its option names (`{}` for a base-only recipe); `importFrom` is the consumer's module. */
|
|
9
5
|
export interface RecipeInfo {
|
|
10
6
|
name: string;
|
|
11
7
|
importFrom: string;
|
|
@@ -22,13 +18,8 @@ export interface RecipeSource {
|
|
|
22
18
|
importFrom: string;
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* (`recipe/` for the framework), so this reads the whole folder; the flat `Recipe.ts` is still read for
|
|
28
|
-
* apps that have not moved yet. Every consumer of `scanRecipes` must go through here — three call sites
|
|
29
|
-
* (AGENTS.md recipe index, `recipeGate` lint, MCP module context) hardcoded the flat path before, and each
|
|
30
|
-
* one fails silently (empty list, no error) when the file is absent.
|
|
31
|
-
*/
|
|
21
|
+
// One recipe per file under `Recipe/` (`recipe/` for the framework), plus the legacy flat `Recipe.ts`. Every
|
|
22
|
+
// `scanRecipes` consumer goes through here: a hardcoded path fails silently (an empty list) when the file is absent.
|
|
32
23
|
export const collectRecipeSources = async (
|
|
33
24
|
uiDirPath: string,
|
|
34
25
|
importFrom: string,
|
|
@@ -51,12 +42,8 @@ export const collectRecipeSources = async (
|
|
|
51
42
|
return [flat, ...fromDir].filter((source): source is RecipeSource => !!source);
|
|
52
43
|
};
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* variant surface + leading JSDoc one-liner. Detection is by the `recipe(tv(...))` call shape (not by name suffix,
|
|
57
|
-
* since base-only recipes like `appScreen` omit the `Recipe` suffix). Being AST-based, it never matches recipe
|
|
58
|
-
* definitions that appear only inside string/template literals (e.g. code examples in docs pages).
|
|
59
|
-
*/
|
|
45
|
+
// Detected by the `recipe(tv(...))` call shape, not the name suffix (base-only recipes like `appScreen` omit it);
|
|
46
|
+
// AST-based, so code examples inside string literals never match.
|
|
60
47
|
export const scanRecipes = (sources: RecipeSource[]): RecipeInfo[] => {
|
|
61
48
|
const recipes: RecipeInfo[] = [];
|
|
62
49
|
for (const source of sources) {
|
|
@@ -73,12 +60,13 @@ export const scanRecipes = (sources: RecipeSource[]): RecipeInfo[] => {
|
|
|
73
60
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
74
61
|
const parsed = parseRecipeCall(declaration.initializer);
|
|
75
62
|
if (!parsed) continue;
|
|
63
|
+
const doc = getLeadingDoc(source.content, statement);
|
|
76
64
|
recipes.push({
|
|
77
65
|
name: declaration.name.text,
|
|
78
66
|
importFrom: source.importFrom,
|
|
79
67
|
variants: parsed.variants,
|
|
80
68
|
...(parsed.defaultVariants ? { defaultVariants: parsed.defaultVariants } : {}),
|
|
81
|
-
...(
|
|
69
|
+
...(doc ? { doc } : {}),
|
|
82
70
|
...(parsed.base ? { base: parsed.base } : {}),
|
|
83
71
|
});
|
|
84
72
|
}
|
|
@@ -90,7 +78,6 @@ export const scanRecipes = (sources: RecipeSource[]): RecipeInfo[] => {
|
|
|
90
78
|
const isExported = (statement: ts.VariableStatement): boolean =>
|
|
91
79
|
statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
92
80
|
|
|
93
|
-
/** Matches `recipe( tv( <ObjectLiteral> ) )` and returns the variant surface, or null for anything else. */
|
|
94
81
|
const parseRecipeCall = (
|
|
95
82
|
initializer: ts.Expression,
|
|
96
83
|
): { variants: Record<string, string[]>; defaultVariants?: Record<string, string>; base?: string } | null => {
|
|
@@ -148,27 +135,12 @@ export interface RecipeDuplicate {
|
|
|
148
135
|
className: string;
|
|
149
136
|
}
|
|
150
137
|
|
|
151
|
-
|
|
152
|
-
* Fraction of a recipe's base tokens an inline className must reproduce to count as a duplicate.
|
|
153
|
-
*
|
|
154
|
-
* Requiring *every* token (the original rule) only caught a verbatim copy of the whole base, which is the one
|
|
155
|
-
* form of duplication that essentially never happens: someone re-authoring a look reproduces the gist, not all
|
|
156
|
-
* eight tokens. So the check passed on exactly the near-duplicates it existed to find, and silently — it is an
|
|
157
|
-
* advisory, so nothing went red. A ratio catches those; false positives are cheap here for the same reason.
|
|
158
|
-
*/
|
|
138
|
+
// A re-authored look reproduces the gist, not every token, so a ratio (not all tokens) catches near-duplicates.
|
|
159
139
|
const DUPLICATE_TOKEN_RATIO = 0.7;
|
|
160
140
|
|
|
161
|
-
/** Minimum base tokens for a recipe to be worth fingerprinting at all. */
|
|
162
141
|
const MIN_FINGERPRINT_TOKENS = 3;
|
|
163
142
|
|
|
164
|
-
|
|
165
|
-
* SSOT advisory: finds JSX `className` string values that hand-rewrite a recipe's base fingerprint instead of
|
|
166
|
-
* consuming the recipe. Only recipes whose base has 3+ distinctive tokens are checked — shorter fingerprints
|
|
167
|
-
* (`grid gap-3` …) are generic utilities and would flood the report with false positives. A className counts as
|
|
168
|
-
* a duplicate once it reproduces {@link DUPLICATE_TOKEN_RATIO} of those tokens, so a near-copy that drops or
|
|
169
|
-
* swaps one still reports. AST-scoped to real `className` attributes, so class strings inside doc-example
|
|
170
|
-
* template literals never match.
|
|
171
|
-
*/
|
|
143
|
+
// Shorter fingerprints (`grid gap-3`) are generic utilities that would flood the report with false positives.
|
|
172
144
|
export const findInlineRecipeDuplicates = (
|
|
173
145
|
recipes: RecipeInfo[],
|
|
174
146
|
files: { path: string; content: string }[],
|
|
@@ -219,7 +191,6 @@ const stringValuesIn = (node: ts.Node): string[] => {
|
|
|
219
191
|
return values;
|
|
220
192
|
};
|
|
221
193
|
|
|
222
|
-
/** The first non-empty line of the JSDoc/line comment immediately preceding the statement, markers stripped. */
|
|
223
194
|
const getLeadingDoc = (fullText: string, node: ts.Node): string | undefined => {
|
|
224
195
|
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
225
196
|
if (!ranges?.length) return undefined;
|
package/repoIdentity.ts
CHANGED
|
@@ -24,15 +24,8 @@ const readRemoteName = (workspaceRoot: string): string | null => {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
* Deriving it from the working directory made every generated file that names the repo — the AGENTS.md title, its
|
|
31
|
-
* `- Repo:` line — depend on what each person happened to call the folder they cloned into, so one commit rendered
|
|
32
|
-
* a different guide per developer and the diff never settled. The origin remote is the one identity every clone
|
|
33
|
-
* shares. `AKAN_PUBLIC_REPO_NAME` is deliberately not consulted: that is a deployment namespace (queue prefixes,
|
|
34
|
-
* cache keys, secret paths) which a monorepo hosting several products legitimately points somewhere else.
|
|
35
|
-
*/
|
|
27
|
+
// The origin remote, not the clone's folder name, so generated files agree across clones. `AKAN_PUBLIC_REPO_NAME` is a
|
|
28
|
+
// deployment namespace (queue prefixes, cache keys) and deliberately not consulted.
|
|
36
29
|
export const resolveRepoName = (workspaceRoot: string): string => {
|
|
37
30
|
const cached = resolved.get(workspaceRoot);
|
|
38
31
|
if (cached) return cached;
|
|
@@ -15,7 +15,6 @@ describe("RouteSourceValidator", () => {
|
|
|
15
15
|
"export const reconnect = true;",
|
|
16
16
|
"export const wsConnect = true;",
|
|
17
17
|
"export const layoutStyle = {};",
|
|
18
|
-
'export const gaTrackingId = "G-1";',
|
|
19
18
|
].join("\n");
|
|
20
19
|
|
|
21
20
|
expect(() => validate(source, "layout", true)).not.toThrow();
|
package/routeSourceValidator.ts
CHANGED
|
@@ -24,15 +24,8 @@ interface ChainStage {
|
|
|
24
24
|
|
|
25
25
|
const chainRoots = new Set(["page", "layout", "rootLayout"]);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* an executor does not pull `typescript` (+65MB resident) into the module graph. Both validators need
|
|
30
|
-
* a real AST — modifier inspection, `export *` rejection, statement-kind walking and call-expression
|
|
31
|
-
* identity — so `Bun.Transpiler.scan()`, which only reports export names, cannot replace them.
|
|
32
|
-
*
|
|
33
|
-
* Import it dynamically (`await import("./routeSourceValidator")`) so long-lived processes that never
|
|
34
|
-
* validate a route source stay lean.
|
|
35
|
-
*/
|
|
27
|
+
// Import dynamically: it pulls in `typescript` (+65MB resident). `Bun.Transpiler.scan()` reports only export names,
|
|
28
|
+
// too little for these checks.
|
|
36
29
|
export class RouteSourceValidator {
|
|
37
30
|
static validateRouteSourceExports(
|
|
38
31
|
source: string,
|
|
@@ -86,18 +79,6 @@ export class RouteSourceValidator {
|
|
|
86
79
|
if (exported.has("head") && exported.has("generateHead")) {
|
|
87
80
|
throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
|
|
88
81
|
}
|
|
89
|
-
if (
|
|
90
|
-
!options.rootLayout &&
|
|
91
|
-
(exported.has("head") || exported.has("generateHead")) &&
|
|
92
|
-
(exported.has("metadata") || exported.has("generateMetadata"))
|
|
93
|
-
) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`[route-convention] head/generateHead and metadata/generateMetadata cannot both be exported in ${filePath}`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
if (exported.has("metadata") && exported.has("generateMetadata")) {
|
|
99
|
-
throw new Error(`[route-convention] metadata and generateMetadata cannot both be exported in ${filePath}`);
|
|
100
|
-
}
|
|
101
82
|
const chain = RouteSourceValidator.#readChain(sourceFile, filePath);
|
|
102
83
|
if (!chain) return { devOnly: RouteSourceValidator.#readDevOnly(sourceFile, filePath) };
|
|
103
84
|
const named = [...exported].filter((name) => name !== "default");
|
|
@@ -116,12 +97,7 @@ export class RouteSourceValidator {
|
|
|
116
97
|
};
|
|
117
98
|
}
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
* The default export as a `page()` / `layout()` / `rootLayout()` chain, or null when the module is the legacy
|
|
121
|
-
* shape. Read off the source for the same reason `devOnly` is: the build enumerates routes without importing
|
|
122
|
-
* them, and `akan sync` should name a `[projectId]` folder whose page declares no `.param("projectId")` before
|
|
123
|
-
* the first request does.
|
|
124
|
-
*/
|
|
100
|
+
// Null for the legacy shape. Read off the source because the build enumerates routes without importing them.
|
|
125
101
|
static #readChain(sourceFile: ts.SourceFile, filePath: string) {
|
|
126
102
|
for (const statement of sourceFile.statements) {
|
|
127
103
|
if (!ts.isExportAssignment(statement) || statement.isExportEquals) continue;
|
|
@@ -185,12 +161,8 @@ export class RouteSourceValidator {
|
|
|
185
161
|
);
|
|
186
162
|
}
|
|
187
163
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* source rather than from an evaluated module — the build never imports route files to enumerate them.
|
|
191
|
-
* That is why only a literal is accepted: anything the parser cannot settle would otherwise ship a
|
|
192
|
-
* route the author believed was excluded.
|
|
193
|
-
*/
|
|
164
|
+
// Only a literal is accepted: the build reads it without importing the route, and a value the parser cannot settle
|
|
165
|
+
// would ship a route its author believed excluded.
|
|
194
166
|
static #readDevOnly(sourceFile: ts.SourceFile, filePath: string): boolean {
|
|
195
167
|
for (const statement of sourceFile.statements) {
|
|
196
168
|
if (!ts.isVariableStatement(statement)) continue;
|
|
@@ -234,13 +206,7 @@ export class RouteSourceValidator {
|
|
|
234
206
|
return current;
|
|
235
207
|
}
|
|
236
208
|
|
|
237
|
-
|
|
238
|
-
* Statically enforces that a `_overrides.tsx` route file is a logic-free activation manifest: a plain module
|
|
239
|
-
* (no `"use client"` — the framework generates the client wrapper) that only imports components and binds them
|
|
240
|
-
* to slots through a single `export default override({ Modal: BrandModal })`. It must not declare components
|
|
241
|
-
* inline or run logic — that keeps the override contract a thin binding layer rather than a second place to
|
|
242
|
-
* author UI. Slot names and value types are validated at compile time by `override`.
|
|
243
|
-
*/
|
|
209
|
+
// A logic-free manifest: imports plus one `export default override({ ... })`; `override` types the slots.
|
|
244
210
|
static validateOverridesSourceExports(source: string, filePath: string) {
|
|
245
211
|
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
246
212
|
const fail = (message: string): never => {
|
|
@@ -248,9 +214,8 @@ export class RouteSourceValidator {
|
|
|
248
214
|
};
|
|
249
215
|
let defaultOverride: ts.ExportAssignment | null = null;
|
|
250
216
|
for (const statement of sourceFile.statements) {
|
|
251
|
-
|
|
217
|
+
//? A "use client" directive is unnecessary (the framework wraps the manifest) but harmless.
|
|
252
218
|
if (ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)) continue;
|
|
253
|
-
// The manifest imports the app components it binds; imports and type-only decls carry no runtime logic.
|
|
254
219
|
if (ts.isImportDeclaration(statement)) continue;
|
|
255
220
|
if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) continue;
|
|
256
221
|
if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|