@akanjs/devkit 3.0.0-beta.9 → 3.0.1
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/CHANGELOG.md +1819 -0
- 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 +34 -31
- package/artifact/implicitRootLayout.ts +60 -107
- package/artifact/routeSeedIndex.test.ts +5 -18
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +39 -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 +506 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +265 -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 +37 -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 +111 -202
- package/integration/devStabilityHarness.test.ts +13 -28
- package/integration/devStabilityHarness.ts +67 -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-static-in-object-light-model/bad.ts +43 -0
- package/lint/__fixtures__/no-static-in-object-light-model/fixture.json +1 -0
- package/lint/__fixtures__/no-static-in-object-light-model/good.ts +42 -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-static-in-object-light-model.grit +27 -0
- 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 +19 -26
- 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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { NativeAppLine } from "./nativeAppLine";
|
|
3
|
+
|
|
4
|
+
describe("NativeAppLine", () => {
|
|
5
|
+
test("a page logger line keeps its level and its logger's name", () => {
|
|
6
|
+
expect(NativeAppLine.read("[page warn] [WsClient] WebSocket message process failed")).toEqual({
|
|
7
|
+
level: "warn",
|
|
8
|
+
message: "[page:WsClient] WebSocket message process failed",
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("a bare console call keeps its level, `log` reading as info", () => {
|
|
13
|
+
expect(NativeAppLine.read("[page log] hello")).toEqual({ level: "info", message: "[page] hello" });
|
|
14
|
+
expect(NativeAppLine.read("[page debug] [akan:frame:x:1] csr.render {}")).toEqual({
|
|
15
|
+
level: "debug",
|
|
16
|
+
message: "[page:akan:frame:x:1] csr.render {}",
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("a continuation line follows its message's level without a prefix of its own", () => {
|
|
21
|
+
expect(NativeAppLine.read("[page+ error] at run (app.js:1:2)")).toEqual({
|
|
22
|
+
level: "error",
|
|
23
|
+
message: " at run (app.js:1:2)",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("a carried server's line keeps the level its logger wrote, and one without a level stays visible", () => {
|
|
28
|
+
const line = "[server] [minimal] 81 - 09/30/2026, 12:30:06 PM ERROR boom +2ms";
|
|
29
|
+
expect(NativeAppLine.read(line)).toEqual({ level: "error", message: line });
|
|
30
|
+
expect(NativeAppLine.read("[server] \u001b[32m[minimal] 81 -\u001b[39m WARN slow").level).toBe("warn");
|
|
31
|
+
expect(NativeAppLine.read("[server] at run (main.js:1:2)").level).toBe("info");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("a second window is named, the first is not", () => {
|
|
35
|
+
expect(NativeAppLine.read("[page#2 info] [App] ready")).toEqual({ level: "info", message: "[page#2:App] ready" });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("logcat lines read by tag and priority", () => {
|
|
39
|
+
expect(NativeAppLine.read("W/AkanNativeConsole( 1234): [WsClient] failed")).toEqual({
|
|
40
|
+
level: "warn",
|
|
41
|
+
message: "[page:WsClient] failed",
|
|
42
|
+
});
|
|
43
|
+
expect(NativeAppLine.read("E/AndroidRuntime( 1234): FATAL EXCEPTION: main")).toEqual({
|
|
44
|
+
level: "error",
|
|
45
|
+
message: "[AndroidRuntime] FATAL EXCEPTION: main",
|
|
46
|
+
});
|
|
47
|
+
expect(
|
|
48
|
+
NativeAppLine.read("I/AkanNative( 1234): pages from http://10.0.2.2:5000 (akan-native dev --hmr)").level,
|
|
49
|
+
).toBe("debug");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("the host's own lines stay visible but for where its pages come from", () => {
|
|
53
|
+
expect(NativeAppLine.read("[akan-native] opened in the browser: https://x.test")).toEqual({
|
|
54
|
+
level: "info",
|
|
55
|
+
message: "[akan-native] opened in the browser: https://x.test",
|
|
56
|
+
});
|
|
57
|
+
expect(
|
|
58
|
+
NativeAppLine.read("[akan-native native] pages from http://127.0.0.1:6000 (akan-native dev --hmr)").level,
|
|
59
|
+
).toBe("debug");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("a line no page or host wrote is kept for debug, or for info when verbose", () => {
|
|
63
|
+
expect(NativeAppLine.read("WebContent[123] Could not signal service")).toEqual({
|
|
64
|
+
level: "debug",
|
|
65
|
+
message: "WebContent[123] Could not signal service",
|
|
66
|
+
});
|
|
67
|
+
expect(NativeAppLine.read("WebContent[123] Could not signal service", { verbose: true }).level).toBe("info");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { LogLevel } from "akanjs/common";
|
|
2
|
+
|
|
3
|
+
export interface NativeAppLineRead {
|
|
4
|
+
level: LogLevel;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//* A line a native dev build prints — the page console its host mirrors, the simulator's console, logcat — read back
|
|
9
|
+
//* at the level the page logged it, so the terminal shows it once, stamped by the app's own logger.
|
|
10
|
+
export class NativeAppLine {
|
|
11
|
+
//? The host's grammar (desktop host.ts, iOS AkanNativeShell.swift): `[page<+><#window> <level>] text`, where `+`
|
|
12
|
+
//? marks a continuation line of a multi-line message and `#n` any window but the first.
|
|
13
|
+
static readonly #page = /^\[page(\+)?(?:#(\d+))? (trace|verbose|debug|log|info|warn|error)\] ?(.*)$/;
|
|
14
|
+
static readonly #logcat = /^([VDIWEFA])\/([^(]+?)\s*\(\s*\d+\):\s?(.*)$/;
|
|
15
|
+
static readonly #named = /^\[([^\]\s]+)\] ([\s\S]*)$/;
|
|
16
|
+
//? A server the desktop app carries (host server.ts): `[server] <its own log line>`, whose level word the akan logger
|
|
17
|
+
//? wrote; a line without one (a crash's stack) is still the app's and stays visible.
|
|
18
|
+
static readonly #server = /^\[server\] /;
|
|
19
|
+
static readonly #serverLevel = /\b(TRACE|VERBOSE|DEBUG|INFO|WARN|ERROR)\b/;
|
|
20
|
+
static readonly #logcatLevels: { [priority: string]: LogLevel } = {
|
|
21
|
+
V: "verbose",
|
|
22
|
+
D: "debug",
|
|
23
|
+
I: "info",
|
|
24
|
+
W: "warn",
|
|
25
|
+
E: "error",
|
|
26
|
+
F: "error",
|
|
27
|
+
A: "error",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** `verbose` keeps the lines no page or host wrote (WebKit, the simulator) at info instead of debug. */
|
|
31
|
+
static read(line: string, { verbose = false }: { verbose?: boolean } = {}): NativeAppLineRead {
|
|
32
|
+
const logcat = NativeAppLine.#logcat.exec(line);
|
|
33
|
+
if (logcat) {
|
|
34
|
+
const [, priority = "I", tag = "", text = ""] = logcat;
|
|
35
|
+
const level = NativeAppLine.#logcatLevels[priority] ?? "info";
|
|
36
|
+
if (tag === "AkanNativeConsole") return NativeAppLine.#pageLine(level, text, { continuation: false });
|
|
37
|
+
if (tag === "AkanNative") return NativeAppLine.#hostLine(`[akan-native] ${text}`, level);
|
|
38
|
+
return { level, message: `[${tag}] ${text}` };
|
|
39
|
+
}
|
|
40
|
+
const page = NativeAppLine.#page.exec(line);
|
|
41
|
+
if (page) {
|
|
42
|
+
const [, continuation, window, level = "info", text = ""] = page;
|
|
43
|
+
return NativeAppLine.#pageLine(level === "log" ? "info" : (level as LogLevel), text, {
|
|
44
|
+
continuation: continuation === "+",
|
|
45
|
+
window,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (line.startsWith("[akan-native")) return NativeAppLine.#hostLine(line, "info");
|
|
49
|
+
if (NativeAppLine.#server.test(line)) {
|
|
50
|
+
const word = NativeAppLine.#serverLevel.exec(Bun.stripANSI(line))?.[1];
|
|
51
|
+
return { level: word ? (word.toLowerCase() as LogLevel) : "info", message: line };
|
|
52
|
+
}
|
|
53
|
+
return { level: verbose ? "info" : "debug", message: line };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static #pageLine(
|
|
57
|
+
level: LogLevel,
|
|
58
|
+
text: string,
|
|
59
|
+
{ continuation, window }: { continuation: boolean; window?: string },
|
|
60
|
+
): NativeAppLineRead {
|
|
61
|
+
if (continuation) return { level, message: ` ${text}` };
|
|
62
|
+
const named = NativeAppLine.#named.exec(text);
|
|
63
|
+
const scope = `page${window ? `#${window}` : ""}${named ? `:${named[1]}` : ""}`;
|
|
64
|
+
return { level, message: `[${scope}] ${named ? named[2] : text}` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//? The host's own lines are about the host, not the app: where its pages come from is boot detail.
|
|
68
|
+
static #hostLine(line: string, level: LogLevel): NativeAppLineRead {
|
|
69
|
+
return { level: /\bpages from\b/.test(line) ? "debug" : level, message: line };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { AkanNativeTarget } from "../akanConfig";
|
|
5
|
+
import { tempDirs } from "../testHelpers";
|
|
6
|
+
import { NativeApi } from "./nativeApi";
|
|
7
|
+
import { NativeConfig } from "./nativeConfig";
|
|
8
|
+
|
|
9
|
+
const makeTempRoot = tempDirs("akan-native-config-");
|
|
10
|
+
const repoApp = path.resolve(import.meta.dir, "../../../../apps/minimal");
|
|
11
|
+
|
|
12
|
+
const minimalTarget: AkanNativeTarget = {
|
|
13
|
+
name: "default",
|
|
14
|
+
indexPath: "/explore",
|
|
15
|
+
appName: "minimal",
|
|
16
|
+
appId: "com.minimal.dev.app",
|
|
17
|
+
version: "0.0.1",
|
|
18
|
+
buildNum: 1,
|
|
19
|
+
permissions: ["push"],
|
|
20
|
+
deepLinks: { schemes: ["minimal"], domains: ["example.com"] },
|
|
21
|
+
ios: { teamId: "TEAMID" },
|
|
22
|
+
android: { googleServices: "secrets/google-services.json", sha256CertFingerprints: ["00:11"] },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const adminTarget: AkanNativeTarget = {
|
|
26
|
+
name: "admin",
|
|
27
|
+
basePath: "admin",
|
|
28
|
+
appName: "Portal Admin",
|
|
29
|
+
appId: "com.portal.admin",
|
|
30
|
+
fileName: "portal-admin",
|
|
31
|
+
version: "2.1.0",
|
|
32
|
+
buildNum: 42,
|
|
33
|
+
icon: "assets/icon.png",
|
|
34
|
+
splash: "assets/splash.png",
|
|
35
|
+
permissions: ["camera", "location", "contacts", "speech"],
|
|
36
|
+
deepLinks: { domains: ["portal.example"] },
|
|
37
|
+
plugins: ["iap"],
|
|
38
|
+
ios: { infoPlist: { ITSAppUsesNonExemptEncryption: false } },
|
|
39
|
+
android: {
|
|
40
|
+
manifest: ['<queries><package android:name="com.kakao.talk" /></queries>'],
|
|
41
|
+
files: { "res/raw/chime.mp3": "assets/chime.mp3" },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const updatesKey = Buffer.alloc(32, 7).toString("base64");
|
|
46
|
+
|
|
47
|
+
describe("NativeConfig.build", () => {
|
|
48
|
+
test("turns the minimal app's push target into the runtime's config, single-instance carrying its link scheme", () => {
|
|
49
|
+
const { config, warnings } = NativeConfig.build({
|
|
50
|
+
appPath: "/repo/apps/minimal",
|
|
51
|
+
target: minimalTarget,
|
|
52
|
+
webDir: "/repo/apps/minimal/.akan/native/default/web",
|
|
53
|
+
contributions: [{ permission: "push", plugins: ["push"] }],
|
|
54
|
+
locales: ["en", "ko"],
|
|
55
|
+
platform: "android",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(warnings).toEqual([]);
|
|
59
|
+
expect(config).toEqual({
|
|
60
|
+
app: { id: "com.minimal.dev.app", name: "minimal", fileName: "minimal", version: "0.0.1", build: 1 },
|
|
61
|
+
web: { dir: "/repo/apps/minimal/.akan/native/default/web" },
|
|
62
|
+
plugins: [...NativeConfig.basePlugins, "single-instance", "push"],
|
|
63
|
+
capabilities: [
|
|
64
|
+
{
|
|
65
|
+
identifier: "app",
|
|
66
|
+
description: "The plugins minimal ships, each with its default permissions",
|
|
67
|
+
permissions: [...NativeConfig.basePlugins, "single-instance", "push"].map((plugin) => `${plugin}:default`),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
deepLinks: { schemes: ["minimal"], domains: ["example.com"] },
|
|
71
|
+
android: {
|
|
72
|
+
debugAppIdSuffix: ".debug",
|
|
73
|
+
googleServices: path.resolve("/repo/apps/minimal", "secrets/google-services.json"),
|
|
74
|
+
},
|
|
75
|
+
keyboard: { resize: "none" },
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("falls back to the builtin permission plugins, names the app in their texts, and lists basePath links per locale", () => {
|
|
80
|
+
const { config, warnings } = NativeConfig.build({
|
|
81
|
+
appPath: "/repo/apps/portal",
|
|
82
|
+
target: adminTarget,
|
|
83
|
+
webDir: "/repo/apps/portal/.akan/native/admin/web",
|
|
84
|
+
contributions: [],
|
|
85
|
+
locales: ["en", "ko"],
|
|
86
|
+
platform: "android",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(warnings).toEqual(["Permission 'speech' has no native plugin yet; the app ships without it."]);
|
|
90
|
+
expect(config).toEqual({
|
|
91
|
+
app: { id: "com.portal.admin", name: "Portal Admin", fileName: "portal-admin", version: "2.1.0", build: 42 },
|
|
92
|
+
web: { dir: "/repo/apps/portal/.akan/native/admin/web" },
|
|
93
|
+
plugins: [...NativeConfig.basePlugins, "camera", "geolocation", "contacts", "iap"],
|
|
94
|
+
capabilities: [
|
|
95
|
+
{
|
|
96
|
+
identifier: "app",
|
|
97
|
+
description: "The plugins Portal Admin ships, each with its default permissions",
|
|
98
|
+
permissions: [...NativeConfig.basePlugins, "camera", "geolocation", "contacts", "iap"].map(
|
|
99
|
+
(plugin) => `${plugin}:default`,
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
usageDescriptions: {
|
|
104
|
+
NSCameraUsageDescription: "Portal Admin requires access to the camera to take photos.",
|
|
105
|
+
NSPhotoLibraryAddUsageDescription: "Portal Admin requires access to the photo library to take photos.",
|
|
106
|
+
NSPhotoLibraryUsageDescription: "Portal Admin requires access to the photo library to take photos.",
|
|
107
|
+
NSLocationAlwaysAndWhenInUseUsageDescription:
|
|
108
|
+
"Portal Admin requires access to the location to get the user's location.",
|
|
109
|
+
NSLocationAlwaysUsageDescription: "Portal Admin requires access to the location to get the user's location.",
|
|
110
|
+
NSLocationWhenInUseUsageDescription: "Portal Admin requires access to the location to get the user's location.",
|
|
111
|
+
NSContactsUsageDescription: "Portal Admin requires access to the contacts to find people you know.",
|
|
112
|
+
},
|
|
113
|
+
deepLinks: { domains: [{ host: "portal.example", pathPrefixes: ["/en/admin", "/ko/admin"] }] },
|
|
114
|
+
native: {
|
|
115
|
+
ios: { infoPlist: { ITSAppUsesNonExemptEncryption: false } },
|
|
116
|
+
android: { manifest: ['<queries><package android:name="com.kakao.talk" /></queries>'] },
|
|
117
|
+
resources: [{ from: path.resolve("/repo/apps/portal", "assets/chime.mp3"), to: "android/res/raw/chime.mp3" }],
|
|
118
|
+
},
|
|
119
|
+
android: { debugAppIdSuffix: ".debug" },
|
|
120
|
+
keyboard: { resize: "none" },
|
|
121
|
+
icon: path.resolve("/repo/apps/portal", "assets/icon.png"),
|
|
122
|
+
splash: { image: path.resolve("/repo/apps/portal", "assets/splash.png") },
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("a plugin that claims a permission replaces the builtin one and adds its Android entries", () => {
|
|
127
|
+
const { config } = NativeConfig.build({
|
|
128
|
+
appPath: "/repo/apps/portal",
|
|
129
|
+
target: {
|
|
130
|
+
...adminTarget,
|
|
131
|
+
permissions: ["camera"],
|
|
132
|
+
plugins: undefined,
|
|
133
|
+
ios: undefined,
|
|
134
|
+
android: undefined,
|
|
135
|
+
icon: undefined,
|
|
136
|
+
splash: undefined,
|
|
137
|
+
},
|
|
138
|
+
webDir: "/web",
|
|
139
|
+
contributions: [
|
|
140
|
+
{
|
|
141
|
+
permission: "camera",
|
|
142
|
+
plugins: ["camera"],
|
|
143
|
+
usageDescriptions: { cameraUsageDescription: "Scan receipts with $(PRODUCT_NAME)." },
|
|
144
|
+
androidPermissions: ["CAMERA"],
|
|
145
|
+
androidFeatures: ["android.hardware.camera"],
|
|
146
|
+
},
|
|
147
|
+
{ permission: "push", plugins: ["push"] },
|
|
148
|
+
],
|
|
149
|
+
locales: ["en"],
|
|
150
|
+
platform: "android",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(config.plugins).toEqual([...NativeConfig.basePlugins, "camera"]);
|
|
154
|
+
expect(config.usageDescriptions).toEqual({ NSCameraUsageDescription: "Scan receipts with Portal Admin." });
|
|
155
|
+
expect(config.native).toEqual({
|
|
156
|
+
android: {
|
|
157
|
+
manifest: [
|
|
158
|
+
'<uses-permission android:name="android.permission.CAMERA" />',
|
|
159
|
+
'<uses-feature android:name="android.hardware.camera" android:required="false" />',
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("updates bring their plugin and follow the binary's backend unless a channel is named", () => {
|
|
166
|
+
const build = (updates: AkanNativeTarget["updates"], env?: "main" | "develop") =>
|
|
167
|
+
NativeConfig.build({
|
|
168
|
+
appPath: "/repo/apps/portal",
|
|
169
|
+
target: { ...adminTarget, updates },
|
|
170
|
+
webDir: "/web",
|
|
171
|
+
contributions: [],
|
|
172
|
+
locales: ["en"],
|
|
173
|
+
platform: "android",
|
|
174
|
+
env,
|
|
175
|
+
}).config;
|
|
176
|
+
const url = "https://updates.example.com/portal";
|
|
177
|
+
expect(build({ url, publicKey: updatesKey }, "develop").updates).toEqual({
|
|
178
|
+
url,
|
|
179
|
+
publicKey: updatesKey,
|
|
180
|
+
channel: "develop",
|
|
181
|
+
});
|
|
182
|
+
expect(build({ url, publicKey: updatesKey, channel: "beta", readyTimeout: 15_000 }, "main").updates).toEqual({
|
|
183
|
+
url,
|
|
184
|
+
publicKey: updatesKey,
|
|
185
|
+
channel: "beta",
|
|
186
|
+
readyTimeout: 15_000,
|
|
187
|
+
});
|
|
188
|
+
expect(build({ url, publicKey: updatesKey }).updates).toEqual({ url, publicKey: updatesKey });
|
|
189
|
+
expect(build({ url, publicKey: updatesKey }, "main").plugins).toContain("updates");
|
|
190
|
+
expect(build(undefined, "main").plugins).not.toContain("updates");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("push, privacy and the icon and splash objects reach the runtime, paths from the app folder", () => {
|
|
194
|
+
const { config } = NativeConfig.build({
|
|
195
|
+
appPath: "/repo/apps/portal",
|
|
196
|
+
target: {
|
|
197
|
+
...adminTarget,
|
|
198
|
+
icon: { image: "assets/icon.png", backgroundColor: "#ffffff" },
|
|
199
|
+
splash: { backgroundColor: { light: "#ffffff", dark: "#000000" }, autoHide: false },
|
|
200
|
+
android: { push: { smallIcon: "assets/noti.png", color: "#ff5a5f", channel: { id: "chat", name: "Chat" } } },
|
|
201
|
+
ios: { privacy: { tracking: false, accessedApis: { DiskSpace: ["E174.1"] } } },
|
|
202
|
+
},
|
|
203
|
+
webDir: "/web",
|
|
204
|
+
contributions: [],
|
|
205
|
+
locales: ["en"],
|
|
206
|
+
platform: "android",
|
|
207
|
+
});
|
|
208
|
+
const abs = (relative: string) => path.resolve("/repo/apps/portal", relative);
|
|
209
|
+
expect(config.icon).toEqual({ image: abs("assets/icon.png"), backgroundColor: "#ffffff" });
|
|
210
|
+
expect(config.splash).toEqual({ backgroundColor: { light: "#ffffff", dark: "#000000" }, autoHide: false });
|
|
211
|
+
expect(config.push).toEqual({
|
|
212
|
+
android: { smallIcon: abs("assets/noti.png"), color: "#ff5a5f", channel: { id: "chat", name: "Chat" } },
|
|
213
|
+
});
|
|
214
|
+
expect(config.privacy).toEqual({ tracking: false, accessedApis: { DiskSpace: ["E174.1"] } });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("an app id per platform gives each build its own", () => {
|
|
218
|
+
const idOn = (platform: "ios" | "android") =>
|
|
219
|
+
NativeConfig.build({
|
|
220
|
+
appPath: "/repo/apps/portal",
|
|
221
|
+
target: { ...adminTarget, appId: { ios: "com.puffinplanet.admin", android: "com.portal.admin" } },
|
|
222
|
+
webDir: "/web",
|
|
223
|
+
contributions: [],
|
|
224
|
+
locales: ["en"],
|
|
225
|
+
platform,
|
|
226
|
+
}).config.app.id;
|
|
227
|
+
expect([idOn("ios"), idOn("android")]).toEqual(["com.puffinplanet.admin", "com.portal.admin"]);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("the runtime accepts both configs as they are", async () => {
|
|
231
|
+
const root = await makeTempRoot();
|
|
232
|
+
for (const file of ["secrets/google-services.json", "assets/chime.mp3"])
|
|
233
|
+
await mkdir(path.dirname(path.join(root, file)), { recursive: true }).then(() =>
|
|
234
|
+
writeFile(path.join(root, file), file.endsWith(".json") ? googleServicesJson : "x"),
|
|
235
|
+
);
|
|
236
|
+
for (const image of ["assets/icon.png", "assets/splash.png"])
|
|
237
|
+
await writeFile(path.join(root, image), Buffer.from(onePixelPng, "base64"));
|
|
238
|
+
await mkdir(path.join(root, "web"), { recursive: true });
|
|
239
|
+
await writeFile(path.join(root, "web/index.html"), "<html><head></head><body></body></html>");
|
|
240
|
+
const api = await NativeApi.load(repoApp);
|
|
241
|
+
|
|
242
|
+
const adminWithEverything: AkanNativeTarget = {
|
|
243
|
+
...adminTarget,
|
|
244
|
+
updates: { url: "https://updates.example.com/portal", publicKey: updatesKey },
|
|
245
|
+
ios: { ...adminTarget.ios, privacy: { tracking: false } },
|
|
246
|
+
android: { ...adminTarget.android, push: { smallIcon: "assets/icon.png", color: "#ff5a5f" } },
|
|
247
|
+
};
|
|
248
|
+
for (const target of [minimalTarget, adminWithEverything]) {
|
|
249
|
+
const { config } = NativeConfig.build({
|
|
250
|
+
appPath: root,
|
|
251
|
+
env: "main",
|
|
252
|
+
target,
|
|
253
|
+
webDir: path.join(root, "web"),
|
|
254
|
+
contributions: [],
|
|
255
|
+
locales: ["en", "ko"],
|
|
256
|
+
platform: "android",
|
|
257
|
+
});
|
|
258
|
+
expect(api.validateConfig(config, { appDir: root })).toEqual([]);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("ships the app's own native plugins by folder and names each by its id in the capability", async () => {
|
|
263
|
+
const root = await makeTempRoot();
|
|
264
|
+
const kiosk = await writeManifest(path.join(root, "native", "kiosk"), "kiosk");
|
|
265
|
+
await writeManifest(path.join(root, "vendor", "led-panel"), "led-panel");
|
|
266
|
+
const { config } = NativeConfig.build({
|
|
267
|
+
appPath: root,
|
|
268
|
+
target: {
|
|
269
|
+
...minimalTarget,
|
|
270
|
+
permissions: [],
|
|
271
|
+
plugins: ["./vendor/led-panel"],
|
|
272
|
+
deepLinks: undefined,
|
|
273
|
+
android: undefined,
|
|
274
|
+
},
|
|
275
|
+
webDir: path.join(root, "web"),
|
|
276
|
+
contributions: [],
|
|
277
|
+
locales: ["en"],
|
|
278
|
+
platform: "macos",
|
|
279
|
+
nativePlugins: [{ id: "kiosk", dir: kiosk, owner: "apps/board" }],
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(config.plugins).toEqual([...NativeConfig.basePlugins, "./vendor/led-panel", kiosk]);
|
|
283
|
+
expect(config.capabilities?.[0]?.permissions).toEqual(
|
|
284
|
+
[...NativeConfig.basePlugins, "led-panel", "kiosk"].map((id) => `${id}:default`),
|
|
285
|
+
);
|
|
286
|
+
await mkdir(path.join(root, "web"), { recursive: true });
|
|
287
|
+
await writeFile(path.join(root, "web/index.html"), "<html><head></head><body></body></html>");
|
|
288
|
+
const api = await NativeApi.load(repoApp);
|
|
289
|
+
expect(api.validateConfig(config, { appDir: root })).toEqual([]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("ships a native/ folder once when the target also lists it by path or by id", async () => {
|
|
293
|
+
const root = await makeTempRoot();
|
|
294
|
+
const kiosk = await writeManifest(path.join(root, "native", "kiosk"), "kiosk");
|
|
295
|
+
const { config } = NativeConfig.build({
|
|
296
|
+
appPath: root,
|
|
297
|
+
target: {
|
|
298
|
+
...minimalTarget,
|
|
299
|
+
permissions: [],
|
|
300
|
+
plugins: ["./native/kiosk/", "kiosk", "haptics"],
|
|
301
|
+
deepLinks: undefined,
|
|
302
|
+
android: undefined,
|
|
303
|
+
},
|
|
304
|
+
webDir: path.join(root, "web"),
|
|
305
|
+
contributions: [],
|
|
306
|
+
locales: ["en"],
|
|
307
|
+
platform: "macos",
|
|
308
|
+
nativePlugins: [{ id: "kiosk", dir: kiosk, owner: "apps/board" }],
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(config.plugins).toEqual([...NativeConfig.basePlugins, kiosk]);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("hands an unattended app's settings on: page recovery, a kiosk window, screen capture and Android autoplay", async () => {
|
|
315
|
+
const root = await makeTempRoot();
|
|
316
|
+
const { config } = NativeConfig.build({
|
|
317
|
+
appPath: root,
|
|
318
|
+
target: {
|
|
319
|
+
...minimalTarget,
|
|
320
|
+
permissions: [],
|
|
321
|
+
deepLinks: undefined,
|
|
322
|
+
android: { autoplay: true },
|
|
323
|
+
desktop: { recovery: "reload", window: { fullscreen: true, skipTaskbar: true }, screenCapture: "auto" },
|
|
324
|
+
},
|
|
325
|
+
webDir: path.join(root, "web"),
|
|
326
|
+
contributions: [],
|
|
327
|
+
locales: ["en"],
|
|
328
|
+
platform: "macos",
|
|
329
|
+
desktopBin: root,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(config.desktop).toEqual({
|
|
333
|
+
recovery: "reload",
|
|
334
|
+
window: { fullscreen: true, skipTaskbar: true },
|
|
335
|
+
screenCapture: "auto",
|
|
336
|
+
bin: root,
|
|
337
|
+
});
|
|
338
|
+
expect(config.android).toEqual({ debugAppIdSuffix: ".debug", autoplay: true });
|
|
339
|
+
await mkdir(path.join(root, "web"), { recursive: true });
|
|
340
|
+
await writeFile(path.join(root, "web/index.html"), "<html><head></head><body></body></html>");
|
|
341
|
+
expect((await NativeApi.load(repoApp)).validateConfig(config, { appDir: root })).toEqual([]);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("a desktop app's own entitlements reach the macOS build, and the runtime accepts them", async () => {
|
|
345
|
+
const root = await makeTempRoot();
|
|
346
|
+
const entitlements = { "com.apple.security.cs.disable-library-validation": true };
|
|
347
|
+
const { config } = NativeConfig.build({
|
|
348
|
+
appPath: root,
|
|
349
|
+
target: {
|
|
350
|
+
...minimalTarget,
|
|
351
|
+
permissions: [],
|
|
352
|
+
deepLinks: undefined,
|
|
353
|
+
android: undefined,
|
|
354
|
+
desktop: { entitlements },
|
|
355
|
+
},
|
|
356
|
+
webDir: path.join(root, "web"),
|
|
357
|
+
contributions: [],
|
|
358
|
+
locales: ["en"],
|
|
359
|
+
platform: "macos",
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(config.native?.macos).toEqual({ entitlements });
|
|
363
|
+
await mkdir(path.join(root, "web"), { recursive: true });
|
|
364
|
+
await writeFile(path.join(root, "web/index.html"), "<html><head></head><body></body></html>");
|
|
365
|
+
expect((await NativeApi.load(repoApp)).validateConfig(config, { appDir: root })).toEqual([]);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("an app with updates ships the updates plugin and its settings", () => {
|
|
369
|
+
const updates = { url: "https://releases.example.com/board", publicKey: `${"a".repeat(43)}=`, channel: "pilot" };
|
|
370
|
+
const { config } = NativeConfig.build({
|
|
371
|
+
appPath: "/repo/apps/board",
|
|
372
|
+
target: { ...minimalTarget, permissions: [], deepLinks: undefined, android: undefined, updates },
|
|
373
|
+
webDir: "/web",
|
|
374
|
+
contributions: [],
|
|
375
|
+
locales: ["en"],
|
|
376
|
+
platform: "macos",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(config.plugins).toEqual([...NativeConfig.basePlugins, "updates"]);
|
|
380
|
+
expect(config.updates).toEqual(updates);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("an updates channel left unnamed is the backend env the binary is built for", () => {
|
|
384
|
+
const updates = { url: "https://releases.example.com/board", publicKey: `${"a".repeat(43)}=` };
|
|
385
|
+
const build = (env?: "debug" | "main", channel?: string) =>
|
|
386
|
+
NativeConfig.build({
|
|
387
|
+
appPath: "/repo/apps/board",
|
|
388
|
+
target: {
|
|
389
|
+
...minimalTarget,
|
|
390
|
+
permissions: [],
|
|
391
|
+
deepLinks: undefined,
|
|
392
|
+
android: undefined,
|
|
393
|
+
updates: { ...updates, ...(channel ? { channel } : {}) },
|
|
394
|
+
},
|
|
395
|
+
webDir: "/web",
|
|
396
|
+
contributions: [],
|
|
397
|
+
locales: ["en"],
|
|
398
|
+
platform: "macos",
|
|
399
|
+
...(env ? { env } : {}),
|
|
400
|
+
}).config.updates;
|
|
401
|
+
|
|
402
|
+
expect(build("debug")).toEqual({ ...updates, channel: "debug" });
|
|
403
|
+
expect(build("main")).toEqual({ ...updates, channel: "main" });
|
|
404
|
+
expect(build("debug", "pilot")).toEqual({ ...updates, channel: "pilot" });
|
|
405
|
+
expect(build()).toEqual(updates);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("keeps a file name the runtime accepts", () => {
|
|
409
|
+
expect(NativeConfig.fileNameOf("minimal")).toBe("minimal");
|
|
410
|
+
expect(NativeConfig.fileNameOf("my app!")).toBe("my-app");
|
|
411
|
+
expect(NativeConfig.fileNameOf("...")).toBe("app");
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const writeManifest = async (dir: string, id: string) => {
|
|
416
|
+
await mkdir(dir, { recursive: true });
|
|
417
|
+
await writeFile(
|
|
418
|
+
path.join(dir, "native-plugin.json"),
|
|
419
|
+
JSON.stringify({ id, apiVersion: 1, methods: [], events: [], web: null, desktop: null, ios: null, android: null }),
|
|
420
|
+
);
|
|
421
|
+
return dir;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const onePixelPng = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
|
|
425
|
+
|
|
426
|
+
const googleServicesJson = JSON.stringify({
|
|
427
|
+
project_info: { project_number: "1", project_id: "demo", storage_bucket: "demo.appspot.com" },
|
|
428
|
+
client: [
|
|
429
|
+
{
|
|
430
|
+
client_info: { mobilesdk_app_id: "1:1:android:1", android_client_info: { package_name: "com.minimal.dev.app" } },
|
|
431
|
+
api_key: [{ current_key: "demo-key" }],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
client_info: { mobilesdk_app_id: "1:1:android:2", android_client_info: { package_name: "com.portal.admin" } },
|
|
435
|
+
api_key: [{ current_key: "demo-key" }],
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
});
|