@akanjs/devkit 3.0.0-beta.3 → 3.0.0-beta.30
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 +2 -2
- package/README.md +3 -2
- 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 +404 -141
- package/akanConfig/akanConfig.ts +317 -274
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +21 -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 +269 -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 +177 -0
- package/mobile/desktopBin.ts +143 -0
- package/mobile/desktopServerStage.test.ts +204 -0
- package/mobile/desktopServerStage.ts +138 -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 +151 -0
- package/mobile/nativeApp.ts +405 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +414 -0
- package/mobile/nativeConfig.ts +296 -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 +30 -25
- package/subspace.ts +81 -115
- package/subspaceConfig.ts +11 -9
- 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/scanInfo.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { rm } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import type {
|
|
4
3
|
AppConfigResult,
|
|
@@ -11,7 +10,7 @@ import type {
|
|
|
11
10
|
} from "./akanConfig";
|
|
12
11
|
import { AkanAppConfig } from "./akanConfig";
|
|
13
12
|
import { AppExecutor, LibExecutor, PkgExecutor, WorkspaceExecutor } from "./executors";
|
|
14
|
-
import { isAllowedLibFacetRootFile, rootAllowedDirs, rootAllowedFiles } from "./workspaceLayout";
|
|
13
|
+
import { isAllowedLibFacetRootFile, rootAllowedDirs, rootAllowedFiles, rootEntryHintOf } from "./workspaceLayout";
|
|
15
14
|
|
|
16
15
|
const scalarFileTypes = ["constant", "dictionary", "document", "template", "unit", "util", "view", "zone"] as const;
|
|
17
16
|
type ScalarFileType = (typeof scalarFileTypes)[number];
|
|
@@ -27,24 +26,16 @@ const serviceFileTypes = [
|
|
|
27
26
|
"zone",
|
|
28
27
|
] as const;
|
|
29
28
|
type ServiceFileType = (typeof serviceFileTypes)[number];
|
|
30
|
-
const databaseFileTypes = [
|
|
31
|
-
"constant",
|
|
32
|
-
"dictionary",
|
|
33
|
-
"document",
|
|
34
|
-
"service",
|
|
35
|
-
"signal",
|
|
36
|
-
"store",
|
|
37
|
-
"template",
|
|
38
|
-
"unit",
|
|
39
|
-
"util",
|
|
40
|
-
"view",
|
|
41
|
-
"zone",
|
|
42
|
-
] as const;
|
|
29
|
+
const databaseFileTypes = ["constant", "dictionary", "document", ...serviceFileTypes.slice(1)] as const;
|
|
43
30
|
type DatabaseFileType = (typeof databaseFileTypes)[number];
|
|
31
|
+
const uiFileTypes = new Set<DatabaseFileType>(["template", "unit", "util", "view", "zone"]);
|
|
32
|
+
const fileTypeOf = (filename: string) =>
|
|
33
|
+
databaseFileTypes.find((type) =>
|
|
34
|
+
filename.endsWith(uiFileTypes.has(type) ? `.${type[0].toUpperCase()}${type.slice(1)}.tsx` : `.${type}.ts`),
|
|
35
|
+
);
|
|
44
36
|
|
|
45
37
|
type ModuleKind = "database" | "service" | "scalar";
|
|
46
38
|
|
|
47
|
-
const generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"] as const;
|
|
48
39
|
const internalLibDirs = new Set(["__lib", "__scalar"]);
|
|
49
40
|
const moduleNonUiFileTypes = {
|
|
50
41
|
database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
|
|
@@ -56,27 +47,19 @@ const moduleUiFileTypes = {
|
|
|
56
47
|
service: new Set(["Util", "Zone"]),
|
|
57
48
|
scalar: new Set(["Template", "Unit"]),
|
|
58
49
|
} satisfies Record<ModuleKind, Set<string>>;
|
|
59
|
-
const testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
|
|
60
50
|
|
|
61
|
-
//
|
|
62
|
-
// load it on demand rather than through the module graph of every process that imports scan info.
|
|
51
|
+
// Lazy: the dependency scanner pulls in `typescript` (~65MB resident), which only scan and sync need.
|
|
63
52
|
const createDependencyScanner = async (exec: AppExecutor | LibExecutor | PkgExecutor) =>
|
|
64
53
|
(await import("./dependencyScanner")).TypeScriptDependencyScanner.from(exec);
|
|
65
54
|
|
|
66
|
-
const isAllowedTestFile = (filename: string) => testFilePattern.test(filename);
|
|
67
55
|
const getScanPath = (exec: AppExecutor | LibExecutor, relativePath: string) =>
|
|
68
56
|
path.posix.join(`${exec.type}s`, exec.name, relativePath.split(path.sep).join("/"));
|
|
69
|
-
async function clearGeneratedRootCapacitorConfigs(exec: AppExecutor | LibExecutor) {
|
|
70
|
-
if (exec.type !== "app") return;
|
|
71
|
-
await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
|
|
72
|
-
}
|
|
73
57
|
const getModuleNameFromPath = (kind: ModuleKind, modulePath: string) => {
|
|
74
58
|
const dirname = path.basename(modulePath);
|
|
75
59
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
76
60
|
};
|
|
77
61
|
|
|
78
62
|
async function assertScanConvention(exec: AppExecutor | LibExecutor, libRoot: { files: string[]; dirs: string[] }) {
|
|
79
|
-
await clearGeneratedRootCapacitorConfigs(exec);
|
|
80
63
|
const violations: string[] = [];
|
|
81
64
|
const addViolation = (relativePath: string, reason: string) => {
|
|
82
65
|
violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
|
|
@@ -85,33 +68,23 @@ async function assertScanConvention(exec: AppExecutor | LibExecutor, libRoot: {
|
|
|
85
68
|
const allowedRootFiles: ReadonlySet<string> = rootAllowedFiles[exec.type];
|
|
86
69
|
const allowedRootDirs: ReadonlySet<string> = rootAllowedDirs[exec.type];
|
|
87
70
|
const { files, dirs } = await exec.getFilesAndDirs(".");
|
|
88
|
-
files
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
addViolation(dirname, `unsupported ${exec.type} root folder`);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
//* An app validates its page tree in `getPageKeys`, which a lib has no equivalent of — so a lib's own
|
|
100
|
-
//* routes went unchecked until whichever app opted into them with `syncPageLibs` blew up on the sync.
|
|
71
|
+
for (const filename of files)
|
|
72
|
+
if (!allowedRootFiles.has(filename))
|
|
73
|
+
addViolation(filename, rootEntryHintOf(exec.type, filename) ?? `unsupported ${exec.type} root file`);
|
|
74
|
+
for (const dirname of dirs)
|
|
75
|
+
if (!allowedRootDirs.has(dirname))
|
|
76
|
+
addViolation(dirname, rootEntryHintOf(exec.type, dirname) ?? `unsupported ${exec.type} root folder`);
|
|
77
|
+
|
|
78
|
+
//* A lib has no `getPageKeys`, so its own route files are validated here.
|
|
101
79
|
if (exec.type === "lib")
|
|
102
80
|
for (const { relativePath, reason } of await exec.getPageConventionViolations()) addViolation(relativePath, reason);
|
|
103
81
|
|
|
104
|
-
libRoot.files
|
|
105
|
-
|
|
106
|
-
.forEach((filename) => {
|
|
82
|
+
for (const filename of libRoot.files)
|
|
83
|
+
if (!isAllowedLibFacetRootFile(filename))
|
|
107
84
|
addViolation(path.join("lib", filename), "unsupported lib facet root file");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
libRoot.dirs
|
|
111
|
-
.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname))
|
|
112
|
-
.forEach((dirname) => {
|
|
85
|
+
for (const dirname of libRoot.dirs)
|
|
86
|
+
if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
|
|
113
87
|
addViolation(path.join("lib", dirname), "unsupported internal lib folder");
|
|
114
|
-
});
|
|
115
88
|
|
|
116
89
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
117
90
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
@@ -146,43 +119,23 @@ async function validateModuleFiles(
|
|
|
146
119
|
|
|
147
120
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
148
121
|
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
if (filename === "index.ts" || filename === "index.tsx" ||
|
|
152
|
-
if (filename === `${moduleName}.abstract.md`)
|
|
153
|
-
|
|
122
|
+
for (const filename of files) {
|
|
123
|
+
const scanPath = getScanPath(exec, path.join(modulePath, filename));
|
|
124
|
+
if (filename === "index.ts" || filename === "index.tsx" || /\.(test|spec)\.(ts|tsx)$/.test(filename)) continue;
|
|
125
|
+
if (filename === `${moduleName}.abstract.md`) continue;
|
|
154
126
|
const uiMatch = filename.match(/^([A-Z][A-Za-z0-9]+)\.([A-Z][A-Za-z0-9]*)\.tsx$/);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
violations.push(
|
|
160
|
-
`${getScanPath(exec, filePath)}: module name mismatch: expected '${uiModuleName}', got '${fileModuleName}'`,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
if (!moduleUiFileTypes[kind].has(fileType)) {
|
|
164
|
-
violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} UI file`);
|
|
165
|
-
}
|
|
166
|
-
return;
|
|
127
|
+
const match = uiMatch ?? filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
|
|
128
|
+
if (!match) {
|
|
129
|
+
violations.push(`${scanPath}: unsupported module file`);
|
|
130
|
+
continue;
|
|
167
131
|
}
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`${getScanPath(exec, filePath)}: module name mismatch: expected '${moduleName}', got '${fileModuleName}'`,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
if (!moduleNonUiFileTypes[kind].has(fileType)) {
|
|
179
|
-
violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} file`);
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
violations.push(`${getScanPath(exec, filePath)}: unsupported module file`);
|
|
185
|
-
});
|
|
132
|
+
const [, fileModuleName, fileType] = match;
|
|
133
|
+
const expectedName = uiMatch ? uiModuleName : moduleName;
|
|
134
|
+
if (fileModuleName !== expectedName)
|
|
135
|
+
violations.push(`${scanPath}: module name mismatch: expected '${expectedName}', got '${fileModuleName}'`);
|
|
136
|
+
if (!(uiMatch ? moduleUiFileTypes : moduleNonUiFileTypes)[kind].has(fileType))
|
|
137
|
+
violations.push(`${scanPath}: unsupported ${kind}${uiMatch ? " UI" : ""} file`);
|
|
138
|
+
}
|
|
186
139
|
}
|
|
187
140
|
|
|
188
141
|
class ScanInfo {
|
|
@@ -239,51 +192,16 @@ class ScanInfo {
|
|
|
239
192
|
} else databaseDirs.push(name);
|
|
240
193
|
});
|
|
241
194
|
|
|
195
|
+
const collect = async (dir: string, name: string, kind: "databases" | "services" | "scalars") => {
|
|
196
|
+
for (const filename of await exec.readdir(dir)) {
|
|
197
|
+
const type = fileTypeOf(filename);
|
|
198
|
+
if (type) (files[type] as { [key in typeof kind]?: string[] })[kind]?.push(name);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
242
201
|
await Promise.all([
|
|
243
|
-
...databaseDirs.map(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (filename.endsWith(".constant.ts")) files.constant.databases.push(name);
|
|
247
|
-
else if (filename.endsWith(".dictionary.ts")) files.dictionary.databases.push(name);
|
|
248
|
-
else if (filename.endsWith(".document.ts")) files.document.databases.push(name);
|
|
249
|
-
else if (filename.endsWith(".service.ts")) files.service.databases.push(name);
|
|
250
|
-
else if (filename.endsWith(".signal.ts")) files.signal.databases.push(name);
|
|
251
|
-
else if (filename.endsWith(".store.ts")) files.store.databases.push(name);
|
|
252
|
-
else if (filename.endsWith(".Template.tsx")) files.template.databases.push(name);
|
|
253
|
-
else if (filename.endsWith(".Unit.tsx")) files.unit.databases.push(name);
|
|
254
|
-
else if (filename.endsWith(".Util.tsx")) files.util.databases.push(name);
|
|
255
|
-
else if (filename.endsWith(".View.tsx")) files.view.databases.push(name);
|
|
256
|
-
else if (filename.endsWith(".Zone.tsx")) files.zone.databases.push(name);
|
|
257
|
-
});
|
|
258
|
-
}),
|
|
259
|
-
...serviceDirs.map(async (dirname) => {
|
|
260
|
-
const name = dirname.slice(1);
|
|
261
|
-
const filenames = await exec.readdir(path.join("lib", dirname));
|
|
262
|
-
filenames.forEach((filename) => {
|
|
263
|
-
if (filename.endsWith(".dictionary.ts")) files.dictionary.services.push(name);
|
|
264
|
-
else if (filename.endsWith(".service.ts")) files.service.services.push(name);
|
|
265
|
-
else if (filename.endsWith(".signal.ts")) files.signal.services.push(name);
|
|
266
|
-
else if (filename.endsWith(".store.ts")) files.store.services.push(name);
|
|
267
|
-
else if (filename.endsWith(".Template.tsx")) files.template.services.push(name);
|
|
268
|
-
else if (filename.endsWith(".Unit.tsx")) files.unit.services.push(name);
|
|
269
|
-
else if (filename.endsWith(".Util.tsx")) files.util.services.push(name);
|
|
270
|
-
else if (filename.endsWith(".View.tsx")) files.view.services.push(name);
|
|
271
|
-
else if (filename.endsWith(".Zone.tsx")) files.zone.services.push(name);
|
|
272
|
-
});
|
|
273
|
-
}),
|
|
274
|
-
...scalarDirs.map(async (name) => {
|
|
275
|
-
const filenames = await exec.readdir(path.join("lib/__scalar", name));
|
|
276
|
-
filenames.forEach((filename) => {
|
|
277
|
-
if (filename.endsWith(".constant.ts")) files.constant.scalars.push(name);
|
|
278
|
-
else if (filename.endsWith(".dictionary.ts")) files.dictionary.scalars.push(name);
|
|
279
|
-
else if (filename.endsWith(".document.ts")) files.document.scalars.push(name);
|
|
280
|
-
else if (filename.endsWith(".Template.tsx")) files.template.scalars.push(name);
|
|
281
|
-
else if (filename.endsWith(".Unit.tsx")) files.unit.scalars.push(name);
|
|
282
|
-
else if (filename.endsWith(".Util.tsx")) files.util.scalars.push(name);
|
|
283
|
-
else if (filename.endsWith(".View.tsx")) files.view.scalars.push(name);
|
|
284
|
-
else if (filename.endsWith(".Zone.tsx")) files.zone.scalars.push(name);
|
|
285
|
-
});
|
|
286
|
-
}),
|
|
202
|
+
...databaseDirs.map((name) => collect(path.join("lib", name), name, "databases")),
|
|
203
|
+
...serviceDirs.map((dirname) => collect(path.join("lib", dirname), dirname.slice(1), "services")),
|
|
204
|
+
...scalarDirs.map((name) => collect(path.join("lib/__scalar", name), name, "scalars")),
|
|
287
205
|
]);
|
|
288
206
|
const routes = exec.type === "lib" ? [] : await (exec as AppExecutor).getPageKeys();
|
|
289
207
|
const common = {
|
|
@@ -309,35 +227,16 @@ class ScanInfo {
|
|
|
309
227
|
constructor(scanResult: ScanResult) {
|
|
310
228
|
this.name = scanResult.name;
|
|
311
229
|
this.scanResult = scanResult;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
this.database.set(modelName, model);
|
|
323
|
-
this.file[key].all.add(modelName);
|
|
324
|
-
this.file[key].databases.add(modelName);
|
|
325
|
-
});
|
|
326
|
-
services?.forEach((serviceName) => {
|
|
327
|
-
const service = this.service.get(serviceName) ?? new Set<ServiceFileType>();
|
|
328
|
-
service.add(key as ServiceFileType);
|
|
329
|
-
this.service.set(serviceName, service);
|
|
330
|
-
this.file[key].all.add(serviceName);
|
|
331
|
-
this.file[key].services.add(serviceName);
|
|
332
|
-
});
|
|
333
|
-
scalars?.forEach((scalarName) => {
|
|
334
|
-
const scalar = this.scalar.get(scalarName) ?? new Set<ScalarFileType>();
|
|
335
|
-
scalar.add(key as ScalarFileType);
|
|
336
|
-
this.scalar.set(scalarName, scalar);
|
|
337
|
-
this.file[key].all.add(scalarName);
|
|
338
|
-
this.file[key].scalars.add(scalarName);
|
|
339
|
-
});
|
|
340
|
-
});
|
|
230
|
+
const modulesOf = { databases: this.database, services: this.service, scalars: this.scalar } as {
|
|
231
|
+
[key in "databases" | "services" | "scalars"]: Map<string, Set<DatabaseFileType>>;
|
|
232
|
+
};
|
|
233
|
+
for (const [key, groups] of Object.entries(scanResult.files) as [DatabaseFileType, { [kind: string]: string[] }][])
|
|
234
|
+
for (const kind of ["databases", "services", "scalars"] as const)
|
|
235
|
+
for (const name of groups[kind] ?? []) {
|
|
236
|
+
modulesOf[kind].set(name, (modulesOf[kind].get(name) ?? new Set()).add(key));
|
|
237
|
+
this.file[key].all.add(name);
|
|
238
|
+
this.file[key][kind].add(name);
|
|
239
|
+
}
|
|
341
240
|
}
|
|
342
241
|
getScanResult() {
|
|
343
242
|
return this.scanResult;
|
|
@@ -351,6 +250,29 @@ class ScanInfo {
|
|
|
351
250
|
getScalarModules() {
|
|
352
251
|
return [...this.scalar.keys()];
|
|
353
252
|
}
|
|
253
|
+
|
|
254
|
+
#sortedLibs: string[] | null = null;
|
|
255
|
+
protected sortedLibs(libDeps: string[]) {
|
|
256
|
+
if (this.#sortedLibs) return this.#sortedLibs;
|
|
257
|
+
const libIndices = LibInfo.getSortedLibIndices();
|
|
258
|
+
this.#sortedLibs = libDeps.sort((libNameA, libNameB) => {
|
|
259
|
+
const indexA = libIndices.get(libNameA);
|
|
260
|
+
const indexB = libIndices.get(libNameB);
|
|
261
|
+
if (indexA === undefined || indexB === undefined)
|
|
262
|
+
throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
|
|
263
|
+
return indexA - indexB;
|
|
264
|
+
});
|
|
265
|
+
return this.#sortedLibs;
|
|
266
|
+
}
|
|
267
|
+
protected sortedLibInfos(libDeps: string[]) {
|
|
268
|
+
return new Map(
|
|
269
|
+
this.sortedLibs(libDeps).map((libName) => {
|
|
270
|
+
const libInfo = LibInfo.libInfos.get(libName);
|
|
271
|
+
if (!libInfo) throw new Error(`LibInfo not found: ${libName}`);
|
|
272
|
+
return [libName, libInfo];
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
354
276
|
}
|
|
355
277
|
|
|
356
278
|
const isAkanFrameworkDependency = (dep: string) => dep === "akanjs" || dep.startsWith("akanjs/");
|
|
@@ -362,7 +284,6 @@ export class AppInfo extends ScanInfo {
|
|
|
362
284
|
|
|
363
285
|
static appInfos = new Map<string, AppInfo>();
|
|
364
286
|
static async fromExecutor(exec: AppExecutor, options: { refresh?: boolean } = {}) {
|
|
365
|
-
// cache check
|
|
366
287
|
const existingAppInfo = AppInfo.appInfos.get(exec.name);
|
|
367
288
|
if (existingAppInfo && !options.refresh) return existingAppInfo;
|
|
368
289
|
const scanResult = await ScanInfo.getScanResult(exec);
|
|
@@ -408,30 +329,11 @@ export class AppInfo extends ScanInfo {
|
|
|
408
329
|
return [...libSet];
|
|
409
330
|
}
|
|
410
331
|
|
|
411
|
-
#sortedLibs: string[] | null = null;
|
|
412
|
-
#getSortedLibs() {
|
|
413
|
-
if (this.#sortedLibs) return this.#sortedLibs;
|
|
414
|
-
const libIndices = LibInfo.getSortedLibIndices();
|
|
415
|
-
this.#sortedLibs = this.libDeps.sort((libNameA, libNameB) => {
|
|
416
|
-
const indexA = libIndices.get(libNameA);
|
|
417
|
-
const indexB = libIndices.get(libNameB);
|
|
418
|
-
if (indexA === undefined || indexB === undefined)
|
|
419
|
-
throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
|
|
420
|
-
return indexA - indexB;
|
|
421
|
-
});
|
|
422
|
-
return this.#sortedLibs;
|
|
423
|
-
}
|
|
424
332
|
getLibs() {
|
|
425
|
-
return this
|
|
333
|
+
return this.sortedLibs(this.libDeps);
|
|
426
334
|
}
|
|
427
335
|
getLibInfos() {
|
|
428
|
-
return
|
|
429
|
-
this.#getSortedLibs().map((libName) => {
|
|
430
|
-
const libInfo = LibInfo.libInfos.get(libName);
|
|
431
|
-
if (!libInfo) throw new Error(`LibInfo not found: ${libName}`);
|
|
432
|
-
return [libName, libInfo];
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
336
|
+
return this.sortedLibInfos(this.libDeps);
|
|
435
337
|
}
|
|
436
338
|
}
|
|
437
339
|
export class LibInfo extends ScanInfo {
|
|
@@ -482,36 +384,16 @@ export class LibInfo extends ScanInfo {
|
|
|
482
384
|
return this.scanResult as LibScanResult;
|
|
483
385
|
}
|
|
484
386
|
|
|
485
|
-
#sortedLibs: string[] | null = null;
|
|
486
|
-
#getSortedLibs() {
|
|
487
|
-
if (this.#sortedLibs) return this.#sortedLibs;
|
|
488
|
-
const libs = LibInfo.getSortedLibIndices();
|
|
489
|
-
this.#sortedLibs = this.scanResult.libDeps.sort((libNameA, libNameB) => {
|
|
490
|
-
const indexA = libs.get(libNameA);
|
|
491
|
-
const indexB = libs.get(libNameB);
|
|
492
|
-
if (indexA === undefined || indexB === undefined)
|
|
493
|
-
throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
|
|
494
|
-
return indexA - indexB;
|
|
495
|
-
});
|
|
496
|
-
return this.#sortedLibs;
|
|
497
|
-
}
|
|
498
387
|
getLibs() {
|
|
499
|
-
return this
|
|
388
|
+
return this.sortedLibs(this.scanResult.libDeps);
|
|
500
389
|
}
|
|
501
390
|
getLibInfo(libName: string) {
|
|
502
391
|
if (!this.getScanResult().libDeps.includes(libName)) return undefined;
|
|
503
|
-
|
|
504
|
-
if (!libSet.has(libName)) throw new Error(`LibInfo is invalid: ${libName}`);
|
|
392
|
+
if (!this.getLibs().includes(libName)) throw new Error(`LibInfo is invalid: ${libName}`);
|
|
505
393
|
return LibInfo.libInfos.get(libName);
|
|
506
394
|
}
|
|
507
395
|
getLibInfos() {
|
|
508
|
-
return
|
|
509
|
-
this.#getSortedLibs().map((libName) => {
|
|
510
|
-
const libInfo = LibInfo.libInfos.get(libName);
|
|
511
|
-
if (!libInfo) throw new Error(`LibInfo not found: ${libName}`);
|
|
512
|
-
return [libName, libInfo];
|
|
513
|
-
}),
|
|
514
|
-
);
|
|
396
|
+
return this.sortedLibInfos(this.scanResult.libDeps);
|
|
515
397
|
}
|
|
516
398
|
}
|
|
517
399
|
|
|
@@ -524,27 +406,25 @@ export class PkgInfo {
|
|
|
524
406
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
525
407
|
const scanner = await createDependencyScanner(exec);
|
|
526
408
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
409
|
+
const workspacePathOf = (resolve: string) => resolve.replace(/^\.\//, "");
|
|
527
410
|
const pkgPathSet = new Set(
|
|
528
411
|
Object.keys(tsconfig.compilerOptions.paths ?? {})
|
|
529
|
-
.filter((path) =>
|
|
412
|
+
.filter((path) =>
|
|
413
|
+
tsconfig.compilerOptions.paths?.[path]?.some((resolve) => workspacePathOf(resolve).startsWith("pkgs/")),
|
|
414
|
+
)
|
|
530
415
|
.map((path) => path.replace("/*", "")),
|
|
531
416
|
);
|
|
532
417
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
533
418
|
const pkgDeps = [...pkgPathDepSet]
|
|
534
419
|
.map((path) => {
|
|
535
420
|
const pathSplitLength = path.split("/").length;
|
|
536
|
-
return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*")
|
|
421
|
+
return workspacePathOf(tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*")
|
|
537
422
|
.split("/")
|
|
538
423
|
.slice(1, 1 + pathSplitLength)
|
|
539
424
|
.join("/");
|
|
540
425
|
})
|
|
541
426
|
.filter((pkg) => pkg !== exec.name);
|
|
542
|
-
|
|
543
|
-
name: exec.name,
|
|
544
|
-
pkgDeps,
|
|
545
|
-
dependencies: [...npmDepSet],
|
|
546
|
-
};
|
|
547
|
-
return pkgScanResult;
|
|
427
|
+
return { name: exec.name, pkgDeps, dependencies: [...npmDepSet] };
|
|
548
428
|
}
|
|
549
429
|
|
|
550
430
|
static #pkgInfos = new Map<string, PkgInfo>();
|
|
@@ -582,23 +462,9 @@ export class WorkspaceInfo {
|
|
|
582
462
|
const [appNames, libNames, pkgNames] = await Promise.all([exec.getApps(), exec.getLibs(), exec.getPkgs()]);
|
|
583
463
|
// TODO: prevent duplicate scan by resolving the dependency graph
|
|
584
464
|
const [appInfos, libInfos, pkgInfos] = await Promise.all([
|
|
585
|
-
Promise.all(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return await app.scan();
|
|
589
|
-
}),
|
|
590
|
-
),
|
|
591
|
-
Promise.all(
|
|
592
|
-
libNames.map(async (libName) => {
|
|
593
|
-
const lib = LibExecutor.from(exec, libName);
|
|
594
|
-
return await lib.scan();
|
|
595
|
-
}),
|
|
596
|
-
),
|
|
597
|
-
Promise.all(
|
|
598
|
-
pkgNames.map(async (pkgName) => {
|
|
599
|
-
return await PkgExecutor.from(exec, pkgName).scan();
|
|
600
|
-
}),
|
|
601
|
-
),
|
|
465
|
+
Promise.all(appNames.map(async (appName) => await AppExecutor.from(exec, appName).scan())),
|
|
466
|
+
Promise.all(libNames.map(async (libName) => await LibExecutor.from(exec, libName).scan())),
|
|
467
|
+
Promise.all(pkgNames.map(async (pkgName) => await PkgExecutor.from(exec, pkgName).scan())),
|
|
602
468
|
]);
|
|
603
469
|
const workspaceInfo = new WorkspaceInfo(
|
|
604
470
|
new Map(appInfos.map((app) => [app.exec.name, app as AppInfo])),
|
package/selectModel.ts
CHANGED
|
@@ -3,9 +3,8 @@ import { select } from "@inquirer/prompts";
|
|
|
3
3
|
|
|
4
4
|
export const selectModel = async (modulePath: string) => {
|
|
5
5
|
const modelNames = readdirSync(`${modulePath}/lib`).filter((dir) => !dir.includes(".") && !dir.startsWith("_"));
|
|
6
|
-
|
|
6
|
+
return await select({
|
|
7
7
|
message: "Select the model to create the unit for",
|
|
8
8
|
choices: modelNames.map((name) => ({ name, value: name })),
|
|
9
9
|
});
|
|
10
|
-
return modelName;
|
|
11
10
|
};
|
package/semver.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
// `Bun.semver.order` compares
|
|
2
|
-
// operator prefix has to come off before it sees the version.
|
|
1
|
+
// `Bun.semver.order` compares ranges loosely (`^3.1049.0` vs `^3.721.0` answers 0), so the operator comes off first.
|
|
3
2
|
const stripRangeOperator = (version: string) => version.replace(/^[\s^~>=<v]+/, "").trim();
|
|
4
3
|
|
|
5
|
-
const parseVersion = (version: string): number[] =>
|
|
6
|
-
|
|
4
|
+
const parseVersion = (version: string): number[] =>
|
|
5
|
+
version
|
|
7
6
|
.replace(/^[^\d]*/, "")
|
|
8
7
|
.split(/[.-]/)
|
|
9
8
|
.map((part) => Number.parseInt(part, 10))
|
|
10
9
|
.map((part) => (Number.isFinite(part) ? part : 0));
|
|
11
|
-
};
|
|
12
10
|
|
|
13
11
|
const compareNumeric = (a: string, b: string): number => {
|
|
14
12
|
const left = parseVersion(a);
|
package/slicePlanner.test.ts
CHANGED
|
@@ -1,30 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir,
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { AppExecutor, Executor, WorkspaceExecutor } from "./executors";
|
|
6
5
|
import { formatSlicePlan, SlicePlanner } from "./slicePlanner";
|
|
6
|
+
import { isolateEnv, tempDirs, writeText as write } from "./testHelpers";
|
|
7
7
|
import type { PackageJson } from "./types";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
process.env = { ...originalEnv };
|
|
14
|
-
process.env.AKAN_PUBLIC_REPO_NAME = "workspace";
|
|
15
|
-
process.env.AKAN_PUBLIC_SERVE_DOMAIN = "example.com";
|
|
16
|
-
process.env.AKAN_PUBLIC_ENV = "local";
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
process.env = { ...originalEnv };
|
|
21
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const write = async (filePath: string, content: string) => {
|
|
25
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
26
|
-
await writeFile(filePath, content);
|
|
27
|
-
};
|
|
9
|
+
isolateEnv({ AKAN_PUBLIC_REPO_NAME: "workspace", AKAN_PUBLIC_SERVE_DOMAIN: "example.com", AKAN_PUBLIC_ENV: "local" });
|
|
10
|
+
const makeTempRoot = tempDirs("akan-slice-");
|
|
28
11
|
|
|
29
12
|
const gitignore = [
|
|
30
13
|
"node_modules",
|
|
@@ -36,8 +19,7 @@ const gitignore = [
|
|
|
36
19
|
|
|
37
20
|
// `AppExecutor.from` and `AppInfo.fromExecutor` both memoise by name, so each fixture needs a fresh one.
|
|
38
21
|
const makeWorkspace = async (appName: string, libName: string) => {
|
|
39
|
-
const root = await
|
|
40
|
-
tempRoots.push(root);
|
|
22
|
+
const root = await makeTempRoot();
|
|
41
23
|
const rootPackageJson: PackageJson = {
|
|
42
24
|
name: "workspace",
|
|
43
25
|
version: "0.0.1",
|
package/slicePlanner.ts
CHANGED
|
@@ -18,12 +18,6 @@ export interface SlicePlan {
|
|
|
18
18
|
warnings: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* The exact file set a single app needs to live in a workspace of its own: the app, its transitive lib
|
|
23
|
-
* closure, the workspace shell around them, and a root manifest for the result.
|
|
24
|
-
*
|
|
25
|
-
* Consumed by `akan plan-slice`, and by anything that moves an app or a lib between workspaces.
|
|
26
|
-
*/
|
|
27
21
|
export class SlicePlanner {
|
|
28
22
|
/** Root entries owned by an app, a lib or an in-tree package rather than by the workspace shell. */
|
|
29
23
|
static readonly memberDirs = ["apps", "libs", "pkgs"];
|
|
@@ -39,13 +33,8 @@ export class SlicePlanner {
|
|
|
39
33
|
];
|
|
40
34
|
static readonly akanPackages = ["akanjs", "@akanjs/devkit", "@akanjs/cli"];
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* `scheduler` and the RSC runtime, but also `ioredis`, `postgres` and `bullmq` the moment an app uses
|
|
45
|
-
* a database or a queue. No source file in the workspace names any of them, so no scan can find them
|
|
46
|
-
* — they are the akan packages' own `peerDependencies`, read wherever those are installed. Anything
|
|
47
|
-
* they declare as a plain dependency is installed with them and needs no root entry.
|
|
48
|
-
*/
|
|
36
|
+
// The akan packages' peerDependencies (react, the RSC runtime, ioredis, postgres, bullmq): the framework imports
|
|
37
|
+
// them but no workspace source names them, so no scan can find them.
|
|
49
38
|
static async #frameworkPeers(workspace: WorkspaceExecutor) {
|
|
50
39
|
const peers = new Set<string>();
|
|
51
40
|
let found = false;
|
|
@@ -67,14 +56,8 @@ export class SlicePlanner {
|
|
|
67
56
|
return dependency.startsWith("@") ? `@types/${dependency.slice(1).replace("/", "__")}` : `@types/${dependency}`;
|
|
68
57
|
}
|
|
69
58
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* version specs kept — that is what makes one branch resolve to one dependency tree everywhere.
|
|
73
|
-
*
|
|
74
|
-
* A `@types/x` package is kept alongside `x`: nothing imports it by name, so no scan can see it, and
|
|
75
|
-
* dropping it breaks the typecheck of every file that imports `x`. `workspaces` is dropped because it
|
|
76
|
-
* names in-tree packages, which a slice never carries — it consumes akanjs from the registry.
|
|
77
|
-
*/
|
|
59
|
+
// `@types/x` is kept alongside `x` since nothing imports it by name; `workspaces` is dropped because a slice
|
|
60
|
+
// consumes akanjs from the registry.
|
|
78
61
|
static async pruneDependencies(
|
|
79
62
|
workspace: WorkspaceExecutor,
|
|
80
63
|
rootPackageJson: PackageJson,
|
|
@@ -82,9 +65,8 @@ export class SlicePlanner {
|
|
|
82
65
|
) {
|
|
83
66
|
const peers = await SlicePlanner.#frameworkPeers(workspace);
|
|
84
67
|
const { workspaces: _workspaces, ...rest } = rootPackageJson;
|
|
85
|
-
//* Without
|
|
86
|
-
//*
|
|
87
|
-
//* whole instead and say why.
|
|
68
|
+
//* Without the peer list every peer looks unused, and pruning it breaks the server at its first redis or
|
|
69
|
+
//* postgres call.
|
|
88
70
|
if (!peers)
|
|
89
71
|
return {
|
|
90
72
|
packageJson: rest as PackageJson,
|
|
@@ -121,10 +103,6 @@ export class SlicePlanner {
|
|
|
121
103
|
this.#app = app;
|
|
122
104
|
}
|
|
123
105
|
|
|
124
|
-
async #trackedFiles(paths: string[]) {
|
|
125
|
-
return await this.#app.workspace.listGitFiles(paths);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
106
|
async #untrackedFiles(paths: string[]) {
|
|
129
107
|
const [tracked, all] = await Promise.all([
|
|
130
108
|
this.#app.workspace.listGitFiles(paths),
|
|
@@ -170,14 +148,16 @@ export class SlicePlanner {
|
|
|
170
148
|
const slicePaths = [`apps/${this.#app.name}`, ...libs.map((lib) => `libs/${lib}`)];
|
|
171
149
|
|
|
172
150
|
const [appFiles, allTracked, untracked, rootPackageJson] = await Promise.all([
|
|
173
|
-
this.#
|
|
174
|
-
this.#
|
|
151
|
+
this.#app.workspace.listGitFiles([`apps/${this.#app.name}`]),
|
|
152
|
+
this.#app.workspace.listGitFiles(["."]),
|
|
175
153
|
this.#untrackedFiles(slicePaths),
|
|
176
154
|
this.#app.workspace.getPackageJson(),
|
|
177
155
|
]);
|
|
178
156
|
|
|
179
157
|
const libFiles = Object.fromEntries(
|
|
180
|
-
await Promise.all(
|
|
158
|
+
await Promise.all(
|
|
159
|
+
libs.map(async (lib) => [lib, await this.#app.workspace.listGitFiles([`libs/${lib}`])] as const),
|
|
160
|
+
),
|
|
181
161
|
);
|
|
182
162
|
const rootFiles = allTracked.filter((file) => !this.#isMemberFile(file));
|
|
183
163
|
|
|
@@ -211,7 +191,6 @@ export class SlicePlanner {
|
|
|
211
191
|
}
|
|
212
192
|
}
|
|
213
193
|
|
|
214
|
-
/** Top-level entry each path sits under, so a long tree (`infra/**`) reads as one line. */
|
|
215
194
|
const rootEntriesOf = (rootFiles: string[]) => [...new Set(rootFiles.map((file) => file.split("/")[0] ?? file))].sort();
|
|
216
195
|
|
|
217
196
|
export function formatSlicePlan(plan: SlicePlan) {
|
package/spinner.test.ts
CHANGED
|
@@ -6,10 +6,7 @@ interface TtyStub {
|
|
|
6
6
|
rawModeCalls: boolean[];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* ora only reaches for stdin when both streams look interactive, so the stub has to fake a tty on
|
|
11
|
-
* stderr (which decides `isEnabled`) as well as on stdin.
|
|
12
|
-
*/
|
|
9
|
+
// ora only reaches for stdin when both streams look interactive, so stderr (which decides `isEnabled`) is faked too.
|
|
13
10
|
const stubTty = (): TtyStub => {
|
|
14
11
|
const rawModeCalls: boolean[] = [];
|
|
15
12
|
const stdin = process.stdin as NodeJS.ReadStream & { setRawMode?: (mode: boolean) => unknown };
|
|
@@ -41,8 +38,7 @@ const stubTty = (): TtyStub => {
|
|
|
41
38
|
};
|
|
42
39
|
|
|
43
40
|
describe("Spinner", () => {
|
|
44
|
-
// A raw terminal at spawn
|
|
45
|
-
// how `akan start` used to leave a terminal that no longer turns Ctrl+C into SIGINT.
|
|
41
|
+
// A Bun child snapshots a raw terminal at spawn and writes it back on exit, leaving Ctrl+C no longer a SIGINT.
|
|
46
42
|
test("never puts the terminal into raw mode while it spins", () => {
|
|
47
43
|
const tty = stubTty();
|
|
48
44
|
try {
|
|
@@ -59,8 +55,7 @@ describe("Spinner", () => {
|
|
|
59
55
|
expect(Spinner.oraOptions.discardStdin).toBe(false);
|
|
60
56
|
});
|
|
61
57
|
|
|
62
|
-
// An unsized pty reports `isTTY: true` with `columns: 0`, which turns ora's clear loop into an
|
|
63
|
-
// infinite one — the process then writes cursor moves until it is SIGKILLed.
|
|
58
|
+
// An unsized pty reports `isTTY: true` with `columns: 0`, which turns ora's clear loop into an infinite one.
|
|
64
59
|
test("refuses to animate against a tty that reports no width", () => {
|
|
65
60
|
expect(Spinner.canAnimate({ isTTY: true, columns: 0 } as NodeJS.WriteStream)).toBe(false);
|
|
66
61
|
expect(Spinner.canAnimate({ isTTY: true, columns: 120 } as NodeJS.WriteStream)).toBe(true);
|