@akanjs/devkit 3.0.0-beta.2 → 3.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- 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 +1 -3
- package/akanApp/akanApp.host.ts +82 -254
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +85 -69
- package/akanConfig/akanConfig.ts +69 -153
- package/akanConfig/types.ts +1 -0
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +5 -3
- package/applicationBuildRunner.ts +54 -61
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +9 -30
- package/artifact/implicitRootLayout.ts +34 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +6 -1
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +32 -16
- package/capacitorApp.ts +110 -238
- 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 +70 -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/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 +132 -106
- package/executors.ts +194 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +6 -8
- 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 +62 -37
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- 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 +46 -69
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +8 -32
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +23 -46
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +49 -161
- package/index.ts +5 -17
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- 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/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +10 -5
- 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 +197 -285
- 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 +87 -216
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/stripAnsi.ts +3 -0
- package/subspace.test.ts +30 -25
- package/subspace.ts +81 -115
- package/subspaceConfig.ts +11 -9
- package/testHelpers.ts +194 -0
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/types.ts +1 -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.ts +4 -19
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- 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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import type { App } from "../commandDecorators";
|
|
4
3
|
import { AsyncDefaultExportDetector } from "../transforms/asyncDefaultExportDetector";
|
|
@@ -11,23 +10,17 @@ export interface PageEntry {
|
|
|
11
10
|
|
|
12
11
|
const LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
13
12
|
|
|
14
|
-
async function appHasStModule(appCwdPath: string): Promise<boolean> {
|
|
15
|
-
return Bun.file(path.join(appCwdPath, "lib", "st.ts")).exists();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
const IMPLICIT_LAYOUT_DIR = path.join(".akan", "generated", "root-layouts");
|
|
19
14
|
const IMPLICIT_DICT_DIR = path.join(".akan", "generated", "dict");
|
|
20
15
|
const IMPLICIT_OVERRIDES_DIR = path.join(".akan", "generated", "overrides");
|
|
21
16
|
const OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* resolve it — and the author never writes `"use client"`.
|
|
30
|
-
*/
|
|
18
|
+
const importSpecifier = (fromAbsPath: string, toAbsPath: string) => {
|
|
19
|
+
const rel = path.relative(path.dirname(fromAbsPath), toAbsPath).split(path.sep).join("/");
|
|
20
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// UiOverrideProvider is a client component, so `_overrides.tsx` mounts through a generated "use client" layout.
|
|
31
24
|
async function writeGeneratedOverridesLayoutFile(opts: {
|
|
32
25
|
appCwdPath: string;
|
|
33
26
|
key: string;
|
|
@@ -35,12 +28,10 @@ async function writeGeneratedOverridesLayoutFile(opts: {
|
|
|
35
28
|
}): Promise<string> {
|
|
36
29
|
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
37
30
|
const absPath = path.join(path.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
38
|
-
const userRel = path.relative(path.dirname(absPath), opts.userAbsPath).split(path.sep).join("/");
|
|
39
|
-
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
40
31
|
const source = `"use client";
|
|
41
32
|
import { UiOverrideProvider } from "akanjs/ui";
|
|
42
33
|
import { createElement, type ReactNode } from "react";
|
|
43
|
-
import value from ${JSON.stringify(
|
|
34
|
+
import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
|
|
44
35
|
|
|
45
36
|
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
46
37
|
return createElement(UiOverrideProvider, { value }, children);
|
|
@@ -73,10 +64,6 @@ function implicitRootLayoutAbsPath(appCwdPath: string, segments: string[]): stri
|
|
|
73
64
|
return path.join(path.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
74
65
|
}
|
|
75
66
|
|
|
76
|
-
function implicitDictionaryMacroAbsPath(appCwdPath: string): string {
|
|
77
|
-
return path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
67
|
export function isRootBoundarySegments(segments: string[], basePaths: Iterable<string>): boolean {
|
|
81
68
|
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
82
69
|
if (firstVisibleIndex === -1) return segments.length <= 1;
|
|
@@ -99,10 +86,7 @@ function findRootBoundaries(pageKeys: string[], appCwdPath: string, basePaths: I
|
|
|
99
86
|
segments,
|
|
100
87
|
});
|
|
101
88
|
}
|
|
102
|
-
|
|
103
|
-
if (!hasExplicitRootBoundary && boundaries.size === 0) {
|
|
104
|
-
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
105
|
-
}
|
|
89
|
+
if (boundaries.size === 0) boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
106
90
|
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
107
91
|
}
|
|
108
92
|
|
|
@@ -123,11 +107,6 @@ function findExplicitRootLayoutAbsPath(pageKeys: string[], appCwdPath: string):
|
|
|
123
107
|
return rootLayoutKey ? path.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
124
108
|
}
|
|
125
109
|
|
|
126
|
-
function routePrefixForSegments(segments: string[]): string | null {
|
|
127
|
-
const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
|
|
128
|
-
return visible[0] ?? null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
110
|
async function assertEnvClientConvention(appCwdPath: string, appName: string) {
|
|
132
111
|
const envPath = path.join(appCwdPath, "env", "env.client.ts");
|
|
133
112
|
if (!(await Bun.file(envPath).exists())) {
|
|
@@ -138,8 +117,7 @@ async function assertEnvClientConvention(appCwdPath: string, appName: string) {
|
|
|
138
117
|
}
|
|
139
118
|
|
|
140
119
|
async function writeGeneratedDictionaryMacroFile(appCwdPath: string, appName: string): Promise<string> {
|
|
141
|
-
const absPath =
|
|
142
|
-
await mkdir(path.dirname(absPath), { recursive: true });
|
|
120
|
+
const absPath = path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
143
121
|
await Bun.write(
|
|
144
122
|
absPath,
|
|
145
123
|
`import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
@@ -163,35 +141,20 @@ async function writeGeneratedRootLayoutFile(opts: {
|
|
|
163
141
|
? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName)
|
|
164
142
|
: null;
|
|
165
143
|
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
? path.relative(path.dirname(absPath), dictMacroAbsPath).split(path.sep).join("/")
|
|
169
|
-
: null;
|
|
170
|
-
const dictMacroSpecifier = dictMacroRel ? (dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}`) : null;
|
|
171
|
-
const sourceRel = opts.boundary.sourceAbsPath
|
|
172
|
-
? path.relative(path.dirname(absPath), opts.boundary.sourceAbsPath).split(path.sep).join("/")
|
|
173
|
-
: null;
|
|
174
|
-
const sourceSpecifier = sourceRel ? (sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}`) : null;
|
|
144
|
+
const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
|
|
145
|
+
const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
|
|
175
146
|
const inheritedSourceAbsPath =
|
|
176
147
|
opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
177
|
-
const
|
|
178
|
-
? path.relative(path.dirname(absPath), inheritedSourceAbsPath).split(path.sep).join("/")
|
|
179
|
-
: null;
|
|
180
|
-
const inheritedSourceSpecifier = inheritedSourceRel
|
|
181
|
-
? inheritedSourceRel.startsWith(".")
|
|
182
|
-
? inheritedSourceRel
|
|
183
|
-
: `./${inheritedSourceRel}`
|
|
184
|
-
: null;
|
|
148
|
+
const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
|
|
185
149
|
const clientImport = opts.includeStInit
|
|
186
150
|
? `import { st } from "@apps/${opts.appName}/client";\nvoid st;\n`
|
|
187
151
|
: `import "@apps/${opts.appName}/client";\n`;
|
|
188
|
-
|
|
189
|
-
// the same afterwards, and this file never has to know which shape the app wrote.
|
|
152
|
+
//? resolveRouteModule reads a rootLayout() chain and the legacy named exports alike.
|
|
190
153
|
const inheritedLabel = inheritedSourceAbsPath ? path.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
191
154
|
const inheritedImport = inheritedSourceSpecifier
|
|
192
155
|
? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};\nconst inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;\n`
|
|
193
156
|
: "const inheritedLayout: LayoutModule = {};\n";
|
|
194
|
-
const prefix =
|
|
157
|
+
const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
|
|
195
158
|
const userLabel = opts.boundary.sourceAbsPath ? path.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
196
159
|
const userImport = sourceSpecifier
|
|
197
160
|
? `import * as userModule from ${JSON.stringify(sourceSpecifier)};\nconst userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;\nconst UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;\n`
|
|
@@ -200,9 +163,8 @@ async function writeGeneratedRootLayoutFile(opts: {
|
|
|
200
163
|
? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath)
|
|
201
164
|
: false;
|
|
202
165
|
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
203
|
-
// React has no async client component, so
|
|
204
|
-
//
|
|
205
|
-
// the layout's own awaits instead of streaming it as its own Flight chunk.
|
|
166
|
+
// React has no async client component, so CSR awaits an async layout's node; RSC keeps the element, since
|
|
167
|
+
// awaiting there would hold the shell behind the layout's own awaits instead of streaming it.
|
|
206
168
|
const layoutSignature = isAsyncUserLayout
|
|
207
169
|
? "export default async function GeneratedLayout"
|
|
208
170
|
: "export default function GeneratedLayout";
|
|
@@ -215,6 +177,19 @@ async function writeGeneratedRootLayoutFile(opts: {
|
|
|
215
177
|
: "";
|
|
216
178
|
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
217
179
|
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
180
|
+
const layoutExports = `export async function generateHead(props: PageProps) {
|
|
181
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
182
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
183
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
184
|
+
return inheritedLayout.head;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
188
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
189
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
190
|
+
|
|
191
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
192
|
+
${layoutBinding} return `;
|
|
218
193
|
const source = opts.includeSystemProvider
|
|
219
194
|
? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
220
195
|
import { loadFonts, resolveRouteModule } from "akanjs/client";
|
|
@@ -229,26 +204,7 @@ if (defaultFonts.length > 1) throw new Error("[route-convention] only one defaul
|
|
|
229
204
|
const defaultFont = defaultFonts[0];
|
|
230
205
|
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
234
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
235
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
236
|
-
return inheritedLayout.head;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export async function generateMetadata(props: PageProps) {
|
|
240
|
-
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
241
|
-
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
242
|
-
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
243
|
-
return inheritedLayout.metadata;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
247
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
248
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
249
|
-
|
|
250
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
251
|
-
${layoutBinding} return (
|
|
207
|
+
${layoutExports}(
|
|
252
208
|
<System.Provider
|
|
253
209
|
of={GeneratedLayout as never}
|
|
254
210
|
appName=${JSON.stringify(opts.appName)}
|
|
@@ -258,7 +214,6 @@ ${layoutBinding} return (
|
|
|
258
214
|
theme={userLayout.theme ?? inheritedLayout.theme}
|
|
259
215
|
fonts={loadFonts(userFonts)}
|
|
260
216
|
className={defaultFontClassName}
|
|
261
|
-
gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
|
|
262
217
|
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
263
218
|
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
264
219
|
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
@@ -273,36 +228,13 @@ ${layoutBinding} return (
|
|
|
273
228
|
import { resolveRouteModule } from "akanjs/client";
|
|
274
229
|
import type { ReactNode } from "react";
|
|
275
230
|
${inheritedImport}${userImport}
|
|
276
|
-
|
|
277
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
278
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
279
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
280
|
-
return inheritedLayout.head;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export async function generateMetadata(props: PageProps) {
|
|
284
|
-
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
285
|
-
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
286
|
-
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
287
|
-
return inheritedLayout.metadata;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
291
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
292
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
293
|
-
|
|
294
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
295
|
-
${layoutBinding} return ${layoutReturn};
|
|
231
|
+
${layoutExports}${layoutReturn};
|
|
296
232
|
}
|
|
297
233
|
`;
|
|
298
234
|
await Bun.write(absPath, source);
|
|
299
235
|
return absPath;
|
|
300
236
|
}
|
|
301
237
|
|
|
302
|
-
/**
|
|
303
|
-
* When no root `page/_layout.*` exists on disk, merge a generated implicit root layout
|
|
304
|
-
* (with generated client runtime registration and optional `void st` when `lib/st.ts` exists).
|
|
305
|
-
*/
|
|
306
238
|
export async function resolveSsrPageEntries(opts: {
|
|
307
239
|
appCwdPath: string;
|
|
308
240
|
appName: string;
|
|
@@ -310,7 +242,7 @@ export async function resolveSsrPageEntries(opts: {
|
|
|
310
242
|
basePaths?: Iterable<string>;
|
|
311
243
|
}): Promise<PageEntry[]> {
|
|
312
244
|
const absPageDir = path.resolve(opts.appCwdPath, "page");
|
|
313
|
-
const hasSt = await
|
|
245
|
+
const hasSt = await Bun.file(path.join(opts.appCwdPath, "lib", "st.ts")).exists();
|
|
314
246
|
const basePaths = opts.basePaths ?? [];
|
|
315
247
|
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
316
248
|
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
@@ -325,9 +257,7 @@ export async function resolveSsrPageEntries(opts: {
|
|
|
325
257
|
.filter((key) => !rootLayoutKeys.has(key))
|
|
326
258
|
.map(async (key) => {
|
|
327
259
|
const userAbsPath = path.resolve(absPageDir, key);
|
|
328
|
-
|
|
329
|
-
// so the author writes no directive; the raw manifest becomes a build seed so its slot components (and
|
|
330
|
-
// the manifest itself) enter the client graph / RSC client manifest.
|
|
260
|
+
//? The raw manifest is a seed, so it and its slot components enter the client graph and RSC client manifest.
|
|
331
261
|
if (OVERRIDES_KEY_RE.test(key)) {
|
|
332
262
|
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
333
263
|
appCwdPath: opts.appCwdPath,
|
|
@@ -353,9 +283,7 @@ export async function resolveSsrPageEntries(opts: {
|
|
|
353
283
|
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))],
|
|
354
284
|
})),
|
|
355
285
|
);
|
|
356
|
-
|
|
357
|
-
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
358
|
-
return entries;
|
|
286
|
+
return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
|
|
359
287
|
}
|
|
360
288
|
|
|
361
289
|
export async function resolveSsrPageEntriesForApp(app: App, pageKeys: string[]): Promise<PageEntry[]> {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { computeRouteSeedIndex, serializeRouteSeedIndexForArtifact } from "./routeSeedIndex";
|
|
3
4
|
|
|
4
5
|
describe("computeRouteSeedIndex", () => {
|
|
@@ -19,10 +20,10 @@ describe("computeRouteSeedIndex", () => {
|
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
const leaf = index.entries.find((entry) => entry.routeId === "/:lang/foo");
|
|
22
|
-
expect(leaf?.seeds).toContain("/app/page/foo.tsx");
|
|
23
|
-
expect(leaf?.seeds).not.toContain("/app/page/foo/_layout.tsx");
|
|
24
|
-
expect(leaf?.seeds).toContain("/app/.akan/generated/root-layouts/__root_layout.tsx");
|
|
25
|
-
expect(leaf?.seeds).toContain("/app/page/_layout.tsx");
|
|
23
|
+
expect(leaf?.seeds).toContain(path.resolve("/app/page/foo.tsx"));
|
|
24
|
+
expect(leaf?.seeds).not.toContain(path.resolve("/app/page/foo/_layout.tsx"));
|
|
25
|
+
expect(leaf?.seeds).toContain(path.resolve("/app/.akan/generated/root-layouts/__root_layout.tsx"));
|
|
26
|
+
expect(leaf?.seeds).toContain(path.resolve("/app/page/_layout.tsx"));
|
|
26
27
|
|
|
27
28
|
expect(() =>
|
|
28
29
|
computeRouteSeedIndex([
|
|
@@ -42,8 +43,8 @@ describe("computeRouteSeedIndex", () => {
|
|
|
42
43
|
{ key: "./robots.txt.tsx", moduleAbsPath: "/app/page/robots.txt.tsx" },
|
|
43
44
|
]);
|
|
44
45
|
const robots = index.entries.find((entry) => entry.routeId === "/robots.txt");
|
|
45
|
-
expect(robots?.seeds).not.toContain("/app/.akan/generated/root-layouts/__root_layout.tsx");
|
|
46
|
-
expect(robots?.seeds).not.toContain("/app/page/_layout.tsx");
|
|
46
|
+
expect(robots?.seeds).not.toContain(path.resolve("/app/.akan/generated/root-layouts/__root_layout.tsx"));
|
|
47
|
+
expect(robots?.seeds).not.toContain(path.resolve("/app/page/_layout.tsx"));
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
test("serializes seed paths relative to the artifact directory", () => {
|
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
|
|
3
|
+
import {
|
|
4
|
+
ROUTE_SEED_INDEX_JSON,
|
|
5
|
+
type RouteSeedEntry,
|
|
6
|
+
type RouteSeedIndex,
|
|
7
|
+
} from "akanjs/server/artifact/routeSeedIndexStore";
|
|
3
8
|
import type { PageEntry } from "./implicitRootLayout";
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
* Build-time utilities for the route seed index. `computeRouteSeedIndex`
|
|
7
|
-
* takes resolved `PageEntry` rows (`key` plus absolute module path — files
|
|
8
|
-
* under `app/` or the generated implicit root layout) and produces — for every
|
|
9
|
-
* route — the list of source files that seed the `"use client"` graph walk.
|
|
10
|
-
*
|
|
11
|
-
* The result is serialized to `route-seed-index.json` by
|
|
12
|
-
* `saveRouteSeedIndex` so the runtime server can call
|
|
13
|
-
* `loadRouteSeedIndex` to restore it without re-importing `pages.ts`
|
|
14
|
-
* (which no longer exists) or reparsing loader sources.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export interface RouteSeedEntry {
|
|
18
|
-
routeId: string;
|
|
19
|
-
pattern: string;
|
|
20
|
-
seeds: string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface RouteSeedIndex {
|
|
24
|
-
entries: RouteSeedEntry[];
|
|
25
|
-
globalLayoutFiles: string[];
|
|
26
|
-
}
|
|
10
|
+
export { ROUTE_SEED_INDEX_JSON, type RouteSeedEntry, type RouteSeedIndex };
|
|
27
11
|
|
|
28
12
|
export type SerializedRouteSeedEntry = Pick<RouteSeedEntry, "routeId"> &
|
|
29
13
|
Partial<Pick<RouteSeedEntry, "pattern" | "seeds">>;
|
|
@@ -33,10 +17,6 @@ export interface SerializedRouteSeedIndex {
|
|
|
33
17
|
globalLayoutFiles?: string[];
|
|
34
18
|
}
|
|
35
19
|
|
|
36
|
-
/**
|
|
37
|
-
* Compute the route seed index from `PageEntry`s (disk paths or generated
|
|
38
|
-
* implicit root layout absolute paths).
|
|
39
|
-
*/
|
|
40
20
|
export function computeRouteSeedIndex(pageEntries: PageEntry[]): RouteSeedIndex {
|
|
41
21
|
const layoutsByPrefix = new Map<string, string[]>();
|
|
42
22
|
const pagesBySegments: Array<{
|
|
@@ -51,11 +31,8 @@ export function computeRouteSeedIndex(pageEntries: PageEntry[]): RouteSeedIndex
|
|
|
51
31
|
const parsed = parseRouteModuleKey(key);
|
|
52
32
|
const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
|
|
53
33
|
if (parsed.kind === "layout" || parsed.kind === "overrides") {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const prefix = parsed.routeSegments.join("/");
|
|
57
|
-
const prev = layoutsByPrefix.get(prefix) ?? [];
|
|
58
|
-
layoutsByPrefix.set(prefix, [...prev, ...files]);
|
|
34
|
+
//? Every route under an overrides prefix needs its "use client" wrapper in the client graph, as with a layout.
|
|
35
|
+
layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
|
|
59
36
|
} else if (parsed.kind === "page") {
|
|
60
37
|
pagesBySegments.push({
|
|
61
38
|
key,
|
|
@@ -68,9 +45,7 @@ export function computeRouteSeedIndex(pageEntries: PageEntry[]): RouteSeedIndex
|
|
|
68
45
|
}
|
|
69
46
|
assertUniqueRoutePatterns(pagesBySegments);
|
|
70
47
|
|
|
71
|
-
const
|
|
72
|
-
const globalLayoutFiles = rootLayouts;
|
|
73
|
-
|
|
48
|
+
const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
|
|
74
49
|
const seedEntries: RouteSeedEntry[] = [];
|
|
75
50
|
for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
|
|
76
51
|
const layouts: string[] = [];
|
|
@@ -84,26 +59,18 @@ export function computeRouteSeedIndex(pageEntries: PageEntry[]): RouteSeedIndex
|
|
|
84
59
|
const routeId = pattern || "/";
|
|
85
60
|
seedEntries.push({ routeId, pattern: routeId, seeds });
|
|
86
61
|
}
|
|
87
|
-
// Static segments must beat `:param` ones
|
|
88
|
-
// `/persona/new` resolves to `[personaId]=new`. Sort once at build time
|
|
89
|
-
// so the runtime matcher can stay first-match-wins.
|
|
62
|
+
// Static segments must beat `:param` ones (`/persona/new` is not `[personaId]=new`); the runtime is first-match-wins.
|
|
90
63
|
seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
|
|
91
64
|
return { entries: seedEntries, globalLayoutFiles };
|
|
92
65
|
}
|
|
93
66
|
|
|
94
|
-
export const ROUTE_SEED_INDEX_JSON = "route-seed-index.json";
|
|
95
|
-
|
|
96
67
|
export function serializeRouteSeedIndexForArtifact(
|
|
97
68
|
index: RouteSeedIndex,
|
|
98
69
|
artifactDir: string,
|
|
99
70
|
options: { production?: boolean } = {},
|
|
100
71
|
): SerializedRouteSeedIndex {
|
|
101
72
|
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
102
|
-
if (options.production) {
|
|
103
|
-
return {
|
|
104
|
-
entries: index.entries.map((entry) => ({ routeId: entry.routeId })),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
73
|
+
if (options.production) return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
|
|
107
74
|
return {
|
|
108
75
|
entries: index.entries.map((entry) => ({
|
|
109
76
|
...entry,
|
package/biome.base.json
CHANGED
|
@@ -154,7 +154,12 @@
|
|
|
154
154
|
]
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
|
-
"includes": [
|
|
157
|
+
"includes": [
|
|
158
|
+
"{apps,libs}/**/page/**/*.ts",
|
|
159
|
+
"{apps,libs}/**/page/**/*.tsx",
|
|
160
|
+
"{apps,libs}/**/*.Unit.tsx",
|
|
161
|
+
"{apps,libs}/**/*.View.tsx"
|
|
162
|
+
],
|
|
158
163
|
"plugins": ["./node_modules/@akanjs/devkit/lint/non-scalar-props-restricted.grit"]
|
|
159
164
|
},
|
|
160
165
|
{
|
package/biomeBase.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/** `extends` target for a workspace `biome.json`; Biome resolves it through node_modules. */
|
|
2
2
|
export const biomeBaseConfig = "@akanjs/devkit/biome.base.json";
|
|
3
3
|
|
|
4
|
-
// Biome moves rules between groups across minors
|
|
5
|
-
// `
|
|
6
|
-
// Biome disagrees with the shipped base config therefore fails to load it at all, which is why the version is
|
|
7
|
-
// pinned here instead of resolved to latest at create time. Bump this and `biome.base.json` in one commit, and run
|
|
8
|
-
// `biome migrate --write` in the workspace root and in `pkgs/@akanjs/devkit` so both configs move together.
|
|
4
|
+
// Pinned: Biome moves rules between groups across minors and a stale position is a hard config error. Bump it with
|
|
5
|
+
// `biome.base.json` in one commit, running `biome migrate --write` in the workspace root and in this package.
|
|
9
6
|
export const biomeVersion = "2.5.12";
|
package/capacitorApp.test.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import type { AkanMobileTargetConfig } from "./akanConfig";
|
|
6
5
|
import {
|
|
@@ -24,16 +23,12 @@ import {
|
|
|
24
23
|
sanitizeIosNativeRunEnv,
|
|
25
24
|
selectLocalDevHost,
|
|
26
25
|
sortIosRunTargets,
|
|
26
|
+
toIosInfoPlistUsageDescriptions,
|
|
27
27
|
writeRootCapacitorConfig,
|
|
28
28
|
} from "./capacitorApp";
|
|
29
|
+
import { tempDirs } from "./testHelpers";
|
|
29
30
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const makeTempRoot = async () => {
|
|
33
|
-
const root = await mkdtemp(path.join(os.tmpdir(), "akan-capacitor-app-"));
|
|
34
|
-
tempRoots.push(root);
|
|
35
|
-
return root;
|
|
36
|
-
};
|
|
31
|
+
const makeTempRoot = tempDirs("akan-capacitor-app-");
|
|
37
32
|
|
|
38
33
|
const baseTarget: AkanMobileTargetConfig = {
|
|
39
34
|
name: "default",
|
|
@@ -49,10 +44,6 @@ const baseTarget: AkanMobileTargetConfig = {
|
|
|
49
44
|
files: { android: { "app/google-services.json": "private/google-services.json" } },
|
|
50
45
|
};
|
|
51
46
|
|
|
52
|
-
afterEach(async () => {
|
|
53
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
47
|
describe("materializeCapacitorConfig", () => {
|
|
57
48
|
test("writes only Capacitor fields for release config", () => {
|
|
58
49
|
const config = materializeCapacitorConfig(
|
|
@@ -234,7 +225,7 @@ describe("iOS native run helpers", () => {
|
|
|
234
225
|
expect(deviceCommand.xcodebuildArgs).toContain("id=device-1");
|
|
235
226
|
expect(deviceCommand.xcodebuildArgs).toContain("App QA");
|
|
236
227
|
expect(deviceCommand.appPath).toBe(
|
|
237
|
-
"/repo/apps/minimal/ios/DerivedData/device-1/Build/Products/Debug-iphoneos/App.app",
|
|
228
|
+
path.join("/repo/apps/minimal/ios/DerivedData/device-1/Build/Products/Debug-iphoneos/App.app"),
|
|
238
229
|
);
|
|
239
230
|
|
|
240
231
|
const simulatorCommand = buildIosNativeRunCommand({
|
|
@@ -243,7 +234,7 @@ describe("iOS native run helpers", () => {
|
|
|
243
234
|
});
|
|
244
235
|
expect(simulatorCommand.xcodebuildArgs).toContain("platform=iOS Simulator,id=sim-1");
|
|
245
236
|
expect(simulatorCommand.appPath).toBe(
|
|
246
|
-
"/repo/apps/minimal/ios/DerivedData/sim-1/Build/Products/Debug-iphonesimulator/App.app",
|
|
237
|
+
path.join("/repo/apps/minimal/ios/DerivedData/sim-1/Build/Products/Debug-iphonesimulator/App.app"),
|
|
247
238
|
);
|
|
248
239
|
});
|
|
249
240
|
|
|
@@ -370,3 +361,28 @@ describe("raiseGradleMinSdkVersion", () => {
|
|
|
370
361
|
expect(raiseGradleMinSdkVersion("minSdkVersion = 28", 26)).toBeNull();
|
|
371
362
|
});
|
|
372
363
|
});
|
|
364
|
+
|
|
365
|
+
describe("toIosInfoPlistUsageDescriptions", () => {
|
|
366
|
+
test("writes Apple's real Info.plist keys, not NS + the description name", () => {
|
|
367
|
+
expect(
|
|
368
|
+
toIosInfoPlistUsageDescriptions({
|
|
369
|
+
cameraUsageDescription: "camera",
|
|
370
|
+
photoAddUsageDescription: "add",
|
|
371
|
+
photoUsageDescription: "read",
|
|
372
|
+
locationAlwaysUsageDescription: "always",
|
|
373
|
+
}),
|
|
374
|
+
).toEqual({
|
|
375
|
+
NSCameraUsageDescription: "camera",
|
|
376
|
+
NSPhotoLibraryAddUsageDescription: "add",
|
|
377
|
+
NSPhotoLibraryUsageDescription: "read",
|
|
378
|
+
NSLocationAlwaysAndWhenInUseUsageDescription: "always",
|
|
379
|
+
NSLocationAlwaysUsageDescription: "always",
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("keeps the NS prefix convention for a key it does not know", () => {
|
|
384
|
+
expect(toIosInfoPlistUsageDescriptions({ bluetoothAlwaysUsageDescription: "ble" })).toEqual({
|
|
385
|
+
NSBluetoothAlwaysUsageDescription: "ble",
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
});
|