@akanjs/devkit 3.0.0-beta.3 → 3.0.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEV_RUNTIME_KNOBS.md +44 -26
- package/README.ko.md +2 -2
- package/README.md +3 -2
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +56 -3
- package/akanApp/akanApp.host.ts +218 -278
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +81 -35
- package/akanApp/devHostPolicy.ts +41 -102
- package/akanConfig/akanBin.test.ts +62 -0
- package/akanConfig/akanBin.ts +78 -0
- package/akanConfig/akanConfig.test.ts +427 -141
- package/akanConfig/akanConfig.ts +327 -275
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +21 -8
- package/akanContext.ts +110 -160
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +41 -5
- package/applicationBuildRunner.ts +71 -63
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +32 -30
- package/artifact/implicitRootLayout.ts +58 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +35 -2
- package/biomeBase.ts +2 -5
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +71 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/csrE2e/csrE2eHarness.fixture.ts +505 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +252 -0
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/registryVendor.e2e.test.ts +221 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +176 -106
- package/executors.ts +212 -498
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +22 -9
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +123 -37
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +16 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +122 -110
- package/frontendBuild/csrArtifactBuilder.ts +9 -31
- package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
- package/frontendBuild/csrDevArtifactWriter.ts +307 -0
- package/frontendBuild/csrDevBundler.test.ts +66 -0
- package/frontendBuild/csrDevBundler.ts +95 -0
- package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
- package/frontendBuild/csrDevFactoryCheck.ts +39 -0
- package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
- package/frontendBuild/csrDevModuleCompiler.ts +422 -0
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +145 -0
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +363 -0
- package/frontendBuild/csrDevTypes.ts +61 -0
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +31 -82
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +57 -70
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +59 -32
- package/frontendBuild/hmrWatcher.ts +31 -74
- package/frontendBuild/index.ts +4 -0
- package/frontendBuild/pagesBundleBuilder.ts +28 -30
- package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +100 -62
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
- package/frontendBuild/ssrDevBundler.ts +85 -0
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +147 -63
- package/incrementalBuilder/buildBatchProtocol.ts +13 -38
- package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
- package/incrementalBuilder/buildBatchRunner.ts +26 -38
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
- package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +269 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
- package/index.ts +5 -19
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +105 -199
- package/integration/devStabilityHarness.test.ts +13 -28
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
- package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
- package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
- package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-document-cookie.grit +29 -0
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/lint/no-web-only-api-outside-webkit.grit +44 -0
- package/lint/no-web-storage.grit +30 -0
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/desktopBin.test.ts +177 -0
- package/mobile/desktopBin.ts +143 -0
- package/mobile/desktopServerStage.test.ts +204 -0
- package/mobile/desktopServerStage.ts +138 -0
- package/mobile/index.ts +9 -0
- package/mobile/mobileTarget.test.ts +57 -8
- package/mobile/mobileTarget.ts +45 -26
- package/mobile/nativeApi.test.ts +30 -0
- package/mobile/nativeApi.ts +40 -0
- package/mobile/nativeApp.test.ts +177 -0
- package/mobile/nativeApp.ts +416 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +414 -0
- package/mobile/nativeConfig.ts +293 -0
- package/mobile/nativePluginFolders.test.ts +81 -0
- package/mobile/nativePluginFolders.ts +60 -0
- package/mobile/nativeWebDir.test.ts +80 -0
- package/mobile/nativeWebDir.ts +66 -0
- package/mobile/placeholderAppId.test.ts +13 -0
- package/mobile/placeholderAppId.ts +16 -0
- package/mobile/usageDescriptions.test.ts +27 -0
- package/mobile/usageDescriptions.ts +24 -0
- package/package.json +11 -11
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +200 -286
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +91 -225
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/stripAnsi.ts +3 -0
- package/subspace.test.ts +30 -25
- package/subspace.ts +81 -115
- package/subspaceConfig.ts +11 -9
- package/testHelpers.ts +194 -0
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +42 -20
- package/transforms/serverEnvPlugin.test.ts +103 -0
- package/transforms/serverEnvPlugin.ts +41 -0
- package/transforms/transforms.test.ts +166 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +6 -23
- package/typeChecker.ts +7 -63
- package/types.ts +2 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.test.ts +18 -3
- package/workspaceLayout.ts +22 -23
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/applicationReleasePackager.ts +0 -209
- package/capacitorApp.test.ts +0 -372
- package/capacitorApp.ts +0 -1648
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -2,17 +2,24 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import type { AkanPlugin } from "akanjs";
|
|
5
|
-
import { normalizeRoutePrefix } from "akanjs/base";
|
|
5
|
+
import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
|
|
6
6
|
import { type AkanI18nConfig, resolveAkanI18nConfig } from "akanjs/common";
|
|
7
7
|
import type { AkanImageConfig } from "akanjs/server";
|
|
8
8
|
import type { App, Lib } from "../commandDecorators";
|
|
9
9
|
import { LibExecutor, WorkspaceExecutor } from "../executors";
|
|
10
10
|
import type { BaseDevEnv, PackageJson } from "../types";
|
|
11
|
+
import { AkanBin } from "./akanBin";
|
|
11
12
|
import {
|
|
12
13
|
type AkanApiConfig,
|
|
13
14
|
type AkanAssetsConfig,
|
|
14
|
-
type
|
|
15
|
-
type
|
|
15
|
+
type AkanBinConfig,
|
|
16
|
+
type AkanDatabaseConfig,
|
|
17
|
+
type AkanNativeAppConfig,
|
|
18
|
+
type AkanNativeAppResult,
|
|
19
|
+
type AkanNativeDeepLinks,
|
|
20
|
+
type AkanNativeSettings,
|
|
21
|
+
type AkanNativeTarget,
|
|
22
|
+
type AkanNativeUpdatesConfig,
|
|
16
23
|
type AkanRouteConfig,
|
|
17
24
|
type AkanWebConfig,
|
|
18
25
|
type AkanWebOption,
|
|
@@ -31,78 +38,14 @@ import {
|
|
|
31
38
|
|
|
32
39
|
const DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
|
|
33
40
|
const DEFAULT_OPTIMIZE_IMPORTS = [
|
|
34
|
-
"lucide-react",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
"react-bootstrap",
|
|
40
|
-
"ahooks",
|
|
41
|
-
"@ant-design/icons",
|
|
42
|
-
"@headlessui/react",
|
|
43
|
-
"@headlessui-float/react",
|
|
44
|
-
"@heroicons/react/20/solid",
|
|
45
|
-
"@heroicons/react/24/solid",
|
|
46
|
-
"@heroicons/react/24/outline",
|
|
47
|
-
"@visx/visx",
|
|
48
|
-
"@tremor/react",
|
|
49
|
-
"rxjs",
|
|
50
|
-
"@mui/material",
|
|
51
|
-
"@mui/icons-material",
|
|
52
|
-
"recharts",
|
|
53
|
-
"react-use",
|
|
54
|
-
"@material-ui/core",
|
|
55
|
-
"@material-ui/icons",
|
|
56
|
-
"@tabler/icons-react",
|
|
57
|
-
"mui-core",
|
|
58
|
-
"react-icons/*",
|
|
59
|
-
];
|
|
41
|
+
"lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
|
|
42
|
+
"@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
|
|
43
|
+
"@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
|
|
44
|
+
"@material-ui/icons @tabler/icons-react mui-core react-icons/*",
|
|
45
|
+
].flatMap((line) => line.split(" "));
|
|
60
46
|
const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
|
|
61
47
|
const DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
62
48
|
const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
|
|
63
|
-
// The firebase client (push tokens) and the Capacitor toolchain — `@capacitor/cli` (`npx cap`),
|
|
64
|
-
// `@capacitor/assets` (`npx @capacitor/assets`) plus the `@capacitor/core`/`ios`/`android` runtime
|
|
65
|
-
// and native-platform packages that `npx cap add`/`sync` resolve from the workspace node_modules.
|
|
66
|
-
// All are declared only as optional peers, so a fresh workspace never auto-installs them; the mobile
|
|
67
|
-
// preflight installs them (together with MOBILE_APP_CAPACITOR_PLUGINS) at the workspace root.
|
|
68
|
-
const MOBILE_RUNTIME_PACKAGES = [
|
|
69
|
-
"@capacitor/cli",
|
|
70
|
-
"@capacitor/core",
|
|
71
|
-
"@capacitor/ios",
|
|
72
|
-
"@capacitor/android",
|
|
73
|
-
"@capacitor/assets",
|
|
74
|
-
] as const;
|
|
75
|
-
// Capacitor plugins that must additionally be declared in the *app's* package.json
|
|
76
|
-
// (apps/<app>/package.json): `npx cap sync` discovers plugins by scanning the app directory's
|
|
77
|
-
// dependencies and registers their native code into the iOS/Android projects; packages present only
|
|
78
|
-
// at the workspace root are never registered, so the JS bridge throws
|
|
79
|
-
// `Capacitor plugin "Device" is not available.` at runtime. They are installed at the workspace root
|
|
80
|
-
// alongside MOBILE_RUNTIME_PACKAGES (pinned via optional peers) and declared in the app with a "*"
|
|
81
|
-
// range so bun dedupes them to that hoisted version instead of pinning a second source of truth.
|
|
82
|
-
const MOBILE_APP_CAPACITOR_PLUGINS = [
|
|
83
|
-
"@capacitor/app",
|
|
84
|
-
"@capacitor/browser",
|
|
85
|
-
"@capacitor/camera",
|
|
86
|
-
"@capacitor/core",
|
|
87
|
-
"@capacitor/device",
|
|
88
|
-
"@capacitor/geolocation",
|
|
89
|
-
"@capacitor/haptics",
|
|
90
|
-
"@capacitor/inappbrowser",
|
|
91
|
-
"@capacitor/keyboard",
|
|
92
|
-
"@capacitor/preferences",
|
|
93
|
-
"@capacitor/push-notifications",
|
|
94
|
-
"capacitor-plugin-safe-area",
|
|
95
|
-
] as const;
|
|
96
|
-
const DATABASE_MODE_RUNTIME_PACKAGES = {
|
|
97
|
-
single: [],
|
|
98
|
-
multiple: ["@libsql/client", "bullmq", "ioredis", "protobufjs"],
|
|
99
|
-
cluster: ["bullmq", "ioredis", "postgres", "protobufjs"],
|
|
100
|
-
} satisfies Record<DatabaseMode, readonly string[]>;
|
|
101
|
-
const AKAN_RUNTIME_PACKAGES = new Set<string>([
|
|
102
|
-
...SSR_RUNTIME_PACKAGES,
|
|
103
|
-
...MOBILE_RUNTIME_PACKAGES,
|
|
104
|
-
...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat(),
|
|
105
|
-
]);
|
|
106
49
|
const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
|
|
107
50
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
108
51
|
imageSizes: [32, 48, 64, 96, 128, 256, 384],
|
|
@@ -130,18 +73,14 @@ const normalizeStringList = (values: string[] | undefined) => {
|
|
|
130
73
|
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
131
74
|
};
|
|
132
75
|
|
|
133
|
-
//
|
|
134
|
-
// only (hyphens/spaces dropped), never empty, never starting with a digit.
|
|
76
|
+
// A reverse-DNS / Android package segment: lowercase alphanumerics, never empty, never starting with a digit.
|
|
135
77
|
const sanitizeAppIdSegment = (value: string): string => {
|
|
136
78
|
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
137
79
|
if (!cleaned) return "app";
|
|
138
80
|
return /^[a-z]/.test(cleaned) ? cleaned : `app${cleaned}`;
|
|
139
81
|
};
|
|
140
82
|
|
|
141
|
-
// The
|
|
142
|
-
// (repo) name as the org segment so the default is far less collision-prone than a bare
|
|
143
|
-
// `com.<appName>.app`, which Apple's portal routinely already has claimed. Apps that ship pin an
|
|
144
|
-
// explicit appId, so this only affects unconfigured/dev apps.
|
|
83
|
+
// The repo name is the org segment: a bare `com.<appName>.app` is routinely already claimed on Apple's portal.
|
|
145
84
|
export const deriveDefaultAppId = (orgName: string, appName: string): string =>
|
|
146
85
|
`com.${sanitizeAppIdSegment(orgName)}.${sanitizeAppIdSegment(appName)}`;
|
|
147
86
|
|
|
@@ -159,30 +98,27 @@ const normalizeDeepLinkDomain = (domain: string) => {
|
|
|
159
98
|
}
|
|
160
99
|
};
|
|
161
100
|
|
|
162
|
-
const normalizeDeepLinks = (deepLinks:
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints as string[] | undefined);
|
|
168
|
-
if (!schemes && !domains && !teamId && !sha256CertFingerprints) return undefined;
|
|
169
|
-
return {
|
|
170
|
-
...(schemes ? { schemes } : {}),
|
|
171
|
-
...(domains ? { domains } : {}),
|
|
172
|
-
...(teamId ? { ios: { teamId } } : {}),
|
|
173
|
-
...(sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}),
|
|
174
|
-
} satisfies AkanMobileTargetConfig["deepLinks"];
|
|
101
|
+
const normalizeDeepLinks = (deepLinks: AkanNativeDeepLinks | undefined): AkanNativeDeepLinks | undefined => {
|
|
102
|
+
const schemes = normalizeStringList(deepLinks?.schemes);
|
|
103
|
+
const domains = normalizeStringList(deepLinks?.domains?.map(normalizeDeepLinkDomain));
|
|
104
|
+
if (!schemes && !domains) return undefined;
|
|
105
|
+
return { ...(schemes ? { schemes } : {}), ...(domains ? { domains } : {}) };
|
|
175
106
|
};
|
|
176
107
|
|
|
177
|
-
|
|
178
|
-
type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "docker" | "web"> & {
|
|
108
|
+
type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "docker" | "web" | "bin" | "native"> & {
|
|
179
109
|
docker?: DockerOption;
|
|
180
110
|
web?: AkanWebOption;
|
|
111
|
+
bin?: AkanBinConfig;
|
|
112
|
+
native?: AkanNativeAppConfig;
|
|
181
113
|
};
|
|
182
114
|
|
|
183
|
-
|
|
115
|
+
type LibConfigDeclaration = Omit<DeepPartial<LibConfigResult>, "bin"> & { bin?: AkanBinConfig };
|
|
116
|
+
|
|
184
117
|
export interface LibContributions {
|
|
185
118
|
externalLibs: string[];
|
|
119
|
+
trustedDependencies?: string[];
|
|
120
|
+
/** Per lib, since an app carries only the entries of the libs it depends on. */
|
|
121
|
+
bin?: { lib: string; bin: AkanBinConfig }[];
|
|
186
122
|
docker: LibDockerConfig;
|
|
187
123
|
/** Keep globs rewritten to the app's own `public/`, where `akan sync` mounts each lib's assets. */
|
|
188
124
|
keepFonts?: string[];
|
|
@@ -190,10 +126,19 @@ export interface LibContributions {
|
|
|
190
126
|
|
|
191
127
|
const emptyLibContributions = (): LibContributions => ({
|
|
192
128
|
externalLibs: [],
|
|
129
|
+
trustedDependencies: [],
|
|
130
|
+
bin: [],
|
|
193
131
|
docker: { preRuns: [], postRuns: [] },
|
|
194
132
|
keepFonts: [],
|
|
195
133
|
});
|
|
196
134
|
|
|
135
|
+
const normalizePackageNames = (names: unknown, owner: string): string[] => {
|
|
136
|
+
if (names === undefined) return [];
|
|
137
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || !name.trim()))
|
|
138
|
+
throw new Error(`${owner}: trustedDependencies lists package names`);
|
|
139
|
+
return [...new Set(names.map((name: string) => name.trim()))];
|
|
140
|
+
};
|
|
141
|
+
|
|
197
142
|
const normalizeKeepFonts = (keepFonts: string[] | undefined) => [
|
|
198
143
|
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean)),
|
|
199
144
|
];
|
|
@@ -211,21 +156,25 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
211
156
|
docker: DockerConfig;
|
|
212
157
|
/** The Dockerfile `akan build` writes: the declared string verbatim, or one assembled from the parts. */
|
|
213
158
|
dockerfile: string;
|
|
214
|
-
|
|
159
|
+
database: AkanDatabaseConfig;
|
|
215
160
|
web: AkanWebConfig;
|
|
216
161
|
externalLibs: string[];
|
|
162
|
+
trustedDependencies: string[];
|
|
163
|
+
/** The app's own entries; the libs' are in `libBins`. */
|
|
164
|
+
bin: AkanBinConfig;
|
|
165
|
+
libBins: { lib: string; bin: AkanBinConfig }[];
|
|
217
166
|
barrelImports: string[];
|
|
218
167
|
optimizeImports: string[];
|
|
219
168
|
images: AkanImageConfig;
|
|
220
169
|
i18n: AkanI18nConfig;
|
|
221
170
|
api: AkanApiConfig;
|
|
222
171
|
publicEnv: string[];
|
|
223
|
-
|
|
224
|
-
/** True only when the app's akan.config.ts
|
|
225
|
-
|
|
172
|
+
native: AkanNativeAppResult;
|
|
173
|
+
/** True only when the app's akan.config.ts declares a `native` section (vs. the synthesized default target). */
|
|
174
|
+
hasNativeConfig: boolean;
|
|
226
175
|
secrets: string[];
|
|
227
176
|
assets: AkanAssetsConfig;
|
|
228
|
-
/** Raw setting
|
|
177
|
+
/** Raw setting, resolved against the app's lib deps at sync time. */
|
|
229
178
|
syncPageLibs: string[] | boolean;
|
|
230
179
|
baseDevEnv: BaseDevEnv;
|
|
231
180
|
libs: string[];
|
|
@@ -250,8 +199,17 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
250
199
|
this.baseDevEnv = baseDevEnv;
|
|
251
200
|
this.plugins = plugins;
|
|
252
201
|
this.#applyRoutes(config?.routes);
|
|
253
|
-
this.
|
|
202
|
+
this.database = AkanAppConfig.#database(app, config);
|
|
254
203
|
this.externalLibs = [...new Set([...(config?.externalLibs ?? []), ...libContributions.externalLibs])];
|
|
204
|
+
const owner = `apps/${app.name}/akan.config.ts`;
|
|
205
|
+
this.trustedDependencies = [
|
|
206
|
+
...new Set([
|
|
207
|
+
...normalizePackageNames(config?.trustedDependencies, owner),
|
|
208
|
+
...(libContributions.trustedDependencies ?? []),
|
|
209
|
+
]),
|
|
210
|
+
];
|
|
211
|
+
this.bin = AkanBin.parse(config?.bin, owner, app.cwdPath);
|
|
212
|
+
this.libBins = libContributions.bin ?? [];
|
|
255
213
|
this.barrelImports = [
|
|
256
214
|
...DEFAULT_BARREL_IMPORTS,
|
|
257
215
|
...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
|
|
@@ -279,8 +237,12 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
279
237
|
],
|
|
280
238
|
};
|
|
281
239
|
this.syncPageLibs = (config?.syncPageLibs as string[] | boolean | undefined) ?? false;
|
|
282
|
-
|
|
283
|
-
|
|
240
|
+
if ((config as { mobile?: unknown }).mobile !== undefined)
|
|
241
|
+
throw new Error(
|
|
242
|
+
`apps/${this.app.name}/akan.config.ts declares \`mobile\`, which is now \`native\`: the platform settings sit in native.ios, native.android and native.desktop, and a target overrides them under native.targets.<name>.`,
|
|
243
|
+
);
|
|
244
|
+
this.hasNativeConfig = Boolean(config.native);
|
|
245
|
+
this.native = this.#resolveNativeConfig(config.native);
|
|
284
246
|
this.web = this.#resolveWebConfig(config.web);
|
|
285
247
|
this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
|
|
286
248
|
this.dockerfile = this.#makeDockerfile();
|
|
@@ -288,101 +250,203 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
288
250
|
#resolveWebConfig(web: AkanWebOption | undefined): AkanWebConfig {
|
|
289
251
|
const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
|
|
290
252
|
// `akan build-ios` / `build-android` copy `dist/apps/<app>/csr/<target>.html` into the native project.
|
|
291
|
-
if (!resolved.csr && this.
|
|
253
|
+
if (!resolved.csr && this.hasNativeConfig)
|
|
292
254
|
throw new Error(
|
|
293
|
-
`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares
|
|
255
|
+
`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares a native section; the native apps ship that bundle. Drop the native section or leave CSR on.`,
|
|
294
256
|
);
|
|
295
257
|
return resolved;
|
|
296
258
|
}
|
|
297
|
-
#
|
|
298
|
-
const {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
[defaultTargetName]: {},
|
|
311
|
-
},
|
|
312
|
-
);
|
|
259
|
+
#resolveNativeConfig(native: AkanNativeAppConfig | undefined): AkanNativeAppResult {
|
|
260
|
+
const configPath = `apps/${this.app.name}/akan.config.ts`;
|
|
261
|
+
const { targets: rawTargets, ...root } = native ?? {};
|
|
262
|
+
AkanAppConfig.#assertNativeKeys(root, "native", configPath);
|
|
263
|
+
AkanAppConfig.#assertFiles(root, "native", configPath);
|
|
264
|
+
const appName = root.appName ?? this.app.name;
|
|
265
|
+
const appId = root.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
|
|
266
|
+
const version = root.version ?? "0.0.1";
|
|
267
|
+
const buildNum = root.buildNum ?? 1;
|
|
268
|
+
const named = rawTargets !== undefined && Object.keys(rawTargets).length > 0;
|
|
269
|
+
const entries: [string, AkanNativeSettings][] = named
|
|
270
|
+
? Object.entries(rawTargets)
|
|
271
|
+
: [[this.basePaths.has(this.app.name) ? this.app.name : "default", {}]];
|
|
313
272
|
const targets = Object.fromEntries(
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
if (basePath && !this.basePaths.has(basePath))
|
|
273
|
+
entries.map(([name, own]) => {
|
|
274
|
+
const where = named ? `native.targets.${name}` : "native";
|
|
275
|
+
AkanAppConfig.#assertNativeKeys(own ?? {}, where, configPath);
|
|
276
|
+
const merged = AkanAppConfig.#overlay(root, own ?? {});
|
|
277
|
+
const fallbackBasePath = !named && this.basePaths.has(name) ? name : undefined;
|
|
278
|
+
const basePath = (merged.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
279
|
+
if (basePath && !this.basePaths.has(basePath))
|
|
280
|
+
throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
|
|
281
|
+
const { updates } = merged;
|
|
282
|
+
if (updates && (!updates.url || !updates.publicKey))
|
|
321
283
|
throw new Error(
|
|
322
|
-
|
|
284
|
+
`${where}.updates in ${configPath} has no url or publicKey${named ? "; give them in native.updates or the target's own" : ""}.`,
|
|
323
285
|
);
|
|
324
|
-
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
286
|
+
AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
|
|
287
|
+
const {
|
|
288
|
+
basePath: _basePath,
|
|
289
|
+
indexPath: rawIndexPath,
|
|
290
|
+
deepLinks: rawDeepLinks,
|
|
291
|
+
updates: _updates,
|
|
292
|
+
...rest
|
|
293
|
+
} = merged;
|
|
294
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
295
|
+
const deepLinks = normalizeDeepLinks(rawDeepLinks);
|
|
296
|
+
const { teamId: rawTeamId, ...iosRest } = AkanAppConfig.#platformIndexPath(merged.ios ?? {});
|
|
297
|
+
const teamId = rawTeamId?.trim();
|
|
298
|
+
const { sha256CertFingerprints: rawFingerprints, ...androidRest } = AkanAppConfig.#platformIndexPath(
|
|
299
|
+
merged.android ?? {},
|
|
300
|
+
);
|
|
301
|
+
const fingerprints = normalizeStringList(rawFingerprints);
|
|
302
|
+
const target: AkanNativeTarget = {
|
|
303
|
+
...rest,
|
|
328
304
|
name,
|
|
329
|
-
basePath,
|
|
330
|
-
indexPath,
|
|
331
|
-
deepLinks,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
ios: {
|
|
345
|
-
...rawMobile.ios,
|
|
346
|
-
...target.ios,
|
|
347
|
-
},
|
|
348
|
-
} satisfies AkanMobileTargetConfig;
|
|
349
|
-
return [name, resolved];
|
|
305
|
+
...(basePath ? { basePath } : {}),
|
|
306
|
+
...(indexPath ? { indexPath } : {}),
|
|
307
|
+
...(deepLinks ? { deepLinks } : {}),
|
|
308
|
+
...(merged.ios ? { ios: { ...iosRest, ...(teamId ? { teamId } : {}) } } : {}),
|
|
309
|
+
...(merged.android
|
|
310
|
+
? { android: { ...androidRest, ...(fingerprints ? { sha256CertFingerprints: fingerprints } : {}) } }
|
|
311
|
+
: {}),
|
|
312
|
+
...(merged.desktop ? { desktop: AkanAppConfig.#platformIndexPath(merged.desktop) } : {}),
|
|
313
|
+
appName: merged.appName ?? this.app.name,
|
|
314
|
+
appId: merged.appId ?? appId,
|
|
315
|
+
version: merged.version ?? version,
|
|
316
|
+
buildNum: merged.buildNum ?? buildNum,
|
|
317
|
+
...(updates ? { updates: updates as AkanNativeUpdatesConfig } : {}),
|
|
318
|
+
};
|
|
319
|
+
return [name, target];
|
|
350
320
|
}),
|
|
351
321
|
);
|
|
352
|
-
return {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
322
|
+
return { appName, appId, ...(root.fileName ? { fileName: root.fileName } : {}), version, buildNum, targets };
|
|
323
|
+
}
|
|
324
|
+
static #platformIndexPath<T extends { indexPath?: string }>(section: T): T {
|
|
325
|
+
const { indexPath: rawIndexPath, ...rest } = section;
|
|
326
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
327
|
+
return { ...rest, ...(indexPath ? { indexPath } : {}) } as T;
|
|
328
|
+
}
|
|
329
|
+
/** `own` over `base`: plain objects merge key by key, and any other value, a list included, replaces. */
|
|
330
|
+
static #overlay(base: AkanNativeSettings, own: AkanNativeSettings): AkanNativeSettings {
|
|
331
|
+
const merge = (kept: unknown, value: unknown, top: boolean, key: string): unknown => {
|
|
332
|
+
if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value)) return value;
|
|
333
|
+
//? An icon or splash given as an object is one value: its image and color go together.
|
|
334
|
+
if (top && (key === "icon" || key === "splash")) return value;
|
|
335
|
+
const out: Record<string, unknown> = { ...kept };
|
|
336
|
+
for (const [inner, next] of Object.entries(value))
|
|
337
|
+
if (next !== undefined) out[inner] = merge(out[inner], next, false, inner);
|
|
338
|
+
return out;
|
|
339
|
+
};
|
|
340
|
+
const out: Record<string, unknown> = { ...base };
|
|
341
|
+
for (const [key, value] of Object.entries(own))
|
|
342
|
+
if (value !== undefined) out[key] = merge(out[key], value, true, key);
|
|
343
|
+
return out as AkanNativeSettings;
|
|
344
|
+
}
|
|
345
|
+
static #isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
346
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
347
|
+
}
|
|
348
|
+
static readonly #nativeKeys = {
|
|
349
|
+
native: [
|
|
350
|
+
"basePath",
|
|
351
|
+
"indexPath",
|
|
352
|
+
"appName",
|
|
353
|
+
"appId",
|
|
354
|
+
"fileName",
|
|
355
|
+
"version",
|
|
356
|
+
"buildNum",
|
|
357
|
+
"icon",
|
|
358
|
+
"splash",
|
|
359
|
+
"permissions",
|
|
360
|
+
"plugins",
|
|
361
|
+
"deepLinks",
|
|
362
|
+
"updates",
|
|
363
|
+
"ios",
|
|
364
|
+
"android",
|
|
365
|
+
"desktop",
|
|
366
|
+
],
|
|
367
|
+
ios: ["indexPath", "teamId", "infoPlist", "entitlements", "privacy", "files"],
|
|
368
|
+
android: [
|
|
369
|
+
"indexPath",
|
|
370
|
+
"sha256CertFingerprints",
|
|
371
|
+
"googleServices",
|
|
372
|
+
"push",
|
|
373
|
+
"autoplay",
|
|
374
|
+
"manifest",
|
|
375
|
+
"application",
|
|
376
|
+
"activity",
|
|
377
|
+
"files",
|
|
378
|
+
],
|
|
379
|
+
desktop: ["indexPath", "server", "recovery", "window", "screenCapture"],
|
|
380
|
+
deepLinks: ["schemes", "domains"],
|
|
381
|
+
} as const;
|
|
382
|
+
//* Settings that moved when `mobile` became `native` are named with where they went, instead of "unknown".
|
|
383
|
+
static readonly #movedKeys: Readonly<Record<string, string>> = {
|
|
384
|
+
native: "the platform sections and plugins sit directly in the native section: ios, android, desktop, plugins",
|
|
385
|
+
assets: "icon and splash sit directly in the native section",
|
|
386
|
+
files:
|
|
387
|
+
"ios.files (keyed by the path in the app bundle) or android.files (keyed res/<type>/<file> or assets/<path>)",
|
|
388
|
+
push: "android.push",
|
|
389
|
+
privacy: "ios.privacy",
|
|
390
|
+
"deepLinks.ios": "ios.teamId",
|
|
391
|
+
"deepLinks.android": "android.sha256CertFingerprints",
|
|
392
|
+
};
|
|
393
|
+
static #assertNativeKeys(section: object, where: string, configPath: string) {
|
|
394
|
+
const check = (value: unknown, known: readonly string[], at: string, kind: string) => {
|
|
395
|
+
if (!AkanAppConfig.#isPlainObject(value)) return;
|
|
396
|
+
for (const key of Object.keys(value)) {
|
|
397
|
+
if (known.includes(key)) continue;
|
|
398
|
+
const moved = AkanAppConfig.#movedKeys[kind === "native" ? key : `${kind}.${key}`];
|
|
399
|
+
throw new Error(
|
|
400
|
+
moved
|
|
401
|
+
? `${at}.${key} in ${configPath} has moved: ${moved}.`
|
|
402
|
+
: `${at}.${key} in ${configPath} is not a native setting. Known: ${known.join(", ")}.`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
check(section, AkanAppConfig.#nativeKeys.native, where, "native");
|
|
407
|
+
const settings = section as AkanNativeSettings;
|
|
408
|
+
check(settings.ios, AkanAppConfig.#nativeKeys.ios, `${where}.ios`, "ios");
|
|
409
|
+
check(settings.android, AkanAppConfig.#nativeKeys.android, `${where}.android`, "android");
|
|
410
|
+
check(settings.desktop, AkanAppConfig.#nativeKeys.desktop, `${where}.desktop`, "desktop");
|
|
411
|
+
check(settings.deepLinks, AkanAppConfig.#nativeKeys.deepLinks, `${where}.deepLinks`, "deepLinks");
|
|
412
|
+
}
|
|
413
|
+
static readonly #androidFilePattern = /^(?:res\/[^/]+\/[^/]+|assets\/.+)$/;
|
|
414
|
+
static #assertFiles(settings: AkanNativeSettings, where: string, configPath: string) {
|
|
415
|
+
const check = (
|
|
416
|
+
files: Record<string, string> | undefined,
|
|
417
|
+
at: string,
|
|
418
|
+
valid: (to: string) => boolean,
|
|
419
|
+
shape: string,
|
|
420
|
+
) => {
|
|
421
|
+
for (const [to, from] of Object.entries(files ?? {})) {
|
|
422
|
+
if (typeof from !== "string")
|
|
423
|
+
throw new Error(
|
|
424
|
+
`${at}["${to}"] in ${configPath} must name its source file, relative to the app folder, e.g. { "${shape}": "assets/chime.mp3" }.`,
|
|
425
|
+
);
|
|
426
|
+
if (!valid(to)) throw new Error(`${at}["${to}"] in ${configPath} must land at ${shape}.`);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
check(
|
|
430
|
+
settings.ios?.files,
|
|
431
|
+
`${where}.ios.files`,
|
|
432
|
+
(to) => to.length > 0 && !to.startsWith("/") && !to.split("/").includes(".."),
|
|
433
|
+
"<path in the app bundle>",
|
|
434
|
+
);
|
|
435
|
+
check(
|
|
436
|
+
settings.android?.files,
|
|
437
|
+
`${where}.android.files`,
|
|
438
|
+
(to) => AkanAppConfig.#androidFilePattern.test(to),
|
|
439
|
+
"res/<type>/<file> or assets/<path>",
|
|
440
|
+
);
|
|
365
441
|
}
|
|
366
442
|
#applyRoutes(routes: AkanRouteConfig[] = []) {
|
|
367
443
|
for (const route of routes) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
.flat()
|
|
375
|
-
.forEach((domain) => {
|
|
376
|
-
if (domain) domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
|
|
377
|
-
});
|
|
378
|
-
} else {
|
|
379
|
-
Object.keys(route.domains).forEach((branch) => void this.branches.add(branch));
|
|
380
|
-
Object.values(route.domains)
|
|
381
|
-
.flat()
|
|
382
|
-
.forEach((domain) => {
|
|
383
|
-
if (domain) this.domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
|
|
384
|
-
});
|
|
385
|
-
}
|
|
444
|
+
const basePath = route.basePath ? route.basePath.replace(/^\/+|\/+$/g, "") : null;
|
|
445
|
+
if (basePath !== null) this.basePaths.add(basePath);
|
|
446
|
+
const domains = basePath !== null ? this.subRoutes.getOrInsert(basePath, new Set()) : this.domains;
|
|
447
|
+
for (const branch of Object.keys(route.domains)) this.branches.add(branch);
|
|
448
|
+
for (const domain of Object.values(route.domains).flat())
|
|
449
|
+
if (domain) domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
|
|
386
450
|
}
|
|
387
451
|
const appName = this.app.name.toLowerCase();
|
|
388
452
|
const serveDomain = this.baseDevEnv.serveDomain.toLowerCase();
|
|
@@ -420,21 +484,39 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
420
484
|
command: docker?.command ?? ["bun", "main.js"],
|
|
421
485
|
};
|
|
422
486
|
}
|
|
423
|
-
|
|
487
|
+
/** What the built server runs with in its image; a desktop app's carried server starts from the same values. */
|
|
488
|
+
getProductionEnv(environment: string = this.baseDevEnv.env): Record<string, string> {
|
|
489
|
+
return {
|
|
490
|
+
PORT: "8282",
|
|
491
|
+
NODE_ENV: "production",
|
|
492
|
+
AKAN_PUBLIC_REPO_NAME: this.baseDevEnv.repoName,
|
|
493
|
+
AKAN_PUBLIC_SERVE_DOMAIN: this.baseDevEnv.serveDomain,
|
|
494
|
+
AKAN_PUBLIC_APP_NAME: this.app.name,
|
|
495
|
+
AKAN_PUBLIC_ENV: environment,
|
|
496
|
+
...(this.basePaths.size ? { AKAN_PUBLIC_BASE_PATHS: [...this.basePaths].join(",") } : {}),
|
|
497
|
+
AKAN_PUBLIC_DEFAULT_LOCALE: this.i18n.defaultLocale,
|
|
498
|
+
AKAN_PUBLIC_LOCALES: this.i18n.locales.join(","),
|
|
499
|
+
AKAN_PUBLIC_API_PREFIX: this.api.prefix,
|
|
500
|
+
AKAN_PUBLIC_WS_PREFIX: this.api.websocketPrefix,
|
|
501
|
+
AKAN_PUBLIC_OPERATION_MODE: "cloud",
|
|
502
|
+
AKAN_DATABASE_MODES: this.database.modes.join(","),
|
|
503
|
+
// File logging is off: a container's writable layer is ephemeral and stdout is the collection path.
|
|
504
|
+
AKAN_LOG_TO_FILE: "0",
|
|
505
|
+
// The web env matches what was built (a deployment can only narrow it).
|
|
506
|
+
...(this.web.ssr ? {} : { AKAN_SSR: "false" }),
|
|
507
|
+
...(this.web.csr ? {} : { AKAN_CSR: "false" }),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
dockerfileFor(environment: string): string {
|
|
511
|
+
return this.#makeDockerfile(environment);
|
|
512
|
+
}
|
|
513
|
+
#makeDockerfile(environment?: string): string {
|
|
424
514
|
if (typeof this.docker === "string") return this.docker;
|
|
425
515
|
const { image, preRuns, postRuns, command } = this.docker;
|
|
426
516
|
const preRunScripts = this.#getDockerRunScripts(preRuns);
|
|
427
517
|
const postRunScripts = this.#getDockerRunScripts(postRuns);
|
|
428
518
|
const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
|
|
429
|
-
|
|
430
|
-
// own env, and can never widen it past the artifacts that are in the image.
|
|
431
|
-
// File logging is off in the image: a container's writable layer is ephemeral and nothing collects a
|
|
432
|
-
// file from it, so the rotating files would only fill the node disk (50MB x 100 at `trace`). stdout is
|
|
433
|
-
// the collection path; a deployment that wants the files back sets `AKAN_LOG_TO_FILE=1`.
|
|
434
|
-
const webEnvLines = [
|
|
435
|
-
...(this.web.ssr ? [] : ["ENV AKAN_SSR=false"]),
|
|
436
|
-
...(this.web.csr ? [] : ["ENV AKAN_CSR=false"]),
|
|
437
|
-
].join("\n");
|
|
519
|
+
const envLines = Object.entries(this.getProductionEnv(environment)).map(([key, value]) => `ENV ${key}=${value}`);
|
|
438
520
|
return `${imageScript}
|
|
439
521
|
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
|
|
440
522
|
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
|
|
@@ -446,28 +528,11 @@ COPY ./package.json ./package.json
|
|
|
446
528
|
RUN bun install --production
|
|
447
529
|
${postRunScripts.join("\n")}
|
|
448
530
|
COPY . .
|
|
449
|
-
|
|
450
|
-
ENV NODE_ENV=production
|
|
451
|
-
ENV AKAN_PUBLIC_REPO_NAME=${this.baseDevEnv.repoName}
|
|
452
|
-
ENV AKAN_PUBLIC_SERVE_DOMAIN=${this.baseDevEnv.serveDomain}
|
|
453
|
-
ENV AKAN_PUBLIC_APP_NAME=${this.app.name}
|
|
454
|
-
ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
|
|
455
|
-
${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
|
|
456
|
-
ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
|
|
457
|
-
ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
|
|
458
|
-
ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
|
|
459
|
-
ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
|
|
460
|
-
ENV AKAN_PUBLIC_OPERATION_MODE=cloud
|
|
461
|
-
ENV AKAN_LOG_TO_FILE=0
|
|
462
|
-
${webEnvLines}
|
|
531
|
+
${envLines.join("\n")}
|
|
463
532
|
CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
464
533
|
}
|
|
465
534
|
static #importGeneration = 0;
|
|
466
|
-
|
|
467
|
-
* Bun caches dynamic imports by path, so a plain re-import after the user edits the config file
|
|
468
|
-
* returns the stale module. `bustImportCache` appends a fresh query string to force re-evaluation
|
|
469
|
-
* of the config module itself; modules it imports keep their cached instances.
|
|
470
|
-
*/
|
|
535
|
+
// Bun caches dynamic imports by path; a fresh query string re-evaluates the config module (its imports stay cached).
|
|
471
536
|
static async importConfigModule<T = unknown>(
|
|
472
537
|
cwdPath: string,
|
|
473
538
|
{ bustImportCache = false }: { bustImportCache?: boolean } = {},
|
|
@@ -506,6 +571,10 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
506
571
|
);
|
|
507
572
|
return {
|
|
508
573
|
externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
|
|
574
|
+
trustedDependencies: libConfigs.flatMap((libConfig) => libConfig?.trustedDependencies ?? []),
|
|
575
|
+
bin: libConfigs.flatMap((libConfig) =>
|
|
576
|
+
libConfig && Object.keys(libConfig.bin).length ? [{ lib: libConfig.lib.name, bin: libConfig.bin }] : [],
|
|
577
|
+
),
|
|
509
578
|
docker: {
|
|
510
579
|
preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
|
|
511
580
|
postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? []),
|
|
@@ -519,31 +588,37 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
519
588
|
#resolveProductionDependencyVersion(lib: string) {
|
|
520
589
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
521
590
|
if (rootVersion) return rootVersion;
|
|
522
|
-
//
|
|
523
|
-
// packages (e.g. firebase for the push plugin) resolve a version without the framework
|
|
524
|
-
// hardcoding a per-feature package list.
|
|
591
|
+
// The framework's own (peer)dependencies resolve plugin runtime packages without a hardcoded list.
|
|
525
592
|
const akanPackageJson = getAkanPackageJson();
|
|
526
|
-
|
|
593
|
+
const version = akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
594
|
+
if (!version) throw new Error(`Dependency ${lib} not found in package.json`);
|
|
595
|
+
return version;
|
|
527
596
|
}
|
|
528
597
|
#getProductionRuntimePackages() {
|
|
529
|
-
return [
|
|
598
|
+
return [
|
|
599
|
+
...this.externalLibs,
|
|
600
|
+
...SSR_RUNTIME_PACKAGES,
|
|
601
|
+
...this.database.modes.flatMap((mode) => this.getDatabaseModeRuntimePackages(mode)),
|
|
602
|
+
];
|
|
530
603
|
}
|
|
531
|
-
getDatabaseModeRuntimePackages(databaseMode: DatabaseMode
|
|
532
|
-
return [...
|
|
604
|
+
getDatabaseModeRuntimePackages(databaseMode: DatabaseMode) {
|
|
605
|
+
return [...DatabaseModes.drivers[databaseMode]];
|
|
533
606
|
}
|
|
534
|
-
getMissingDatabaseModeDependencySpecs(databaseMode: DatabaseMode
|
|
607
|
+
getMissingDatabaseModeDependencySpecs(databaseMode: DatabaseMode) {
|
|
535
608
|
return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
|
|
536
609
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
610
|
+
/** The mode a CLI command runs this app in: the shell's `AKAN_DATABASE_MODE` if the app declares it, else the first. */
|
|
611
|
+
resolveDatabaseMode() {
|
|
612
|
+
return DatabaseModes.resolve({
|
|
613
|
+
requested: process.env.AKAN_DATABASE_MODE,
|
|
614
|
+
declared: this.database.modes.join(","),
|
|
615
|
+
local: true,
|
|
616
|
+
});
|
|
544
617
|
}
|
|
545
|
-
|
|
546
|
-
|
|
618
|
+
static #database(app: App, config: AppConfigDeclaration): AkanDatabaseConfig {
|
|
619
|
+
const declared = config?.database?.modes?.filter((mode): mode is DatabaseMode => !!mode);
|
|
620
|
+
const modes = declared?.length ? declared : ["single"];
|
|
621
|
+
return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
|
|
547
622
|
}
|
|
548
623
|
#getMissingDependencySpecs(libs: readonly string[]) {
|
|
549
624
|
const rootDependencies = {
|
|
@@ -552,11 +627,10 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
552
627
|
};
|
|
553
628
|
return libs
|
|
554
629
|
.filter((lib) => !rootDependencies[lib])
|
|
555
|
-
.map((lib) => {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
});
|
|
630
|
+
.map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
|
|
631
|
+
}
|
|
632
|
+
get akanVersion() {
|
|
633
|
+
return getAkanPackageJson().version;
|
|
560
634
|
}
|
|
561
635
|
getProductionPackageJson(data: Partial<PackageJson> = {}): PackageJson {
|
|
562
636
|
return {
|
|
@@ -565,12 +639,12 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
565
639
|
version: "1.0.0",
|
|
566
640
|
main: "./main.js",
|
|
567
641
|
dependencies: Object.fromEntries(
|
|
568
|
-
[...new Set(this.#getProductionRuntimePackages())].map((lib) =>
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}),
|
|
642
|
+
[...new Set(this.#getProductionRuntimePackages())].map((lib) => [
|
|
643
|
+
lib,
|
|
644
|
+
this.#resolveProductionDependencyVersion(lib),
|
|
645
|
+
]),
|
|
573
646
|
),
|
|
647
|
+
...(this.trustedDependencies.length ? { trustedDependencies: this.trustedDependencies } : {}),
|
|
574
648
|
...data,
|
|
575
649
|
};
|
|
576
650
|
}
|
|
@@ -624,13 +698,18 @@ function mergeImageConfig(config: Partial<AkanImageConfig> = {}): AkanImageConfi
|
|
|
624
698
|
export class AkanLibConfig implements LibConfigResult {
|
|
625
699
|
lib: Lib;
|
|
626
700
|
externalLibs: string[];
|
|
701
|
+
trustedDependencies: string[];
|
|
702
|
+
bin: AkanBinConfig;
|
|
627
703
|
docker: LibDockerConfig;
|
|
628
704
|
assets: LibAssetsConfig;
|
|
629
705
|
/** Live-only: plugins declared in this lib's `akan.config.ts` (never serialized). */
|
|
630
706
|
plugins: AkanPlugin[];
|
|
631
|
-
constructor(lib: Lib, config:
|
|
707
|
+
constructor(lib: Lib, config: LibConfigDeclaration, plugins: AkanPlugin[] = []) {
|
|
632
708
|
this.lib = lib;
|
|
633
709
|
this.externalLibs = config?.externalLibs ?? [];
|
|
710
|
+
const owner = `libs/${lib.name}/akan.config.ts`;
|
|
711
|
+
this.trustedDependencies = normalizePackageNames(config?.trustedDependencies, owner);
|
|
712
|
+
this.bin = AkanBin.parse(config?.bin, owner, lib.cwdPath);
|
|
634
713
|
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
635
714
|
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts as string[] | undefined) };
|
|
636
715
|
this.plugins = plugins;
|
|
@@ -638,46 +717,19 @@ export class AkanLibConfig implements LibConfigResult {
|
|
|
638
717
|
static async from(lib: Lib, { bustImportCache = false }: { bustImportCache?: boolean } = {}) {
|
|
639
718
|
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
640
719
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
641
|
-
const { plugins, ...config } = (resolved ?? {}) as
|
|
720
|
+
const { plugins, ...config } = (resolved ?? {}) as LibConfigDeclaration & { plugins?: AkanPlugin[] };
|
|
642
721
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
643
722
|
}
|
|
644
723
|
}
|
|
645
724
|
|
|
646
|
-
// export const getCapacitorConfig = (configImp: AppConfig, appInfo: AppScanResult, tsconfig: TsConfigJson) => {
|
|
647
|
-
// const props: RunnerProps = {
|
|
648
|
-
// type: "app",
|
|
649
|
-
// name: appInfo.name,
|
|
650
|
-
// repoName: appInfo.repoName,
|
|
651
|
-
// serveDomain: appInfo.serveDomain,
|
|
652
|
-
// env: (process.env.AKAN_PUBLIC_ENV ?? "debug") as "testing" | "local" | "debug" | "develop" | "main",
|
|
653
|
-
// libs: appInfo.libDeps,
|
|
654
|
-
// tsconfig,
|
|
655
|
-
// };
|
|
656
|
-
// const config = typeof configImp === "function" ? configImp(props) : configImp;
|
|
657
|
-
// const akanConfig = makeAppConfig(config, props);
|
|
658
|
-
// return akanConfig;
|
|
659
|
-
// };
|
|
660
|
-
|
|
661
725
|
//! need to refactor
|
|
662
|
-
|
|
663
|
-
const
|
|
726
|
+
const shiftBuildNum = async (app: App, delta: number) => {
|
|
727
|
+
const { buildNum } = (await AkanAppConfig.from(app)).native;
|
|
664
728
|
const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
|
|
665
729
|
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
666
|
-
|
|
667
|
-
`buildNum: ${appConfig.mobile.buildNum}`,
|
|
668
|
-
`buildNum: ${appConfig.mobile.buildNum + 1}`,
|
|
669
|
-
);
|
|
670
|
-
//? 개선할 여지가 있는지 확인
|
|
671
|
-
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
730
|
+
fs.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
|
|
672
731
|
};
|
|
673
732
|
|
|
674
|
-
export const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
|
|
678
|
-
const akanConfigContent = akanConfig.replace(
|
|
679
|
-
`buildNum: ${appConfig.mobile.buildNum}`,
|
|
680
|
-
`buildNum: ${appConfig.mobile.buildNum - 1}`,
|
|
681
|
-
);
|
|
682
|
-
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
683
|
-
};
|
|
733
|
+
export const increaseBuildNum = async (app: App) => await shiftBuildNum(app, 1);
|
|
734
|
+
|
|
735
|
+
export const decreaseBuildNum = async (app: App) => await shiftBuildNum(app, -1);
|