@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/subspace.ts
CHANGED
|
@@ -75,15 +75,8 @@ export interface SubspacePullResult {
|
|
|
75
75
|
incoming: SubspaceIncomingCommit[];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
*
|
|
81
|
-
* Push is a squashed snapshot of the workspace's own tracked files, so a subspace's history never
|
|
82
|
-
* carries the workspace's — which is also what keeps one customer's commit messages out of another's
|
|
83
|
-
* repo. Pull is the reverse and rare: it compares the subspace against the last push it received rather
|
|
84
|
-
* than against the workspace, so the diff is exactly the customer's own work however far the workspace
|
|
85
|
-
* has moved on.
|
|
86
|
-
*/
|
|
78
|
+
// Push is a squashed snapshot, so no workspace history (or other customer's commit message) reaches a subspace;
|
|
79
|
+
// pull diffs against the last push received, so it is exactly the customer's own work.
|
|
87
80
|
export class Subspace {
|
|
88
81
|
static readonly anchorFile = "akan.subspace.json";
|
|
89
82
|
/** Never reaches a subspace: it names every other customer's repo. */
|
|
@@ -133,7 +126,6 @@ export class Subspace {
|
|
|
133
126
|
return (await this.#git(["rev-parse", "--short", "HEAD"])).trim();
|
|
134
127
|
}
|
|
135
128
|
|
|
136
|
-
/** Adds the subspace as a remote if absent, then fetches the branch into this workspace's object store. */
|
|
137
129
|
async fetch(branch: string) {
|
|
138
130
|
const remotes = (await this.#git(["remote"])).split("\n").map((line) => line.trim());
|
|
139
131
|
if (remotes.includes(this.#remote)) await this.#git(["remote", "set-url", this.#remote, this.#declaration.repo]);
|
|
@@ -147,10 +139,7 @@ export class Subspace {
|
|
|
147
139
|
}
|
|
148
140
|
}
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
* The last push, located by the one file only a push writes. A tag or a recorded sha would have to be
|
|
152
|
-
* kept in step by hand; this file is already in the subspace's history and cannot drift out of it.
|
|
153
|
-
*/
|
|
142
|
+
//* Located by the one file only a push writes: unlike a tag or a recorded sha, it cannot drift out of the history.
|
|
154
143
|
async anchor(branch: string): Promise<SubspaceAnchor> {
|
|
155
144
|
const ref = `${this.#remote}/${branch}`;
|
|
156
145
|
const commit = (await this.#git(["log", "-1", "--format=%H", ref, "--", Subspace.anchorFile])).trim();
|
|
@@ -184,11 +173,7 @@ export class Subspace {
|
|
|
184
173
|
return Subspace.subspaceOwnedDirs.includes(segments[2] ?? "");
|
|
185
174
|
}
|
|
186
175
|
|
|
187
|
-
|
|
188
|
-
* A library manifest always differs: the subspace's copy carries the `akan.source` stamp a push writes and
|
|
189
|
-
* the workspace's does not. Comparing it with that key removed is what keeps `status` and `diff` from
|
|
190
|
-
* reporting every library as changed forever.
|
|
191
|
-
*/
|
|
176
|
+
// A library manifest always differs by the `akan.source` stamp a push writes, so it is compared without that key.
|
|
192
177
|
#isStamped(file: string) {
|
|
193
178
|
return /^libs\/[^/]+\/package\.json$/.test(file);
|
|
194
179
|
}
|
|
@@ -201,9 +186,7 @@ export class Subspace {
|
|
|
201
186
|
if (!theirs || !ours) return true;
|
|
202
187
|
const normalize = (content: string) => {
|
|
203
188
|
try {
|
|
204
|
-
|
|
205
|
-
delete manifest[LibSource.manifestKey];
|
|
206
|
-
return JSON.stringify(manifest);
|
|
189
|
+
return LibSource.unstamped(content);
|
|
207
190
|
} catch {
|
|
208
191
|
return content;
|
|
209
192
|
}
|
|
@@ -211,7 +194,10 @@ export class Subspace {
|
|
|
211
194
|
return normalize(theirs) !== normalize(ours);
|
|
212
195
|
}
|
|
213
196
|
|
|
214
|
-
async #
|
|
197
|
+
async #changedSliceFiles(ref: string, diffArgs: string[]) {
|
|
198
|
+
const files = (await this.#git(["diff", "--name-only", ...diffArgs]))
|
|
199
|
+
.split("\n")
|
|
200
|
+
.filter((file) => !!file.trim() && !this.#isSubspaceOwned(file));
|
|
215
201
|
const kept = await Promise.all(
|
|
216
202
|
files.map(async (file) => (this.#isStamped(file) ? await this.#differsBeyondStamp(ref, file) : true)),
|
|
217
203
|
);
|
|
@@ -226,14 +212,7 @@ export class Subspace {
|
|
|
226
212
|
]);
|
|
227
213
|
if (!hasBranch) return { name: this.name, branch, hasBranch, anchor, behindPaths: [], driftedLibs: [] };
|
|
228
214
|
const ref = `${this.#remote}/${branch}`;
|
|
229
|
-
const
|
|
230
|
-
const behindPaths = await this.#meaningfulFiles(
|
|
231
|
-
ref,
|
|
232
|
-
diff
|
|
233
|
-
.split("\n")
|
|
234
|
-
.filter((file) => !!file.trim())
|
|
235
|
-
.filter((file) => !this.#isSubspaceOwned(file)),
|
|
236
|
-
);
|
|
215
|
+
const behindPaths = await this.#changedSliceFiles(ref, ["HEAD", ref, "--", ...paths]);
|
|
237
216
|
const driftedLibs = libs.filter((lib) => behindPaths.some((file) => file.startsWith(`libs/${lib}/`)));
|
|
238
217
|
return { name: this.name, branch, hasBranch, anchor, behindPaths, driftedLibs };
|
|
239
218
|
}
|
|
@@ -273,11 +252,7 @@ export class Subspace {
|
|
|
273
252
|
return held;
|
|
274
253
|
}
|
|
275
254
|
|
|
276
|
-
|
|
277
|
-
* Puts back only the files the workspace does not ship. The workspace still owns the tracked env switch files
|
|
278
|
-
* (`env.server.ts` / `env.client.ts`), which is what keeps them in step; everything else under `env/` —
|
|
279
|
-
* the per-environment values, which the workspace gitignores and never had — belongs to the subspace and wins.
|
|
280
|
-
*/
|
|
255
|
+
// The workspace owns the tracked env switch files; the per-environment values it gitignores belong to the subspace.
|
|
281
256
|
async #restoreSubspaceOwned(held: { from: string; to: string }[]) {
|
|
282
257
|
for (const { from, to } of held) await Subspace.#copyMissing(to, from);
|
|
283
258
|
await rm(path.join(this.#clonePath, Subspace.holdDir), { recursive: true, force: true });
|
|
@@ -293,25 +268,26 @@ export class Subspace {
|
|
|
293
268
|
}
|
|
294
269
|
}
|
|
295
270
|
|
|
296
|
-
|
|
297
|
-
* `git archive` emits exactly the tracked files at HEAD, which is why the copy needs no exclude list of
|
|
298
|
-
* its own: generated barrels, the `(libs)`/`public/libs` symlinks, env values, secrets and the lockfile
|
|
299
|
-
* are all outside git and cannot enter the archive. Directory arguments only — a file list would put
|
|
300
|
-
* route paths like `page/(docs)/…` through a shell.
|
|
301
|
-
*/
|
|
271
|
+
// `git archive` emits only tracked files, so generated barrels, lib symlinks, env values and secrets never enter.
|
|
302
272
|
async #extract(paths: string[], excludes: string[] = []) {
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
await this.#workspace.
|
|
306
|
-
|
|
307
|
-
|
|
273
|
+
// Relative, because GNU tar reads a drive-letter archive name (`C:\…`) as `host:path` on a remote host.
|
|
274
|
+
const archivePath = path.relative(this.#workspace.workspaceRoot, `${this.#clonePath}.tar`);
|
|
275
|
+
await this.#workspace.spawn("git", ["archive", "-o", archivePath, "HEAD", "--", ...paths]);
|
|
276
|
+
try {
|
|
277
|
+
await this.#workspace.spawn("tar", [
|
|
278
|
+
"-x",
|
|
279
|
+
"-f",
|
|
280
|
+
archivePath,
|
|
281
|
+
"-C",
|
|
282
|
+
this.#clonePath,
|
|
283
|
+
...excludes.map((entry) => `--exclude=${entry}`),
|
|
284
|
+
]);
|
|
285
|
+
} finally {
|
|
286
|
+
await rm(path.join(this.#workspace.workspaceRoot, archivePath), { force: true });
|
|
287
|
+
}
|
|
308
288
|
}
|
|
309
289
|
|
|
310
|
-
|
|
311
|
-
* Verify-only, and an allowlist rather than a filter so no secret can reach a customer's clone even by
|
|
312
|
-
* accident. `AKAN_PUBLIC_*` values are the ones akan embeds in every client bundle, so they are public
|
|
313
|
-
* by construction; the cloud workspace id is pinned to `local` rather than copied.
|
|
314
|
-
*/
|
|
290
|
+
// An allowlist, not a filter, so no secret can reach a customer's clone; `AKAN_PUBLIC_*` ships in every bundle anyway.
|
|
315
291
|
#localEnv() {
|
|
316
292
|
const { serveDomain } = WorkspaceExecutor.getBaseDevEnv();
|
|
317
293
|
return {
|
|
@@ -324,13 +300,8 @@ export class Subspace {
|
|
|
324
300
|
};
|
|
325
301
|
}
|
|
326
302
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
* `.env` cannot run `akan sync` at all — and one can never arrive with the slice, since every akan
|
|
330
|
-
* workspace gitignores it and the subspace's real values are its own. Excluded through the clone's
|
|
331
|
-
* `.git/info/exclude` rather than through the copied `.gitignore`: a workspace that omitted the pattern
|
|
332
|
-
* would otherwise commit this file into the customer's repo.
|
|
333
|
-
*/
|
|
303
|
+
// The CLI needs a `.env` to recognise a workspace root and none arrives with the slice. Excluded through
|
|
304
|
+
// `.git/info/exclude`, not the copied `.gitignore`, so it can never be committed to the customer's repo.
|
|
334
305
|
async #writeLocalEnv() {
|
|
335
306
|
const excludePath = path.join(this.#clonePath, ".git/info/exclude");
|
|
336
307
|
const exclude = (await FileSys.fileExists(excludePath)) ? await FileSys.readText(excludePath) : "";
|
|
@@ -344,11 +315,7 @@ export class Subspace {
|
|
|
344
315
|
await FileSys.writeText(envPath, `${lines.join("\n")}\n`);
|
|
345
316
|
}
|
|
346
317
|
|
|
347
|
-
|
|
348
|
-
* A workspace's root manifest is the union of every app it holds, so shipping it verbatim installs
|
|
349
|
-
* every other customer's dependency tree in this repo. It is rebuilt from this subspace's own slices
|
|
350
|
-
* instead, keeping the workspace's exact version specs.
|
|
351
|
-
*/
|
|
318
|
+
// The root manifest is the union of every app's dependencies, so it is rebuilt from this subspace's slices.
|
|
352
319
|
async #rewriteManifest(plans: SlicePlan[]) {
|
|
353
320
|
const manifestPath = path.join(this.#clonePath, "package.json");
|
|
354
321
|
if (!(await FileSys.fileExists(manifestPath))) return [];
|
|
@@ -373,8 +340,7 @@ export class Subspace {
|
|
|
373
340
|
for (const member of Subspace.memberDirs)
|
|
374
341
|
await rm(path.join(this.#clonePath, member), { recursive: true, force: true });
|
|
375
342
|
|
|
376
|
-
//*
|
|
377
|
-
//* workflows live there), so a root entry the workspace does not have is left alone rather than deleted.
|
|
343
|
+
//* Overlaid, never reconciled: a subspace owns its deployment, so a root entry the workspace lacks is left alone.
|
|
378
344
|
await this.#extract(["."], ["apps/*", "libs/*", "pkgs/*"]);
|
|
379
345
|
await this.#extract(paths);
|
|
380
346
|
await this.#restoreSubspaceOwned(held);
|
|
@@ -390,16 +356,13 @@ export class Subspace {
|
|
|
390
356
|
return { libs, pruned };
|
|
391
357
|
}
|
|
392
358
|
|
|
393
|
-
|
|
394
|
-
* The `akan:secrets` block akan generates lists every app in the workspace by name, so it is filtered down to
|
|
395
|
-
* this subspace's own apps. `bun.lock` is un-ignored because a subspace commits its lockfile — that is what
|
|
396
|
-
* makes two subspaces on one branch resolve the same dependency tree rather than merely the same ranges.
|
|
397
|
-
*/
|
|
359
|
+
// `akan:secrets` names every workspace app, so it is filtered; `bun.lock` is un-ignored because a subspace commits it.
|
|
398
360
|
async #rewriteGitignore() {
|
|
399
361
|
const gitignorePath = path.join(this.#clonePath, ".gitignore");
|
|
400
362
|
if (!(await FileSys.fileExists(gitignorePath))) return;
|
|
401
363
|
const [begin = "", end = ""] = Subspace.secretsMarkers;
|
|
402
|
-
|
|
364
|
+
// `git archive` checks text out as CRLF under Windows Git's default `core.autocrlf=true`.
|
|
365
|
+
const lines = (await FileSys.readText(gitignorePath)).split(/\r?\n/);
|
|
403
366
|
const beginIdx = lines.indexOf(begin);
|
|
404
367
|
const endIdx = lines.indexOf(end);
|
|
405
368
|
const filtered =
|
|
@@ -431,12 +394,8 @@ export class Subspace {
|
|
|
431
394
|
}
|
|
432
395
|
}
|
|
433
396
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
* by its position in the *workspace's* workspace. The hash follows the same rule — the library's own git files
|
|
437
|
-
* with `env/` and the stamp itself left out — and is only written when it would change, so an
|
|
438
|
-
* up-to-date subspace stays clean and the push is skipped.
|
|
439
|
-
*/
|
|
397
|
+
// Not through `LibSource`, which addresses a library by its position in the workspace. The hash skips `env/` and
|
|
398
|
+
// the stamp itself.
|
|
440
399
|
async #stampLib(clone: Executor, lib: string, branch: string) {
|
|
441
400
|
const manifestPath = path.join(this.#clonePath, "libs", lib, "package.json");
|
|
442
401
|
if (!(await FileSys.fileExists(manifestPath))) return;
|
|
@@ -445,9 +404,8 @@ export class Subspace {
|
|
|
445
404
|
const [hash, previous] = await Promise.all([this.#hashLib(clone, lib), this.#committedStamp(clone, lib)]);
|
|
446
405
|
const manifest = (await FileSys.readJson(manifestPath)) as Record<string, unknown>;
|
|
447
406
|
const akan = (manifest[LibSource.manifestKey] ?? {}) as Record<string, unknown>;
|
|
448
|
-
//* The extraction overwrote the manifest
|
|
449
|
-
//*
|
|
450
|
-
//* byte-identical to what is committed — otherwise every push is dirty and none is ever skipped.
|
|
407
|
+
//* The extraction overwrote the manifest, so the previous stamp comes from the clone's HEAD; reusing its
|
|
408
|
+
//* `syncedAt` when nothing moved keeps the file byte-identical, or no push would ever be skipped.
|
|
451
409
|
const unchanged = previous?.origin === origin && previous.sha === sha && previous.hash === hash;
|
|
452
410
|
const syncedAt = unchanged ? previous.syncedAt : new Date().toISOString();
|
|
453
411
|
manifest[LibSource.manifestKey] = { ...akan, source: { origin, sha, hash, syncedAt } };
|
|
@@ -480,26 +438,15 @@ export class Subspace {
|
|
|
480
438
|
.split("\0")
|
|
481
439
|
.filter((file) => !!file && !this.#isSubspaceOwned(file))
|
|
482
440
|
.sort();
|
|
483
|
-
//*
|
|
484
|
-
//*
|
|
485
|
-
//* index is only reconciled by the `git add -A` a push commits with, long after the stamp is written.
|
|
441
|
+
//* The index still lists files the workspace deleted since the last push (only the push's `git add -A`
|
|
442
|
+
//* reconciles it), and opening one throws.
|
|
486
443
|
const present = await Promise.all(
|
|
487
444
|
candidates.map(async (file) => await FileSys.entryExists(path.join(this.#clonePath, file))),
|
|
488
445
|
);
|
|
489
446
|
const files = candidates.filter((_, index) => present[index]);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
hasher.update(file);
|
|
494
|
-
hasher.update("\0");
|
|
495
|
-
if (file === `libs/${lib}/package.json`) {
|
|
496
|
-
const manifest = JSON.parse(content) as Record<string, unknown>;
|
|
497
|
-
delete manifest[LibSource.manifestKey];
|
|
498
|
-
hasher.update(JSON.stringify(manifest));
|
|
499
|
-
} else hasher.update(content);
|
|
500
|
-
hasher.update("\0");
|
|
501
|
-
}
|
|
502
|
-
return hasher.digest("hex").slice(0, 32);
|
|
447
|
+
return await LibSource.hashFiles(files, `libs/${lib}/package.json`, (file) =>
|
|
448
|
+
FileSys.readText(path.join(this.#clonePath, file)),
|
|
449
|
+
);
|
|
503
450
|
}
|
|
504
451
|
|
|
505
452
|
/** Direction is subspace → workspace, so the patch reads as what a push would apply rather than its inverse. */
|
|
@@ -519,25 +466,14 @@ export class Subspace {
|
|
|
519
466
|
const scoped = filter ? paths.filter((entry) => entry.startsWith(filter) || filter.startsWith(entry)) : paths;
|
|
520
467
|
if (!scoped.length) return { files: [], patch: "" };
|
|
521
468
|
const from = `${this.#remote}/${branch}`;
|
|
522
|
-
const
|
|
523
|
-
const names = await this.#git(["diff", "--name-only", from, "HEAD", "--", ...pathspec]);
|
|
524
|
-
const files = await this.#meaningfulFiles(
|
|
525
|
-
from,
|
|
526
|
-
names
|
|
527
|
-
.split("\n")
|
|
528
|
-
.filter((file) => !!file.trim())
|
|
529
|
-
.filter((file) => !this.#isSubspaceOwned(file)),
|
|
530
|
-
);
|
|
469
|
+
const files = await this.#changedSliceFiles(from, [from, "HEAD", "--", ...(filter ? [filter] : scoped)]);
|
|
531
470
|
if (!files.length) return { files: [], patch: "" };
|
|
532
471
|
//? argv, not a shell: a route path such as `page/(docs)/…` would need quoting through one.
|
|
533
472
|
return { files, patch: await this.#git(["diff", from, "HEAD", "--", ...files]) };
|
|
534
473
|
}
|
|
535
474
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
* abort the push to the rest of the subspaces. The env is passed explicitly so the child sees the values
|
|
539
|
-
* written into the clone instead of inheriting the workspace's own.
|
|
540
|
-
*/
|
|
475
|
+
// Refuses rather than throws, so one failing subspace does not abort the rest; the env is explicit so the child
|
|
476
|
+
// sees the clone's values, not the workspace's.
|
|
541
477
|
async #verify(clone: Executor) {
|
|
542
478
|
const env = { ...process.env, ...this.#localEnv() };
|
|
543
479
|
try {
|
|
@@ -628,10 +564,7 @@ export class Subspace {
|
|
|
628
564
|
await this.#git(["apply", "--3way", absolute]);
|
|
629
565
|
}
|
|
630
566
|
|
|
631
|
-
|
|
632
|
-
* A library hunk is never applied by default: the workspace is the one copy every other subspace is pushed from,
|
|
633
|
-
* so adopting one customer's edit silently would ship it to all of them.
|
|
634
|
-
*/
|
|
567
|
+
// Not applied by default: every subspace is pushed from the workspace, so one customer's lib edit would ship to all.
|
|
635
568
|
async #savePatch(range: string, files: string[], label: string) {
|
|
636
569
|
const patchPath = path.join(this.#workspace.workspaceRoot, ".akan/subspace", `${this.name}-${label}.patch`);
|
|
637
570
|
await mkdir(path.dirname(patchPath), { recursive: true });
|
package/subspaceConfig.ts
CHANGED
|
@@ -5,13 +5,7 @@ import { FileSys } from "./fileSys";
|
|
|
5
5
|
|
|
6
6
|
export type { SubspaceConfigInput, SubspaceDeclaration };
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
* `akan.subspace.ts` at the workspace root: which customer repo each app is mirrored to.
|
|
10
|
-
*
|
|
11
|
-
* There is no branch field. The branch is whichever one the workspace is on, so the same declaration
|
|
12
|
-
* serves `develop` and `main`, and every subspace on one branch holds the same akanjs version and the
|
|
13
|
-
* same library source.
|
|
14
|
-
*/
|
|
8
|
+
// No branch field: the workspace's current branch is used, so every subspace on it holds the same akanjs and libs.
|
|
15
9
|
export class SubspaceConfig {
|
|
16
10
|
static readonly fileName = "akan.subspace.ts";
|
|
17
11
|
static readonly defaultPushableBranches = ["main", "develop", "debug"];
|
|
@@ -48,8 +42,7 @@ export class SubspaceConfig {
|
|
|
48
42
|
throw new Error(`${SubspaceConfig.fileName}: subspace "${subspace.name}" declares no apps`);
|
|
49
43
|
for (const app of subspace.apps) {
|
|
50
44
|
const owner = appOwners.get(app);
|
|
51
|
-
//* One app never goes to two subspaces:
|
|
52
|
-
//* this declaration cannot express. Share code through a lib instead.
|
|
45
|
+
//* One app never goes to two subspaces: per-subspace domains and ids are not expressible. Share a lib instead.
|
|
53
46
|
if (owner)
|
|
54
47
|
throw new Error(
|
|
55
48
|
`${SubspaceConfig.fileName}: app "${app}" is claimed by both "${owner}" and "${subspace.name}"`,
|
|
@@ -58,8 +51,7 @@ export class SubspaceConfig {
|
|
|
58
51
|
}
|
|
59
52
|
if (!subspace.workspaceId) continue;
|
|
60
53
|
const idOwner = idOwners.get(subspace.workspaceId);
|
|
61
|
-
//* An env upload replaces the cloud workspace's archive whole, so two subspaces
|
|
62
|
-
//* each overwrite the other's values.
|
|
54
|
+
//* An env upload replaces the cloud workspace's archive whole, so two subspaces would overwrite each other.
|
|
63
55
|
if (idOwner)
|
|
64
56
|
throw new Error(
|
|
65
57
|
`${SubspaceConfig.fileName}: subspaces "${idOwner}" and "${subspace.name}" declare the same workspaceId`,
|
package/testHelpers.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { afterEach, beforeEach } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
// Executors are imported on first use: they load akanjs/base, which patches the String/Boolean/Date globals.
|
|
7
|
+
const executors = () => import("@akanjs/devkit/executors");
|
|
8
|
+
|
|
9
|
+
export interface CallRecord {
|
|
10
|
+
name: string;
|
|
11
|
+
args: unknown[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const createCallRecorder = () => {
|
|
15
|
+
const calls: CallRecord[] = [];
|
|
16
|
+
return {
|
|
17
|
+
calls,
|
|
18
|
+
/** `Returns` only types the stub: it always returns `undefined`, so use it where the result is not read. */
|
|
19
|
+
record<Returns = void>(name: string, ...args: unknown[]): Returns {
|
|
20
|
+
calls.push({ name, args });
|
|
21
|
+
return undefined as unknown as Returns;
|
|
22
|
+
},
|
|
23
|
+
names() {
|
|
24
|
+
return calls.map((call) => call.name);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const createFakeSpinner = (recorder = createCallRecorder()) => ({
|
|
30
|
+
succeed: (message?: string) => recorder.record("spinner.succeed", message),
|
|
31
|
+
fail: (message?: string) => recorder.record("spinner.fail", message),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const createFakeExecutor = <Extra extends object = object>(
|
|
35
|
+
name: string,
|
|
36
|
+
extra: Extra = {} as Extra,
|
|
37
|
+
recorder = createCallRecorder(),
|
|
38
|
+
) =>
|
|
39
|
+
({
|
|
40
|
+
name,
|
|
41
|
+
cwdPath: `/workspace/${name}`,
|
|
42
|
+
workspaceRoot: "/workspace",
|
|
43
|
+
spinning: (message: string) => {
|
|
44
|
+
recorder.record(`${name}.spinning`, message);
|
|
45
|
+
return createFakeSpinner(recorder);
|
|
46
|
+
},
|
|
47
|
+
spawn: async (...args: unknown[]) => {
|
|
48
|
+
recorder.record(`${name}.spawn`, ...args);
|
|
49
|
+
return "";
|
|
50
|
+
},
|
|
51
|
+
exec: async (...args: unknown[]) => {
|
|
52
|
+
recorder.record(`${name}.exec`, ...args);
|
|
53
|
+
return "";
|
|
54
|
+
},
|
|
55
|
+
scan: async (...args: unknown[]) => {
|
|
56
|
+
recorder.record(`${name}.scan`, ...args);
|
|
57
|
+
return { name };
|
|
58
|
+
},
|
|
59
|
+
scanSync: async (...args: unknown[]) => {
|
|
60
|
+
recorder.record(`${name}.scanSync`, ...args);
|
|
61
|
+
return { name };
|
|
62
|
+
},
|
|
63
|
+
...extra,
|
|
64
|
+
}) as Extra & {
|
|
65
|
+
name: string;
|
|
66
|
+
cwdPath: string;
|
|
67
|
+
workspaceRoot: string;
|
|
68
|
+
spinning: (message: string) => ReturnType<typeof createFakeSpinner>;
|
|
69
|
+
spawn: (...args: unknown[]) => Promise<string>;
|
|
70
|
+
exec: (...args: unknown[]) => Promise<string>;
|
|
71
|
+
scan: (...args: unknown[]) => Promise<{ name: string }>;
|
|
72
|
+
scanSync: (...args: unknown[]) => Promise<{ name: string }>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const makeCliTempWorkspace = async (parentDir = os.tmpdir()) => {
|
|
76
|
+
await mkdir(parentDir, { recursive: true });
|
|
77
|
+
const root = await mkdtemp(path.join(parentDir, "akan-cli-"));
|
|
78
|
+
await writeText(path.join(root, ".gitignore"), "");
|
|
79
|
+
await writeText(
|
|
80
|
+
path.join(root, ".env"),
|
|
81
|
+
["AKAN_PUBLIC_REPO_NAME=repo", "AKAN_PUBLIC_SERVE_DOMAIN=localhost", "AKAN_PUBLIC_ENV=local", ""].join("\n"),
|
|
82
|
+
);
|
|
83
|
+
const { WorkspaceExecutor } = await executors();
|
|
84
|
+
const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
|
|
85
|
+
return { root, workspace };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Registers the afterEach that removes the root of every value the returned tracker was handed.
|
|
89
|
+
export const tempRoots = (remove = (root: string) => rm(root, { recursive: true, force: true })) => {
|
|
90
|
+
const roots: string[] = [];
|
|
91
|
+
afterEach(async () => {
|
|
92
|
+
await Promise.all(roots.splice(0).map((root) => remove(root)));
|
|
93
|
+
});
|
|
94
|
+
return <Temp extends { root: string }>(temp: Temp) => {
|
|
95
|
+
roots.push(temp.root);
|
|
96
|
+
return temp;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Registers the afterEach that removes every directory the returned factory created.
|
|
101
|
+
export const tempDirs = (prefix: string, remove?: (root: string) => Promise<void>) => {
|
|
102
|
+
const track = tempRoots(remove);
|
|
103
|
+
return async () => track({ root: await mkdtemp(path.join(os.tmpdir(), prefix)) }).root;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const isolateEnv = (env: Record<string, string> = {}) => {
|
|
107
|
+
const originalEnv = { ...process.env };
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
process.env = { ...originalEnv, ...env };
|
|
110
|
+
});
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
process.env = { ...originalEnv };
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const writeText = async (filePath: string, content: string) => {
|
|
117
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
118
|
+
await writeFile(filePath, content);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const writeJson = async (filePath: string, value: object) => {
|
|
122
|
+
await writeText(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const tsconfigJson = { compilerOptions: { target: "ESNext", paths: {} } };
|
|
126
|
+
|
|
127
|
+
export const createTempApp = async (appName = "demo", parentDir?: string) => {
|
|
128
|
+
const { root, workspace } = await makeCliTempWorkspace(parentDir);
|
|
129
|
+
await writeJson(path.join(root, "package.json"), {
|
|
130
|
+
name: "repo",
|
|
131
|
+
version: "1.0.0",
|
|
132
|
+
description: "repo",
|
|
133
|
+
dependencies: {
|
|
134
|
+
react: "19.0.0",
|
|
135
|
+
"react-dom": "19.0.0",
|
|
136
|
+
"react-server-dom-webpack": "19.0.0",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await writeJson(path.join(root, "tsconfig.json"), { ...tsconfigJson, references: [] });
|
|
140
|
+
await writeJson(path.join(root, "apps", appName, "tsconfig.json"), tsconfigJson);
|
|
141
|
+
await writeJson(path.join(root, "apps", appName, "package.json"), {
|
|
142
|
+
name: appName,
|
|
143
|
+
version: "1.0.0",
|
|
144
|
+
description: appName,
|
|
145
|
+
dependencies: {},
|
|
146
|
+
devDependencies: {},
|
|
147
|
+
});
|
|
148
|
+
await writeText(path.join(root, "apps", appName, "akan.config.ts"), "export default {};\n");
|
|
149
|
+
await mkdir(path.join(root, "apps", appName, "lib", "__scalar"), { recursive: true });
|
|
150
|
+
const app = (await executors()).AppExecutor.from(workspace, appName);
|
|
151
|
+
return { root, workspace, app };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const createTempLib = async (libName = "shared") => {
|
|
155
|
+
const { root, workspace } = await makeCliTempWorkspace();
|
|
156
|
+
await writeJson(path.join(root, "package.json"), {
|
|
157
|
+
name: "repo",
|
|
158
|
+
version: "1.0.0",
|
|
159
|
+
description: "repo",
|
|
160
|
+
dependencies: {},
|
|
161
|
+
devDependencies: {},
|
|
162
|
+
});
|
|
163
|
+
await writeJson(path.join(root, "tsconfig.json"), { ...tsconfigJson, references: [] });
|
|
164
|
+
const lib = (await executors()).LibExecutor.from(workspace, libName);
|
|
165
|
+
return { root, workspace, lib };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const createTempPackage = async (pkgName = "@sample/tool") => {
|
|
169
|
+
const { root, workspace } = await makeCliTempWorkspace();
|
|
170
|
+
await writeJson(path.join(root, "package.json"), {
|
|
171
|
+
name: "repo",
|
|
172
|
+
version: "1.0.0",
|
|
173
|
+
description: "repo",
|
|
174
|
+
dependencies: { lodash: "4.0.0" },
|
|
175
|
+
devDependencies: { typescript: "6.0.0" },
|
|
176
|
+
});
|
|
177
|
+
await writeJson(path.join(root, "tsconfig.json"), tsconfigJson);
|
|
178
|
+
await writeJson(path.join(root, "pkgs", pkgName, "package.json"), {
|
|
179
|
+
name: pkgName,
|
|
180
|
+
version: "0.1.0",
|
|
181
|
+
description: "tool",
|
|
182
|
+
exports: {},
|
|
183
|
+
});
|
|
184
|
+
await writeJson(path.join(root, "pkgs", pkgName, "tsconfig.json"), tsconfigJson);
|
|
185
|
+
await writeText(path.join(root, "pkgs", pkgName, "index.ts"), 'import "lodash";\nexport const value = 1;\n');
|
|
186
|
+
const pkg = (await executors()).PkgExecutor.from(workspace, pkgName);
|
|
187
|
+
return { root, workspace, pkg };
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const createTempModule = async (moduleName = "post") => {
|
|
191
|
+
const { root, workspace, app } = await createTempApp("demo");
|
|
192
|
+
const module = (await executors()).ModuleExecutor.from(app, moduleName);
|
|
193
|
+
return { root, workspace, app, module };
|
|
194
|
+
};
|
|
@@ -7,8 +7,7 @@ type TypeScript = typeof ts;
|
|
|
7
7
|
export class AsyncDefaultExportDetector {
|
|
8
8
|
static #typescriptLoad: Promise<TypeScript> | undefined;
|
|
9
9
|
|
|
10
|
-
// `typescript`
|
|
11
|
-
// layout generator, so the compiler loads on first use rather than at import (`entryModuleGraph.test.ts`).
|
|
10
|
+
// Lazy: `typescript` is ~70 MB resident and the cli entry reaches this file (`entryModuleGraph.test.ts`).
|
|
12
11
|
static #loadTypescript(): Promise<TypeScript> {
|
|
13
12
|
AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then(
|
|
14
13
|
(mod) => (mod.default ?? mod) as TypeScript,
|
|
@@ -25,7 +24,7 @@ export class AsyncDefaultExportDetector {
|
|
|
25
24
|
source,
|
|
26
25
|
typescript.ScriptTarget.Latest,
|
|
27
26
|
true,
|
|
28
|
-
|
|
27
|
+
/\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS,
|
|
29
28
|
);
|
|
30
29
|
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
31
30
|
} catch {
|
|
@@ -72,8 +71,7 @@ export class AsyncDefaultExportDetector {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
75
|
-
const
|
|
76
|
-
for (const specifier of exportClause.elements) {
|
|
74
|
+
for (const specifier of statement.exportClause.elements) {
|
|
77
75
|
if (specifier.name.text !== "default") continue;
|
|
78
76
|
defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
|
|
79
77
|
}
|
|
@@ -96,10 +94,4 @@ export class AsyncDefaultExportDetector {
|
|
|
96
94
|
this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword),
|
|
97
95
|
);
|
|
98
96
|
}
|
|
99
|
-
|
|
100
|
-
static #scriptKind(typescript: TypeScript, moduleAbsPath: string): ts.ScriptKind {
|
|
101
|
-
return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx")
|
|
102
|
-
? typescript.ScriptKind.TSX
|
|
103
|
-
: typescript.ScriptKind.TS;
|
|
104
|
-
}
|
|
105
97
|
}
|