@bitmagic/cli 0.1.83-dev.1 → 0.1.83-dev.3
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/README.md +69 -0
- package/assets/skills/publishing-to-steam/SKILL.md +218 -0
- package/dist/assets/vxl-summary.d.ts +6 -20
- package/dist/assets/vxl-summary.js +9 -100
- package/dist/assets/vxl-summary.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/check.js +5 -0
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/forge.js +1 -1
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/ios.js +88 -136
- package/dist/commands/ios.js.map +1 -1
- package/dist/commands/steam-build.d.ts +67 -0
- package/dist/commands/steam-build.js +286 -0
- package/dist/commands/steam-build.js.map +1 -0
- package/dist/commands/steam-portal.d.ts +1 -0
- package/dist/commands/steam-portal.js +319 -0
- package/dist/commands/steam-portal.js.map +1 -0
- package/dist/commands/steam-upload.d.ts +37 -0
- package/dist/commands/steam-upload.js +174 -0
- package/dist/commands/steam-upload.js.map +1 -0
- package/dist/commands/steam.d.ts +1 -0
- package/dist/commands/steam.js +504 -0
- package/dist/commands/steam.js.map +1 -0
- package/dist/commands/verify.js +6 -0
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/asset-viewer.js +5 -4
- package/dist/editor/asset-viewer.js.map +1 -1
- package/dist/editor/server.js +1 -1
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +3 -3
- package/dist/forge/browser-host.d.ts +1 -1
- package/dist/forge/browser-host.js +1 -1
- package/dist/forge/browser-host.js.map +1 -1
- package/dist/ios/native-overrides.d.ts +27 -0
- package/dist/ios/native-overrides.js +87 -0
- package/dist/ios/native-overrides.js.map +1 -0
- package/dist/ios/record-validate.d.ts +1 -33
- package/dist/ios/record-validate.js +2 -100
- package/dist/ios/record-validate.js.map +1 -1
- package/dist/ios/store-bridge.d.ts +26 -0
- package/dist/ios/store-bridge.js +224 -0
- package/dist/ios/store-bridge.js.map +1 -0
- package/dist/ios/storekit-config.d.ts +15 -0
- package/dist/ios/storekit-config.js +106 -0
- package/dist/ios/storekit-config.js.map +1 -0
- package/dist/ios/xcode-template.d.ts +12 -3
- package/dist/ios/xcode-template.js +158 -27
- package/dist/ios/xcode-template.js.map +1 -1
- package/dist/package/collect-urls.d.ts +1 -1
- package/dist/package/collect-urls.js +10 -2
- package/dist/package/collect-urls.js.map +1 -1
- package/dist/package/offline-payload.d.ts +35 -0
- package/dist/package/offline-payload.js +142 -0
- package/dist/package/offline-payload.js.map +1 -0
- package/dist/package/packaged-check.d.ts +39 -0
- package/dist/package/packaged-check.js +104 -0
- package/dist/package/packaged-check.js.map +1 -0
- package/dist/package/transform.d.ts +4 -4
- package/dist/package/transform.js +3 -3
- package/dist/project/published-three.d.ts +14 -0
- package/dist/project/published-three.js +91 -0
- package/dist/project/published-three.js.map +1 -0
- package/dist/publish/bundle.js +6 -0
- package/dist/publish/bundle.js.map +1 -1
- package/dist/scaffold/project-files.js +7 -4
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.js +2 -0
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/skill-files.js +3 -1
- package/dist/scaffold/skill-files.js.map +1 -1
- package/dist/scaffold/steam-skill-files.d.ts +2 -0
- package/dist/scaffold/steam-skill-files.js +7 -0
- package/dist/scaffold/steam-skill-files.js.map +1 -0
- package/dist/steam/art-generate.d.ts +55 -0
- package/dist/steam/art-generate.js +174 -0
- package/dist/steam/art-generate.js.map +1 -0
- package/dist/steam/art-render.d.ts +25 -0
- package/dist/steam/art-render.js +157 -0
- package/dist/steam/art-render.js.map +1 -0
- package/dist/steam/art-use.d.ts +20 -0
- package/dist/steam/art-use.js +84 -0
- package/dist/steam/art-use.js.map +1 -0
- package/dist/steam/art.d.ts +98 -0
- package/dist/steam/art.js +243 -0
- package/dist/steam/art.js.map +1 -0
- package/dist/steam/check.d.ts +21 -0
- package/dist/steam/check.js +113 -0
- package/dist/steam/check.js.map +1 -0
- package/dist/steam/config.d.ts +57 -0
- package/dist/steam/config.js +145 -0
- package/dist/steam/config.js.map +1 -0
- package/dist/steam/electron-cache.d.ts +11 -0
- package/dist/steam/electron-cache.js +105 -0
- package/dist/steam/electron-cache.js.map +1 -0
- package/dist/steam/fuses.d.ts +16 -0
- package/dist/steam/fuses.js +75 -0
- package/dist/steam/fuses.js.map +1 -0
- package/dist/steam/icon-files.d.ts +16 -0
- package/dist/steam/icon-files.js +70 -0
- package/dist/steam/icon-files.js.map +1 -0
- package/dist/steam/image-info.d.ts +16 -0
- package/dist/steam/image-info.js +89 -0
- package/dist/steam/image-info.js.map +1 -0
- package/dist/steam/launch-check.d.ts +12 -0
- package/dist/steam/launch-check.js +101 -0
- package/dist/steam/launch-check.js.map +1 -0
- package/dist/steam/media.d.ts +48 -0
- package/dist/steam/media.js +155 -0
- package/dist/steam/media.js.map +1 -0
- package/dist/steam/package-macos.d.ts +52 -0
- package/dist/steam/package-macos.js +268 -0
- package/dist/steam/package-macos.js.map +1 -0
- package/dist/steam/package-windows.d.ts +23 -0
- package/dist/steam/package-windows.js +83 -0
- package/dist/steam/package-windows.js.map +1 -0
- package/dist/steam/paths.d.ts +35 -0
- package/dist/steam/paths.js +44 -0
- package/dist/steam/paths.js.map +1 -0
- package/dist/steam/playtest-shots.d.ts +18 -0
- package/dist/steam/playtest-shots.js +83 -0
- package/dist/steam/playtest-shots.js.map +1 -0
- package/dist/steam/portal-recipes.d.ts +25 -0
- package/dist/steam/portal-recipes.js +61 -0
- package/dist/steam/portal-recipes.js.map +1 -0
- package/dist/steam/portal.d.ts +58 -0
- package/dist/steam/portal.js +340 -0
- package/dist/steam/portal.js.map +1 -0
- package/dist/steam/raster.d.ts +58 -0
- package/dist/steam/raster.js +285 -0
- package/dist/steam/raster.js.map +1 -0
- package/dist/steam/shell-files.d.ts +17 -0
- package/dist/steam/shell-files.js +268 -0
- package/dist/steam/shell-files.js.map +1 -0
- package/dist/steam/specs.d.ts +61 -0
- package/dist/steam/specs.js +103 -0
- package/dist/steam/specs.js.map +1 -0
- package/dist/steam/steampipe.d.ts +46 -0
- package/dist/steam/steampipe.js +170 -0
- package/dist/steam/steampipe.js.map +1 -0
- package/dist/steam/store-copy.d.ts +88 -0
- package/dist/steam/store-copy.js +214 -0
- package/dist/steam/store-copy.js.map +1 -0
- package/dist/steam/tags.d.ts +9 -0
- package/dist/steam/tags.js +52 -0
- package/dist/steam/tags.js.map +1 -0
- package/dist/steam/trailer.d.ts +34 -0
- package/dist/steam/trailer.js +141 -0
- package/dist/steam/trailer.js.map +1 -0
- package/dist/theme/kernel.d.ts +14 -0
- package/dist/theme/kernel.js +15 -0
- package/dist/theme/kernel.js.map +1 -0
- package/dist/theme/theme-io.js +11 -16
- package/dist/theme/theme-io.js.map +1 -1
- package/dist/vendor/hud-theme/catalog.js +71 -0
- package/dist/vendor/hud-theme/engine/hud/ThemeTokens.js +115 -0
- package/dist/{theme/theme-color-math.js → vendor/hud-theme/engine/hud/colorMath.js} +13 -14
- package/dist/vendor/hud-theme/engine/hud/decorations.js +179 -0
- package/dist/vendor/hud-theme/engine/hud/fonts.js +147 -0
- package/dist/vendor/hud-theme/engine/hud/presets.js +80 -0
- package/dist/vendor/hud-theme/engine/hud/resolveWorldTheme.js +66 -0
- package/dist/vendor/hud-theme/engine/hud/themes/bitmagic.js +29 -0
- package/dist/vendor/hud-theme/engine/hud/themes/nexus.js +69 -0
- package/dist/vendor/hud-theme/engine/hud/themes/racing.js +57 -0
- package/dist/vendor/hud-theme/engine/hud/themes/rift-raider.js +63 -0
- package/dist/vendor/hud-theme/engine/hud/themes/simulator.js +55 -0
- package/dist/vendor/hud-theme/engine/hud/themes/village-keep.js +108 -0
- package/dist/vendor/hud-theme/engine/hud/validateTheme.js +535 -0
- package/dist/vendor/hud-theme/index.js +26 -0
- package/dist/{theme/theme-merge.js → vendor/hud-theme/merge.js} +0 -1
- package/dist/{theme/theme-report.js → vendor/hud-theme/report.js} +8 -9
- package/dist/{theme/theme-stored.js → vendor/hud-theme/stored.js} +6 -9
- package/dist/{theme/theme-validate.js → vendor/hud-theme/validate-strict.js} +102 -164
- package/dist/verify/browser.d.ts +11 -0
- package/dist/verify/browser.js +222 -155
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/categories.d.ts +1 -1
- package/dist/verify/categories.js +1 -0
- package/dist/verify/categories.js.map +1 -1
- package/dist/verify/classify.d.ts +14 -2
- package/dist/verify/classify.js +23 -4
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/hud-theme.d.ts +19 -14
- package/dist/verify/hud-theme.js +23 -17
- package/dist/verify/hud-theme.js.map +1 -1
- package/dist/verify/mobile-parity.d.ts +1 -1
- package/dist/verify/mobile-parity.js +1 -1
- package/dist/verify/start.d.ts +3 -2
- package/dist/verify/start.js +1 -1
- package/dist/verify/start.js.map +1 -1
- package/package.json +8 -6
- package/dist/theme/hud-theme-catalog.d.ts +0 -235
- package/dist/theme/hud-theme-catalog.js +0 -389
- package/dist/theme/hud-theme-catalog.js.map +0 -1
- package/dist/theme/theme-color-math.d.ts +0 -21
- package/dist/theme/theme-color-math.js.map +0 -1
- package/dist/theme/theme-merge.d.ts +0 -36
- package/dist/theme/theme-merge.js.map +0 -1
- package/dist/theme/theme-report.d.ts +0 -28
- package/dist/theme/theme-report.js.map +0 -1
- package/dist/theme/theme-stored.d.ts +0 -21
- package/dist/theme/theme-stored.js.map +0 -1
- package/dist/theme/theme-validate.d.ts +0 -41
- package/dist/theme/theme-validate.js.map +0 -1
|
@@ -1,101 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
* headless system Chrome as an iPhone, `?platform=mobile` — in two modes, once per renderer a
|
|
4
|
-
* device can boot (`ios build` runs each mode on WebGPU and on WebGL).
|
|
5
|
-
*
|
|
6
|
-
* - `record`: remote requests are allowed through and every one on a non-denylisted host is
|
|
7
|
-
* reported as a miss for the caller to vendor. This is the empirical net under the static
|
|
8
|
-
* collector: whatever the running game actually fetches, however the URL was constructed.
|
|
9
|
-
* - `offline`: every remote request is aborted. The run must still pass `classifyVerifyRun`
|
|
10
|
-
* (the same "did the game render" verdict verify and the publish smoke check use) with zero
|
|
11
|
-
* remote asset requests — that is the design's acceptance criterion for "no internet needed".
|
|
12
|
-
*
|
|
13
|
-
* Denylisted endpoints are the online-only features the engine already degrades without
|
|
14
|
-
* (game-server, play-auth, play sessions, fonts, analytics) — aborted in BOTH passes so the
|
|
15
|
-
* record run observes the same world the device will.
|
|
16
|
-
*/
|
|
17
|
-
import * as fs from 'fs';
|
|
18
|
-
import * as path from 'path';
|
|
19
|
-
import { browserLaunchPlan } from '../browser-gl.js';
|
|
20
|
-
import { runHeadlessCheck } from '../verify/browser.js';
|
|
21
|
-
import { DEFAULT_START_INTERACTION } from '../verify/start.js';
|
|
22
|
-
import { classifyVerifyRun } from '../verify/classify.js';
|
|
23
|
-
import { mobileContextOptions } from '../verify/device.js';
|
|
24
|
-
import { startPayloadServer } from './local-server.js';
|
|
25
|
-
/** Long enough for a forged level's worth of local loads; adaptive settle exits earlier. */
|
|
26
|
-
export const PACKAGED_CHECK_SETTLE_MS = 25_000;
|
|
27
|
-
/**
|
|
28
|
-
* Online-only endpoints the packaged game may talk to when a network exists, and must live
|
|
29
|
-
* without when one does not. Never vendored, aborted during both passes.
|
|
30
|
-
*/
|
|
31
|
-
export function isOnlineOnlyUrl(url) {
|
|
32
|
-
let parsed;
|
|
33
|
-
try {
|
|
34
|
-
parsed = new URL(url);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
const host = parsed.hostname;
|
|
40
|
-
return (host.includes('game-server')
|
|
41
|
-
|| parsed.pathname.endsWith('/play-auth.html')
|
|
42
|
-
|| host === 'fonts.googleapis.com'
|
|
43
|
-
|| host === 'fonts.gstatic.com'
|
|
44
|
-
|| host === 'esm.sh'
|
|
45
|
-
|| host === 'esm.run'
|
|
46
|
-
|| host.endsWith('.sentry.io')
|
|
47
|
-
|| host.endsWith('google-analytics.com')
|
|
48
|
-
|| host.endsWith('googletagmanager.com')
|
|
49
|
-
// Local dev services (api-server 3002, game-server 1666, agent 4111, lid relay 8765) —
|
|
50
|
-
// NOT the payload server, whose exact origin is allowed before this predicate runs.
|
|
51
|
-
|| host === 'localhost'
|
|
52
|
-
|| host === '127.0.0.1');
|
|
53
|
-
}
|
|
54
|
-
export async function runPackagedCheck(options) {
|
|
55
|
-
fs.mkdirSync(options.artifactDir, { recursive: true });
|
|
56
|
-
const consolePath = path.join(options.artifactDir, 'console.log');
|
|
57
|
-
const screenshotPath = path.join(options.artifactDir, 'screenshot.png');
|
|
58
|
-
const server = await startPayloadServer(options.payloadDir);
|
|
59
|
-
const misses = new Set();
|
|
60
|
-
const routes = async (context) => {
|
|
61
|
-
await context.route('**/*', (route) => {
|
|
62
|
-
const url = route.request().url();
|
|
63
|
-
if (url.startsWith(server.origin)) {
|
|
64
|
-
void route.continue();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (isOnlineOnlyUrl(url)) {
|
|
68
|
-
void route.abort('internetdisconnected');
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
misses.add(url);
|
|
72
|
-
if (options.mode === 'record')
|
|
73
|
-
void route.continue();
|
|
74
|
-
else
|
|
75
|
-
void route.abort('internetdisconnected');
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
try {
|
|
79
|
-
const observations = await runHeadlessCheck({
|
|
80
|
-
url: `${server.origin}/index.html?platform=mobile&renderer=${options.renderer}`,
|
|
81
|
-
screenshotPath,
|
|
82
|
-
consolePath,
|
|
83
|
-
settleMs: options.settleMs ?? PACKAGED_CHECK_SETTLE_MS,
|
|
84
|
-
startInteraction: DEFAULT_START_INTERACTION,
|
|
85
|
-
launch: browserLaunchPlan(process.env, options.renderer),
|
|
86
|
-
requestedRenderer: options.renderer,
|
|
87
|
-
device: mobileContextOptions(options.orientation ?? 'portrait'),
|
|
88
|
-
routes,
|
|
89
|
-
});
|
|
90
|
-
return {
|
|
91
|
-
result: classifyVerifyRun(observations),
|
|
92
|
-
misses: [...misses],
|
|
93
|
-
consolePath,
|
|
94
|
-
screenshotPath,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
await server.close();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
1
|
+
// Compatibility entry point for the iOS packager.
|
|
2
|
+
export * from '../package/packaged-check.js';
|
|
101
3
|
//# sourceMappingURL=record-validate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-validate.js","sourceRoot":"","sources":["../../src/ios/record-validate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"record-validate.js","sourceRoot":"","sources":["../../src/ios/record-validate.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,cAAc,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's App Store bridge (§13 of docs/ios-export-design.md): StoreKit 2 behind one
|
|
3
|
+
* `WKScriptMessageHandlerWithReply`, so the page's `postMessage` returns a promise and no request
|
|
4
|
+
* ids are needed. The engine side is `game/src/engine/store/NativeStoreBackend.ts`; the message
|
|
5
|
+
* shapes below are the contract between them, so change both or neither.
|
|
6
|
+
*
|
|
7
|
+
* Ops (page → app, each answered once):
|
|
8
|
+
* status { canMakePayments }
|
|
9
|
+
* products { ids } → { products, missing }
|
|
10
|
+
* purchase { productId, quantity?, appAccountToken? }
|
|
11
|
+
* → { status: purchased | pending | cancelled | unverified | failed, transaction?, reason? }
|
|
12
|
+
* unfinished {} → { transactions }
|
|
13
|
+
* finish { transactionId } → { finished }
|
|
14
|
+
* Event (app → page): a `bmstore:transaction` window event whenever StoreKit delivers a purchase
|
|
15
|
+
* outside a purchase() call. It carries nothing; the page answers it by asking for `unfinished`,
|
|
16
|
+
* so a page that was not listening yet loses nothing.
|
|
17
|
+
*
|
|
18
|
+
* A verified purchase is never finished here. The engine records the credit first and then asks
|
|
19
|
+
* for `finish`; until then StoreKit keeps re-delivering the transaction, which is what makes an
|
|
20
|
+
* app killed between payment and crediting safe.
|
|
21
|
+
*
|
|
22
|
+
* Same literal rules as xcode-template.ts: no Swift `\(...)` interpolation, no backticks.
|
|
23
|
+
*/
|
|
24
|
+
export declare const STORE_HANDLER_NAME = "bmStore";
|
|
25
|
+
export declare const STORE_TRANSACTION_EVENT = "bmstore:transaction";
|
|
26
|
+
export declare const STORE_BRIDGE_SWIFT = "import Foundation\nimport StoreKit\nimport WebKit\n\n/// App Store purchases for the game page. See the ops table in the CLI's store-bridge.ts.\nfinal class StoreBridge: NSObject, WKScriptMessageHandlerWithReply {\n static let handlerName = \"bmStore\"\n static let transactionEvent = \"bmstore:transaction\"\n\n weak var webView: WKWebView?\n private var updatesTask: Task<Void, Never>?\n /// Verified, not yet finished transactions, by id \u2014 so finish never has to search.\n private var pending: [UInt64: Transaction] = [:]\n\n deinit {\n updatesTask?.cancel()\n }\n\n /// Listen for purchases that complete outside a purchase() call: Ask to Buy approvals,\n /// payment-method fixes, a purchase interrupted by the app being killed. Apple's guidance is\n /// to start this at launch, before any purchase, or such transactions can be missed.\n @MainActor\n func start() {\n updatesTask = Task { [weak self] in\n for await result in Transaction.updates {\n self?.received(result)\n }\n }\n }\n\n @MainActor\n private func received(_ result: VerificationResult<Transaction>) {\n guard case .verified(let transaction) = result else { return }\n pending[transaction.id] = transaction\n let script = \"window.dispatchEvent(new CustomEvent('\" + Self.transactionEvent + \"'))\"\n webView?.evaluateJavaScript(script, completionHandler: nil)\n }\n\n func userContentController(\n _ userContentController: WKUserContentController,\n didReceive message: WKScriptMessage,\n replyHandler: @escaping (Any?, String?) -> Void\n ) {\n guard let body = message.body as? [String: Any], let op = body[\"op\"] as? String else {\n replyHandler(nil, \"A store request needs an op.\")\n return\n }\n Task { @MainActor in\n do {\n let reply = try await self.handle(op: op, body: body)\n replyHandler(reply, nil)\n } catch {\n replyHandler(nil, String(describing: error))\n }\n }\n }\n\n @MainActor\n private func handle(op: String, body: [String: Any]) async throws -> Any {\n switch op {\n case \"status\":\n return [\"canMakePayments\": AppStore.canMakePayments]\n case \"products\":\n let ids = (body[\"ids\"] as? [String]) ?? []\n let products = try await Product.products(for: ids)\n let found = Set(products.map { $0.id })\n return [\n \"products\": products.map { Self.productInfo($0) },\n \"missing\": ids.filter { !found.contains($0) },\n ]\n case \"purchase\":\n return try await purchase(body)\n case \"unfinished\":\n var transactions: [[String: Any]] = []\n for await result in Transaction.unfinished {\n guard case .verified(let transaction) = result else { continue }\n pending[transaction.id] = transaction\n transactions.append(Self.transactionInfo(transaction, jws: result.jwsRepresentation))\n }\n return [\"transactions\": transactions]\n case \"finish\":\n guard let text = body[\"transactionId\"] as? String, let id = UInt64(text) else {\n throw StoreBridgeError.badRequest(\"finish needs a transactionId string\")\n }\n return [\"finished\": await finish(id)]\n default:\n throw StoreBridgeError.badRequest(\"Unknown store op \" + op)\n }\n }\n\n @MainActor\n private func purchase(_ body: [String: Any]) async throws -> Any {\n guard let productId = body[\"productId\"] as? String else {\n throw StoreBridgeError.badRequest(\"purchase needs a productId\")\n }\n guard let product = try await Product.products(for: [productId]).first else {\n return [\n \"status\": \"failed\",\n \"reason\": \"The App Store did not return the product \" + productId + \".\",\n ]\n }\n var options: Set<Product.PurchaseOption> = []\n if let quantity = body[\"quantity\"] as? Int, quantity > 1 {\n options.insert(.quantity(quantity))\n }\n if let token = body[\"appAccountToken\"] as? String, let uuid = UUID(uuidString: token) {\n options.insert(.appAccountToken(uuid))\n }\n let result = try await product.purchase(options: options)\n switch result {\n case .success(let verification):\n switch verification {\n case .verified(let transaction):\n pending[transaction.id] = transaction\n return [\n \"status\": \"purchased\",\n \"transaction\": Self.transactionInfo(transaction, jws: verification.jwsRepresentation),\n ]\n case .unverified(_, let error):\n return [\"status\": \"unverified\", \"reason\": String(describing: error)]\n }\n case .userCancelled:\n return [\"status\": \"cancelled\"]\n case .pending:\n return [\"status\": \"pending\"]\n @unknown default:\n return [\"status\": \"failed\", \"reason\": \"StoreKit returned an unknown purchase result.\"]\n }\n }\n\n @MainActor\n private func finish(_ id: UInt64) async -> Bool {\n if let transaction = pending.removeValue(forKey: id) {\n await transaction.finish()\n return true\n }\n for await result in Transaction.unfinished {\n if case .verified(let transaction) = result, transaction.id == id {\n await transaction.finish()\n return true\n }\n }\n return false\n }\n\n private static func typeName(_ type: Product.ProductType) -> String {\n if type == .consumable { return \"consumable\" }\n if type == .nonConsumable { return \"nonConsumable\" }\n if type == .autoRenewable { return \"autoRenewable\" }\n if type == .nonRenewable { return \"nonRenewable\" }\n return \"other\"\n }\n\n private static func productInfo(_ product: Product) -> [String: Any] {\n return [\n \"id\": product.id,\n \"type\": typeName(product.type),\n \"displayName\": product.displayName,\n \"description\": product.description,\n \"displayPrice\": product.displayPrice,\n \"price\": NSDecimalNumber(decimal: product.price).doubleValue,\n \"currencyCode\": product.priceFormatStyle.currencyCode,\n ]\n }\n\n /// Ids go to the page as strings: a transaction id is a UInt64 and need not fit a double.\n private static func transactionInfo(_ transaction: Transaction, jws: String) -> [String: Any] {\n var info: [String: Any] = [\n \"transactionId\": String(transaction.id),\n \"originalTransactionId\": String(transaction.originalID),\n \"productId\": transaction.productID,\n \"productType\": typeName(transaction.productType),\n \"quantity\": transaction.purchasedQuantity,\n \"purchaseDate\": transaction.purchaseDate.timeIntervalSince1970 * 1000,\n \"revoked\": transaction.revocationDate != nil,\n \"jws\": jws,\n ]\n if #available(iOS 16.0, *) {\n info[\"environment\"] = transaction.environment.rawValue\n }\n if let token = transaction.appAccountToken {\n info[\"appAccountToken\"] = token.uuidString\n }\n return info\n }\n}\n\nenum StoreBridgeError: Error, CustomStringConvertible {\n case badRequest(String)\n\n var description: String {\n switch self {\n case .badRequest(let message):\n return message\n }\n }\n}\n";
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's App Store bridge (§13 of docs/ios-export-design.md): StoreKit 2 behind one
|
|
3
|
+
* `WKScriptMessageHandlerWithReply`, so the page's `postMessage` returns a promise and no request
|
|
4
|
+
* ids are needed. The engine side is `game/src/engine/store/NativeStoreBackend.ts`; the message
|
|
5
|
+
* shapes below are the contract between them, so change both or neither.
|
|
6
|
+
*
|
|
7
|
+
* Ops (page → app, each answered once):
|
|
8
|
+
* status { canMakePayments }
|
|
9
|
+
* products { ids } → { products, missing }
|
|
10
|
+
* purchase { productId, quantity?, appAccountToken? }
|
|
11
|
+
* → { status: purchased | pending | cancelled | unverified | failed, transaction?, reason? }
|
|
12
|
+
* unfinished {} → { transactions }
|
|
13
|
+
* finish { transactionId } → { finished }
|
|
14
|
+
* Event (app → page): a `bmstore:transaction` window event whenever StoreKit delivers a purchase
|
|
15
|
+
* outside a purchase() call. It carries nothing; the page answers it by asking for `unfinished`,
|
|
16
|
+
* so a page that was not listening yet loses nothing.
|
|
17
|
+
*
|
|
18
|
+
* A verified purchase is never finished here. The engine records the credit first and then asks
|
|
19
|
+
* for `finish`; until then StoreKit keeps re-delivering the transaction, which is what makes an
|
|
20
|
+
* app killed between payment and crediting safe.
|
|
21
|
+
*
|
|
22
|
+
* Same literal rules as xcode-template.ts: no Swift `\(...)` interpolation, no backticks.
|
|
23
|
+
*/
|
|
24
|
+
export const STORE_HANDLER_NAME = 'bmStore';
|
|
25
|
+
export const STORE_TRANSACTION_EVENT = 'bmstore:transaction';
|
|
26
|
+
export const STORE_BRIDGE_SWIFT = `import Foundation
|
|
27
|
+
import StoreKit
|
|
28
|
+
import WebKit
|
|
29
|
+
|
|
30
|
+
/// App Store purchases for the game page. See the ops table in the CLI's store-bridge.ts.
|
|
31
|
+
final class StoreBridge: NSObject, WKScriptMessageHandlerWithReply {
|
|
32
|
+
static let handlerName = "${STORE_HANDLER_NAME}"
|
|
33
|
+
static let transactionEvent = "${STORE_TRANSACTION_EVENT}"
|
|
34
|
+
|
|
35
|
+
weak var webView: WKWebView?
|
|
36
|
+
private var updatesTask: Task<Void, Never>?
|
|
37
|
+
/// Verified, not yet finished transactions, by id — so finish never has to search.
|
|
38
|
+
private var pending: [UInt64: Transaction] = [:]
|
|
39
|
+
|
|
40
|
+
deinit {
|
|
41
|
+
updatesTask?.cancel()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Listen for purchases that complete outside a purchase() call: Ask to Buy approvals,
|
|
45
|
+
/// payment-method fixes, a purchase interrupted by the app being killed. Apple's guidance is
|
|
46
|
+
/// to start this at launch, before any purchase, or such transactions can be missed.
|
|
47
|
+
@MainActor
|
|
48
|
+
func start() {
|
|
49
|
+
updatesTask = Task { [weak self] in
|
|
50
|
+
for await result in Transaction.updates {
|
|
51
|
+
self?.received(result)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@MainActor
|
|
57
|
+
private func received(_ result: VerificationResult<Transaction>) {
|
|
58
|
+
guard case .verified(let transaction) = result else { return }
|
|
59
|
+
pending[transaction.id] = transaction
|
|
60
|
+
let script = "window.dispatchEvent(new CustomEvent('" + Self.transactionEvent + "'))"
|
|
61
|
+
webView?.evaluateJavaScript(script, completionHandler: nil)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func userContentController(
|
|
65
|
+
_ userContentController: WKUserContentController,
|
|
66
|
+
didReceive message: WKScriptMessage,
|
|
67
|
+
replyHandler: @escaping (Any?, String?) -> Void
|
|
68
|
+
) {
|
|
69
|
+
guard let body = message.body as? [String: Any], let op = body["op"] as? String else {
|
|
70
|
+
replyHandler(nil, "A store request needs an op.")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
Task { @MainActor in
|
|
74
|
+
do {
|
|
75
|
+
let reply = try await self.handle(op: op, body: body)
|
|
76
|
+
replyHandler(reply, nil)
|
|
77
|
+
} catch {
|
|
78
|
+
replyHandler(nil, String(describing: error))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@MainActor
|
|
84
|
+
private func handle(op: String, body: [String: Any]) async throws -> Any {
|
|
85
|
+
switch op {
|
|
86
|
+
case "status":
|
|
87
|
+
return ["canMakePayments": AppStore.canMakePayments]
|
|
88
|
+
case "products":
|
|
89
|
+
let ids = (body["ids"] as? [String]) ?? []
|
|
90
|
+
let products = try await Product.products(for: ids)
|
|
91
|
+
let found = Set(products.map { $0.id })
|
|
92
|
+
return [
|
|
93
|
+
"products": products.map { Self.productInfo($0) },
|
|
94
|
+
"missing": ids.filter { !found.contains($0) },
|
|
95
|
+
]
|
|
96
|
+
case "purchase":
|
|
97
|
+
return try await purchase(body)
|
|
98
|
+
case "unfinished":
|
|
99
|
+
var transactions: [[String: Any]] = []
|
|
100
|
+
for await result in Transaction.unfinished {
|
|
101
|
+
guard case .verified(let transaction) = result else { continue }
|
|
102
|
+
pending[transaction.id] = transaction
|
|
103
|
+
transactions.append(Self.transactionInfo(transaction, jws: result.jwsRepresentation))
|
|
104
|
+
}
|
|
105
|
+
return ["transactions": transactions]
|
|
106
|
+
case "finish":
|
|
107
|
+
guard let text = body["transactionId"] as? String, let id = UInt64(text) else {
|
|
108
|
+
throw StoreBridgeError.badRequest("finish needs a transactionId string")
|
|
109
|
+
}
|
|
110
|
+
return ["finished": await finish(id)]
|
|
111
|
+
default:
|
|
112
|
+
throw StoreBridgeError.badRequest("Unknown store op " + op)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@MainActor
|
|
117
|
+
private func purchase(_ body: [String: Any]) async throws -> Any {
|
|
118
|
+
guard let productId = body["productId"] as? String else {
|
|
119
|
+
throw StoreBridgeError.badRequest("purchase needs a productId")
|
|
120
|
+
}
|
|
121
|
+
guard let product = try await Product.products(for: [productId]).first else {
|
|
122
|
+
return [
|
|
123
|
+
"status": "failed",
|
|
124
|
+
"reason": "The App Store did not return the product " + productId + ".",
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
var options: Set<Product.PurchaseOption> = []
|
|
128
|
+
if let quantity = body["quantity"] as? Int, quantity > 1 {
|
|
129
|
+
options.insert(.quantity(quantity))
|
|
130
|
+
}
|
|
131
|
+
if let token = body["appAccountToken"] as? String, let uuid = UUID(uuidString: token) {
|
|
132
|
+
options.insert(.appAccountToken(uuid))
|
|
133
|
+
}
|
|
134
|
+
let result = try await product.purchase(options: options)
|
|
135
|
+
switch result {
|
|
136
|
+
case .success(let verification):
|
|
137
|
+
switch verification {
|
|
138
|
+
case .verified(let transaction):
|
|
139
|
+
pending[transaction.id] = transaction
|
|
140
|
+
return [
|
|
141
|
+
"status": "purchased",
|
|
142
|
+
"transaction": Self.transactionInfo(transaction, jws: verification.jwsRepresentation),
|
|
143
|
+
]
|
|
144
|
+
case .unverified(_, let error):
|
|
145
|
+
return ["status": "unverified", "reason": String(describing: error)]
|
|
146
|
+
}
|
|
147
|
+
case .userCancelled:
|
|
148
|
+
return ["status": "cancelled"]
|
|
149
|
+
case .pending:
|
|
150
|
+
return ["status": "pending"]
|
|
151
|
+
@unknown default:
|
|
152
|
+
return ["status": "failed", "reason": "StoreKit returned an unknown purchase result."]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@MainActor
|
|
157
|
+
private func finish(_ id: UInt64) async -> Bool {
|
|
158
|
+
if let transaction = pending.removeValue(forKey: id) {
|
|
159
|
+
await transaction.finish()
|
|
160
|
+
return true
|
|
161
|
+
}
|
|
162
|
+
for await result in Transaction.unfinished {
|
|
163
|
+
if case .verified(let transaction) = result, transaction.id == id {
|
|
164
|
+
await transaction.finish()
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private static func typeName(_ type: Product.ProductType) -> String {
|
|
172
|
+
if type == .consumable { return "consumable" }
|
|
173
|
+
if type == .nonConsumable { return "nonConsumable" }
|
|
174
|
+
if type == .autoRenewable { return "autoRenewable" }
|
|
175
|
+
if type == .nonRenewable { return "nonRenewable" }
|
|
176
|
+
return "other"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private static func productInfo(_ product: Product) -> [String: Any] {
|
|
180
|
+
return [
|
|
181
|
+
"id": product.id,
|
|
182
|
+
"type": typeName(product.type),
|
|
183
|
+
"displayName": product.displayName,
|
|
184
|
+
"description": product.description,
|
|
185
|
+
"displayPrice": product.displayPrice,
|
|
186
|
+
"price": NSDecimalNumber(decimal: product.price).doubleValue,
|
|
187
|
+
"currencyCode": product.priceFormatStyle.currencyCode,
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Ids go to the page as strings: a transaction id is a UInt64 and need not fit a double.
|
|
192
|
+
private static func transactionInfo(_ transaction: Transaction, jws: String) -> [String: Any] {
|
|
193
|
+
var info: [String: Any] = [
|
|
194
|
+
"transactionId": String(transaction.id),
|
|
195
|
+
"originalTransactionId": String(transaction.originalID),
|
|
196
|
+
"productId": transaction.productID,
|
|
197
|
+
"productType": typeName(transaction.productType),
|
|
198
|
+
"quantity": transaction.purchasedQuantity,
|
|
199
|
+
"purchaseDate": transaction.purchaseDate.timeIntervalSince1970 * 1000,
|
|
200
|
+
"revoked": transaction.revocationDate != nil,
|
|
201
|
+
"jws": jws,
|
|
202
|
+
]
|
|
203
|
+
if #available(iOS 16.0, *) {
|
|
204
|
+
info["environment"] = transaction.environment.rawValue
|
|
205
|
+
}
|
|
206
|
+
if let token = transaction.appAccountToken {
|
|
207
|
+
info["appAccountToken"] = token.uuidString
|
|
208
|
+
}
|
|
209
|
+
return info
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
enum StoreBridgeError: Error, CustomStringConvertible {
|
|
214
|
+
case badRequest(String)
|
|
215
|
+
|
|
216
|
+
var description: String {
|
|
217
|
+
switch self {
|
|
218
|
+
case .badRequest(let message):
|
|
219
|
+
return message
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
`;
|
|
224
|
+
//# sourceMappingURL=store-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-bridge.js","sourceRoot":"","sources":["../../src/ios/store-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAC5C,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;gCAMF,kBAAkB;qCACb,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8L3D,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface StoreKitProductInput {
|
|
2
|
+
id: string;
|
|
3
|
+
/** Test price, e.g. "0.99". Defaults to 0.99. */
|
|
4
|
+
price?: string;
|
|
5
|
+
/** Test display name. Defaults to the id. */
|
|
6
|
+
name?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface StoreKitUpsertResult {
|
|
9
|
+
created: boolean;
|
|
10
|
+
added: string[];
|
|
11
|
+
alreadyListed: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function validateProductId(id: string): void;
|
|
14
|
+
/** Add the consumables the file does not list yet; create the file when there is none. */
|
|
15
|
+
export declare function upsertStoreKitConsumables(filePath: string, products: readonly StoreKitProductInput[]): StoreKitUpsertResult;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ios/StoreKit.storekit` — Xcode's local StoreKit test catalogue (§13 of
|
|
3
|
+
* docs/ios-export-design.md). With it, a run started from Xcode answers products and purchases
|
|
4
|
+
* from this file instead of the App Store, so a shop can be exercised in the Simulator before the
|
|
5
|
+
* creator has set anything up in App Store Connect.
|
|
6
|
+
*
|
|
7
|
+
* The file is the creator's: `bitmagic ios storekit` only ever ADDS consumables it does not
|
|
8
|
+
* already list, and everything else (prices, names, other product types edited in Xcode) is kept
|
|
9
|
+
* verbatim. Prices and names here are test values; the real ones come from App Store Connect.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { CliError } from '../errors.js';
|
|
15
|
+
/** Apple's rule for a product id: letters, digits, underscores and periods. */
|
|
16
|
+
const PRODUCT_ID = /^[A-Za-z0-9._]+$/;
|
|
17
|
+
/** Stable 8-hex ids, so regenerating the same product writes the same file. */
|
|
18
|
+
function internalId(seed) {
|
|
19
|
+
return createHash('sha256').update(seed).digest('hex').slice(0, 8).toUpperCase();
|
|
20
|
+
}
|
|
21
|
+
function emptyCatalogue(seed) {
|
|
22
|
+
return {
|
|
23
|
+
identifier: internalId(`catalogue:${seed}`),
|
|
24
|
+
nonRenewingSubscriptions: [],
|
|
25
|
+
products: [],
|
|
26
|
+
settings: {
|
|
27
|
+
_failTransactionsEnabled: false,
|
|
28
|
+
_locale: 'en_US',
|
|
29
|
+
_storefront: 'USA',
|
|
30
|
+
},
|
|
31
|
+
subscriptionGroups: [],
|
|
32
|
+
version: { major: 2, minor: 0 },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function consumable(input) {
|
|
36
|
+
const name = input.name ?? input.id;
|
|
37
|
+
return {
|
|
38
|
+
displayPrice: input.price ?? '0.99',
|
|
39
|
+
familyShareable: false,
|
|
40
|
+
internalID: internalId(`product:${input.id}`),
|
|
41
|
+
localizations: [{ description: name, displayName: name, locale: 'en_US' }],
|
|
42
|
+
productID: input.id,
|
|
43
|
+
referenceName: name,
|
|
44
|
+
type: 'Consumable',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function validateProductId(id) {
|
|
48
|
+
if (!PRODUCT_ID.test(id)) {
|
|
49
|
+
throw new CliError(`"${id}" is not a valid App Store product id: use letters, digits, underscores and periods `
|
|
50
|
+
+ '(e.g. gems_100 or com.studio.game.gems100).', 2);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function validatePrice(price) {
|
|
54
|
+
if (!/^\d+(\.\d{1,2})?$/.test(price)) {
|
|
55
|
+
throw new CliError(`--price must be a plain amount such as 0.99, not "${price}".`, 2);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Add the consumables the file does not list yet; create the file when there is none. */
|
|
59
|
+
export function upsertStoreKitConsumables(filePath, products) {
|
|
60
|
+
for (const product of products) {
|
|
61
|
+
validateProductId(product.id);
|
|
62
|
+
if (product.price !== undefined)
|
|
63
|
+
validatePrice(product.price);
|
|
64
|
+
}
|
|
65
|
+
const created = !fs.existsSync(filePath);
|
|
66
|
+
let catalogue;
|
|
67
|
+
if (created) {
|
|
68
|
+
catalogue = emptyCatalogue(products.map((p) => p.id).join(','));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw new CliError(`${filePath} is not valid JSON (${err.message}). Fix or delete it.`, 3);
|
|
77
|
+
}
|
|
78
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
79
|
+
throw new CliError(`${filePath} is not a StoreKit configuration file. Fix or delete it.`, 3);
|
|
80
|
+
}
|
|
81
|
+
catalogue = parsed;
|
|
82
|
+
}
|
|
83
|
+
const list = Array.isArray(catalogue.products) ? catalogue.products : [];
|
|
84
|
+
const listed = new Set(list.flatMap((entry) => {
|
|
85
|
+
const id = entry?.productID;
|
|
86
|
+
return typeof id === 'string' ? [id] : [];
|
|
87
|
+
}));
|
|
88
|
+
const added = [];
|
|
89
|
+
const alreadyListed = [];
|
|
90
|
+
for (const product of products) {
|
|
91
|
+
if (listed.has(product.id)) {
|
|
92
|
+
alreadyListed.push(product.id);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
list.push(consumable(product));
|
|
96
|
+
listed.add(product.id);
|
|
97
|
+
added.push(product.id);
|
|
98
|
+
}
|
|
99
|
+
catalogue.products = list;
|
|
100
|
+
if (created || added.length > 0) {
|
|
101
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
102
|
+
fs.writeFileSync(filePath, `${JSON.stringify(catalogue, null, 2)}\n`);
|
|
103
|
+
}
|
|
104
|
+
return { created, added, alreadyListed };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=storekit-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storekit-config.js","sourceRoot":"","sources":["../../src/ios/storekit-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { IosAppConfig } from './app-config.js';
|
|
2
|
+
import { type NativeOverrides } from './native-overrides.js';
|
|
2
3
|
export declare const TARGET_NAME = "Game";
|
|
3
4
|
export declare const PAYLOAD_DIR_NAME = "GamePayload";
|
|
4
5
|
export declare const URL_SCHEME = "bmapp";
|
|
5
6
|
/** The Info.plist orientation array for the project's declaration (§6). */
|
|
6
7
|
export declare function orientationValues(orientation: 'portrait' | 'landscape' | undefined): string[];
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* The hook creator Swift (ios/Sources/) plugs into, and the list of adopters this build found.
|
|
10
|
+
* Generated because Swift has no static registration: the CLI scans the creator's files for
|
|
11
|
+
* classes adopting the protocol (native-overrides.ts) and lists them here. Each needs an init
|
|
12
|
+
* that takes no arguments; one that lacks it fails the build with Swift's own error, naming it.
|
|
10
13
|
*/
|
|
11
|
-
export declare function
|
|
14
|
+
export declare function nativeExtensionsSwift(classNames: readonly string[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Write the whole project (everything except the payload contents, the icon PNG and the
|
|
17
|
+
* creator's own files, which have their own producers). Existing files are overwritten — the
|
|
18
|
+
* project is CLI-owned. `overrides` describes what `copyNativeOverrides` puts in it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stampXcodeProject(projectDir: string, config: IosAppConfig, overrides?: NativeOverrides): void;
|