@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/executors.ts
CHANGED
|
@@ -7,10 +7,11 @@ import {
|
|
|
7
7
|
type SpawnOptions,
|
|
8
8
|
spawn,
|
|
9
9
|
} from "node:child_process";
|
|
10
|
-
import { readFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import {
|
|
12
12
|
copyFile,
|
|
13
13
|
cp as cpEntry,
|
|
14
|
+
lstat,
|
|
14
15
|
mkdir,
|
|
15
16
|
readdir as readDirEntries,
|
|
16
17
|
realpath,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
} from "node:fs/promises";
|
|
21
22
|
import path from "node:path";
|
|
22
23
|
import { pathToFileURL } from "node:url";
|
|
23
|
-
import type { AkanPlugin, AkanSyncContext
|
|
24
|
+
import type { AkanPlugin, AkanSyncContext } from "akanjs";
|
|
24
25
|
import {
|
|
25
26
|
capitalize,
|
|
26
27
|
getPageSourceFileViolation,
|
|
@@ -42,14 +43,12 @@ import {
|
|
|
42
43
|
import { AkanAppConfig, AkanLibConfig, decreaseBuildNum, increaseBuildNum } from "./akanConfig";
|
|
43
44
|
import { getRootBoundarySegments, isRootBoundarySegments } from "./artifact/implicitRootLayout";
|
|
44
45
|
import { CodegenLock } from "./codegenLock";
|
|
45
|
-
import { FileSys } from "./fileSys";
|
|
46
|
-
import { getDirname } from "./getDirname";
|
|
46
|
+
import { FileSys, getDirname } from "./fileSys";
|
|
47
47
|
import { Linter } from "./linter";
|
|
48
48
|
import { resolveRepoName } from "./repoIdentity";
|
|
49
49
|
import { AppInfo, LibInfo, PkgInfo, WorkspaceInfo } from "./scanInfo";
|
|
50
50
|
import { Spinner } from "./spinner";
|
|
51
|
-
// Type-only:
|
|
52
|
-
// the resident module graph.
|
|
51
|
+
// Type-only: `getTypeChecker` loads it on demand to keep `typescript` out of the resident module graph.
|
|
53
52
|
import type { TypeChecker } from "./typeChecker";
|
|
54
53
|
import type { FileContent, PackageJson, TsConfigJson } from "./types";
|
|
55
54
|
|
|
@@ -62,41 +61,15 @@ export interface PageRoot {
|
|
|
62
61
|
keyPrefix: string;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
const staticTemplateFileExtensions = new Set(
|
|
66
|
-
".avif"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
".eot",
|
|
71
|
-
".gif",
|
|
72
|
-
".html",
|
|
73
|
-
".ico",
|
|
74
|
-
".jpeg",
|
|
75
|
-
".jpg",
|
|
76
|
-
".js",
|
|
77
|
-
".json",
|
|
78
|
-
".map",
|
|
79
|
-
".md",
|
|
80
|
-
".mjs",
|
|
81
|
-
".mp3",
|
|
82
|
-
".mp4",
|
|
83
|
-
".ogg",
|
|
84
|
-
".otf",
|
|
85
|
-
".pdf",
|
|
86
|
-
".png",
|
|
87
|
-
".svg",
|
|
88
|
-
".ttf",
|
|
89
|
-
".txt",
|
|
90
|
-
".wasm",
|
|
91
|
-
".wav",
|
|
92
|
-
".webm",
|
|
93
|
-
".webp",
|
|
94
|
-
".woff",
|
|
95
|
-
".woff2",
|
|
96
|
-
".xml",
|
|
97
|
-
]);
|
|
64
|
+
const staticTemplateFileExtensions = new Set(
|
|
65
|
+
".avif .bmp .cjs .css .eot .gif .html .ico .jpeg .jpg .js .json .map .md .mjs .mp3 .mp4 .ogg .otf .pdf .png .svg"
|
|
66
|
+
.split(" ")
|
|
67
|
+
.concat(".ttf .txt .wasm .wav .webm .webp .woff .woff2 .xml".split(" ")),
|
|
68
|
+
);
|
|
98
69
|
|
|
99
|
-
|
|
70
|
+
//? A backslash is a path separator on Windows but an escape in a POSIX shell, so only there does it force quoting.
|
|
71
|
+
const plainCommandArgPattern = process.platform === "win32" ? /^[\w@%+=:,./\\-]+$/ : /^[\w@%+=:,./-]+$/;
|
|
72
|
+
const formatCommandArg = (value: string) => (plainCommandArgPattern.test(value) ? value : JSON.stringify(value));
|
|
100
73
|
|
|
101
74
|
const formatCommandForDisplay = (command: string, args: string[] = []) =>
|
|
102
75
|
[command, ...args].map(formatCommandArg).join(" ");
|
|
@@ -155,7 +128,7 @@ export const execEmoji = {
|
|
|
155
128
|
pkg: "📦",
|
|
156
129
|
dist: "💿",
|
|
157
130
|
module: "⚙️",
|
|
158
|
-
default: "✈️",
|
|
131
|
+
default: "✈️",
|
|
159
132
|
};
|
|
160
133
|
|
|
161
134
|
const parseEnvFile = (envPath: string): Record<string, string> => {
|
|
@@ -183,6 +156,11 @@ const parseEnvFile = (envPath: string): Record<string, string> => {
|
|
|
183
156
|
return env;
|
|
184
157
|
};
|
|
185
158
|
|
|
159
|
+
const withCapitalizedDict = (dict: { [key: string]: string } = {}) => ({
|
|
160
|
+
...dict,
|
|
161
|
+
...Object.fromEntries(Object.entries(dict).map(([key, value]) => [capitalize(key), capitalize(value)])),
|
|
162
|
+
});
|
|
163
|
+
|
|
186
164
|
export class Executor {
|
|
187
165
|
static verbose = false;
|
|
188
166
|
static setVerbose(verbose: boolean) {
|
|
@@ -208,158 +186,53 @@ export class Executor {
|
|
|
208
186
|
#stderr(data: Buffer) {
|
|
209
187
|
Logger.raw(chalk.red(data.toString()));
|
|
210
188
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
189
|
+
#settle(
|
|
190
|
+
proc: ChildProcess,
|
|
191
|
+
target: { command: string; args?: string[]; cwd: string },
|
|
192
|
+
{ onClose = false, keepLogs = false, redStderr = false } = {},
|
|
193
|
+
): Promise<{ code: number | null; signal: NodeJS.Signals | null; stdout: string }> {
|
|
214
194
|
let stdout = "";
|
|
215
195
|
let stderr = "";
|
|
216
196
|
proc.stdout?.on("data", (data: Buffer) => {
|
|
217
197
|
stdout += data.toString();
|
|
198
|
+
if (keepLogs) this.logs.push(data.toString());
|
|
218
199
|
this.#stdout(data);
|
|
219
200
|
});
|
|
220
201
|
proc.stderr?.on("data", (data: Buffer) => {
|
|
221
202
|
stderr += data.toString();
|
|
222
|
-
this
|
|
203
|
+
if (keepLogs) this.logs.push(data.toString());
|
|
204
|
+
if (redStderr) this.#stderr(data);
|
|
205
|
+
else this.#stdout(data); // 정상로그도 stderr로 나옴
|
|
223
206
|
});
|
|
224
207
|
return new Promise((resolve, reject) => {
|
|
225
|
-
proc.on("error", (
|
|
226
|
-
reject(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
stdout,
|
|
233
|
-
stderr,
|
|
234
|
-
cause: error,
|
|
235
|
-
}),
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
proc.on("exit", (code, signal) => {
|
|
239
|
-
if (code || signal)
|
|
240
|
-
reject(
|
|
241
|
-
new CommandExecutionError({
|
|
242
|
-
command,
|
|
243
|
-
cwd,
|
|
244
|
-
code,
|
|
245
|
-
signal,
|
|
246
|
-
stdout,
|
|
247
|
-
stderr,
|
|
248
|
-
}),
|
|
249
|
-
);
|
|
250
|
-
else resolve({ code, signal });
|
|
208
|
+
proc.on("error", (cause) =>
|
|
209
|
+
reject(new CommandExecutionError({ ...target, code: null, signal: null, stdout, stderr, cause })),
|
|
210
|
+
);
|
|
211
|
+
proc.on(onClose ? "close" : "exit", (code: number | null, signal: NodeJS.Signals | null) => {
|
|
212
|
+
if ((onClose ? code !== 0 : code) || signal)
|
|
213
|
+
reject(new CommandExecutionError({ ...target, code, signal, stdout, stderr }));
|
|
214
|
+
else resolve({ code, signal, stdout });
|
|
251
215
|
});
|
|
252
216
|
});
|
|
253
217
|
}
|
|
218
|
+
exec(command: string, options: ExecOptions = {}): Promise<unknown> {
|
|
219
|
+
const target = { command, cwd: options.cwd?.toString() ?? this.cwdPath };
|
|
220
|
+
const proc = exec(command, { cwd: this.cwdPath, ...options });
|
|
221
|
+
return this.#settle(proc, target).then(({ code, signal }) => ({ code, signal }));
|
|
222
|
+
}
|
|
254
223
|
|
|
255
224
|
spawn(command: string, args: string[] = [], options: SpawnOptions = {}): Promise<string> {
|
|
256
|
-
const
|
|
257
|
-
const proc = spawn(command, args, {
|
|
258
|
-
|
|
259
|
-
// stdio: "inherit",
|
|
260
|
-
...options,
|
|
261
|
-
});
|
|
262
|
-
let stdout = "";
|
|
263
|
-
let stderr = "";
|
|
264
|
-
|
|
265
|
-
proc.stdout?.on("data", (data: Buffer) => {
|
|
266
|
-
stdout += data.toString();
|
|
267
|
-
this.logs.push(data.toString());
|
|
268
|
-
this.#stdout(data);
|
|
269
|
-
});
|
|
270
|
-
proc.stderr?.on("data", (data: Buffer) => {
|
|
271
|
-
stderr += data.toString();
|
|
272
|
-
this.logs.push(data.toString());
|
|
273
|
-
this.#stdout(data); // 정상로그도 stderr로 나옴
|
|
274
|
-
});
|
|
275
|
-
return new Promise((resolve, reject) => {
|
|
276
|
-
proc.on("error", (error) => {
|
|
277
|
-
reject(
|
|
278
|
-
new CommandExecutionError({
|
|
279
|
-
command,
|
|
280
|
-
args,
|
|
281
|
-
cwd,
|
|
282
|
-
code: null,
|
|
283
|
-
signal: null,
|
|
284
|
-
stdout,
|
|
285
|
-
stderr,
|
|
286
|
-
cause: error,
|
|
287
|
-
}),
|
|
288
|
-
);
|
|
289
|
-
});
|
|
290
|
-
proc.on("close", (code, signal) => {
|
|
291
|
-
if (code !== 0 || signal)
|
|
292
|
-
reject(
|
|
293
|
-
new CommandExecutionError({
|
|
294
|
-
command,
|
|
295
|
-
args,
|
|
296
|
-
cwd,
|
|
297
|
-
code,
|
|
298
|
-
signal,
|
|
299
|
-
stdout,
|
|
300
|
-
stderr,
|
|
301
|
-
}),
|
|
302
|
-
);
|
|
303
|
-
else resolve(stdout);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
225
|
+
const target = { command, args, cwd: options.cwd?.toString() ?? this.cwdPath };
|
|
226
|
+
const proc = spawn(command, args, { cwd: this.cwdPath, ...options });
|
|
227
|
+
return this.#settle(proc, target, { onClose: true, keepLogs: true }).then(({ stdout }) => stdout);
|
|
306
228
|
}
|
|
307
229
|
spawnSync(command: string, args: string[] = [], options: SpawnOptions = {}): ChildProcess {
|
|
308
|
-
|
|
309
|
-
cwd: this.cwdPath,
|
|
310
|
-
// stdio: "inherit",
|
|
311
|
-
...options,
|
|
312
|
-
});
|
|
313
|
-
return proc;
|
|
230
|
+
return spawn(command, args, { cwd: this.cwdPath, ...options });
|
|
314
231
|
}
|
|
315
|
-
fork(modulePath: string, args: string[] = [], options: ForkOptions = {}) {
|
|
316
|
-
const
|
|
317
|
-
const proc = fork(modulePath, args, {
|
|
318
|
-
|
|
319
|
-
// stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
320
|
-
...options,
|
|
321
|
-
});
|
|
322
|
-
let stdout = "";
|
|
323
|
-
let stderr = "";
|
|
324
|
-
proc.stdout?.on("data", (data: Buffer) => {
|
|
325
|
-
stdout += data.toString();
|
|
326
|
-
this.#stdout(data);
|
|
327
|
-
});
|
|
328
|
-
proc.stderr?.on("data", (data: Buffer) => {
|
|
329
|
-
stderr += data.toString();
|
|
330
|
-
this.#stderr(data);
|
|
331
|
-
});
|
|
332
|
-
return new Promise((resolve, reject) => {
|
|
333
|
-
proc.on("error", (error) => {
|
|
334
|
-
reject(
|
|
335
|
-
new CommandExecutionError({
|
|
336
|
-
command: modulePath,
|
|
337
|
-
args,
|
|
338
|
-
cwd,
|
|
339
|
-
code: null,
|
|
340
|
-
signal: null,
|
|
341
|
-
stdout,
|
|
342
|
-
stderr,
|
|
343
|
-
cause: error,
|
|
344
|
-
}),
|
|
345
|
-
);
|
|
346
|
-
});
|
|
347
|
-
proc.on("exit", (code, signal) => {
|
|
348
|
-
if (code || signal)
|
|
349
|
-
reject(
|
|
350
|
-
new CommandExecutionError({
|
|
351
|
-
command: modulePath,
|
|
352
|
-
args,
|
|
353
|
-
cwd,
|
|
354
|
-
code,
|
|
355
|
-
signal,
|
|
356
|
-
stdout,
|
|
357
|
-
stderr,
|
|
358
|
-
}),
|
|
359
|
-
);
|
|
360
|
-
else resolve({ code, signal });
|
|
361
|
-
});
|
|
362
|
-
});
|
|
232
|
+
fork(modulePath: string, args: string[] = [], options: ForkOptions = {}): Promise<unknown> {
|
|
233
|
+
const target = { command: modulePath, args, cwd: options.cwd?.toString() ?? this.cwdPath };
|
|
234
|
+
const proc = fork(modulePath, args, { cwd: this.cwdPath, ...options });
|
|
235
|
+
return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
|
|
363
236
|
}
|
|
364
237
|
getPath(filePath: string) {
|
|
365
238
|
if (path.isAbsolute(filePath)) return filePath;
|
|
@@ -400,16 +273,14 @@ export class Executor {
|
|
|
400
273
|
}
|
|
401
274
|
async getFilesAndDirs(dirPath: string): Promise<{ files: string[]; dirs: string[] }> {
|
|
402
275
|
const fullDirPath = this.getPath(dirPath);
|
|
403
|
-
const
|
|
404
|
-
const files = Array.from(
|
|
405
|
-
const
|
|
406
|
-
const allEntries = Array.from(dirGlob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
|
|
276
|
+
const glob = new Bun.Glob("*");
|
|
277
|
+
const files = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
|
|
278
|
+
const allEntries = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
|
|
407
279
|
const dirs = allEntries.filter((entry) => !files.includes(entry));
|
|
408
280
|
return { files, dirs };
|
|
409
281
|
}
|
|
410
282
|
async exists(filePath: string) {
|
|
411
|
-
|
|
412
|
-
return await FileSys.exists(readPath);
|
|
283
|
+
return await FileSys.exists(this.getPath(filePath));
|
|
413
284
|
}
|
|
414
285
|
async remove(filePath: string) {
|
|
415
286
|
const readPath = this.getPath(filePath);
|
|
@@ -418,8 +289,7 @@ export class Executor {
|
|
|
418
289
|
return this;
|
|
419
290
|
}
|
|
420
291
|
async removeDir(dirPath: string) {
|
|
421
|
-
//* XXX: `path.join(…, ".")` drops a trailing
|
|
422
|
-
//* and wipes its target, while `rm -rf link` only unlinks it.
|
|
292
|
+
//* XXX: `path.join(…, ".")` drops a trailing slash: `rm -rf link/` wipes the target, `rm -rf link` only unlinks.
|
|
423
293
|
const readPath = path.join(this.getPath(dirPath), ".");
|
|
424
294
|
if (await FileSys.entryExists(readPath)) await rm(readPath, { recursive: true, force: true });
|
|
425
295
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
@@ -433,8 +303,7 @@ export class Executor {
|
|
|
433
303
|
const writePath = this.getPath(filePath);
|
|
434
304
|
const dir = path.dirname(writePath);
|
|
435
305
|
if (!(await FileSys.dirExists(dir))) await mkdir(dir, { recursive: true });
|
|
436
|
-
//? Biome
|
|
437
|
-
//? loses a byte to `akan lint` and takes it back on the next `akan sync` — a permanent one-line git diff.
|
|
306
|
+
//? Biome ends every .json with a newline; without one, `akan lint` and `akan sync` would flip-flop the file.
|
|
438
307
|
let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}\n`;
|
|
439
308
|
|
|
440
309
|
if (await FileSys.fileExists(writePath)) {
|
|
@@ -461,12 +330,10 @@ export class Executor {
|
|
|
461
330
|
return { filePath, content };
|
|
462
331
|
}
|
|
463
332
|
async readFile(filePath: string) {
|
|
464
|
-
|
|
465
|
-
return await FileSys.readText(readPath);
|
|
333
|
+
return await FileSys.readText(this.getPath(filePath));
|
|
466
334
|
}
|
|
467
335
|
async readJson(filePath: string) {
|
|
468
|
-
|
|
469
|
-
return await FileSys.readJson<object>(readPath);
|
|
336
|
+
return await FileSys.readJson<object>(this.getPath(filePath));
|
|
470
337
|
}
|
|
471
338
|
async cp(srcPath: string, destPath: string, { dereference = false }: { dereference?: boolean } = {}) {
|
|
472
339
|
const src = this.getPath(srcPath);
|
|
@@ -474,9 +341,10 @@ export class Executor {
|
|
|
474
341
|
if (!(await FileSys.exists(src))) return;
|
|
475
342
|
const isDirectory = (await stat(src)).isDirectory();
|
|
476
343
|
if (!(await FileSys.exists(dest)) && isDirectory) await mkdir(dest, { recursive: true });
|
|
477
|
-
//* `cp -r` keeps symlinks on GNU
|
|
478
|
-
//*
|
|
479
|
-
if (dereference
|
|
344
|
+
//* `cp -r` keeps symlinks on GNU but follows them on macOS; Windows has no `cp -r <dir>/.` and rarely the
|
|
345
|
+
//* privilege a symlink needs, so it always copies real files.
|
|
346
|
+
if (dereference || process.platform === "win32")
|
|
347
|
+
await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
|
|
480
348
|
else await $`cp -r ${src}${isDirectory ? "/." : ""} ${dest}`;
|
|
481
349
|
}
|
|
482
350
|
log(msg: string) {
|
|
@@ -498,19 +366,10 @@ export class Executor {
|
|
|
498
366
|
#tsconfig: TsConfigJson | null = null;
|
|
499
367
|
async getTsConfig(pathname = "tsconfig.json", { refresh }: { refresh?: boolean } = {}): Promise<TsConfigJson> {
|
|
500
368
|
if (this.#tsconfig && !refresh) return this.#tsconfig;
|
|
501
|
-
|
|
369
|
+
let tsconfig = (await this.readJson(pathname)) as TsConfigJson;
|
|
502
370
|
if (tsconfig.extends) {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
...extendsTsconfig,
|
|
506
|
-
...tsconfig,
|
|
507
|
-
compilerOptions: {
|
|
508
|
-
...extendsTsconfig.compilerOptions,
|
|
509
|
-
...tsconfig.compilerOptions,
|
|
510
|
-
},
|
|
511
|
-
} as TsConfigJson;
|
|
512
|
-
this.#tsconfig = result;
|
|
513
|
-
return result;
|
|
371
|
+
const base = await this.getTsConfig(tsconfig.extends, { refresh });
|
|
372
|
+
tsconfig = { ...base, ...tsconfig, compilerOptions: { ...base.compilerOptions, ...tsconfig.compilerOptions } };
|
|
514
373
|
}
|
|
515
374
|
this.#tsconfig = tsconfig;
|
|
516
375
|
return tsconfig;
|
|
@@ -558,6 +417,11 @@ export class Executor {
|
|
|
558
417
|
dict: { [key: string]: string } = {},
|
|
559
418
|
options: { [key: string]: unknown } = {},
|
|
560
419
|
): Promise<FileContent | null> {
|
|
420
|
+
const convertPath = (target: string) =>
|
|
421
|
+
Object.entries(dict).reduce(
|
|
422
|
+
(converted, [key, value]) => converted.replace(new RegExp(`__${key}__`, "g"), value),
|
|
423
|
+
target,
|
|
424
|
+
);
|
|
561
425
|
if (targetPath.endsWith(".ts") || targetPath.endsWith(".tsx")) {
|
|
562
426
|
const getContent = (await import(templatePath)) as {
|
|
563
427
|
default: (
|
|
@@ -570,32 +434,20 @@ export class Executor {
|
|
|
570
434
|
if (result === null) return null;
|
|
571
435
|
const filename = typeof result === "object" ? result.filename : path.basename(targetPath).replace(".js", ".ts");
|
|
572
436
|
const content = typeof result === "object" ? result.content : result;
|
|
573
|
-
const
|
|
574
|
-
const convertedTargetPath = Object.entries(dict).reduce(
|
|
575
|
-
(path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
576
|
-
`${dirname}/${filename}`,
|
|
577
|
-
);
|
|
437
|
+
const convertedTargetPath = convertPath(`${path.dirname(targetPath)}/${filename}`);
|
|
578
438
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
579
439
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
580
440
|
} else if (targetPath.endsWith(".template")) {
|
|
581
441
|
const content = await FileSys.readText(templatePath);
|
|
582
|
-
const convertedTargetPath =
|
|
583
|
-
(path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
584
|
-
targetPath.slice(0, -9),
|
|
585
|
-
);
|
|
442
|
+
const convertedTargetPath = convertPath(targetPath.slice(0, -9));
|
|
586
443
|
const convertedContent = Object.entries(dict).reduce(
|
|
587
444
|
(data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value),
|
|
588
445
|
content,
|
|
589
446
|
);
|
|
590
447
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
591
|
-
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
592
|
-
overwrite,
|
|
593
|
-
});
|
|
448
|
+
return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
|
|
594
449
|
} else if (staticTemplateFileExtensions.has(path.extname(targetPath).toLowerCase())) {
|
|
595
|
-
const convertedTargetPath =
|
|
596
|
-
(path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
597
|
-
targetPath,
|
|
598
|
-
);
|
|
450
|
+
const convertedTargetPath = convertPath(targetPath);
|
|
599
451
|
const writePath = this.getPath(convertedTargetPath);
|
|
600
452
|
const dirname = path.dirname(writePath);
|
|
601
453
|
if (!(await FileSys.dirExists(dirname))) await mkdir(dirname, { recursive: true });
|
|
@@ -621,13 +473,11 @@ export class Executor {
|
|
|
621
473
|
}): Promise<FileContent[]> {
|
|
622
474
|
const templateRoot = await this.#resolveTemplateRoot();
|
|
623
475
|
const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
|
|
624
|
-
|
|
625
|
-
if ((await stat(prefixTemplatePath)).isFile()) {
|
|
626
|
-
const filename = path.basename(prefixTemplatePath);
|
|
476
|
+
if ((await stat(templatePath)).isFile()) {
|
|
627
477
|
const fileContent = await this.#applyTemplateFile(
|
|
628
478
|
{
|
|
629
|
-
templatePath
|
|
630
|
-
targetPath: path.join(basePath,
|
|
479
|
+
templatePath,
|
|
480
|
+
targetPath: path.join(basePath, path.basename(templatePath)),
|
|
631
481
|
scanInfo,
|
|
632
482
|
overwrite,
|
|
633
483
|
},
|
|
@@ -685,25 +535,13 @@ export class Executor {
|
|
|
685
535
|
options?: { [key: string]: unknown };
|
|
686
536
|
overwrite?: boolean;
|
|
687
537
|
}): Promise<FileContent[]> {
|
|
688
|
-
const
|
|
689
|
-
...(options.dict ?? {}),
|
|
690
|
-
...Object.fromEntries(
|
|
691
|
-
Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]),
|
|
692
|
-
),
|
|
693
|
-
};
|
|
694
|
-
const fileContents = await this._applyTemplate({ ...options, dict });
|
|
538
|
+
const fileContents = await this._applyTemplate({ ...options, dict: withCapitalizedDict(options.dict) });
|
|
695
539
|
await this.#formatAppliedTemplate(fileContents);
|
|
696
540
|
return fileContents;
|
|
697
541
|
}
|
|
698
542
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
* a model named Task and wrong for one named Zoo, and `organizeImports` fails `biome check` — so a
|
|
702
|
-
* scaffold that is not formatted on the way out is red for most model names, whatever the template says.
|
|
703
|
-
*
|
|
704
|
-
* Best-effort: `create-akan-workspace` scaffolds before `bun install`, so there is no local Biome binary
|
|
705
|
-
* and often no config above the target yet. An unformatted file is a lint fix; a failed scaffold is not.
|
|
706
|
-
*/
|
|
543
|
+
// A template cannot sort the identifiers it emits (`Task` vs `Zoo`). Best-effort: `create-akan-workspace`
|
|
544
|
+
// scaffolds before `bun install`, with no local Biome binary yet.
|
|
707
545
|
async #formatAppliedTemplate(fileContents: FileContent[]) {
|
|
708
546
|
const filePaths = fileContents
|
|
709
547
|
.map((fileContent) => fileContent.filePath)
|
|
@@ -716,9 +554,7 @@ export class Executor {
|
|
|
716
554
|
this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
|
|
717
555
|
}
|
|
718
556
|
}
|
|
719
|
-
// Async so `typescript` (~65MB resident)
|
|
720
|
-
// not by every process that imports an executor. `typeCheckAsync` below runs in a subprocess and
|
|
721
|
-
// never touches this path.
|
|
557
|
+
// Async so `typescript` (~65MB resident) loads only in the commands that typecheck.
|
|
722
558
|
async getTypeChecker() {
|
|
723
559
|
const { TypeChecker } = await import("./typeChecker");
|
|
724
560
|
this.typeChecker ??= new TypeChecker(this);
|
|
@@ -784,10 +620,10 @@ export class Executor {
|
|
|
784
620
|
filePath: string,
|
|
785
621
|
{ fix = false, dryRun = false }: { fix?: boolean; dryRun?: boolean } = {},
|
|
786
622
|
): Promise<{
|
|
787
|
-
results: unknown[];
|
|
623
|
+
results: unknown[];
|
|
788
624
|
message: string;
|
|
789
|
-
errors: unknown[];
|
|
790
|
-
warnings: unknown[];
|
|
625
|
+
errors: unknown[];
|
|
626
|
+
warnings: unknown[];
|
|
791
627
|
}> {
|
|
792
628
|
const path = this.getPath(filePath);
|
|
793
629
|
const linter = this.getLinter();
|
|
@@ -814,7 +650,6 @@ export class WorkspaceExecutor extends Executor {
|
|
|
814
650
|
this.repoName = repoName;
|
|
815
651
|
}
|
|
816
652
|
|
|
817
|
-
static #execs = new Map<string, WorkspaceExecutor>();
|
|
818
653
|
static fromRoot({
|
|
819
654
|
workspaceRoot = process.cwd(),
|
|
820
655
|
repoName = resolveRepoName(workspaceRoot),
|
|
@@ -822,7 +657,7 @@ export class WorkspaceExecutor extends Executor {
|
|
|
822
657
|
workspaceRoot?: string;
|
|
823
658
|
repoName?: string;
|
|
824
659
|
} = {}) {
|
|
825
|
-
return
|
|
660
|
+
return new WorkspaceExecutor({ workspaceRoot, repoName });
|
|
826
661
|
}
|
|
827
662
|
static getBaseDevEnv(envPath?: string) {
|
|
828
663
|
// Bun auto-loads .env, so we use process.env directly
|
|
@@ -871,11 +706,9 @@ export class WorkspaceExecutor extends Executor {
|
|
|
871
706
|
return await WorkspaceInfo.fromExecutor(this);
|
|
872
707
|
}
|
|
873
708
|
async getApps() {
|
|
874
|
-
if (!(await FileSys.dirExists(`${this.workspaceRoot}/apps`))) return [];
|
|
875
709
|
return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
|
|
876
710
|
}
|
|
877
711
|
async getLibs() {
|
|
878
|
-
if (!(await FileSys.dirExists(`${this.workspaceRoot}/libs`))) return [];
|
|
879
712
|
return await this.#getDirHasFile(`${this.workspaceRoot}/libs`, "akan.config.ts");
|
|
880
713
|
}
|
|
881
714
|
async getSyss() {
|
|
@@ -883,7 +716,6 @@ export class WorkspaceExecutor extends Executor {
|
|
|
883
716
|
return [appNames, libNames] as [string[], string[]];
|
|
884
717
|
}
|
|
885
718
|
async getPkgs() {
|
|
886
|
-
if (!(await FileSys.dirExists(`${this.workspaceRoot}/pkgs`))) return [];
|
|
887
719
|
return await this.#getDirHasFile(`${this.workspaceRoot}/pkgs`, "package.json");
|
|
888
720
|
}
|
|
889
721
|
async getExecs() {
|
|
@@ -946,13 +778,7 @@ export class WorkspaceExecutor extends Executor {
|
|
|
946
778
|
async hasChanges() {
|
|
947
779
|
return !!(await this.spawn("git", ["status", "--porcelain"])).trim();
|
|
948
780
|
}
|
|
949
|
-
/**
|
|
950
|
-
* Workspace-relative paths git knows about, sorted. `untracked` adds files that exist but are not
|
|
951
|
-
* committed yet, still honoring `.gitignore` — which is what a freshly copied library looks like.
|
|
952
|
-
*
|
|
953
|
-
* Reading the file set from git is what keeps generated barrels, the `page/**` and `public/libs`
|
|
954
|
-
* symlinks, env values and the lockfile out of it without any caller restating that list.
|
|
955
|
-
*/
|
|
781
|
+
/** Sorted workspace-relative paths; `untracked` adds uncommitted files, still honoring `.gitignore`. */
|
|
956
782
|
async listGitFiles(paths: string[], { untracked = false }: { untracked?: boolean } = {}) {
|
|
957
783
|
if (!paths.length) return [];
|
|
958
784
|
const mode = untracked ? ["--cached", "--others", "--exclude-standard"] : ["--cached"];
|
|
@@ -963,15 +789,16 @@ export class WorkspaceExecutor extends Executor {
|
|
|
963
789
|
.sort();
|
|
964
790
|
}
|
|
965
791
|
async #getDirHasFile(basePath: string, targetFilename: string) {
|
|
966
|
-
|
|
792
|
+
if (!(await FileSys.dirExists(basePath))) return [];
|
|
793
|
+
//? `local/` is gitignored scratch: a test's temporary workspace there has a package.json of its own.
|
|
794
|
+
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit", "local"];
|
|
967
795
|
const getDirs = async (dirname: string, maxDepth = 3, results: string[] = [], prefix = "") => {
|
|
968
796
|
const dirs = await this.readdir(dirname);
|
|
969
797
|
await Promise.all(
|
|
970
798
|
dirs.map(async (dir) => {
|
|
971
799
|
if (AVOID_DIRS.includes(dir)) return;
|
|
972
800
|
const dirPath = path.join(dirname, dir);
|
|
973
|
-
//*
|
|
974
|
-
//* this runs for `getApps`, so throwing here would break every command until it is repaired.
|
|
801
|
+
//* Not `stat`: a dangling symlink would throw, and this walk backs `getApps`, i.e. every command.
|
|
975
802
|
if (await FileSys.dirExists(dirPath)) {
|
|
976
803
|
const hasTargetFile = await FileSys.fileExists(path.join(dirPath, targetFilename));
|
|
977
804
|
if (hasTargetFile) results.push(`${prefix}${dir}`);
|
|
@@ -981,50 +808,34 @@ export class WorkspaceExecutor extends Executor {
|
|
|
981
808
|
);
|
|
982
809
|
return results;
|
|
983
810
|
};
|
|
984
|
-
return await getDirs(basePath);
|
|
811
|
+
return (await getDirs(basePath)).sort((a, b) => a.localeCompare(b));
|
|
985
812
|
}
|
|
986
813
|
|
|
987
|
-
async
|
|
814
|
+
async #fromEverySys<T>(read: (sys: SysExecutor) => Promise<T[]>): Promise<T[]> {
|
|
988
815
|
const [appNames, libNames] = await this.getSyss();
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
).flat(),
|
|
996
|
-
];
|
|
997
|
-
return scalarConstantExampleFiles;
|
|
816
|
+
const fromApps = await Promise.all(appNames.map((appName) => read(AppExecutor.from(this, appName))));
|
|
817
|
+
const fromLibs = await Promise.all(libNames.map((libName) => read(LibExecutor.from(this, libName))));
|
|
818
|
+
return [...fromApps.flat(), ...fromLibs.flat()];
|
|
819
|
+
}
|
|
820
|
+
async getScalarConstantFiles() {
|
|
821
|
+
return await this.#fromEverySys((sys) => sys.getScalarConstantFiles());
|
|
998
822
|
}
|
|
999
823
|
async getConstantFiles() {
|
|
1000
|
-
|
|
1001
|
-
const moduleConstantExampleFiles = [
|
|
1002
|
-
...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getConstantFiles()))).flat(),
|
|
1003
|
-
...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getConstantFiles()))).flat(),
|
|
1004
|
-
];
|
|
1005
|
-
return moduleConstantExampleFiles;
|
|
824
|
+
return await this.#fromEverySys((sys) => sys.getConstantFiles());
|
|
1006
825
|
}
|
|
1007
826
|
async getDictionaryFiles() {
|
|
1008
|
-
|
|
1009
|
-
const moduleDictionaryExampleFiles = [
|
|
1010
|
-
...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getDictionaryFiles()))).flat(),
|
|
1011
|
-
...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getDictionaryFiles()))).flat(),
|
|
1012
|
-
];
|
|
1013
|
-
return moduleDictionaryExampleFiles;
|
|
827
|
+
return await this.#fromEverySys((sys) => sys.getDictionaryFiles());
|
|
1014
828
|
}
|
|
1015
829
|
async getViewFiles() {
|
|
1016
|
-
|
|
1017
|
-
const viewExampleFiles = [
|
|
1018
|
-
...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getViewsSourceCode()))).flat(),
|
|
1019
|
-
...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getViewsSourceCode()))).flat(),
|
|
1020
|
-
];
|
|
1021
|
-
return viewExampleFiles;
|
|
830
|
+
return await this.#fromEverySys((sys) => sys.getViewsSourceCode());
|
|
1022
831
|
}
|
|
1023
832
|
}
|
|
1024
833
|
|
|
1025
|
-
interface
|
|
834
|
+
interface NamedExecutorOptions {
|
|
1026
835
|
workspace?: WorkspaceExecutor;
|
|
1027
836
|
name: string;
|
|
837
|
+
}
|
|
838
|
+
interface SysExecutorOptions extends NamedExecutorOptions {
|
|
1028
839
|
type: "app" | "lib";
|
|
1029
840
|
}
|
|
1030
841
|
|
|
@@ -1093,29 +904,12 @@ export class SysExecutor extends Executor {
|
|
|
1093
904
|
options: { exec: this, facet },
|
|
1094
905
|
}),
|
|
1095
906
|
),
|
|
1096
|
-
...
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}),
|
|
1103
|
-
),
|
|
1104
|
-
...scanInfo.getServiceModules().map((model) =>
|
|
1105
|
-
this._applyTemplate({
|
|
1106
|
-
basePath: `lib/_${model}`,
|
|
1107
|
-
template: "moduleRoot",
|
|
1108
|
-
scanInfo,
|
|
1109
|
-
dict: { model, Model: capitalize(model) },
|
|
1110
|
-
}),
|
|
1111
|
-
),
|
|
1112
|
-
...scanInfo.getScalarModules().map((model) =>
|
|
1113
|
-
this._applyTemplate({
|
|
1114
|
-
basePath: `lib/__scalar/${model}`,
|
|
1115
|
-
template: "moduleRoot",
|
|
1116
|
-
scanInfo,
|
|
1117
|
-
dict: { model, Model: capitalize(model) },
|
|
1118
|
-
}),
|
|
907
|
+
...[
|
|
908
|
+
...scanInfo.getDatabaseModules().map((model) => [model, `lib/${model}`]),
|
|
909
|
+
...scanInfo.getServiceModules().map((model) => [model, `lib/_${model}`]),
|
|
910
|
+
...scanInfo.getScalarModules().map((model) => [model, `lib/__scalar/${model}`]),
|
|
911
|
+
].map(([model, basePath]) =>
|
|
912
|
+
this._applyTemplate({ basePath, template: "moduleRoot", scanInfo, dict: { model, Model: capitalize(model) } }),
|
|
1119
913
|
),
|
|
1120
914
|
];
|
|
1121
915
|
}
|
|
@@ -1137,9 +931,7 @@ export class SysExecutor extends Executor {
|
|
|
1137
931
|
: await LibInfo.fromExecutor(this as unknown as LibExecutor, {
|
|
1138
932
|
refresh,
|
|
1139
933
|
});
|
|
1140
|
-
//* `writeLib`
|
|
1141
|
-
//* regenerates the same ones — so this region races the other dev servers in the workspace and the
|
|
1142
|
-
//* builders that watch what it writes.
|
|
934
|
+
//* Locked: `writeLib` rewrites dependency-lib barrels that other dev servers regenerate and watch too.
|
|
1143
935
|
if (write)
|
|
1144
936
|
await CodegenLock.run(this.workspace.workspaceRoot, `scan:${this.name}`, async () => {
|
|
1145
937
|
await Promise.all(this.#getScanTemplateTasks(scanInfo));
|
|
@@ -1156,11 +948,7 @@ export class SysExecutor extends Executor {
|
|
|
1156
948
|
this.#scanInfo = scanInfo;
|
|
1157
949
|
return scanInfo;
|
|
1158
950
|
}
|
|
1159
|
-
/**
|
|
1160
|
-
* 스코프 에이전트 색인(apps|libs/<name>/AGENTS.md) 재생성 — own + 의존 lib 레시피만 싣는다(프레임워크
|
|
1161
|
-
* 레시피는 루트 AGENTS.md 소관). scan(write) 경로에 물려 있어 sync/build/start 어디를 지나도 갱신되고,
|
|
1162
|
-
* `akan lint` 가 같은 렌더 결과와 비교해 신선도를 강제한다. 마커 밖 내용은 사용자 소유라 보존한다.
|
|
1163
|
-
*/
|
|
951
|
+
/** Rewrites only the marker block of the scoped AGENTS.md; content outside the markers belongs to the user. */
|
|
1164
952
|
async syncAgentsIndex(scanInfo?: AppInfo | LibInfo) {
|
|
1165
953
|
const info = scanInfo ?? (await this.scan({ write: false }));
|
|
1166
954
|
const scope = { type: this.type, name: this.name };
|
|
@@ -1215,69 +1003,49 @@ export class SysExecutor extends Executor {
|
|
|
1215
1003
|
}
|
|
1216
1004
|
|
|
1217
1005
|
async getDatabaseModules() {
|
|
1218
|
-
|
|
1219
|
-
.filter((name) => !name.startsWith("_") && !name.
|
|
1220
|
-
.filter((name) =>
|
|
1221
|
-
return databaseModules;
|
|
1006
|
+
return (await this.readdir("lib"))
|
|
1007
|
+
.filter((name) => !name.startsWith("_") && !name.endsWith(".ts"))
|
|
1008
|
+
.filter((name) => existsSync(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
|
|
1222
1009
|
}
|
|
1223
1010
|
|
|
1224
1011
|
async getServiceModules() {
|
|
1225
|
-
|
|
1012
|
+
return (await this.readdir("lib"))
|
|
1226
1013
|
.filter((name) => name.startsWith("_") && !name.startsWith("__"))
|
|
1227
|
-
.filter((name) =>
|
|
1228
|
-
return serviceModules;
|
|
1014
|
+
.filter((name) => existsSync(`${this.cwdPath}/lib/${name}/${name.slice(1)}.service.ts`));
|
|
1229
1015
|
}
|
|
1230
1016
|
|
|
1231
1017
|
async getScalarModules() {
|
|
1232
|
-
|
|
1018
|
+
return (await this.readdir("lib/__scalar"))
|
|
1233
1019
|
.filter((name) => !name.startsWith("_"))
|
|
1234
|
-
.filter((name) =>
|
|
1235
|
-
return scalarModules;
|
|
1020
|
+
.filter((name) => existsSync(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
|
|
1236
1021
|
}
|
|
1237
1022
|
|
|
1023
|
+
async #getComponentModules(role: "View" | "Unit" | "Template") {
|
|
1024
|
+
return (await this.readdir("lib"))
|
|
1025
|
+
.filter((name) => !name.startsWith("_") && !name.endsWith(".ts"))
|
|
1026
|
+
.filter((name) => existsSync(`${this.cwdPath}/lib/${name}/${capitalize(name)}.${role}.tsx`));
|
|
1027
|
+
}
|
|
1028
|
+
async #getComponentSources(role: "View" | "Unit" | "Template") {
|
|
1029
|
+
const modules = await this.#getComponentModules(role);
|
|
1030
|
+
return Promise.all(modules.map((name) => this.getLocalFile(`lib/${name}/${capitalize(name)}.${role}.tsx`)));
|
|
1031
|
+
}
|
|
1238
1032
|
async getViewComponents() {
|
|
1239
|
-
|
|
1240
|
-
.filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
|
|
1241
|
-
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.View.tsx`).exists());
|
|
1242
|
-
return viewComponents;
|
|
1033
|
+
return await this.#getComponentModules("View");
|
|
1243
1034
|
}
|
|
1244
|
-
|
|
1245
1035
|
async getUnitComponents() {
|
|
1246
|
-
|
|
1247
|
-
.filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
|
|
1248
|
-
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Unit.tsx`).exists());
|
|
1249
|
-
return unitComponents;
|
|
1036
|
+
return await this.#getComponentModules("Unit");
|
|
1250
1037
|
}
|
|
1251
1038
|
async getTemplateComponents() {
|
|
1252
|
-
|
|
1253
|
-
.filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
|
|
1254
|
-
.filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Template.tsx`).exists());
|
|
1255
|
-
return templateComponents;
|
|
1039
|
+
return await this.#getComponentModules("Template");
|
|
1256
1040
|
}
|
|
1257
|
-
|
|
1258
1041
|
async getViewsSourceCode() {
|
|
1259
|
-
|
|
1260
|
-
return Promise.all(
|
|
1261
|
-
viewComponents.map((viewComponent) =>
|
|
1262
|
-
this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`),
|
|
1263
|
-
),
|
|
1264
|
-
);
|
|
1042
|
+
return await this.#getComponentSources("View");
|
|
1265
1043
|
}
|
|
1266
1044
|
async getUnitsSourceCode() {
|
|
1267
|
-
|
|
1268
|
-
return Promise.all(
|
|
1269
|
-
unitComponents.map((unitComponent) =>
|
|
1270
|
-
this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`),
|
|
1271
|
-
),
|
|
1272
|
-
);
|
|
1045
|
+
return await this.#getComponentSources("Unit");
|
|
1273
1046
|
}
|
|
1274
1047
|
async getTemplatesSourceCode() {
|
|
1275
|
-
|
|
1276
|
-
return Promise.all(
|
|
1277
|
-
templateComponents.map((templateComponent) =>
|
|
1278
|
-
this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`),
|
|
1279
|
-
),
|
|
1280
|
-
);
|
|
1048
|
+
return await this.#getComponentSources("Template");
|
|
1281
1049
|
}
|
|
1282
1050
|
|
|
1283
1051
|
async getScalarConstantFiles() {
|
|
@@ -1292,7 +1060,9 @@ export class SysExecutor extends Executor {
|
|
|
1292
1060
|
async getScalarDictionaryFiles() {
|
|
1293
1061
|
const scalarModules = await this.getScalarModules();
|
|
1294
1062
|
return Promise.all(
|
|
1295
|
-
scalarModules.map((scalarModule) =>
|
|
1063
|
+
scalarModules.map((scalarModule) =>
|
|
1064
|
+
this.getLocalFile(`lib/__scalar/${scalarModule}/${scalarModule}.dictionary.ts`),
|
|
1065
|
+
),
|
|
1296
1066
|
);
|
|
1297
1067
|
}
|
|
1298
1068
|
|
|
@@ -1326,30 +1096,15 @@ export class SysExecutor extends Executor {
|
|
|
1326
1096
|
dict?: { [key: string]: string };
|
|
1327
1097
|
overwrite?: boolean;
|
|
1328
1098
|
}): Promise<FileContent[]> {
|
|
1329
|
-
const dict = {
|
|
1330
|
-
...(options.dict ?? {}),
|
|
1331
|
-
...Object.fromEntries(
|
|
1332
|
-
Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]),
|
|
1333
|
-
),
|
|
1334
|
-
};
|
|
1335
1099
|
const scanInfo = await this.scan();
|
|
1336
|
-
const fileContents = await this._applyTemplate({
|
|
1337
|
-
...options,
|
|
1338
|
-
scanInfo,
|
|
1339
|
-
dict,
|
|
1340
|
-
});
|
|
1100
|
+
const fileContents = await this._applyTemplate({ ...options, scanInfo, dict: withCapitalizedDict(options.dict) });
|
|
1341
1101
|
await this.scan();
|
|
1342
1102
|
return fileContents;
|
|
1343
1103
|
}
|
|
1344
1104
|
}
|
|
1345
1105
|
|
|
1346
|
-
interface AppExecutorOptions {
|
|
1347
|
-
workspace?: WorkspaceExecutor;
|
|
1348
|
-
name: string;
|
|
1349
|
-
}
|
|
1350
1106
|
export class AppExecutor extends SysExecutor {
|
|
1351
|
-
// `typescript`
|
|
1352
|
-
// ever imports an executor. Route validation is the sole consumer here and is already async.
|
|
1107
|
+
// Lazy: the validator pulls in `typescript` (~65MB resident).
|
|
1353
1108
|
static #routeSourceValidator: typeof import("./routeSourceValidator").RouteSourceValidator | null = null;
|
|
1354
1109
|
static async #getRouteSourceValidator() {
|
|
1355
1110
|
AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator")).RouteSourceValidator;
|
|
@@ -1357,29 +1112,31 @@ export class AppExecutor extends SysExecutor {
|
|
|
1357
1112
|
}
|
|
1358
1113
|
dist: Executor;
|
|
1359
1114
|
override emoji = execEmoji.app;
|
|
1360
|
-
constructor({ workspace, name }:
|
|
1115
|
+
constructor({ workspace, name }: NamedExecutorOptions) {
|
|
1361
1116
|
super({ workspace, name, type: "app" });
|
|
1362
1117
|
this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/apps/${name}`);
|
|
1363
1118
|
}
|
|
1364
|
-
static #execs = new Map<string, AppExecutor>();
|
|
1365
1119
|
static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1120
|
+
return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
1121
|
+
}
|
|
1122
|
+
//* Not dev output: native builds and the update releases waiting to be uploaded, and the downloaded `bin` sources.
|
|
1123
|
+
static readonly #keptOnStart = [path.join(".akan", "native"), path.join(".akan", "cache", "bin")];
|
|
1124
|
+
async #removeDevOutput(dir = ".akan") {
|
|
1125
|
+
const entries = await readDirEntries(this.getPath(dir)).catch(() => [] as string[]);
|
|
1126
|
+
await Promise.all(
|
|
1127
|
+
entries.map(async (name) => {
|
|
1128
|
+
const entry = path.join(dir, name);
|
|
1129
|
+
if (AppExecutor.#keptOnStart.includes(entry)) return;
|
|
1130
|
+
if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path.sep}`)))
|
|
1131
|
+
return await this.#removeDevOutput(entry);
|
|
1132
|
+
await this.removeDir(entry);
|
|
1133
|
+
}),
|
|
1134
|
+
);
|
|
1370
1135
|
}
|
|
1371
1136
|
getEnv() {
|
|
1372
1137
|
return WorkspaceExecutor.getBaseDevEnv().env;
|
|
1373
1138
|
}
|
|
1374
|
-
/**
|
|
1375
|
-
* This app's dev port, derived from its position in the sorted `apps/` listing so several apps can run
|
|
1376
|
-
* at once without colliding.
|
|
1377
|
-
*
|
|
1378
|
-
* `AKAN_DEV_PORT` pins it instead, because the derived value *moves*: the index shifts whenever any other
|
|
1379
|
-
* app directory appears or disappears, and a dev host recomputes this on every restart. So a session that
|
|
1380
|
-
* adds an app relands its dev server on a different port, and anything that reserved a port relative to
|
|
1381
|
-
* the old one is left pointing at nothing.
|
|
1382
|
-
*/
|
|
1139
|
+
/** `AKAN_DEV_PORT` pins it; the derived port moves whenever an app directory appears or disappears. */
|
|
1383
1140
|
async getDevPort() {
|
|
1384
1141
|
const pinned = Number(process.env.AKAN_DEV_PORT);
|
|
1385
1142
|
if (Number.isInteger(pinned) && pinned > 0 && pinned <= 65_535) return pinned;
|
|
@@ -1390,10 +1147,8 @@ export class AppExecutor extends SysExecutor {
|
|
|
1390
1147
|
return basePort + appIndex + portOffset;
|
|
1391
1148
|
}
|
|
1392
1149
|
getCommandEnv(env: Record<string, string> = {}): Record<string, string> {
|
|
1393
|
-
const
|
|
1394
|
-
const
|
|
1395
|
-
const PORT = (basePort + portOffset).toString();
|
|
1396
|
-
const AKAN_PUBLIC_SERVER_PORT = portOffset ? (8282 + portOffset).toString() : undefined;
|
|
1150
|
+
const { portOffset } = WorkspaceExecutor.getBaseDevEnv();
|
|
1151
|
+
const PORT = (8282 + portOffset).toString();
|
|
1397
1152
|
return {
|
|
1398
1153
|
...process.env,
|
|
1399
1154
|
AKAN_PUBLIC_APP_NAME: this.name,
|
|
@@ -1401,48 +1156,48 @@ export class AppExecutor extends SysExecutor {
|
|
|
1401
1156
|
NODE_NO_WARNINGS: "1",
|
|
1402
1157
|
PORT,
|
|
1403
1158
|
AKAN_PUBLIC_CLIENT_PORT: PORT,
|
|
1404
|
-
...(
|
|
1159
|
+
...(portOffset ? { AKAN_PUBLIC_SERVER_PORT: PORT } : {}),
|
|
1405
1160
|
...env,
|
|
1406
1161
|
};
|
|
1407
1162
|
}
|
|
1163
|
+
async getDatabaseModeEnv() {
|
|
1164
|
+
const akanConfig = await this.getConfig();
|
|
1165
|
+
return {
|
|
1166
|
+
AKAN_DATABASE_MODE: akanConfig.resolveDatabaseMode(),
|
|
1167
|
+
AKAN_DATABASE_MODES: akanConfig.database.modes.join(","),
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1408
1170
|
async prepareCommand(type: "build" | "start") {
|
|
1409
1171
|
const akanConfig = await this.getConfig();
|
|
1410
|
-
const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
|
|
1411
1172
|
const routeEnv = {
|
|
1412
1173
|
AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
|
|
1413
1174
|
AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
|
|
1414
1175
|
AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
|
|
1415
|
-
|
|
1176
|
+
...(await this.getDatabaseModeEnv()),
|
|
1416
1177
|
};
|
|
1417
1178
|
Object.assign(process.env, routeEnv);
|
|
1418
1179
|
if (type === "build") {
|
|
1419
|
-
//* `scanSync`
|
|
1420
|
-
//* generated against and typechecked. Drop the cache so the build phases re-read it without them.
|
|
1180
|
+
//* `scanSync` read the route set unfiltered; drop the cache so build phases re-read it without dev-only routes.
|
|
1421
1181
|
this.#excludeDevOnlyPages = true;
|
|
1422
1182
|
this.#pageKeys = null;
|
|
1423
1183
|
await this.dist.removeDir(this.dist.cwdPath);
|
|
1424
1184
|
await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
|
|
1425
|
-
//* Lib assets are symlinks
|
|
1426
|
-
//*
|
|
1427
|
-
//* `public/` has exactly one reader — the web router's catch-all — so an api-only build leaves it out.
|
|
1185
|
+
//* Lib assets are symlinks, which neither the docker context nor the release tarball follows out of dist.
|
|
1186
|
+
//* Only the web router reads `public/`, so an api-only build leaves it out.
|
|
1428
1187
|
await Promise.all([
|
|
1429
1188
|
this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
|
|
1430
1189
|
...(akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []),
|
|
1431
1190
|
]);
|
|
1432
1191
|
} else {
|
|
1433
|
-
await this
|
|
1434
|
-
//? `
|
|
1435
|
-
//? builder is also the file watcher, so switching it off would take server-code HMR with it.
|
|
1192
|
+
await this.#removeDevOutput();
|
|
1193
|
+
//? `akan start` keeps the full dev surface: the incremental builder is also the file watcher.
|
|
1436
1194
|
if (!akanConfig.web.ssr || !akanConfig.web.csr)
|
|
1437
1195
|
this.logger.verbose(
|
|
1438
1196
|
`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`,
|
|
1439
1197
|
);
|
|
1440
1198
|
}
|
|
1441
|
-
//?
|
|
1442
|
-
//?
|
|
1443
|
-
//? and a dev server that believes it is production serves from the production route cache, whose every
|
|
1444
|
-
//? route throws because a dev artifact carries no routes manifest. Written into `process.env` and not only
|
|
1445
|
-
//? into the child env because the builder runs here and bakes NODE_ENV into the bundles it emits.
|
|
1199
|
+
//? Bun auto-loads `.env`, so NODE_ENV=production can arrive unexported and send the dev server to the production
|
|
1200
|
+
//? route cache. Set on `process.env` because the builder runs here and bakes NODE_ENV into its bundles.
|
|
1446
1201
|
if (type === "start") process.env.NODE_ENV = "development";
|
|
1447
1202
|
const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
|
|
1448
1203
|
const env = this.getCommandEnv({
|
|
@@ -1450,18 +1205,14 @@ export class AppExecutor extends SysExecutor {
|
|
|
1450
1205
|
...routeEnv,
|
|
1451
1206
|
...(devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}),
|
|
1452
1207
|
});
|
|
1453
|
-
// `
|
|
1454
|
-
//
|
|
1455
|
-
// which filters to AKAN_PUBLIC_*) sees AKAN_PUBLIC_APP_NAME — otherwise SSR throws
|
|
1456
|
-
// "environment variable AKAN_PUBLIC_APP_NAME is required". Only AKAN_PUBLIC_* is baked into bundles, so
|
|
1457
|
-
// this does not leak non-public env.
|
|
1458
|
-
// The port keys are this machine's dev allocation, and `define` turns an `AKAN_PUBLIC_*` into a literal the
|
|
1459
|
-
// artifact can never be run with a different value for — a baked port would outrank the `PORT` the container
|
|
1460
|
-
// is started with and send every SSR self-call to a port nothing bound.
|
|
1208
|
+
// `build` runs its phases in this process, so the env is published here (only AKAN_PUBLIC_* reaches bundles).
|
|
1209
|
+
// Ports and the operation mode are dropped: `define` would bake them in and outrank the container's own values.
|
|
1461
1210
|
if (type === "build") {
|
|
1462
1211
|
const buildEnv = { ...env };
|
|
1463
1212
|
delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
|
|
1464
1213
|
delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
|
|
1214
|
+
delete buildEnv.AKAN_PUBLIC_OPERATION_MODE;
|
|
1215
|
+
delete process.env.AKAN_PUBLIC_OPERATION_MODE;
|
|
1465
1216
|
Object.assign(process.env, buildEnv);
|
|
1466
1217
|
}
|
|
1467
1218
|
return { env };
|
|
@@ -1470,14 +1221,15 @@ export class AppExecutor extends SysExecutor {
|
|
|
1470
1221
|
getPublicEnv(...patterns: string[]) {
|
|
1471
1222
|
if (this.#publicEnv) return this.#publicEnv;
|
|
1472
1223
|
const searchPatterns = [...patterns, "AKAN_PUBLIC_*"];
|
|
1473
|
-
const regexes = searchPatterns.map(
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1224
|
+
const regexes = searchPatterns.map(
|
|
1225
|
+
(pattern) =>
|
|
1226
|
+
new RegExp(
|
|
1227
|
+
`^${pattern
|
|
1228
|
+
.split("*")
|
|
1229
|
+
.map((part) => part.replace(/[.+^${}()|[\]\\?]/g, "\\$&"))
|
|
1230
|
+
.join(".*")}$`,
|
|
1231
|
+
),
|
|
1232
|
+
);
|
|
1481
1233
|
const publicEnv: Record<string, string> = {};
|
|
1482
1234
|
for (const [k, v] of Object.entries(process.env)) {
|
|
1483
1235
|
if (typeof v !== "string") continue;
|
|
@@ -1490,14 +1242,11 @@ export class AppExecutor extends SysExecutor {
|
|
|
1490
1242
|
#akanConfig: AkanAppConfig | null = null;
|
|
1491
1243
|
override async getConfig({ refresh }: { refresh?: boolean } = {}) {
|
|
1492
1244
|
if (this.#akanConfig && !refresh) return this.#akanConfig;
|
|
1493
|
-
// A refresh means the config file may have been edited; bust the import cache so the fresh
|
|
1494
|
-
// module is evaluated instead of Bun's cached instance.
|
|
1495
1245
|
this.#akanConfig = await AkanAppConfig.from(this, { bustImportCache: refresh });
|
|
1496
1246
|
return this.#akanConfig;
|
|
1497
1247
|
}
|
|
1498
1248
|
|
|
1499
1249
|
#pageKeys: string[] | null = null;
|
|
1500
|
-
/** Set once `prepareCommand("build")` runs, so every later consumer of the route set agrees on it. */
|
|
1501
1250
|
#excludeDevOnlyPages = false;
|
|
1502
1251
|
async getPageKeys({ refresh }: { refresh?: boolean } = {}): Promise<string[]> {
|
|
1503
1252
|
if (this.#pageKeys && !refresh) return this.#pageKeys;
|
|
@@ -1507,7 +1256,9 @@ export class AppExecutor extends SysExecutor {
|
|
|
1507
1256
|
const owners = new Map<string, { absPath: string; fromLib: boolean }>();
|
|
1508
1257
|
const devOnlyKeys = new Set<string>();
|
|
1509
1258
|
const devOnlyDirs: string[] = [];
|
|
1510
|
-
|
|
1259
|
+
const pageRoots = await this.getPageRoots();
|
|
1260
|
+
const libKeyPrefixes = pageRoots.map((root) => root.keyPrefix).filter(Boolean);
|
|
1261
|
+
for (const root of pageRoots) {
|
|
1511
1262
|
if (!(await FileSys.dirExists(root.dir))) continue;
|
|
1512
1263
|
for await (const rel of glob.scan({
|
|
1513
1264
|
cwd: root.dir,
|
|
@@ -1517,6 +1268,8 @@ export class AppExecutor extends SysExecutor {
|
|
|
1517
1268
|
const segments = rel.split(path.sep);
|
|
1518
1269
|
if (segments.some((s) => s === "node_modules")) continue;
|
|
1519
1270
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
1271
|
+
//* A lib folder that is a copy rather than a link (the Windows fallback) is walked into from `page` too.
|
|
1272
|
+
if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix))) continue;
|
|
1520
1273
|
const absPath = path.join(root.dir, ...segments);
|
|
1521
1274
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
1522
1275
|
if (!isRouteSourceFile(posix)) continue;
|
|
@@ -1532,8 +1285,7 @@ export class AppExecutor extends SysExecutor {
|
|
|
1532
1285
|
const fromLib = !!root.keyPrefix;
|
|
1533
1286
|
const routeId = `${parsed.kind}:${parsed.pattern}`;
|
|
1534
1287
|
const owner = owners.get(routeId);
|
|
1535
|
-
//* App-owned routes
|
|
1536
|
-
//* collision once a synced lib is involved, where neither side can see the other.
|
|
1288
|
+
//* App-owned routes may collide (two groups, one pattern); a synced lib cannot see the other side.
|
|
1537
1289
|
if (owner && (owner.fromLib || fromLib)) {
|
|
1538
1290
|
throw new Error(
|
|
1539
1291
|
`[route-convention] duplicate ${parsed.kind} route "${parsed.pattern}" in app "${this.name}":\n- ${owner.absPath}\n- ${absPath}`,
|
|
@@ -1552,8 +1304,7 @@ export class AppExecutor extends SysExecutor {
|
|
|
1552
1304
|
});
|
|
1553
1305
|
if (info.devOnly) {
|
|
1554
1306
|
devOnlyKeys.add(key);
|
|
1555
|
-
//* A
|
|
1556
|
-
//* pages behind would ship them stripped of the chrome they were written under.
|
|
1307
|
+
//* A dev-only layout takes its whole subtree, or its pages would ship without their chrome.
|
|
1557
1308
|
if (parsed.kind === "layout") devOnlyDirs.push(key.replace(/[^/]+$/, ""));
|
|
1558
1309
|
}
|
|
1559
1310
|
}
|
|
@@ -1571,12 +1322,7 @@ export class AppExecutor extends SysExecutor {
|
|
|
1571
1322
|
this.verbose(`[route] excluded ${dropped.length} dev-only route file(s) from the build: ${dropped.join(", ")}`);
|
|
1572
1323
|
return pageKeys.filter((key) => !isDevOnly(key));
|
|
1573
1324
|
}
|
|
1574
|
-
/**
|
|
1575
|
-
* Every directory that contributes route files, as `page`-relative key prefixes. Lib roots are the
|
|
1576
|
-
* symlinks `syncPages` created, so route keys stay app-relative while `realDir` is what a file watcher
|
|
1577
|
-
* reports. Enumeration never crosses a symlink (neither Bun's glob nor TypeScript's `include` does),
|
|
1578
|
-
* which is why linked page folders have to be listed here instead of found by walking `page`.
|
|
1579
|
-
*/
|
|
1325
|
+
/** Lib roots are the `syncPages` symlinks, listed because neither Bun's glob nor TS `include` crosses one. */
|
|
1580
1326
|
async getPageRoots(): Promise<PageRoot[]> {
|
|
1581
1327
|
const akanConfig = await this.getConfig();
|
|
1582
1328
|
const pageDir = `${this.cwdPath}/page`;
|
|
@@ -1597,18 +1343,16 @@ export class AppExecutor extends SysExecutor {
|
|
|
1597
1343
|
}
|
|
1598
1344
|
|
|
1599
1345
|
static readonly #pageLibsDir = "(libs)";
|
|
1600
|
-
/** Where `(libs)` may live: once at the page root, or once per basePath when the app declares subRoutes. */
|
|
1601
1346
|
static #pageLibParents(basePaths: Iterable<string>): string[] {
|
|
1602
1347
|
const parents = [...basePaths].map((basePath) => `${basePath}/`);
|
|
1603
1348
|
return parents.length ? parents : [""];
|
|
1604
1349
|
}
|
|
1605
|
-
/**
|
|
1350
|
+
/** Whether the linked page set changed, which makes the app's route keys stale. */
|
|
1606
1351
|
async syncPages(libDeps: string[]): Promise<boolean> {
|
|
1607
1352
|
const akanConfig = await this.getConfig();
|
|
1608
1353
|
const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
|
|
1609
1354
|
const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
|
|
1610
|
-
//*
|
|
1611
|
-
//* exactly the ones a sync has to clean up.
|
|
1355
|
+
//* Not `getPageRoots`: it drops links whose target is gone, exactly the ones a sync must clean up.
|
|
1612
1356
|
const linked = (
|
|
1613
1357
|
await Promise.all(
|
|
1614
1358
|
parents.map(async (parent) => {
|
|
@@ -1619,7 +1363,11 @@ export class AppExecutor extends SysExecutor {
|
|
|
1619
1363
|
)
|
|
1620
1364
|
).flat();
|
|
1621
1365
|
const wanted = parents.flatMap((parent) => libs.map((lib) => `${parent}${AppExecutor.#pageLibsDir}/(${lib})/`));
|
|
1622
|
-
|
|
1366
|
+
//* A copy — the Windows fallback when a junction is refused — does not follow its lib, so only links are current.
|
|
1367
|
+
const isLink = async (key: string) =>
|
|
1368
|
+
(await lstat(`${this.cwdPath}/page/${key.slice(0, -1)}`).catch(() => null))?.isSymbolicLink() ?? false;
|
|
1369
|
+
if (linked.sort().join(",") === wanted.sort().join(",") && (await Promise.all(linked.map(isLink))).every(Boolean))
|
|
1370
|
+
return false;
|
|
1623
1371
|
await Promise.all(
|
|
1624
1372
|
parents.map((parent) => this.removeDir(`${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`)),
|
|
1625
1373
|
);
|
|
@@ -1674,8 +1422,7 @@ export class AppExecutor extends SysExecutor {
|
|
|
1674
1422
|
);
|
|
1675
1423
|
}
|
|
1676
1424
|
static async #linkLibAsset(targetPath: string, linkPath: string) {
|
|
1677
|
-
//*
|
|
1678
|
-
//* Windows junctions are the exception and resolve their target as an absolute path.
|
|
1425
|
+
//* Relative, so it survives a remount (containers, CI); a Windows junction needs an absolute target.
|
|
1679
1426
|
const isWindows = process.platform === "win32";
|
|
1680
1427
|
try {
|
|
1681
1428
|
const target = isWindows ? targetPath : path.relative(path.dirname(linkPath), targetPath);
|
|
@@ -1699,8 +1446,6 @@ export class AppExecutor extends SysExecutor {
|
|
|
1699
1446
|
if (write) await this.#runPluginSyncAssets();
|
|
1700
1447
|
return scanInfo;
|
|
1701
1448
|
}
|
|
1702
|
-
//* build-time asset generation is delegated to plugins (e.g. the push plugin writes
|
|
1703
|
-
//* public/firebase-messaging-sw.js). The framework itself no longer knows about firebase.
|
|
1704
1449
|
async #runPluginSyncAssets() {
|
|
1705
1450
|
const plugins = await this.collectPlugins();
|
|
1706
1451
|
if (!plugins.some((plugin) => plugin.syncAssets)) return;
|
|
@@ -1714,6 +1459,7 @@ export class AppExecutor extends SysExecutor {
|
|
|
1714
1459
|
executor: this,
|
|
1715
1460
|
getPath: (rel) => this.getPath(rel),
|
|
1716
1461
|
fileExists: (rel) => FileSys.fileExists(this.getPath(rel)),
|
|
1462
|
+
readFile: (rel) => this.readFile(rel),
|
|
1717
1463
|
writeFile: async (rel, content, opts) => {
|
|
1718
1464
|
await this.writeFile(rel, content, opts);
|
|
1719
1465
|
},
|
|
@@ -1731,8 +1477,6 @@ export class AppExecutor extends SysExecutor {
|
|
|
1731
1477
|
},
|
|
1732
1478
|
};
|
|
1733
1479
|
}
|
|
1734
|
-
//* Aggregate plugins declared by this app's akan.config plus each of its lib dependencies'
|
|
1735
|
-
//* akan.config. This is how a lib (e.g. libs/util) opts an app into a feature turnkey.
|
|
1736
1480
|
async collectPlugins(): Promise<AkanPlugin[]> {
|
|
1737
1481
|
const scanInfo = (await this.scan({ write: false })) as AppInfo;
|
|
1738
1482
|
const libDeps = scanInfo.getLibs();
|
|
@@ -1751,17 +1495,6 @@ export class AppExecutor extends SysExecutor {
|
|
|
1751
1495
|
return true;
|
|
1752
1496
|
});
|
|
1753
1497
|
}
|
|
1754
|
-
async getPluginRuntimePackages(): Promise<string[]> {
|
|
1755
|
-
const plugins = await this.collectPlugins();
|
|
1756
|
-
const appConfig = await this.getConfig();
|
|
1757
|
-
const ctx: PluginRuntimeContext = {
|
|
1758
|
-
appName: this.name,
|
|
1759
|
-
mobile: appConfig.mobile,
|
|
1760
|
-
hasMobilePermission: (permission) =>
|
|
1761
|
-
Object.values(appConfig.mobile.targets).some((target) => target.permissions?.includes(permission) ?? false),
|
|
1762
|
-
};
|
|
1763
|
-
return [...new Set(plugins.flatMap((plugin) => plugin.runtimePackages?.(ctx) ?? []))];
|
|
1764
|
-
}
|
|
1765
1498
|
async increaseBuildNum() {
|
|
1766
1499
|
await increaseBuildNum(this);
|
|
1767
1500
|
}
|
|
@@ -1769,23 +1502,15 @@ export class AppExecutor extends SysExecutor {
|
|
|
1769
1502
|
await decreaseBuildNum(this);
|
|
1770
1503
|
}
|
|
1771
1504
|
}
|
|
1772
|
-
interface LibExecutorOptions {
|
|
1773
|
-
workspace?: WorkspaceExecutor;
|
|
1774
|
-
name: string;
|
|
1775
|
-
}
|
|
1776
1505
|
export class LibExecutor extends SysExecutor {
|
|
1777
1506
|
dist: Executor;
|
|
1778
1507
|
override emoji = execEmoji.lib;
|
|
1779
|
-
constructor({ workspace, name }:
|
|
1508
|
+
constructor({ workspace, name }: NamedExecutorOptions) {
|
|
1780
1509
|
super({ workspace, name, type: "lib" });
|
|
1781
1510
|
this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/libs/${name}`);
|
|
1782
1511
|
}
|
|
1783
|
-
static #execs = new Map<string, LibExecutor>();
|
|
1784
1512
|
static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
|
|
1785
|
-
|
|
1786
|
-
if (exec) return exec;
|
|
1787
|
-
else if (executor instanceof WorkspaceExecutor) return new LibExecutor({ workspace: executor, name });
|
|
1788
|
-
else return new LibExecutor({ workspace: executor.workspace, name });
|
|
1513
|
+
return new LibExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
1789
1514
|
}
|
|
1790
1515
|
|
|
1791
1516
|
#akanConfig: AkanLibConfig | null = null;
|
|
@@ -1796,31 +1521,25 @@ export class LibExecutor extends SysExecutor {
|
|
|
1796
1521
|
}
|
|
1797
1522
|
}
|
|
1798
1523
|
|
|
1799
|
-
interface PkgExecutorOptions {
|
|
1800
|
-
workspace?: WorkspaceExecutor;
|
|
1801
|
-
name: string;
|
|
1802
|
-
}
|
|
1803
1524
|
export class PkgExecutor extends Executor {
|
|
1804
1525
|
workspace: WorkspaceExecutor;
|
|
1805
1526
|
override name: string;
|
|
1806
1527
|
dist: Executor;
|
|
1807
1528
|
override emoji = execEmoji.pkg;
|
|
1808
|
-
constructor({ workspace = WorkspaceExecutor.fromRoot(), name }:
|
|
1529
|
+
constructor({ workspace = WorkspaceExecutor.fromRoot(), name }: NamedExecutorOptions) {
|
|
1809
1530
|
super(name, `${workspace.workspaceRoot}/pkgs/${name}`);
|
|
1810
1531
|
this.workspace = workspace;
|
|
1811
1532
|
this.name = name;
|
|
1812
1533
|
this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/pkgs/${name}`);
|
|
1813
1534
|
}
|
|
1814
1535
|
static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
|
|
1815
|
-
|
|
1816
|
-
return new PkgExecutor({ workspace: executor.workspace, name });
|
|
1536
|
+
return new PkgExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
1817
1537
|
}
|
|
1818
1538
|
|
|
1819
1539
|
#scanInfo: PkgInfo | null = null;
|
|
1820
1540
|
async scan({ refresh }: { refresh?: boolean } = {}): Promise<PkgInfo> {
|
|
1821
1541
|
if (this.#scanInfo && !refresh) return this.#scanInfo;
|
|
1822
1542
|
const scanInfo = await PkgInfo.fromExecutor(this, { refresh });
|
|
1823
|
-
// this.writeJson("akan.pkg.json", pkgScanResult);
|
|
1824
1543
|
this.#scanInfo = scanInfo;
|
|
1825
1544
|
return scanInfo;
|
|
1826
1545
|
}
|
|
@@ -1831,6 +1550,9 @@ export class PkgExecutor extends Executor {
|
|
|
1831
1550
|
};
|
|
1832
1551
|
const rootVersion = rootDeps[dep];
|
|
1833
1552
|
if (rootVersion) return rootVersion;
|
|
1553
|
+
// Overrides never reach a consumer's install, so a pinned transitive dep is published as a dependency.
|
|
1554
|
+
const overrideVersion = rootPackageJson.overrides?.[dep];
|
|
1555
|
+
if (typeof overrideVersion === "string") return overrideVersion;
|
|
1834
1556
|
|
|
1835
1557
|
try {
|
|
1836
1558
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
@@ -1858,17 +1580,9 @@ export class PkgExecutor extends Executor {
|
|
|
1858
1580
|
if (missingDeps.length > 0)
|
|
1859
1581
|
throw new Error(`Missing dependency versions in root package.json: ${missingDeps.join(", ")}`);
|
|
1860
1582
|
|
|
1861
|
-
const
|
|
1862
|
-
names.map((dep) =>
|
|
1863
|
-
|
|
1864
|
-
if (!version) throw new Error(`Missing dependency versions in root package.json: ${dep}`);
|
|
1865
|
-
return [dep, version] as const;
|
|
1866
|
-
});
|
|
1867
|
-
|
|
1868
|
-
return {
|
|
1869
|
-
dependencies: Object.fromEntries(toDependencyEntries(dependencyNames)),
|
|
1870
|
-
devDependencies: Object.fromEntries(toDependencyEntries(devDependencyNames)),
|
|
1871
|
-
};
|
|
1583
|
+
const toDependencyMap = (names: string[]) =>
|
|
1584
|
+
Object.fromEntries(names.map((dep) => [dep, dependencyVersions.get(dep) as string]));
|
|
1585
|
+
return { dependencies: toDependencyMap(dependencyNames), devDependencies: toDependencyMap(devDependencyNames) };
|
|
1872
1586
|
}
|
|
1873
1587
|
async updatePackageJsonDependencies(
|
|
1874
1588
|
dependencies: string[] = [],
|