create-apexjs 0.6.6 → 0.6.8

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.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/templates/default/AGENTS.md +6 -6
  3. package/templates/default/layouts/default.alpine +48 -19
  4. package/templates/features/data/db/index.ts +25 -24
  5. package/templates/mobile/android/README.md +53 -0
  6. package/templates/mobile/android/app/build.gradle.kts +30 -0
  7. package/templates/mobile/android/app/src/main/AndroidManifest.xml +24 -0
  8. package/templates/mobile/android/app/src/main/java/site/apexjs/shell/ApexBridge.kt +59 -0
  9. package/templates/mobile/android/app/src/main/java/site/apexjs/shell/ApexDbStore.kt +31 -0
  10. package/templates/mobile/android/app/src/main/java/site/apexjs/shell/ApexEngine.kt +66 -0
  11. package/templates/mobile/android/app/src/main/java/site/apexjs/shell/ApexInterceptor.kt +83 -0
  12. package/templates/mobile/android/app/src/main/java/site/apexjs/shell/MainActivity.kt +96 -0
  13. package/templates/mobile/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  14. package/templates/mobile/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  15. package/templates/mobile/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  16. package/templates/mobile/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  17. package/templates/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  18. package/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  19. package/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  20. package/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  21. package/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  22. package/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  23. package/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  24. package/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  25. package/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  26. package/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  27. package/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  28. package/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  29. package/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  30. package/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  31. package/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  32. package/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  33. package/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  34. package/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  35. package/templates/mobile/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  36. package/templates/mobile/android/app/src/main/res/values/strings.xml +4 -0
  37. package/templates/mobile/android/app/src/main/res/values/themes.xml +11 -0
  38. package/templates/mobile/android/build.gradle.kts +4 -0
  39. package/templates/mobile/android/gradle.properties +3 -0
  40. package/templates/mobile/android/play_store_512.png +0 -0
  41. package/templates/mobile/android/settings.gradle.kts +4 -0
  42. package/templates/mobile/apex-bridge.js +22 -0
  43. package/templates/mobile/gen-mobile-assets.mjs +101 -0
  44. package/templates/mobile/ios/ApexApp.swift +106 -0
  45. package/templates/mobile/ios/ApexDbStore.swift +74 -0
  46. package/templates/mobile/ios/ApexEngine.swift +253 -0
  47. package/templates/mobile/ios/ApexSchemeHandler.swift +349 -0
  48. package/templates/mobile/ios/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
  49. package/templates/mobile/ios/Assets.xcassets/AppIcon.appiconset/icon-1024.png +0 -0
  50. package/templates/mobile/ios/Assets.xcassets/LaunchBackground.colorset/Contents.json +20 -0
  51. package/templates/mobile/ios/Info.plist +60 -0
  52. package/templates/mobile/ios/README.md +153 -0
  53. package/templates/mobile/ios/Tests/ApexEngineTests.swift +74 -0
  54. package/templates/mobile/ios/project.yml +75 -0
@@ -0,0 +1,60 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ Info.plist for the Apex iOS shell.
4
+
5
+ Mirrors AndroidManifest.xml: a launcher app with a native launch screen and NO network
6
+ entitlement required (the backend runs on-device, offline). The animated pages/splash.alpine
7
+ route takes over after this static launch screen — the two-stage splash handoff.
8
+
9
+ Most $(…) values are Xcode build-setting placeholders; set them in the target's General tab.
10
+ -->
11
+ <plist version="1.0">
12
+ <dict>
13
+ <key>CFBundleDevelopmentRegion</key>
14
+ <string>en</string>
15
+ <key>CFBundleDisplayName</key>
16
+ <string>Apex</string>
17
+ <key>CFBundleExecutable</key>
18
+ <string>$(EXECUTABLE_NAME)</string>
19
+ <key>CFBundleIdentifier</key>
20
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
21
+ <key>CFBundleInfoDictionaryVersion</key>
22
+ <string>6.0</string>
23
+ <key>CFBundleName</key>
24
+ <string>$(PRODUCT_NAME)</string>
25
+ <key>CFBundlePackageType</key>
26
+ <string>APPL</string>
27
+ <key>CFBundleShortVersionString</key>
28
+ <string>1.0</string>
29
+ <key>CFBundleVersion</key>
30
+ <string>1</string>
31
+ <key>LSRequiresIPhoneOS</key>
32
+ <true/>
33
+
34
+ <!-- Native cold-start launch screen: a solid brand-colour screen shown before any JS runs.
35
+ Uses the LaunchBackground colour asset (#0b1120) in Assets.xcassets. The animated
36
+ pages/splash.alpine renders immediately after and dismisses to '/'. -->
37
+ <key>UILaunchScreen</key>
38
+ <dict>
39
+ <key>UIColorName</key>
40
+ <string>LaunchBackground</string>
41
+ </dict>
42
+
43
+ <key>UISupportedInterfaceOrientations</key>
44
+ <array>
45
+ <string>UIInterfaceOrientationPortrait</string>
46
+ <string>UIInterfaceOrientationLandscapeLeft</string>
47
+ <string>UIInterfaceOrientationLandscapeRight</string>
48
+ </array>
49
+ <key>UISupportedInterfaceOrientations~ipad</key>
50
+ <array>
51
+ <string>UIInterfaceOrientationPortrait</string>
52
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
53
+ <string>UIInterfaceOrientationLandscapeLeft</string>
54
+ <string>UIInterfaceOrientationLandscapeRight</string>
55
+ </array>
56
+
57
+ <!-- Status-bar handled by SwiftUI/WebView; no scene manifest needed for the SwiftUI App
58
+ lifecycle (@main struct App). -->
59
+ </dict>
60
+ </plist>
@@ -0,0 +1,153 @@
1
+ # Apex iOS shell
2
+
3
+ > ⚠️ **UNVERIFIED — needs a Mac + Xcode to compile and run.** These files were written on Linux,
4
+ > mirroring the already-shipped, device-verified **Android** shell (`../android/`). No part of the
5
+ > iOS build has been compiled or run — treat it as a well-formed starting point, not a proven app.
6
+ > See **"For the Mac tester"** at the bottom for exactly what to check first.
7
+
8
+ A native iOS app that runs your Apex backend **on the device** (offline, no server, no port) and
9
+ renders it in a `WKWebView`. It is the 1:1 counterpart of the Android shell, using the two iOS
10
+ platform capabilities that make it *simpler* than Android:
11
+
12
+ | Concern | Android | iOS (here) |
13
+ |---|---|---|
14
+ | JS engine | `androidx.javascriptengine` (out-of-process, async boot, **no host callbacks**) | **JavaScriptCore `JSContext`** (in-process, sync boot, **host callbacks + exception handler**) |
15
+ | Request interception | `WebViewClient.shouldInterceptRequest` (**cannot read POST body**) | **`WKURLSchemeHandler`** for `apex://` (**reads `httpBody` directly**) |
16
+ | Body-bearing `fetch()`/POST | needs a **2nd path**: `@JavascriptInterface` bridge + document-start `fetch` patch | **not needed** — one scheme handler covers navigations *and* POST |
17
+ | Cookies (HttpOnly session) | `CookieManager` | small `ApexCookieJar` inside the scheme handler (custom schemes bypass `WKHTTPCookieStore`) |
18
+ | DB persistence | `apex-db.b64` in `filesDir`, atomic rename | `apex-db.b64` in Application Support, `FileManager.replaceItemAt` |
19
+
20
+ ## What's here
21
+
22
+ ```
23
+ ios/
24
+ ├─ ApexApp.swift # @main SwiftUI App: boots engine, builds WKWebView, loads apex://localhost/splash
25
+ ├─ ApexEngine.swift # JSContext wrapper — loads server.mjs + apex-bridge.js, runs __apexHandle
26
+ ├─ ApexSchemeHandler.swift # WKURLSchemeHandler for apex:// → engine (assets from bundle) + cookie jar
27
+ ├─ ApexDbStore.swift # DB snapshot persistence (Application Support, atomic)
28
+ ├─ Info.plist # launch screen + app config (no network entitlement needed — offline)
29
+ └─ Assets.xcassets/
30
+ ├─ AppIcon.appiconset/ # app icon (already present)
31
+ └─ LaunchBackground.colorset/ # #0b1120 launch-screen colour
32
+ ```
33
+
34
+ ## Architecture (identical contract to Android)
35
+
36
+ ```
37
+ WKWebView ── every request ──► ApexSchemeHandler (apex:// scheme)
38
+ │ /assets/*, /favicon.svg → bundle files
39
+ │ everything else → engine, WITH body + injected Cookie
40
+
41
+ ApexEngine.handle(requestJSON)
42
+ │ globalThis.__apexHandle(json) (apex-bridge.js)
43
+ │ └─ APEX.run(request) → { status, headers, body }
44
+
45
+ {status,headers,body} ── served back ──► WKWebView hydrates
46
+ ```
47
+
48
+ Because interception is a **function call**, there's no listener and no port — nothing for iOS to
49
+ suspend when the app backgrounds. That's the whole point.
50
+
51
+ ## Create the Xcode project & drop these files in
52
+
53
+ There is no `.xcodeproj` in this folder (it's binary/machine-specific). Create one on the Mac:
54
+
55
+ 1. **Xcode → New → App.** Interface: **SwiftUI**, Language: **Swift**. Product name e.g. `ApexShell`.
56
+ Set the **Bundle Identifier** and a signing team in *Signing & Capabilities*.
57
+ 2. **Delete** the auto-generated `ContentView.swift` and the generated `…App.swift` (this repo's
58
+ `ApexApp.swift` provides the `@main` App).
59
+ 3. **Add these files** to the target (drag into the project navigator, *Copy items if needed* off if
60
+ you reference them in place): `ApexApp.swift`, `ApexEngine.swift`, `ApexSchemeHandler.swift`,
61
+ `ApexDbStore.swift`.
62
+ 4. **Info.plist / Assets:** either use this folder's `Info.plist` (set *Build Settings → Info.plist
63
+ File* to point at it) or copy its `UILaunchScreen` + keys into the generated one. Add the
64
+ `LaunchBackground` colour set (and your `AppIcon`) to the target's asset catalog.
65
+ 5. **Framework:** `import JavaScriptCore` — it's a **system framework**, no dependency to add
66
+ (link it in *Frameworks, Libraries, and Embedded Content* if the linker complains).
67
+
68
+ ## Copy the built bundle + client assets into the app
69
+
70
+ From your **Apex app root**, build the mobile bundle:
71
+
72
+ ```bash
73
+ apex build --mobile # → dist/mobile/server.mjs
74
+ apex build # (or your normal build) → dist/assets/*, dist/favicon.svg
75
+ ```
76
+
77
+ Then add these to the Xcode target so they land in the app bundle **Resources**:
78
+
79
+ | Source | Add to bundle as | Read by |
80
+ |---|---|---|
81
+ | `dist/mobile/server.mjs` | `server.mjs` (bundle root) | `ApexEngine` → `Bundle.main.url(forResource:"server", withExtension:"mjs")` |
82
+ | `native-shell/apex-bridge.js` | `apex-bridge.js` (bundle root) | `ApexEngine` → `…forResource:"apex-bridge", withExtension:"js"` |
83
+ | `dist/assets/*` | an **`assets/` folder reference** (blue folder) | `ApexSchemeHandler` → `…forResource:base, withExtension:ext, subdirectory:"assets"` |
84
+ | `dist/favicon.svg` | `favicon.svg` (bundle root) | `ApexSchemeHandler` |
85
+
86
+ > **Important — use a *folder reference* (blue), not a *group* (yellow), for `assets/`.** A folder
87
+ > reference preserves the `assets/` subdirectory so `subdirectory: "assets"` lookups resolve. A
88
+ > group flattens filenames into the bundle root and the `/assets/...` path lookups will 404.
89
+
90
+ Also copy `native-shell/splash.alpine` into your app's `pages/splash.alpine` (the animated intro the
91
+ shell loads first) and rebuild — see `../NATIVE_SHELL.md`.
92
+
93
+ ## Run
94
+
95
+ Select an iOS Simulator (or a provisioned device) and **⌘R**. You should see: the native launch
96
+ screen (#0b1120), then the animated `splash.alpine`, then your app — all served by the Apex server
97
+ running inside the app on JavaScriptCore. Turn the network off: it still works.
98
+
99
+ ## On-device drivers
100
+
101
+ `apex build --mobile` reports routes needing `crypto.subtle` (sessions) or `@libsql/client/web`
102
+ (DB) — same as Android. JavaScriptCore provides a real `crypto`? **Not by default** — JSCore has no
103
+ WebCrypto. The bundle ships a stub so non-session routes boot; for real sessions, expose a native
104
+ WebCrypto shim as a host object on the `JSContext` (the JSContext host-callback capability makes
105
+ this straightforward — add it in `ApexEngine` before evaluating `server.mjs`). For DB, alias
106
+ `@libsql/client` → `@libsql/client/web` (sql.js asm.js) before bundling, as on Android.
107
+
108
+ ## For the Mac tester (check these first — likeliest failure points)
109
+
110
+ 1. **Bundle resources present.** If you see *"Apex engine failed to start"*, `server.mjs` /
111
+ `apex-bridge.js` aren't in the bundle. Confirm they're in *Build Phases → Copy Bundle Resources*.
112
+ 2. **`/assets/*` 404 → white page after splash.** Almost certainly the `assets/` folder was added as
113
+ a *group* (yellow) not a *folder reference* (blue). Re-add as a blue folder. Verify by loading
114
+ `apex://localhost/assets/<one-file>.js` and watching the Xcode console.
115
+ 3. **⭐ POST body reaches the engine.** This is the one genuinely unproven iOS assumption. The task's
116
+ premise is that `WKURLSchemeHandler` exposes `httpBody` for custom schemes — but some iOS
117
+ versions have historically **dropped** the body from custom-scheme requests. **Test a POST**
118
+ (log in, or add a guestbook entry) and confirm the engine receives a non-empty `body`. If it's
119
+ empty: fall back to the Android approach — add a `WKScriptMessageHandler` (`configuration.
120
+ userContentController.add(...)`) plus a document-start `fetch` patch (port `FETCH_PATCH` from
121
+ `../android/.../MainActivity.kt`) that forwards `fetch()` bodies to the handler. The engine and
122
+ cookie/DB plumbing here are unchanged either way.
123
+ 4. **Promise resolution.** `ApexEngine.handle` resolves the JS promise via `.then(onFulfilled,
124
+ onRejected)` and assumes JavaScriptCore drains its microtask queue within the same JS turn (true
125
+ for our fully-synchronous in-memory pipeline). If a request ever hangs, that assumption broke —
126
+ pump with `JSVirtualMachine`/a run-loop tick, or wrap the handler to resolve on a `Promise`
127
+ settled callback.
128
+ 5. **HttpOnly session cookie** survives a full page navigation and a cold start (login, force-quit,
129
+ relaunch → still logged in). The jar persists to `UserDefaults`.
130
+ 6. **DB snapshot** survives a cold start (create data, force-quit, relaunch → data still there). File
131
+ is `apex-db.b64` in Application Support.
132
+
133
+ ## Testing without a Mac — CI on the iOS Simulator
134
+
135
+ No Mac? The `ios-shell` GitHub Actions workflow (`.github/workflows/ios.yml`) builds this shell and
136
+ runs `Tests/ApexEngineTests.swift` on the iOS Simulator on a **free GitHub-hosted macOS runner**.
137
+ Trigger it from the repo's **Actions → ios-shell → Run workflow** (or it runs on pushes touching
138
+ `ios/`). It:
139
+
140
+ 1. builds the packages + the showcase `--mobile` bundle,
141
+ 2. stages `server.mjs` / `apex-bridge.js` / `assets/` / `favicon.svg` into `ios/Generated/`,
142
+ 3. `xcodegen generate` (from `project.yml` — no committed `.xcodeproj`),
143
+ 4. `xcodebuild test` on an iPhone simulator.
144
+
145
+ `ApexEngineTests` proves the bundle — the shim, SSR + API pipeline, **asm.js SQLite**, sealed-cookie
146
+ auth, and the snapshot persistence seam — actually runs under **JavaScriptCore** (iOS's engine),
147
+ which is the main iOS unknown. It does NOT exercise `WKURLSchemeHandler` (the custom-scheme POST-body
148
+ question in the checklist above) — that still needs a device/UI test on a real Mac + iPhone.
149
+
150
+ ### To run on your actual iPhone
151
+ Open the generated project on a Mac (`brew install xcodegen && cd ios && xcodegen generate && open
152
+ ApexShell.xcodeproj`), copy the four `Generated/` items into the app's Resources, set a free Apple ID
153
+ signing team, pick your iPhone, and Run. A free personal team gives a 7-day provisioning profile.
@@ -0,0 +1,74 @@
1
+ // ApexEngineTests — runs on the iOS Simulator in CI (GitHub Actions macOS runner), no Mac needed
2
+ // locally. This is the highest-value iOS check: it proves the `apex build --mobile` bundle — the
3
+ // SHIM, the whole SSR + API pipeline, and the **asm.js SQLite** DB — actually runs under
4
+ // **JavaScriptCore** (iOS's engine, different from Android's V8). It drives ApexEngine directly, so
5
+ // it does NOT depend on WKURLSchemeHandler (the one remaining device-only unknown — POST bodies on
6
+ // a custom scheme — still needs a real device/UI test).
7
+ import JavaScriptCore
8
+ import XCTest
9
+
10
+ @testable import ApexShell
11
+
12
+ final class ApexEngineTests: XCTestCase {
13
+ /// server.mjs + apex-bridge.js are copied into the TEST bundle by CI (see project.yml resources).
14
+ private func makeEngine(snapshot: String? = nil) throws -> ApexEngine {
15
+ try ApexEngine(snapshot: snapshot, bundle: Bundle(for: Self.self))
16
+ }
17
+
18
+ private func request(_ url: String, method: String = "GET", body: String? = nil) -> String {
19
+ let obj: [String: Any] = [
20
+ "url": url,
21
+ "method": method,
22
+ "headers": ["content-type": "application/json"],
23
+ "body": body as Any? ?? NSNull(),
24
+ ]
25
+ let data = try! JSONSerialization.data(withJSONObject: obj)
26
+ return String(data: data, encoding: .utf8)!
27
+ }
28
+
29
+ /// SSR of a DB-backed page renders the seeded rows — proves JSContext runs the bundle + asm.js DB.
30
+ func testGuestbookRendersSeededRowsOnJavaScriptCore() async throws {
31
+ let engine = try makeEngine()
32
+ let res = await engine.handle(request("apex://localhost/guestbook"))
33
+ XCTAssertTrue(res.contains("Ada Lovelace"), "seeded row missing — bundle/DB didn't run on JSCore. Got: \(res.prefix(400))")
34
+ XCTAssertTrue(res.contains("Alan Turing"))
35
+ }
36
+
37
+ /// POST inserts through the Drizzle deferred proxy on asm.js SQLite, and it persists across a
38
+ /// simulated cold start via the snapshot seam.
39
+ func testPostInsertsAndSurvivesSnapshotRestore() async throws {
40
+ let engine = try makeEngine()
41
+ _ = await engine.handle(request("apex://localhost/guestbook")) // lazy-init + seed the DB
42
+
43
+ let post = await engine.handle(
44
+ request(
45
+ "apex://localhost/api/messages",
46
+ method: "POST",
47
+ body: #"{"author":"iOS CI","body":"runs on JavaScriptCore"}"#
48
+ )
49
+ )
50
+ XCTAssertTrue(post.contains("iOS CI"), "POST /api/messages did not echo the created row: \(post.prefix(400))")
51
+
52
+ let after = await engine.handle(request("apex://localhost/guestbook"))
53
+ XCTAssertTrue(after.contains("iOS CI"), "new message not visible after POST")
54
+
55
+ // Cold-start persistence: export bytes, boot a fresh engine with them, data survives.
56
+ let snap = engine.snapshot()
57
+ XCTAssertFalse(snap.isEmpty, "snapshot export returned empty")
58
+ let restored = try makeEngine(snapshot: snap)
59
+ let restoredBody = await restored.handle(request("apex://localhost/guestbook"))
60
+ XCTAssertTrue(restoredBody.contains("iOS CI"), "message did not survive snapshot restore")
61
+ }
62
+
63
+ /// Sealed-cookie auth works on JSCore (HMAC, no WebCrypto): login sets a cookie, whoami is gated.
64
+ func testAuthLoginOnJavaScriptCore() async throws {
65
+ let engine = try makeEngine()
66
+ let anon = await engine.handle(request("apex://localhost/api/whoami"))
67
+ XCTAssertTrue(anon.contains("401"), "gated whoami should be 401 when anonymous: \(anon.prefix(200))")
68
+
69
+ let login = await engine.handle(
70
+ request("apex://localhost/api/login", method: "POST", body: #"{"name":"Ada"}"#)
71
+ )
72
+ XCTAssertTrue(login.contains("apex-session"), "login should set a session cookie: \(login.prefix(300))")
73
+ }
74
+ }
@@ -0,0 +1,75 @@
1
+ # XcodeGen spec — generates ApexShell.xcodeproj from source, so the iOS project can be built on a
2
+ # CI macOS runner (or any Mac) without hand-authoring an .xcodeproj in Xcode.
3
+ # brew install xcodegen && (cd ios && xcodegen generate)
4
+ # The app + test bundle resources (server.mjs, apex-bridge.js, assets/, favicon.svg) are produced by
5
+ # `apex build --mobile` and copied into ios/Generated/ before generating (see .github/workflows/ios.yml).
6
+ name: ApexShell
7
+ options:
8
+ bundleIdPrefix: site.apexjs
9
+ deploymentTarget:
10
+ iOS: "15.0"
11
+ createIntermediateGroups: true
12
+
13
+ settings:
14
+ base:
15
+ SWIFT_VERSION: "5.0"
16
+ CODE_SIGNING_ALLOWED: "NO" # simulator builds/tests need no signing
17
+
18
+ targets:
19
+ ApexShell:
20
+ type: application
21
+ platform: iOS
22
+ sources:
23
+ - path: ApexApp.swift
24
+ - path: ApexEngine.swift
25
+ - path: ApexSchemeHandler.swift
26
+ - path: ApexDbStore.swift
27
+ - path: Assets.xcassets
28
+ - path: Generated/server.mjs
29
+ type: file
30
+ buildPhase: resources
31
+ - path: Generated/apex-bridge.js
32
+ type: file
33
+ buildPhase: resources
34
+ - path: Generated/assets
35
+ type: folder # blue folder reference → served verbatim at /assets/*
36
+ buildPhase: resources
37
+ - path: Generated/favicon.svg
38
+ type: file
39
+ buildPhase: resources
40
+ settings:
41
+ base:
42
+ PRODUCT_BUNDLE_IDENTIFIER: site.apexjs.shell
43
+ INFOPLIST_FILE: Info.plist
44
+ GENERATE_INFOPLIST_FILE: "NO"
45
+ TARGETED_DEVICE_FAMILY: "1,2"
46
+
47
+ ApexShellTests:
48
+ type: bundle.unit-test
49
+ platform: iOS
50
+ sources:
51
+ - path: Tests
52
+ # The engine test loads the bundle from its OWN (test) bundle, so copy the JS here too.
53
+ - path: Generated/server.mjs
54
+ type: file
55
+ buildPhase: resources
56
+ - path: Generated/apex-bridge.js
57
+ type: file
58
+ buildPhase: resources
59
+ dependencies:
60
+ - target: ApexShell
61
+ settings:
62
+ base:
63
+ TEST_HOST: "$(BUILT_PRODUCTS_DIR)/ApexShell.app/ApexShell"
64
+ BUNDLE_LOADER: "$(TEST_HOST)"
65
+ PRODUCT_BUNDLE_IDENTIFIER: site.apexjs.shell.tests
66
+
67
+ schemes:
68
+ ApexShell:
69
+ build:
70
+ targets:
71
+ ApexShell: all
72
+ ApexShellTests: [test]
73
+ test:
74
+ targets:
75
+ - ApexShellTests