@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/akanConfig/akanConfig.ts
CHANGED
|
@@ -2,17 +2,25 @@ 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 AkanNativeDesktopConfig,
|
|
21
|
+
type AkanNativeSettings,
|
|
22
|
+
type AkanNativeTarget,
|
|
23
|
+
type AkanNativeUpdatesConfig,
|
|
16
24
|
type AkanRouteConfig,
|
|
17
25
|
type AkanWebConfig,
|
|
18
26
|
type AkanWebOption,
|
|
@@ -31,78 +39,14 @@ import {
|
|
|
31
39
|
|
|
32
40
|
const DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
|
|
33
41
|
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
|
-
];
|
|
42
|
+
"lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
|
|
43
|
+
"@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
|
|
44
|
+
"@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
|
|
45
|
+
"@material-ui/icons @tabler/icons-react mui-core react-icons/*",
|
|
46
|
+
].flatMap((line) => line.split(" "));
|
|
60
47
|
const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
|
|
61
48
|
const DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
|
|
62
49
|
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
50
|
const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
|
|
107
51
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
108
52
|
imageSizes: [32, 48, 64, 96, 128, 256, 384],
|
|
@@ -130,18 +74,14 @@ const normalizeStringList = (values: string[] | undefined) => {
|
|
|
130
74
|
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
131
75
|
};
|
|
132
76
|
|
|
133
|
-
//
|
|
134
|
-
// only (hyphens/spaces dropped), never empty, never starting with a digit.
|
|
77
|
+
// A reverse-DNS / Android package segment: lowercase alphanumerics, never empty, never starting with a digit.
|
|
135
78
|
const sanitizeAppIdSegment = (value: string): string => {
|
|
136
79
|
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
137
80
|
if (!cleaned) return "app";
|
|
138
81
|
return /^[a-z]/.test(cleaned) ? cleaned : `app${cleaned}`;
|
|
139
82
|
};
|
|
140
83
|
|
|
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.
|
|
84
|
+
// The repo name is the org segment: a bare `com.<appName>.app` is routinely already claimed on Apple's portal.
|
|
145
85
|
export const deriveDefaultAppId = (orgName: string, appName: string): string =>
|
|
146
86
|
`com.${sanitizeAppIdSegment(orgName)}.${sanitizeAppIdSegment(appName)}`;
|
|
147
87
|
|
|
@@ -159,30 +99,27 @@ const normalizeDeepLinkDomain = (domain: string) => {
|
|
|
159
99
|
}
|
|
160
100
|
};
|
|
161
101
|
|
|
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"];
|
|
102
|
+
const normalizeDeepLinks = (deepLinks: AkanNativeDeepLinks | undefined): AkanNativeDeepLinks | undefined => {
|
|
103
|
+
const schemes = normalizeStringList(deepLinks?.schemes);
|
|
104
|
+
const domains = normalizeStringList(deepLinks?.domains?.map(normalizeDeepLinkDomain));
|
|
105
|
+
if (!schemes && !domains) return undefined;
|
|
106
|
+
return { ...(schemes ? { schemes } : {}), ...(domains ? { domains } : {}) };
|
|
175
107
|
};
|
|
176
108
|
|
|
177
|
-
|
|
178
|
-
type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "docker" | "web"> & {
|
|
109
|
+
type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "docker" | "web" | "bin" | "native"> & {
|
|
179
110
|
docker?: DockerOption;
|
|
180
111
|
web?: AkanWebOption;
|
|
112
|
+
bin?: AkanBinConfig;
|
|
113
|
+
native?: AkanNativeAppConfig;
|
|
181
114
|
};
|
|
182
115
|
|
|
183
|
-
|
|
116
|
+
type LibConfigDeclaration = Omit<DeepPartial<LibConfigResult>, "bin"> & { bin?: AkanBinConfig };
|
|
117
|
+
|
|
184
118
|
export interface LibContributions {
|
|
185
119
|
externalLibs: string[];
|
|
120
|
+
trustedDependencies?: string[];
|
|
121
|
+
/** Per lib, since an app carries only the entries of the libs it depends on. */
|
|
122
|
+
bin?: { lib: string; bin: AkanBinConfig }[];
|
|
186
123
|
docker: LibDockerConfig;
|
|
187
124
|
/** Keep globs rewritten to the app's own `public/`, where `akan sync` mounts each lib's assets. */
|
|
188
125
|
keepFonts?: string[];
|
|
@@ -190,10 +127,19 @@ export interface LibContributions {
|
|
|
190
127
|
|
|
191
128
|
const emptyLibContributions = (): LibContributions => ({
|
|
192
129
|
externalLibs: [],
|
|
130
|
+
trustedDependencies: [],
|
|
131
|
+
bin: [],
|
|
193
132
|
docker: { preRuns: [], postRuns: [] },
|
|
194
133
|
keepFonts: [],
|
|
195
134
|
});
|
|
196
135
|
|
|
136
|
+
const normalizePackageNames = (names: unknown, owner: string): string[] => {
|
|
137
|
+
if (names === undefined) return [];
|
|
138
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || !name.trim()))
|
|
139
|
+
throw new Error(`${owner}: trustedDependencies lists package names`);
|
|
140
|
+
return [...new Set(names.map((name: string) => name.trim()))];
|
|
141
|
+
};
|
|
142
|
+
|
|
197
143
|
const normalizeKeepFonts = (keepFonts: string[] | undefined) => [
|
|
198
144
|
...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean)),
|
|
199
145
|
];
|
|
@@ -211,21 +157,25 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
211
157
|
docker: DockerConfig;
|
|
212
158
|
/** The Dockerfile `akan build` writes: the declared string verbatim, or one assembled from the parts. */
|
|
213
159
|
dockerfile: string;
|
|
214
|
-
|
|
160
|
+
database: AkanDatabaseConfig;
|
|
215
161
|
web: AkanWebConfig;
|
|
216
162
|
externalLibs: string[];
|
|
163
|
+
trustedDependencies: string[];
|
|
164
|
+
/** The app's own entries; the libs' are in `libBins`. */
|
|
165
|
+
bin: AkanBinConfig;
|
|
166
|
+
libBins: { lib: string; bin: AkanBinConfig }[];
|
|
217
167
|
barrelImports: string[];
|
|
218
168
|
optimizeImports: string[];
|
|
219
169
|
images: AkanImageConfig;
|
|
220
170
|
i18n: AkanI18nConfig;
|
|
221
171
|
api: AkanApiConfig;
|
|
222
172
|
publicEnv: string[];
|
|
223
|
-
|
|
224
|
-
/** True only when the app's akan.config.ts
|
|
225
|
-
|
|
173
|
+
native: AkanNativeAppResult;
|
|
174
|
+
/** True only when the app's akan.config.ts declares a `native` section (vs. the synthesized default target). */
|
|
175
|
+
hasNativeConfig: boolean;
|
|
226
176
|
secrets: string[];
|
|
227
177
|
assets: AkanAssetsConfig;
|
|
228
|
-
/** Raw setting
|
|
178
|
+
/** Raw setting, resolved against the app's lib deps at sync time. */
|
|
229
179
|
syncPageLibs: string[] | boolean;
|
|
230
180
|
baseDevEnv: BaseDevEnv;
|
|
231
181
|
libs: string[];
|
|
@@ -250,8 +200,17 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
250
200
|
this.baseDevEnv = baseDevEnv;
|
|
251
201
|
this.plugins = plugins;
|
|
252
202
|
this.#applyRoutes(config?.routes);
|
|
253
|
-
this.
|
|
203
|
+
this.database = AkanAppConfig.#database(app, config);
|
|
254
204
|
this.externalLibs = [...new Set([...(config?.externalLibs ?? []), ...libContributions.externalLibs])];
|
|
205
|
+
const owner = `apps/${app.name}/akan.config.ts`;
|
|
206
|
+
this.trustedDependencies = [
|
|
207
|
+
...new Set([
|
|
208
|
+
...normalizePackageNames(config?.trustedDependencies, owner),
|
|
209
|
+
...(libContributions.trustedDependencies ?? []),
|
|
210
|
+
]),
|
|
211
|
+
];
|
|
212
|
+
this.bin = AkanBin.parse(config?.bin, owner, app.cwdPath);
|
|
213
|
+
this.libBins = libContributions.bin ?? [];
|
|
255
214
|
this.barrelImports = [
|
|
256
215
|
...DEFAULT_BARREL_IMPORTS,
|
|
257
216
|
...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
|
|
@@ -279,8 +238,12 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
279
238
|
],
|
|
280
239
|
};
|
|
281
240
|
this.syncPageLibs = (config?.syncPageLibs as string[] | boolean | undefined) ?? false;
|
|
282
|
-
|
|
283
|
-
|
|
241
|
+
if ((config as { mobile?: unknown }).mobile !== undefined)
|
|
242
|
+
throw new Error(
|
|
243
|
+
`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>.`,
|
|
244
|
+
);
|
|
245
|
+
this.hasNativeConfig = Boolean(config.native);
|
|
246
|
+
this.native = this.#resolveNativeConfig(config.native);
|
|
284
247
|
this.web = this.#resolveWebConfig(config.web);
|
|
285
248
|
this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
|
|
286
249
|
this.dockerfile = this.#makeDockerfile();
|
|
@@ -288,101 +251,234 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
288
251
|
#resolveWebConfig(web: AkanWebOption | undefined): AkanWebConfig {
|
|
289
252
|
const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
|
|
290
253
|
// `akan build-ios` / `build-android` copy `dist/apps/<app>/csr/<target>.html` into the native project.
|
|
291
|
-
if (!resolved.csr && this.
|
|
254
|
+
if (!resolved.csr && this.hasNativeConfig)
|
|
292
255
|
throw new Error(
|
|
293
|
-
`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares
|
|
256
|
+
`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
257
|
);
|
|
295
258
|
return resolved;
|
|
296
259
|
}
|
|
297
|
-
#
|
|
298
|
-
const {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
[defaultTargetName]: {},
|
|
311
|
-
},
|
|
312
|
-
);
|
|
260
|
+
#resolveNativeConfig(native: AkanNativeAppConfig | undefined): AkanNativeAppResult {
|
|
261
|
+
const configPath = `apps/${this.app.name}/akan.config.ts`;
|
|
262
|
+
const { targets: rawTargets, ...root } = native ?? {};
|
|
263
|
+
AkanAppConfig.#assertNativeKeys(root, "native", configPath);
|
|
264
|
+
AkanAppConfig.#assertFiles(root, "native", configPath);
|
|
265
|
+
const appName = root.appName ?? this.app.name;
|
|
266
|
+
const appId = root.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
|
|
267
|
+
const version = root.version ?? "0.0.1";
|
|
268
|
+
const buildNum = root.buildNum ?? 1;
|
|
269
|
+
const named = rawTargets !== undefined && Object.keys(rawTargets).length > 0;
|
|
270
|
+
const entries: [string, AkanNativeSettings][] = named
|
|
271
|
+
? Object.entries(rawTargets)
|
|
272
|
+
: [[this.basePaths.has(this.app.name) ? this.app.name : "default", {}]];
|
|
313
273
|
const targets = Object.fromEntries(
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
if (basePath && !this.basePaths.has(basePath))
|
|
274
|
+
entries.map(([name, own]) => {
|
|
275
|
+
const where = named ? `native.targets.${name}` : "native";
|
|
276
|
+
AkanAppConfig.#assertNativeKeys(own ?? {}, where, configPath);
|
|
277
|
+
const merged = AkanAppConfig.#overlay(root, own ?? {});
|
|
278
|
+
const fallbackBasePath = !named && this.basePaths.has(name) ? name : undefined;
|
|
279
|
+
const basePath = (merged.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
280
|
+
if (basePath && !this.basePaths.has(basePath))
|
|
281
|
+
throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
|
|
282
|
+
const { updates } = merged;
|
|
283
|
+
if (updates && (!updates.url || !updates.publicKey))
|
|
321
284
|
throw new Error(
|
|
322
|
-
|
|
285
|
+
`${where}.updates in ${configPath} has no url or publicKey${named ? "; give them in native.updates or the target's own" : ""}.`,
|
|
323
286
|
);
|
|
324
|
-
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
287
|
+
AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
|
|
288
|
+
const {
|
|
289
|
+
basePath: _basePath,
|
|
290
|
+
indexPath: rawIndexPath,
|
|
291
|
+
deepLinks: rawDeepLinks,
|
|
292
|
+
updates: _updates,
|
|
293
|
+
...rest
|
|
294
|
+
} = merged;
|
|
295
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
296
|
+
const deepLinks = normalizeDeepLinks(rawDeepLinks);
|
|
297
|
+
const { teamId: rawTeamId, ...iosRest } = AkanAppConfig.#platformIndexPath(merged.ios ?? {});
|
|
298
|
+
const teamId = rawTeamId?.trim();
|
|
299
|
+
const { sha256CertFingerprints: rawFingerprints, ...androidRest } = AkanAppConfig.#platformIndexPath(
|
|
300
|
+
merged.android ?? {},
|
|
301
|
+
);
|
|
302
|
+
const fingerprints = normalizeStringList(rawFingerprints);
|
|
303
|
+
const target: AkanNativeTarget = {
|
|
304
|
+
...rest,
|
|
328
305
|
name,
|
|
329
|
-
basePath,
|
|
330
|
-
indexPath,
|
|
331
|
-
deepLinks,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
306
|
+
...(basePath ? { basePath } : {}),
|
|
307
|
+
...(indexPath ? { indexPath } : {}),
|
|
308
|
+
...(deepLinks ? { deepLinks } : {}),
|
|
309
|
+
...(merged.ios ? { ios: { ...iosRest, ...(teamId ? { teamId } : {}) } } : {}),
|
|
310
|
+
...(merged.android
|
|
311
|
+
? { android: { ...androidRest, ...(fingerprints ? { sha256CertFingerprints: fingerprints } : {}) } }
|
|
312
|
+
: {}),
|
|
313
|
+
...(merged.desktop
|
|
314
|
+
? {
|
|
315
|
+
desktop: AkanAppConfig.#desktopServer(
|
|
316
|
+
AkanAppConfig.#platformIndexPath(merged.desktop),
|
|
317
|
+
`${where}.desktop.server`,
|
|
318
|
+
configPath,
|
|
319
|
+
),
|
|
320
|
+
}
|
|
321
|
+
: {}),
|
|
322
|
+
appName: merged.appName ?? this.app.name,
|
|
323
|
+
appId: merged.appId ?? appId,
|
|
324
|
+
version: merged.version ?? version,
|
|
325
|
+
buildNum: merged.buildNum ?? buildNum,
|
|
326
|
+
...(updates ? { updates: updates as AkanNativeUpdatesConfig } : {}),
|
|
327
|
+
};
|
|
328
|
+
return [name, target];
|
|
350
329
|
}),
|
|
351
330
|
);
|
|
352
|
-
return {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
331
|
+
return { appName, appId, ...(root.fileName ? { fileName: root.fileName } : {}), version, buildNum, targets };
|
|
332
|
+
}
|
|
333
|
+
//? `true` or `{ omit }`: each omitted name trimmed once, so two targets that omit the same packages stage one server.
|
|
334
|
+
static #desktopServer<T extends { server?: AkanNativeDesktopConfig["server"] }>(
|
|
335
|
+
desktop: T,
|
|
336
|
+
where: string,
|
|
337
|
+
configPath: string,
|
|
338
|
+
): T {
|
|
339
|
+
const { server } = desktop;
|
|
340
|
+
if (server === undefined || typeof server === "boolean") return desktop;
|
|
341
|
+
if (!AkanAppConfig.#isPlainObject(server))
|
|
342
|
+
throw new Error(`${where} in ${configPath} must be true or { omit: string[] }.`);
|
|
343
|
+
const omit = server.omit;
|
|
344
|
+
if (omit !== undefined && (!Array.isArray(omit) || omit.some((name) => typeof name !== "string" || !name.trim())))
|
|
345
|
+
throw new Error(`${where}.omit in ${configPath} must be a list of package names.`);
|
|
346
|
+
const names = [...new Set((omit ?? []).map((name) => name.trim()))].sort();
|
|
347
|
+
return { ...desktop, server: { omit: names } };
|
|
348
|
+
}
|
|
349
|
+
static #platformIndexPath<T extends { indexPath?: string }>(section: T): T {
|
|
350
|
+
const { indexPath: rawIndexPath, ...rest } = section;
|
|
351
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
352
|
+
return { ...rest, ...(indexPath ? { indexPath } : {}) } as T;
|
|
353
|
+
}
|
|
354
|
+
/** `own` over `base`: plain objects merge key by key, and any other value, a list included, replaces. */
|
|
355
|
+
static #overlay(base: AkanNativeSettings, own: AkanNativeSettings): AkanNativeSettings {
|
|
356
|
+
const merge = (kept: unknown, value: unknown, top: boolean, key: string): unknown => {
|
|
357
|
+
if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value)) return value;
|
|
358
|
+
//? An icon or splash given as an object is one value: its image and color go together.
|
|
359
|
+
if (top && (key === "icon" || key === "splash")) return value;
|
|
360
|
+
const out: Record<string, unknown> = { ...kept };
|
|
361
|
+
for (const [inner, next] of Object.entries(value))
|
|
362
|
+
if (next !== undefined) out[inner] = merge(out[inner], next, false, inner);
|
|
363
|
+
return out;
|
|
364
|
+
};
|
|
365
|
+
const out: Record<string, unknown> = { ...base };
|
|
366
|
+
for (const [key, value] of Object.entries(own))
|
|
367
|
+
if (value !== undefined) out[key] = merge(out[key], value, true, key);
|
|
368
|
+
return out as AkanNativeSettings;
|
|
369
|
+
}
|
|
370
|
+
static #isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
371
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
372
|
+
}
|
|
373
|
+
static readonly #nativeKeys = {
|
|
374
|
+
native: [
|
|
375
|
+
"basePath",
|
|
376
|
+
"indexPath",
|
|
377
|
+
"appName",
|
|
378
|
+
"appId",
|
|
379
|
+
"fileName",
|
|
380
|
+
"version",
|
|
381
|
+
"buildNum",
|
|
382
|
+
"icon",
|
|
383
|
+
"splash",
|
|
384
|
+
"permissions",
|
|
385
|
+
"plugins",
|
|
386
|
+
"deepLinks",
|
|
387
|
+
"updates",
|
|
388
|
+
"ios",
|
|
389
|
+
"android",
|
|
390
|
+
"desktop",
|
|
391
|
+
],
|
|
392
|
+
ios: ["indexPath", "teamId", "infoPlist", "entitlements", "privacy", "files"],
|
|
393
|
+
android: [
|
|
394
|
+
"indexPath",
|
|
395
|
+
"sha256CertFingerprints",
|
|
396
|
+
"googleServices",
|
|
397
|
+
"push",
|
|
398
|
+
"autoplay",
|
|
399
|
+
"manifest",
|
|
400
|
+
"application",
|
|
401
|
+
"activity",
|
|
402
|
+
"files",
|
|
403
|
+
],
|
|
404
|
+
desktop: ["indexPath", "server", "recovery", "window", "screenCapture", "entitlements"],
|
|
405
|
+
desktopServer: ["omit"],
|
|
406
|
+
deepLinks: ["schemes", "domains"],
|
|
407
|
+
} as const;
|
|
408
|
+
//* Settings that moved when `mobile` became `native` are named with where they went, instead of "unknown".
|
|
409
|
+
static readonly #movedKeys: Readonly<Record<string, string>> = {
|
|
410
|
+
native: "the platform sections and plugins sit directly in the native section: ios, android, desktop, plugins",
|
|
411
|
+
assets: "icon and splash sit directly in the native section",
|
|
412
|
+
files:
|
|
413
|
+
"ios.files (keyed by the path in the app bundle) or android.files (keyed res/<type>/<file> or assets/<path>)",
|
|
414
|
+
push: "android.push",
|
|
415
|
+
privacy: "ios.privacy",
|
|
416
|
+
"deepLinks.ios": "ios.teamId",
|
|
417
|
+
"deepLinks.android": "android.sha256CertFingerprints",
|
|
418
|
+
};
|
|
419
|
+
static #assertNativeKeys(section: object, where: string, configPath: string) {
|
|
420
|
+
const check = (value: unknown, known: readonly string[], at: string, kind: string) => {
|
|
421
|
+
if (!AkanAppConfig.#isPlainObject(value)) return;
|
|
422
|
+
for (const key of Object.keys(value)) {
|
|
423
|
+
if (known.includes(key)) continue;
|
|
424
|
+
const moved = AkanAppConfig.#movedKeys[kind === "native" ? key : `${kind}.${key}`];
|
|
425
|
+
throw new Error(
|
|
426
|
+
moved
|
|
427
|
+
? `${at}.${key} in ${configPath} has moved: ${moved}.`
|
|
428
|
+
: `${at}.${key} in ${configPath} is not a native setting. Known: ${known.join(", ")}.`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
check(section, AkanAppConfig.#nativeKeys.native, where, "native");
|
|
433
|
+
const settings = section as AkanNativeSettings;
|
|
434
|
+
check(settings.ios, AkanAppConfig.#nativeKeys.ios, `${where}.ios`, "ios");
|
|
435
|
+
check(settings.android, AkanAppConfig.#nativeKeys.android, `${where}.android`, "android");
|
|
436
|
+
check(settings.desktop, AkanAppConfig.#nativeKeys.desktop, `${where}.desktop`, "desktop");
|
|
437
|
+
check(
|
|
438
|
+
settings.desktop?.server,
|
|
439
|
+
AkanAppConfig.#nativeKeys.desktopServer,
|
|
440
|
+
`${where}.desktop.server`,
|
|
441
|
+
"desktopServer",
|
|
442
|
+
);
|
|
443
|
+
check(settings.deepLinks, AkanAppConfig.#nativeKeys.deepLinks, `${where}.deepLinks`, "deepLinks");
|
|
444
|
+
}
|
|
445
|
+
static readonly #androidFilePattern = /^(?:res\/[^/]+\/[^/]+|assets\/.+)$/;
|
|
446
|
+
static #assertFiles(settings: AkanNativeSettings, where: string, configPath: string) {
|
|
447
|
+
const check = (
|
|
448
|
+
files: Record<string, string> | undefined,
|
|
449
|
+
at: string,
|
|
450
|
+
valid: (to: string) => boolean,
|
|
451
|
+
shape: string,
|
|
452
|
+
) => {
|
|
453
|
+
for (const [to, from] of Object.entries(files ?? {})) {
|
|
454
|
+
if (typeof from !== "string")
|
|
455
|
+
throw new Error(
|
|
456
|
+
`${at}["${to}"] in ${configPath} must name its source file, relative to the app folder, e.g. { "${shape}": "assets/chime.mp3" }.`,
|
|
457
|
+
);
|
|
458
|
+
if (!valid(to)) throw new Error(`${at}["${to}"] in ${configPath} must land at ${shape}.`);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
check(
|
|
462
|
+
settings.ios?.files,
|
|
463
|
+
`${where}.ios.files`,
|
|
464
|
+
(to) => to.length > 0 && !to.startsWith("/") && !to.split("/").includes(".."),
|
|
465
|
+
"<path in the app bundle>",
|
|
466
|
+
);
|
|
467
|
+
check(
|
|
468
|
+
settings.android?.files,
|
|
469
|
+
`${where}.android.files`,
|
|
470
|
+
(to) => AkanAppConfig.#androidFilePattern.test(to),
|
|
471
|
+
"res/<type>/<file> or assets/<path>",
|
|
472
|
+
);
|
|
365
473
|
}
|
|
366
474
|
#applyRoutes(routes: AkanRouteConfig[] = []) {
|
|
367
475
|
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
|
-
}
|
|
476
|
+
const basePath = route.basePath ? route.basePath.replace(/^\/+|\/+$/g, "") : null;
|
|
477
|
+
if (basePath !== null) this.basePaths.add(basePath);
|
|
478
|
+
const domains = basePath !== null ? this.subRoutes.getOrInsert(basePath, new Set()) : this.domains;
|
|
479
|
+
for (const branch of Object.keys(route.domains)) this.branches.add(branch);
|
|
480
|
+
for (const domain of Object.values(route.domains).flat())
|
|
481
|
+
if (domain) domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
|
|
386
482
|
}
|
|
387
483
|
const appName = this.app.name.toLowerCase();
|
|
388
484
|
const serveDomain = this.baseDevEnv.serveDomain.toLowerCase();
|
|
@@ -420,21 +516,39 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
420
516
|
command: docker?.command ?? ["bun", "main.js"],
|
|
421
517
|
};
|
|
422
518
|
}
|
|
423
|
-
|
|
519
|
+
/** What the built server runs with in its image; a desktop app's carried server starts from the same values. */
|
|
520
|
+
getProductionEnv(environment: string = this.baseDevEnv.env): Record<string, string> {
|
|
521
|
+
return {
|
|
522
|
+
PORT: "8282",
|
|
523
|
+
NODE_ENV: "production",
|
|
524
|
+
AKAN_PUBLIC_REPO_NAME: this.baseDevEnv.repoName,
|
|
525
|
+
AKAN_PUBLIC_SERVE_DOMAIN: this.baseDevEnv.serveDomain,
|
|
526
|
+
AKAN_PUBLIC_APP_NAME: this.app.name,
|
|
527
|
+
AKAN_PUBLIC_ENV: environment,
|
|
528
|
+
...(this.basePaths.size ? { AKAN_PUBLIC_BASE_PATHS: [...this.basePaths].join(",") } : {}),
|
|
529
|
+
AKAN_PUBLIC_DEFAULT_LOCALE: this.i18n.defaultLocale,
|
|
530
|
+
AKAN_PUBLIC_LOCALES: this.i18n.locales.join(","),
|
|
531
|
+
AKAN_PUBLIC_API_PREFIX: this.api.prefix,
|
|
532
|
+
AKAN_PUBLIC_WS_PREFIX: this.api.websocketPrefix,
|
|
533
|
+
AKAN_PUBLIC_OPERATION_MODE: "cloud",
|
|
534
|
+
AKAN_DATABASE_MODES: this.database.modes.join(","),
|
|
535
|
+
// File logging is off: a container's writable layer is ephemeral and stdout is the collection path.
|
|
536
|
+
AKAN_LOG_TO_FILE: "0",
|
|
537
|
+
// The web env matches what was built (a deployment can only narrow it).
|
|
538
|
+
...(this.web.ssr ? {} : { AKAN_SSR: "false" }),
|
|
539
|
+
...(this.web.csr ? {} : { AKAN_CSR: "false" }),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
dockerfileFor(environment: string): string {
|
|
543
|
+
return this.#makeDockerfile(environment);
|
|
544
|
+
}
|
|
545
|
+
#makeDockerfile(environment?: string): string {
|
|
424
546
|
if (typeof this.docker === "string") return this.docker;
|
|
425
547
|
const { image, preRuns, postRuns, command } = this.docker;
|
|
426
548
|
const preRunScripts = this.#getDockerRunScripts(preRuns);
|
|
427
549
|
const postRunScripts = this.#getDockerRunScripts(postRuns);
|
|
428
550
|
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");
|
|
551
|
+
const envLines = Object.entries(this.getProductionEnv(environment)).map(([key, value]) => `ENV ${key}=${value}`);
|
|
438
552
|
return `${imageScript}
|
|
439
553
|
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
554
|
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
|
|
@@ -446,28 +560,11 @@ COPY ./package.json ./package.json
|
|
|
446
560
|
RUN bun install --production
|
|
447
561
|
${postRunScripts.join("\n")}
|
|
448
562
|
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}
|
|
563
|
+
${envLines.join("\n")}
|
|
463
564
|
CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
464
565
|
}
|
|
465
566
|
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
|
-
*/
|
|
567
|
+
// Bun caches dynamic imports by path; a fresh query string re-evaluates the config module (its imports stay cached).
|
|
471
568
|
static async importConfigModule<T = unknown>(
|
|
472
569
|
cwdPath: string,
|
|
473
570
|
{ bustImportCache = false }: { bustImportCache?: boolean } = {},
|
|
@@ -506,6 +603,10 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
506
603
|
);
|
|
507
604
|
return {
|
|
508
605
|
externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
|
|
606
|
+
trustedDependencies: libConfigs.flatMap((libConfig) => libConfig?.trustedDependencies ?? []),
|
|
607
|
+
bin: libConfigs.flatMap((libConfig) =>
|
|
608
|
+
libConfig && Object.keys(libConfig.bin).length ? [{ lib: libConfig.lib.name, bin: libConfig.bin }] : [],
|
|
609
|
+
),
|
|
509
610
|
docker: {
|
|
510
611
|
preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
|
|
511
612
|
postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? []),
|
|
@@ -519,31 +620,37 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
519
620
|
#resolveProductionDependencyVersion(lib: string) {
|
|
520
621
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
521
622
|
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.
|
|
623
|
+
// The framework's own (peer)dependencies resolve plugin runtime packages without a hardcoded list.
|
|
525
624
|
const akanPackageJson = getAkanPackageJson();
|
|
526
|
-
|
|
625
|
+
const version = akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
626
|
+
if (!version) throw new Error(`Dependency ${lib} not found in package.json`);
|
|
627
|
+
return version;
|
|
527
628
|
}
|
|
528
629
|
#getProductionRuntimePackages() {
|
|
529
|
-
return [
|
|
630
|
+
return [
|
|
631
|
+
...this.externalLibs,
|
|
632
|
+
...SSR_RUNTIME_PACKAGES,
|
|
633
|
+
...this.database.modes.flatMap((mode) => this.getDatabaseModeRuntimePackages(mode)),
|
|
634
|
+
];
|
|
530
635
|
}
|
|
531
|
-
getDatabaseModeRuntimePackages(databaseMode: DatabaseMode
|
|
532
|
-
return [...
|
|
636
|
+
getDatabaseModeRuntimePackages(databaseMode: DatabaseMode) {
|
|
637
|
+
return [...DatabaseModes.drivers[databaseMode]];
|
|
533
638
|
}
|
|
534
|
-
getMissingDatabaseModeDependencySpecs(databaseMode: DatabaseMode
|
|
639
|
+
getMissingDatabaseModeDependencySpecs(databaseMode: DatabaseMode) {
|
|
535
640
|
return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
|
|
536
641
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
642
|
+
/** The mode a CLI command runs this app in: the shell's `AKAN_DATABASE_MODE` if the app declares it, else the first. */
|
|
643
|
+
resolveDatabaseMode() {
|
|
644
|
+
return DatabaseModes.resolve({
|
|
645
|
+
requested: process.env.AKAN_DATABASE_MODE,
|
|
646
|
+
declared: this.database.modes.join(","),
|
|
647
|
+
local: true,
|
|
648
|
+
});
|
|
544
649
|
}
|
|
545
|
-
|
|
546
|
-
|
|
650
|
+
static #database(app: App, config: AppConfigDeclaration): AkanDatabaseConfig {
|
|
651
|
+
const declared = config?.database?.modes?.filter((mode): mode is DatabaseMode => !!mode);
|
|
652
|
+
const modes = declared?.length ? declared : ["single"];
|
|
653
|
+
return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
|
|
547
654
|
}
|
|
548
655
|
#getMissingDependencySpecs(libs: readonly string[]) {
|
|
549
656
|
const rootDependencies = {
|
|
@@ -552,11 +659,10 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
552
659
|
};
|
|
553
660
|
return libs
|
|
554
661
|
.filter((lib) => !rootDependencies[lib])
|
|
555
|
-
.map((lib) => {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
});
|
|
662
|
+
.map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
|
|
663
|
+
}
|
|
664
|
+
get akanVersion() {
|
|
665
|
+
return getAkanPackageJson().version;
|
|
560
666
|
}
|
|
561
667
|
getProductionPackageJson(data: Partial<PackageJson> = {}): PackageJson {
|
|
562
668
|
return {
|
|
@@ -565,12 +671,12 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
565
671
|
version: "1.0.0",
|
|
566
672
|
main: "./main.js",
|
|
567
673
|
dependencies: Object.fromEntries(
|
|
568
|
-
[...new Set(this.#getProductionRuntimePackages())].map((lib) =>
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}),
|
|
674
|
+
[...new Set(this.#getProductionRuntimePackages())].map((lib) => [
|
|
675
|
+
lib,
|
|
676
|
+
this.#resolveProductionDependencyVersion(lib),
|
|
677
|
+
]),
|
|
573
678
|
),
|
|
679
|
+
...(this.trustedDependencies.length ? { trustedDependencies: this.trustedDependencies } : {}),
|
|
574
680
|
...data,
|
|
575
681
|
};
|
|
576
682
|
}
|
|
@@ -624,13 +730,18 @@ function mergeImageConfig(config: Partial<AkanImageConfig> = {}): AkanImageConfi
|
|
|
624
730
|
export class AkanLibConfig implements LibConfigResult {
|
|
625
731
|
lib: Lib;
|
|
626
732
|
externalLibs: string[];
|
|
733
|
+
trustedDependencies: string[];
|
|
734
|
+
bin: AkanBinConfig;
|
|
627
735
|
docker: LibDockerConfig;
|
|
628
736
|
assets: LibAssetsConfig;
|
|
629
737
|
/** Live-only: plugins declared in this lib's `akan.config.ts` (never serialized). */
|
|
630
738
|
plugins: AkanPlugin[];
|
|
631
|
-
constructor(lib: Lib, config:
|
|
739
|
+
constructor(lib: Lib, config: LibConfigDeclaration, plugins: AkanPlugin[] = []) {
|
|
632
740
|
this.lib = lib;
|
|
633
741
|
this.externalLibs = config?.externalLibs ?? [];
|
|
742
|
+
const owner = `libs/${lib.name}/akan.config.ts`;
|
|
743
|
+
this.trustedDependencies = normalizePackageNames(config?.trustedDependencies, owner);
|
|
744
|
+
this.bin = AkanBin.parse(config?.bin, owner, lib.cwdPath);
|
|
634
745
|
this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
|
|
635
746
|
this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts as string[] | undefined) };
|
|
636
747
|
this.plugins = plugins;
|
|
@@ -638,46 +749,19 @@ export class AkanLibConfig implements LibConfigResult {
|
|
|
638
749
|
static async from(lib: Lib, { bustImportCache = false }: { bustImportCache?: boolean } = {}) {
|
|
639
750
|
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
640
751
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
641
|
-
const { plugins, ...config } = (resolved ?? {}) as
|
|
752
|
+
const { plugins, ...config } = (resolved ?? {}) as LibConfigDeclaration & { plugins?: AkanPlugin[] };
|
|
642
753
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
643
754
|
}
|
|
644
755
|
}
|
|
645
756
|
|
|
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
757
|
//! need to refactor
|
|
662
|
-
|
|
663
|
-
const
|
|
758
|
+
const shiftBuildNum = async (app: App, delta: number) => {
|
|
759
|
+
const { buildNum } = (await AkanAppConfig.from(app)).native;
|
|
664
760
|
const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
|
|
665
761
|
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);
|
|
762
|
+
fs.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
|
|
672
763
|
};
|
|
673
764
|
|
|
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
|
-
};
|
|
765
|
+
export const increaseBuildNum = async (app: App) => await shiftBuildNum(app, 1);
|
|
766
|
+
|
|
767
|
+
export const decreaseBuildNum = async (app: App) => await shiftBuildNum(app, -1);
|