@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/mobile/mobileTarget.ts
CHANGED
|
@@ -1,45 +1,52 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AkanNativeAppId } from "akanjs";
|
|
2
|
+
import type { AkanAppConfig, AkanNativeTarget, NativeEnv } from "../akanConfig";
|
|
2
3
|
import type { App } from "../commandDecorators";
|
|
3
4
|
|
|
4
5
|
export type MobilePlatform = "ios" | "android";
|
|
6
|
+
export type DesktopPlatform = "macos" | "windows" | "linux";
|
|
7
|
+
export type NativePlatform = MobilePlatform | DesktopPlatform;
|
|
5
8
|
export type MobileTargetSelection = string | "all" | undefined;
|
|
6
9
|
|
|
7
10
|
export interface ResolvedMobileTarget {
|
|
8
11
|
name: string;
|
|
9
|
-
config:
|
|
12
|
+
config: AkanNativeTarget;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
export const MOBILE_ENVS = ["local", "debug", "develop", "main"] as const satisfies readonly
|
|
15
|
+
export const MOBILE_ENVS = ["local", "debug", "develop", "main"] as const satisfies readonly NativeEnv[];
|
|
16
|
+
|
|
17
|
+
const trimSlashes = (value: string) => value.replace(/^\/+|\/+$/g, "");
|
|
13
18
|
|
|
14
19
|
export const getMobileTargets = async (app: App): Promise<ResolvedMobileTarget[]> => {
|
|
15
20
|
const config = await app.getConfig();
|
|
16
|
-
return Object.entries(config.
|
|
21
|
+
return Object.entries(config.native.targets).map(([name, target]) => ({ name, config: target }));
|
|
17
22
|
};
|
|
18
23
|
|
|
24
|
+
//* An app with basePaths builds a CSR page per basePath and none at its root, so a target naming no basePath opens
|
|
25
|
+
//* nothing on its own there; it is the template each basePath's target is made from.
|
|
26
|
+
const isTemplateOnly = ({ basePaths, native }: Pick<AkanAppConfig, "basePaths" | "native">) =>
|
|
27
|
+
basePaths.size > 0 && Object.values(native.targets).every((target) => !target.basePath);
|
|
28
|
+
|
|
19
29
|
export const getMobileTargetChoices = async (app: App): Promise<string[]> => {
|
|
20
30
|
const config = await app.getConfig();
|
|
21
|
-
const targetNames = Object.keys(config.
|
|
22
|
-
if (targetNames.length > 0) return targetNames;
|
|
23
|
-
|
|
24
|
-
return basePaths;
|
|
31
|
+
const targetNames = Object.keys(config.native.targets);
|
|
32
|
+
if (targetNames.length > 0 && !isTemplateOnly(config)) return targetNames;
|
|
33
|
+
return [...config.basePaths];
|
|
25
34
|
};
|
|
26
35
|
|
|
27
36
|
const resolveMobileTargetByBasePath = (
|
|
28
37
|
targets: ResolvedMobileTarget[],
|
|
29
38
|
basePath: string,
|
|
30
39
|
): ResolvedMobileTarget | undefined => {
|
|
31
|
-
const normalizedBasePath = basePath
|
|
32
|
-
const byBasePath = targets.find(
|
|
40
|
+
const normalizedBasePath = trimSlashes(basePath);
|
|
41
|
+
const byBasePath = targets.find(
|
|
42
|
+
({ config }) => config.basePath !== undefined && trimSlashes(config.basePath) === normalizedBasePath,
|
|
43
|
+
);
|
|
33
44
|
if (byBasePath) return byBasePath;
|
|
34
45
|
const [template] = targets;
|
|
35
46
|
if (!template) return undefined;
|
|
36
47
|
return {
|
|
37
48
|
name: normalizedBasePath,
|
|
38
|
-
config: {
|
|
39
|
-
...template.config,
|
|
40
|
-
name: normalizedBasePath,
|
|
41
|
-
basePath: normalizedBasePath,
|
|
42
|
-
},
|
|
49
|
+
config: { ...template.config, name: normalizedBasePath, basePath: normalizedBasePath },
|
|
43
50
|
};
|
|
44
51
|
};
|
|
45
52
|
|
|
@@ -49,17 +56,17 @@ export const resolveMobileTargets = async (
|
|
|
49
56
|
): Promise<ResolvedMobileTarget[]> => {
|
|
50
57
|
const config = await app.getConfig();
|
|
51
58
|
const targets = await getMobileTargets(app);
|
|
52
|
-
if (targets.length === 0) throw new Error(`No
|
|
53
|
-
if (!selection && targets.length === 1) return targets;
|
|
59
|
+
if (targets.length === 0) throw new Error(`No native targets configured for ${app.name}`);
|
|
60
|
+
if (!selection && targets.length === 1 && !isTemplateOnly(config)) return targets;
|
|
54
61
|
if (!selection) {
|
|
55
62
|
const choices = await getMobileTargetChoices(app);
|
|
56
63
|
if (choices.length === 1) return resolveMobileTargets(app, choices[0]);
|
|
57
|
-
throw new Error(`Multiple
|
|
64
|
+
throw new Error(`Multiple native targets found for ${app.name}. Pass --target <${choices.join("|")}|all>.`);
|
|
58
65
|
}
|
|
59
66
|
if (selection === "all") {
|
|
60
|
-
if (Object.keys(config.
|
|
67
|
+
if (Object.keys(config.native.targets).length > 1) return targets;
|
|
61
68
|
const basePaths = [...config.basePaths];
|
|
62
|
-
if (basePaths.length > 1) {
|
|
69
|
+
if (basePaths.length > 1 || isTemplateOnly(config)) {
|
|
63
70
|
return basePaths.flatMap((basePath) => {
|
|
64
71
|
const resolved = resolveMobileTargetByBasePath(targets, basePath);
|
|
65
72
|
return resolved ? [resolved] : [];
|
|
@@ -70,18 +77,30 @@ export const resolveMobileTargets = async (
|
|
|
70
77
|
const target = targets.find((candidate) => candidate.name === selection);
|
|
71
78
|
if (target) return [target];
|
|
72
79
|
const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
|
|
73
|
-
if (basePathTarget && config.basePaths.has(selection
|
|
80
|
+
if (basePathTarget && config.basePaths.has(trimSlashes(selection))) return [basePathTarget];
|
|
74
81
|
const choices = await getMobileTargetChoices(app);
|
|
75
|
-
throw new Error(`
|
|
82
|
+
throw new Error(`Native target '${selection}' was not found. Available: ${choices.join(", ")}`);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const resolveAppId = (appId: AkanNativeAppId, platform: NativePlatform): string => {
|
|
86
|
+
if (typeof appId === "string") return appId;
|
|
87
|
+
const id = appId[platform] ?? appId.default;
|
|
88
|
+
if (!id) throw new Error(`native.appId names no id for ${platform}; give it ${platform} or default.`);
|
|
89
|
+
return id;
|
|
76
90
|
};
|
|
77
91
|
|
|
78
|
-
export const
|
|
79
|
-
|
|
92
|
+
export const appIdsOf = (appId: AkanNativeAppId): string[] =>
|
|
93
|
+
typeof appId === "string" ? [appId] : Object.values(appId).filter((id): id is string => !!id);
|
|
94
|
+
|
|
95
|
+
export const resolveMobilePath = (target: AkanNativeTarget, pathname: string) => {
|
|
96
|
+
const basePath = trimSlashes(target.basePath ?? "");
|
|
80
97
|
const normalizedPath = `/${pathname.replace(/^\/+/, "")}`;
|
|
81
98
|
if (!basePath) return normalizedPath;
|
|
82
99
|
if (normalizedPath === `/${basePath}` || normalizedPath.startsWith(`/${basePath}/`)) return normalizedPath;
|
|
83
100
|
return `/${basePath}${normalizedPath === "/" ? "" : normalizedPath}`;
|
|
84
101
|
};
|
|
85
102
|
|
|
86
|
-
export const targetHtmlFilename = (target:
|
|
87
|
-
|
|
103
|
+
export const targetHtmlFilename = (target: AkanNativeTarget) => {
|
|
104
|
+
const basePath = trimSlashes(target.basePath ?? "");
|
|
105
|
+
return basePath ? `${basePath}.html` : "index.html";
|
|
106
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, realpath, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { tempDirs } from "../testHelpers";
|
|
5
|
+
import { NativeApi } from "./nativeApi";
|
|
6
|
+
|
|
7
|
+
const makeTempRoot = tempDirs("akan-native-api-");
|
|
8
|
+
const repoApp = path.resolve(import.meta.dir, "../../../../apps/minimal");
|
|
9
|
+
|
|
10
|
+
describe("NativeApi", () => {
|
|
11
|
+
test("inside this repo, loads the workspace package and checks its major", async () => {
|
|
12
|
+
expect(NativeApi.entryOf(repoApp)).toBe(path.resolve(import.meta.dir, "../../native/packages/cli/src/api.ts"));
|
|
13
|
+
const api = await NativeApi.load(repoApp);
|
|
14
|
+
expect(api.API_VERSION.split(".")[0]).toBe(String(NativeApi.supportedMajor));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("elsewhere, looks for the copy akanjs vendors, and says so when it is missing", async () => {
|
|
18
|
+
const root = await realpath(await makeTempRoot());
|
|
19
|
+
await mkdir(path.join(root, "node_modules/akanjs"), { recursive: true });
|
|
20
|
+
await writeFile(
|
|
21
|
+
path.join(root, "node_modules/akanjs/package.json"),
|
|
22
|
+
JSON.stringify({ name: "akanjs", exports: { "./package.json": "./package.json" } }),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(NativeApi.entryOf(root)).toBe(
|
|
26
|
+
path.join(root, "node_modules/akanjs/vendor/@akanjs/native/packages/cli/src/api.ts"),
|
|
27
|
+
);
|
|
28
|
+
await expect(NativeApi.load(root)).rejects.toThrow("The installed akanjs has no native build");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type * as NativeBuildApi from "@akanjs/native/api";
|
|
3
|
+
|
|
4
|
+
export type NativeBuildApiModule = typeof NativeBuildApi;
|
|
5
|
+
|
|
6
|
+
export class NativeApi {
|
|
7
|
+
//* The build API versions itself; a major this devkit was not written against is refused before anything builds.
|
|
8
|
+
static readonly supportedMajor = 0;
|
|
9
|
+
|
|
10
|
+
/** The API of the akanjs the app installed: the workspace package inside this repo, akanjs's vendored copy elsewhere. */
|
|
11
|
+
static entryOf(appDir: string): string {
|
|
12
|
+
const workspaceEntry = NativeApi.#resolve("@akanjs/native/api", appDir);
|
|
13
|
+
if (workspaceEntry) return workspaceEntry;
|
|
14
|
+
const akanjsPackage = NativeApi.#resolve("akanjs/package.json", appDir);
|
|
15
|
+
if (!akanjsPackage) throw new Error(`akanjs is not installed for ${appDir}; the native build ships inside it.`);
|
|
16
|
+
return path.join(path.dirname(akanjsPackage), "vendor", "@akanjs", "native", "packages", "cli", "src", "api.ts");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static async load(appDir: string): Promise<NativeBuildApiModule> {
|
|
20
|
+
const entry = NativeApi.entryOf(appDir);
|
|
21
|
+
if (!(await Bun.file(entry).exists()))
|
|
22
|
+
throw new Error(`The installed akanjs has no native build at ${entry}; update akanjs.`);
|
|
23
|
+
const api = (await import(entry)) as NativeBuildApiModule;
|
|
24
|
+
const major = Number(api.API_VERSION.split(".")[0]);
|
|
25
|
+
if (major !== NativeApi.supportedMajor)
|
|
26
|
+
throw new Error(
|
|
27
|
+
`The native build API is ${api.API_VERSION}, and this akan CLI speaks ${NativeApi.supportedMajor}.x; install matching versions of akanjs and @akanjs/cli.`,
|
|
28
|
+
);
|
|
29
|
+
return api;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static #resolve(specifier: string, from: string) {
|
|
33
|
+
try {
|
|
34
|
+
return Bun.resolveSync(specifier, from);
|
|
35
|
+
} catch {
|
|
36
|
+
// Not installed where the app can reach it, which the caller answers with the next place to look.
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { AkanPlugin } from "akanjs";
|
|
4
|
+
import type { AkanNativeTarget } from "../akanConfig";
|
|
5
|
+
import type { App } from "../commandDecorators";
|
|
6
|
+
import { tempDirs, writeText } from "../testHelpers";
|
|
7
|
+
import { NativeApp } from "./nativeApp";
|
|
8
|
+
import { NativeConfig } from "./nativeConfig";
|
|
9
|
+
|
|
10
|
+
const makeTempRoot = tempDirs("akan-native-app-");
|
|
11
|
+
|
|
12
|
+
const target = (config: Partial<AkanNativeTarget> = {}) => ({
|
|
13
|
+
name: config.name ?? "default",
|
|
14
|
+
config: {
|
|
15
|
+
name: config.name ?? "default",
|
|
16
|
+
appName: "Portal",
|
|
17
|
+
appId: "com.portal.app",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
buildNum: 1,
|
|
20
|
+
...config,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const appDir = "/repo/apps/portal";
|
|
25
|
+
|
|
26
|
+
const fakeApp = (plugins: AkanPlugin[] = []) =>
|
|
27
|
+
({
|
|
28
|
+
name: "portal",
|
|
29
|
+
cwdPath: appDir,
|
|
30
|
+
workspace: { workspaceRoot: "/repo" },
|
|
31
|
+
getScanInfo: () => ({ getLibs: () => [] }),
|
|
32
|
+
dist: { cwdPath: "/repo/dist/apps/portal" },
|
|
33
|
+
getConfig: async () => ({ i18n: { locales: ["en", "ko"] }, api: { prefix: "/api", websocketPrefix: "/ws" } }),
|
|
34
|
+
collectPlugins: async () => plugins,
|
|
35
|
+
logger: { warn: () => undefined },
|
|
36
|
+
}) as unknown as App;
|
|
37
|
+
|
|
38
|
+
describe("NativeApp", () => {
|
|
39
|
+
test("keeps each target's web root and each platform's output apart", () => {
|
|
40
|
+
const admin = new NativeApp(fakeApp(), target({ name: "admin", basePath: "admin" }));
|
|
41
|
+
|
|
42
|
+
expect(admin.web.dir).toBe(path.join(appDir, ".akan/native/admin/web"));
|
|
43
|
+
expect(admin.outDir("ios")).toBe(path.join(appDir, ".akan/native/admin/build/ios"));
|
|
44
|
+
expect(admin.outDir("android")).toBe(path.join(appDir, ".akan/native/admin/build/android"));
|
|
45
|
+
expect(admin.devOutDir("macos")).toBe(path.join(appDir, ".akan/native/admin/dev/macos"));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("a dev build bundles no page a release build left, and builds apart from the release it would empty", async () => {
|
|
49
|
+
const app = {
|
|
50
|
+
...fakeApp(),
|
|
51
|
+
cwdPath: await makeTempRoot(),
|
|
52
|
+
logger: { info: () => undefined, debug: () => undefined, warn: () => undefined },
|
|
53
|
+
} as unknown as App;
|
|
54
|
+
const nativeApp = new NativeApp(app, target());
|
|
55
|
+
await writeText(path.join(nativeApp.web.dir, "index.html"), "<html>the last release</html>");
|
|
56
|
+
const seen: { outDir?: string; leftover: boolean }[] = [];
|
|
57
|
+
nativeApp.prepare = async () =>
|
|
58
|
+
({
|
|
59
|
+
config: {},
|
|
60
|
+
api: {
|
|
61
|
+
dev: async ({ outDir }: { outDir?: string }) => {
|
|
62
|
+
seen.push({ outDir, leftover: await Bun.file(path.join(nativeApp.web.dir, "index.html")).exists() });
|
|
63
|
+
return { gateway: "http://127.0.0.1:9", exited: Promise.resolve(0), stop: async () => undefined };
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}) as never;
|
|
67
|
+
|
|
68
|
+
await nativeApp.dev("windows", { upstream: "http://localhost:8282", lang: "en" });
|
|
69
|
+
|
|
70
|
+
expect(seen).toEqual([{ outDir: nativeApp.devOutDir("windows"), leftover: false }]);
|
|
71
|
+
expect(path.relative(nativeApp.outDir("windows"), nativeApp.devOutDir("windows"))).toStartWith("..");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a dev boot that came up says so in one line: platform, device, dev server, time", () => {
|
|
75
|
+
const app = { ...fakeApp(), name: "portal" } as unknown as App;
|
|
76
|
+
const startedAt = performance.now() - 12_400;
|
|
77
|
+
expect(new NativeApp(app, target()).readyLine("macos", { upstream: "http://localhost:8283", startedAt })).toMatch(
|
|
78
|
+
/^portal desktop ready · http:\/\/localhost:8283 · 12\.\ds$/,
|
|
79
|
+
);
|
|
80
|
+
expect(
|
|
81
|
+
new NativeApp(app, target({ name: "admin" })).readyLine("ios", {
|
|
82
|
+
upstream: "http://localhost:8283",
|
|
83
|
+
device: { name: "iPhone 16" },
|
|
84
|
+
startedAt,
|
|
85
|
+
}),
|
|
86
|
+
).toMatch(/^portal\/admin ios ready · iPhone 16 · http:\/\/localhost:8283 · 12\.\ds$/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("an installer is what a person downloads, on the desktops that have one", () => {
|
|
90
|
+
expect(() => NativeApp.assertInstaller("windows", true)).not.toThrow();
|
|
91
|
+
expect(() => NativeApp.assertInstaller("macos", true)).not.toThrow();
|
|
92
|
+
expect(() => NativeApp.assertInstaller("linux", true)).not.toThrow();
|
|
93
|
+
expect(() => NativeApp.assertInstaller("ios", true)).toThrow(/not for ios/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("opens a dev build on its target's home, the CSR shell under the locale", () => {
|
|
97
|
+
expect(new NativeApp(fakeApp(), target({ indexPath: "/explore" })).startPath("en", "ios")).toBe(
|
|
98
|
+
"/en/explore?csr=true&akanMobileTarget=default&akanMobileIndexPath=%2Fexplore",
|
|
99
|
+
);
|
|
100
|
+
expect(new NativeApp(fakeApp(), target({ name: "admin", basePath: "/admin/" })).startPath("ko", "android")).toBe(
|
|
101
|
+
"/ko/admin?csr=true&akanMobileTarget=admin&akanMobileBasePath=admin",
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a platform section's indexPath wins on that platform, and the others keep the target's", () => {
|
|
106
|
+
const app = new NativeApp(fakeApp(), target({ indexPath: "/mobile", desktop: { indexPath: "/", server: true } }));
|
|
107
|
+
|
|
108
|
+
expect(app.indexPath("ios")).toBe("/mobile");
|
|
109
|
+
expect(app.indexPath("android")).toBe("/mobile");
|
|
110
|
+
expect(app.indexPath("macos")).toBe("/");
|
|
111
|
+
expect(app.indexPath("windows")).toBe("/");
|
|
112
|
+
expect(app.startPath("en", "macos")).toBe("/en?csr=true&akanMobileTarget=default&akanMobileIndexPath=%2F");
|
|
113
|
+
expect(app.startPath("en", "ios")).toBe(
|
|
114
|
+
"/en/mobile?csr=true&akanMobileTarget=default&akanMobileIndexPath=%2Fmobile",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("assembles each platform's web root with that platform's indexPath", async () => {
|
|
119
|
+
const root = await makeTempRoot();
|
|
120
|
+
await writeText(path.join(root, "dist/csr/index.html"), "<html><head></head><body></body></html>");
|
|
121
|
+
const app = { ...fakeApp(), cwdPath: root, dist: { cwdPath: path.join(root, "dist") } } as unknown as App;
|
|
122
|
+
const nativeApp = new NativeApp(app, target({ indexPath: "/mobile", desktop: { indexPath: "/console" } }));
|
|
123
|
+
const injected = async () => await Bun.file(path.join(nativeApp.web.dir, "index.html")).text();
|
|
124
|
+
|
|
125
|
+
await nativeApp.assembleWeb("linux");
|
|
126
|
+
expect(await injected()).toContain('"indexPath":"/console"');
|
|
127
|
+
await nativeApp.assembleWeb("android");
|
|
128
|
+
expect(await injected()).toContain('"indexPath":"/mobile"');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("builds its config from the app's locales and the plugins' native contributions", async () => {
|
|
132
|
+
const push: AkanPlugin = { name: "push-notification", native: { permission: "push", plugins: ["push"] } };
|
|
133
|
+
const app = new NativeApp(
|
|
134
|
+
fakeApp([push, { name: "other" }]),
|
|
135
|
+
target({ basePath: undefined, permissions: ["push"] }),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const { config, warnings } = await app.config({ platform: "android" });
|
|
139
|
+
|
|
140
|
+
expect(warnings).toEqual([]);
|
|
141
|
+
expect(config.plugins).toEqual([...NativeConfig.basePlugins, "push"]);
|
|
142
|
+
expect(config.web.dir).toBe(path.join(appDir, ".akan/native/default/web"));
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("a config nothing is built from (the update key's) stages no desktop bin", async () => {
|
|
146
|
+
const { config } = await new NativeApp(fakeApp(), target({ basePath: undefined })).config({
|
|
147
|
+
platform: "macos",
|
|
148
|
+
stageBin: false,
|
|
149
|
+
});
|
|
150
|
+
expect(config.desktop?.bin).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("follows the updates channel of the backend env it is built for", async () => {
|
|
154
|
+
const updates = { url: "https://releases.example.com/portal", publicKey: `${"a".repeat(43)}=` };
|
|
155
|
+
const built = async (env?: "debug" | "main") =>
|
|
156
|
+
(await new NativeApp(fakeApp(), target({ basePath: undefined, updates }), env).config({ platform: "android" }))
|
|
157
|
+
.config.updates;
|
|
158
|
+
|
|
159
|
+
expect(await built("main")).toEqual({ ...updates, channel: "main" });
|
|
160
|
+
expect(await built("debug")).toEqual({ ...updates, channel: "debug" });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("a desktop app is this computer's platform", () => {
|
|
164
|
+
expect(["darwin", "win32", "linux"].map((host) => NativeApp.desktopPlatform(host as NodeJS.Platform))).toEqual([
|
|
165
|
+
"macos",
|
|
166
|
+
"windows",
|
|
167
|
+
"linux",
|
|
168
|
+
]);
|
|
169
|
+
expect(() => NativeApp.desktopPlatform("freebsd")).toThrow("A desktop app builds on macOS, Windows or Linux");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("signs an Android release with the upload key the environment names, and says which part is missing", () => {
|
|
173
|
+
expect(
|
|
174
|
+
NativeApp.androidSigning({
|
|
175
|
+
MYAPP_RELEASE_STORE_FILE: "keys/upload.jks",
|
|
176
|
+
MYAPP_RELEASE_STORE_PASSWORD: "store",
|
|
177
|
+
MYAPP_RELEASE_KEY_ALIAS: "upload",
|
|
178
|
+
}),
|
|
179
|
+
).toEqual({ keystore: "keys/upload.jks", storePassword: "store", alias: "upload" });
|
|
180
|
+
expect(() => NativeApp.androidSigning({ MYAPP_RELEASE_STORE_FILE: "keys/upload.jks" })).toThrow(
|
|
181
|
+
"set MYAPP_RELEASE_STORE_PASSWORD, MYAPP_RELEASE_KEY_ALIAS in the environment",
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
});
|