craft-native 0.0.123 → 0.0.125
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 +3 -0
- package/dist/android/src/index.d.ts +3 -0
- package/dist/android/src/index.js +22 -3
- package/dist/android/templates/AndroidManifest.xml.template +1 -1
- package/dist/android/templates/CraftNativeFlatList.kt.template +151 -7
- package/dist/android/templates/CraftNativeRefreshableFlatList.kt.template +29 -0
- package/dist/android/templates/MainActivity.kt.template +17 -0
- package/dist/android/templates/MainActivityNative.kt.template +1085 -84
- package/dist/android/templates/build.gradle.kts.app.template +1 -0
- package/dist/api/mobile.d.ts +250 -2
- package/dist/cli.js +3 -3
- package/dist/index.cjs +22 -1
- package/dist/index.js +22 -1
- package/dist/ios/src/index.d.ts +125 -3
- package/dist/ios/src/index.js +284 -47
- package/dist/ios/templates/CraftApp.swift +1186 -69
- package/dist/ios/templates/CraftHybrid.swift +1196 -0
- package/dist/ios/templates/CraftNativeActions.swift +310 -10
- package/dist/ios/templates/CraftNativeFlatList.swift +226 -17
- package/dist/ios/templates/CraftNativeScreen.swift +2217 -220
- package/dist/ios/templates/CraftNativeUI.swift +562 -0
- package/dist/ios/templates/Info.plist.template +2 -1
- package/dist/mobile.js +204 -0
- package/package.json +1 -1
package/dist/ios/src/index.js
CHANGED
|
@@ -4,6 +4,129 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statS
|
|
|
4
4
|
import { dirname, join, resolve } from "path";
|
|
5
5
|
var {$ } = globalThis.Bun;
|
|
6
6
|
var TEMPLATES_DIR = join(dirname(import.meta.dir), "templates");
|
|
7
|
+
var CRAFT_VERSION = (() => {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(join(dirname(import.meta.dir), "package.json"), "utf8")).version ?? "0.0.0";
|
|
10
|
+
} catch {
|
|
11
|
+
return "0.0.0";
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
function normalizeNativePath(raw) {
|
|
15
|
+
if (typeof raw !== "string")
|
|
16
|
+
return null;
|
|
17
|
+
let path = raw;
|
|
18
|
+
const cut = path.search(/[?#]/);
|
|
19
|
+
if (cut >= 0)
|
|
20
|
+
path = path.slice(0, cut);
|
|
21
|
+
if (!path.startsWith("/"))
|
|
22
|
+
return null;
|
|
23
|
+
path = path.replace(/\/{2,}/g, "/");
|
|
24
|
+
if (path.length > 1 && path.endsWith("/"))
|
|
25
|
+
path = path.slice(0, -1);
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
function routeSegments(path) {
|
|
29
|
+
return path === "/" ? [] : path.slice(1).split("/");
|
|
30
|
+
}
|
|
31
|
+
function matchNativePath(nativeScreens, raw) {
|
|
32
|
+
const path = normalizeNativePath(raw);
|
|
33
|
+
if (path === null)
|
|
34
|
+
return null;
|
|
35
|
+
const routes = Object.entries(nativeScreens ?? {}).map(([pattern, screen]) => ({ pattern: normalizeNativePath(pattern), screen })).filter((route) => route.pattern !== null && !!route.screen).map((route) => ({ ...route, segments: routeSegments(route.pattern) }));
|
|
36
|
+
const wild = (segments) => segments.at(-1) === "*" ? 1 : 0;
|
|
37
|
+
const fixed = (segments) => segments.filter((s) => s !== "*" && !s.startsWith(":")).length;
|
|
38
|
+
routes.sort((a, b) => wild(a.segments) - wild(b.segments) || fixed(b.segments) - fixed(a.segments) || b.segments.length - a.segments.length || (a.pattern < b.pattern ? -1 : a.pattern > b.pattern ? 1 : 0));
|
|
39
|
+
const decode = (value) => {
|
|
40
|
+
try {
|
|
41
|
+
return decodeURIComponent(value);
|
|
42
|
+
} catch {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const parts = routeSegments(path);
|
|
47
|
+
for (const route of routes) {
|
|
48
|
+
const params = {};
|
|
49
|
+
let matched = true;
|
|
50
|
+
for (let i = 0;i < route.segments.length; i++) {
|
|
51
|
+
const segment = route.segments[i];
|
|
52
|
+
if (segment === "*") {
|
|
53
|
+
params["*"] = parts.slice(i).join("/");
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (i >= parts.length) {
|
|
57
|
+
matched = false;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if (segment.startsWith(":")) {
|
|
61
|
+
if (!parts[i]) {
|
|
62
|
+
matched = false;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
params[segment.slice(1)] = decode(parts[i]);
|
|
66
|
+
} else if (segment !== parts[i]) {
|
|
67
|
+
matched = false;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!matched)
|
|
72
|
+
continue;
|
|
73
|
+
if (route.segments.at(-1) !== "*" && route.segments.length !== parts.length)
|
|
74
|
+
continue;
|
|
75
|
+
const query = raw.indexOf("?");
|
|
76
|
+
if (query >= 0) {
|
|
77
|
+
for (const item of raw.slice(query + 1).split("#")[0].split("&")) {
|
|
78
|
+
if (!item)
|
|
79
|
+
continue;
|
|
80
|
+
const [rawKey = "", ...rest] = item.split("=");
|
|
81
|
+
const key = decode(rawKey);
|
|
82
|
+
if (!key || key in params)
|
|
83
|
+
continue;
|
|
84
|
+
params[key] = decode(rest.join("=").replace(/\+/g, " "));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { path, screen: route.screen, params };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function isHybrid(config) {
|
|
92
|
+
return (config.renderer ?? "web") === "web" && Object.keys(config.nativeScreens ?? {}).length > 0;
|
|
93
|
+
}
|
|
94
|
+
function hybridConfigProblems(config) {
|
|
95
|
+
const problems = [];
|
|
96
|
+
const screens = Object.entries(config.nativeScreens ?? {});
|
|
97
|
+
if (screens.length && config.renderer === "native")
|
|
98
|
+
problems.push('nativeScreens mixes native screens into a web app; it needs renderer: "web" (renderer: "native" is all native already).');
|
|
99
|
+
for (const [pattern, screen] of screens) {
|
|
100
|
+
if (normalizeNativePath(pattern) !== pattern || /[?#]/.test(pattern))
|
|
101
|
+
problems.push(`nativeScreens path "${pattern}" must be an absolute path with no query, hash or trailing slash.`);
|
|
102
|
+
if (routeSegments(normalizeNativePath(pattern) ?? "/").slice(0, -1).includes("*"))
|
|
103
|
+
problems.push(`nativeScreens path "${pattern}" may only end in *.`);
|
|
104
|
+
if (typeof screen !== "string" || !/^[A-Za-z_$][\w$-]*$/.test(screen))
|
|
105
|
+
problems.push(`nativeScreens screen "${String(screen)}" for "${pattern}" must be a screen name from the native bundle.`);
|
|
106
|
+
}
|
|
107
|
+
const ids = new Set;
|
|
108
|
+
for (const tab of config.tabs ?? []) {
|
|
109
|
+
if (!tab || typeof tab.id !== "string" || !tab.id || typeof tab.title !== "string" || !tab.title)
|
|
110
|
+
problems.push("Each tab needs an id (its root path) and a title.");
|
|
111
|
+
else if (ids.has(tab.id))
|
|
112
|
+
problems.push(`Tab "${tab.id}" is listed twice.`);
|
|
113
|
+
else
|
|
114
|
+
ids.add(tab.id);
|
|
115
|
+
}
|
|
116
|
+
for (const [key, target] of Object.entries(config.shareStorage ?? {})) {
|
|
117
|
+
if (!key || typeof target !== "string" || !target)
|
|
118
|
+
problems.push(`shareStorage "${key}" needs the Keychain key native screens read it under.`);
|
|
119
|
+
}
|
|
120
|
+
return problems;
|
|
121
|
+
}
|
|
122
|
+
function installNativeBundle(source, output) {
|
|
123
|
+
if (!existsSync(source))
|
|
124
|
+
throw new Error(`Native bundle not found: ${source}`);
|
|
125
|
+
const destination = join(output, "dist", "native-screen.js");
|
|
126
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
127
|
+
if (resolve(source) !== resolve(destination))
|
|
128
|
+
cpSync(source, destination);
|
|
129
|
+
}
|
|
7
130
|
var DEFAULT_CONFIG = {
|
|
8
131
|
renderer: "web",
|
|
9
132
|
version: "1.0.0",
|
|
@@ -171,6 +294,8 @@ function plistArray(values, indent = 2) {
|
|
|
171
294
|
}
|
|
172
295
|
function renderBackgroundModes(config) {
|
|
173
296
|
const modes = new Set;
|
|
297
|
+
if (config.backgroundRefresh?.enabled)
|
|
298
|
+
modes.add("fetch");
|
|
174
299
|
if (config.enableBackgroundLocation)
|
|
175
300
|
modes.add("location");
|
|
176
301
|
if (config.enableBackgroundTasks)
|
|
@@ -184,12 +309,30 @@ function renderBackgroundModes(config) {
|
|
|
184
309
|
${plistArray([...modes])}
|
|
185
310
|
</array>`;
|
|
186
311
|
}
|
|
312
|
+
function backgroundRefreshIdentifier(config) {
|
|
313
|
+
if (!config.backgroundRefresh?.enabled)
|
|
314
|
+
return null;
|
|
315
|
+
return config.backgroundRefresh.identifier?.trim() || `${config.bundleId}.refresh`;
|
|
316
|
+
}
|
|
317
|
+
function renderBackgroundTaskIdentifiers(config) {
|
|
318
|
+
const identifier = backgroundRefreshIdentifier(config);
|
|
319
|
+
if (!identifier)
|
|
320
|
+
return "";
|
|
321
|
+
return ` <key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
322
|
+
<array>
|
|
323
|
+
${plistArray([identifier])}
|
|
324
|
+
</array>`;
|
|
325
|
+
}
|
|
326
|
+
function associatedDomainEntries(domains) {
|
|
327
|
+
return [...new Set((domains ?? []).map((value) => value.trim()).filter(Boolean).map((value) => /^[a-z]+:/i.test(value) ? value : `applinks:${value}`))];
|
|
328
|
+
}
|
|
187
329
|
function renderEntitlements(config) {
|
|
188
330
|
const entries = [];
|
|
189
|
-
|
|
331
|
+
const associatedDomains = associatedDomainEntries(config.associatedDomains);
|
|
332
|
+
if (associatedDomains.length) {
|
|
190
333
|
entries.push(` <key>com.apple.developer.associated-domains</key>
|
|
191
334
|
<array>
|
|
192
|
-
${plistArray(
|
|
335
|
+
${plistArray(associatedDomains)}
|
|
193
336
|
</array>`);
|
|
194
337
|
}
|
|
195
338
|
if (config.appGroups?.length) {
|
|
@@ -414,6 +557,102 @@ function renderLaunchScreen(config) {
|
|
|
414
557
|
return ` <key>UIColorName</key>
|
|
415
558
|
<string>LaunchBackground</string>${image}`;
|
|
416
559
|
}
|
|
560
|
+
function hexChannels(hex) {
|
|
561
|
+
const value = hex?.trim().replace(/^#/, "") ?? "";
|
|
562
|
+
const full = value.length === 3 ? [...value].map((digit) => digit + digit).join("") : value.slice(0, 6);
|
|
563
|
+
if (!/^[0-9a-f]{6}$/i.test(full))
|
|
564
|
+
return null;
|
|
565
|
+
return [0, 2, 4].map((index) => Number.parseInt(full.slice(index, index + 2), 16));
|
|
566
|
+
}
|
|
567
|
+
function readableTextOn(background) {
|
|
568
|
+
const channels = hexChannels(background);
|
|
569
|
+
if (!channels)
|
|
570
|
+
return "#0f172a";
|
|
571
|
+
const [r, g, b] = channels.map((channel) => {
|
|
572
|
+
const c = channel / 255;
|
|
573
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
574
|
+
});
|
|
575
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
576
|
+
return luminance > 0.4 ? "#0f172a" : "#f8fafc";
|
|
577
|
+
}
|
|
578
|
+
function renderOfflinePage(config) {
|
|
579
|
+
const light = hexChannels(config.backgroundColor) ? config.backgroundColor : "#ffffff";
|
|
580
|
+
const dark = hexChannels(config.backgroundColorDark) ? config.backgroundColorDark : light;
|
|
581
|
+
const name = xmlEscape(config.appName);
|
|
582
|
+
return `<!DOCTYPE html>
|
|
583
|
+
<html lang="en">
|
|
584
|
+
<head>
|
|
585
|
+
<meta charset="UTF-8">
|
|
586
|
+
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
587
|
+
<meta name="color-scheme" content="light dark">
|
|
588
|
+
<title>${name}</title>
|
|
589
|
+
<style>
|
|
590
|
+
:root { --background: ${light}; --text: ${readableTextOn(light)}; }
|
|
591
|
+
@media (prefers-color-scheme: dark) {
|
|
592
|
+
:root { --background: ${dark}; --text: ${readableTextOn(dark)}; }
|
|
593
|
+
}
|
|
594
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
595
|
+
html, body { height: 100%; }
|
|
596
|
+
body {
|
|
597
|
+
font: -apple-system-body;
|
|
598
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
599
|
+
background: var(--background);
|
|
600
|
+
color: var(--text);
|
|
601
|
+
display: flex;
|
|
602
|
+
align-items: center;
|
|
603
|
+
justify-content: center;
|
|
604
|
+
text-align: center;
|
|
605
|
+
padding: calc(env(safe-area-inset-top) + 24px) calc(env(safe-area-inset-right) + 32px) calc(env(safe-area-inset-bottom) + 24px) calc(env(safe-area-inset-left) + 32px);
|
|
606
|
+
-webkit-user-select: none;
|
|
607
|
+
user-select: none;
|
|
608
|
+
-webkit-tap-highlight-color: transparent;
|
|
609
|
+
}
|
|
610
|
+
main { max-width: 22rem; }
|
|
611
|
+
.app { font: -apple-system-subheadline; font-weight: 600; opacity: 0.6; margin-bottom: 0.5rem; }
|
|
612
|
+
h1 { font: -apple-system-title1; font-weight: 700; margin-bottom: 0.75rem; }
|
|
613
|
+
p { opacity: 0.72; line-height: 1.4; }
|
|
614
|
+
button {
|
|
615
|
+
font: -apple-system-headline;
|
|
616
|
+
margin-top: 1.75rem;
|
|
617
|
+
min-width: 9rem;
|
|
618
|
+
min-height: 44px;
|
|
619
|
+
padding: 0.75rem 1.75rem;
|
|
620
|
+
border: 0;
|
|
621
|
+
border-radius: 999px;
|
|
622
|
+
background: var(--text);
|
|
623
|
+
color: var(--background);
|
|
624
|
+
cursor: pointer;
|
|
625
|
+
}
|
|
626
|
+
button:active { opacity: 0.7; }
|
|
627
|
+
button:disabled { opacity: 0.5; }
|
|
628
|
+
</style>
|
|
629
|
+
</head>
|
|
630
|
+
<body>
|
|
631
|
+
<main>
|
|
632
|
+
<div class="app">${name}</div>
|
|
633
|
+
<h1>You're offline</h1>
|
|
634
|
+
<p>${name} could not reach the internet. Check your connection, then try again.</p>
|
|
635
|
+
<button id="retry" type="button">Retry</button>
|
|
636
|
+
</main>
|
|
637
|
+
<script>
|
|
638
|
+
(function () {
|
|
639
|
+
var button = document.getElementById('retry');
|
|
640
|
+
function retry() {
|
|
641
|
+
button.disabled = true;
|
|
642
|
+
button.textContent = 'Retrying';
|
|
643
|
+
var handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.craft;
|
|
644
|
+
if (handler) handler.postMessage({ action: 'retryRemote' });
|
|
645
|
+
else location.reload();
|
|
646
|
+
// Still here a while later means the page is still out of reach.
|
|
647
|
+
setTimeout(function () { button.disabled = false; button.textContent = 'Retry'; }, 12000);
|
|
648
|
+
}
|
|
649
|
+
button.addEventListener('click', retry);
|
|
650
|
+
})();
|
|
651
|
+
</script>
|
|
652
|
+
</body>
|
|
653
|
+
</html>
|
|
654
|
+
`;
|
|
655
|
+
}
|
|
417
656
|
function syncWebAssets(source, output) {
|
|
418
657
|
const sourcePath = resolve(source);
|
|
419
658
|
if (!existsSync(sourcePath))
|
|
@@ -520,16 +759,24 @@ async function init(options) {
|
|
|
520
759
|
}
|
|
521
760
|
if (config.enableBackgroundLocation)
|
|
522
761
|
config.enableGeolocation = true;
|
|
762
|
+
const problems = hybridConfigProblems(config);
|
|
763
|
+
if (problems.length)
|
|
764
|
+
throw new Error(problems.join(`
|
|
765
|
+
`));
|
|
766
|
+
if (Object.keys(config.shareStorage ?? {}).length)
|
|
767
|
+
config.enableSecureStorage = true;
|
|
523
768
|
writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
524
769
|
const swiftTemplate = readFileSync(join(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
525
|
-
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
770
|
+
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{CRAFT_VERSION\}\}/g, CRAFT_VERSION);
|
|
526
771
|
writeFileSync(join(output, "Sources", `${name}App.swift`), swiftSource);
|
|
527
772
|
cpSync(join(TEMPLATES_DIR, "CraftNativeScreen.swift"), join(output, "Sources", "CraftNativeScreen.swift"));
|
|
528
773
|
cpSync(join(TEMPLATES_DIR, "CraftNativeFlatList.swift"), join(output, "Sources", "CraftNativeFlatList.swift"));
|
|
529
774
|
cpSync(join(TEMPLATES_DIR, "CraftNativeActions.swift"), join(output, "Sources", "CraftNativeActions.swift"));
|
|
530
775
|
cpSync(join(TEMPLATES_DIR, "CraftNativeMutation.swift"), join(output, "Sources", "CraftNativeMutation.swift"));
|
|
776
|
+
cpSync(join(TEMPLATES_DIR, "CraftNativeUI.swift"), join(output, "Sources", "CraftNativeUI.swift"));
|
|
777
|
+
cpSync(join(TEMPLATES_DIR, "CraftHybrid.swift"), join(output, "Sources", "CraftHybrid.swift"));
|
|
531
778
|
const infoPlistTemplate = readFileSync(join(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
532
|
-
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, renderAppearance(config).interfaceStyle).replace(/\{\{STATUS_BAR_STYLE\}\}/g, renderAppearance(config).statusBarStyle).replace(/\{\{ORIENTATIONS\}\}/g, renderOrientations(config)).replace(/\{\{USAGE_DESCRIPTIONS\}\}/g, renderUsageDescriptions(config)).replace(/\{\{URL_TYPES\}\}/g, renderUrlTypes(config)).replace(/\{\{APP_BOUND_DOMAINS\}\}/g, renderAppBoundDomains(config)).replace(/\{\{BACKGROUND_MODES\}\}/g, renderBackgroundModes(config)).replace(/\{\{LAUNCH_SCREEN\}\}/g, renderLaunchScreen(config)).replace(/\{\{NON_EXEMPT_ENCRYPTION\}\}/g, config.usesNonExemptEncryption ? "true" : "false").replace(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
779
|
+
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, renderAppearance(config).interfaceStyle).replace(/\{\{STATUS_BAR_STYLE\}\}/g, renderAppearance(config).statusBarStyle).replace(/\{\{ORIENTATIONS\}\}/g, renderOrientations(config)).replace(/\{\{USAGE_DESCRIPTIONS\}\}/g, renderUsageDescriptions(config)).replace(/\{\{URL_TYPES\}\}/g, renderUrlTypes(config)).replace(/\{\{APP_BOUND_DOMAINS\}\}/g, renderAppBoundDomains(config)).replace(/\{\{BACKGROUND_MODES\}\}/g, renderBackgroundModes(config)).replace(/\{\{BACKGROUND_TASK_IDENTIFIERS\}\}/g, renderBackgroundTaskIdentifiers(config)).replace(/\{\{LAUNCH_SCREEN\}\}/g, renderLaunchScreen(config)).replace(/\{\{NON_EXEMPT_ENCRYPTION\}\}/g, config.usesNonExemptEncryption ? "true" : "false").replace(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
533
780
|
<true/>
|
|
534
781
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
535
782
|
<true/>` : "");
|
|
@@ -619,51 +866,19 @@ ${nativeDependencies.join(`
|
|
|
619
866
|
writeFileSync(join(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
620
867
|
writeFileSync(join(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
621
868
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
630
|
-
body {
|
|
631
|
-
font-family: -apple-system, system-ui, sans-serif;
|
|
632
|
-
background: ${config.backgroundColor};
|
|
633
|
-
color: white;
|
|
634
|
-
min-height: 100vh;
|
|
635
|
-
display: flex;
|
|
636
|
-
justify-content: center;
|
|
637
|
-
align-items: center;
|
|
638
|
-
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
|
639
|
-
}
|
|
640
|
-
.container { text-align: center; padding: 2rem; }
|
|
641
|
-
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
642
|
-
p { opacity: 0.7; }
|
|
643
|
-
.ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }
|
|
644
|
-
</style>
|
|
645
|
-
</head>
|
|
646
|
-
<body>
|
|
647
|
-
<div class="container">
|
|
648
|
-
<h1>\u26A1 ${xmlEscape(displayName)}</h1>
|
|
649
|
-
<p>Built with Craft iOS</p>
|
|
650
|
-
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
651
|
-
</div>
|
|
652
|
-
<script>
|
|
653
|
-
window.addEventListener('craftReady', (e) => {
|
|
654
|
-
document.getElementById('status').textContent = \`\u2713 Craft bridge ready (platform: \${e.detail.platform})\`;
|
|
655
|
-
console.log('Craft capabilities:', e.detail.capabilities);
|
|
656
|
-
});
|
|
657
|
-
</script>
|
|
658
|
-
</body>
|
|
659
|
-
</html>`;
|
|
660
|
-
writeFileSync(join(output, "dist", "index.html"), placeholderHtml);
|
|
869
|
+
writeFileSync(join(output, "dist", "index.html"), renderOfflinePage(config));
|
|
870
|
+
if (isHybrid(config)) {
|
|
871
|
+
if (config.nativeBundle)
|
|
872
|
+
installNativeBundle(config.nativeBundle, output);
|
|
873
|
+
else
|
|
874
|
+
console.warn(" \u26A0 nativeScreens is set without nativeBundle: every path opens in the web view until `craft ios build --native-bundle` supplies one.");
|
|
875
|
+
}
|
|
661
876
|
console.log("\u2705 Project initialized");
|
|
662
877
|
console.log("");
|
|
663
878
|
console.log("Next steps:");
|
|
664
879
|
console.log(` 1. cd ${output}`);
|
|
665
880
|
if (config.renderer === "native") {
|
|
666
|
-
console.log(" 2. Compile your screen: stx
|
|
881
|
+
console.log(" 2. Compile your screen: stx native compile Screen.stx --format bundle --output screen.js");
|
|
667
882
|
console.log(" 3. Run: craft ios build --native-bundle screen.js");
|
|
668
883
|
} else {
|
|
669
884
|
console.log(" 2. Add your web content to dist/index.html");
|
|
@@ -694,11 +909,15 @@ async function build(options) {
|
|
|
694
909
|
cpSync(nativeBundlePath, destination);
|
|
695
910
|
}
|
|
696
911
|
if (!existsSync(join(output, "dist", "native-screen.js"))) {
|
|
697
|
-
throw new Error("Native iOS mode needs dist/native-screen.js. Compile a .stx screen with stx
|
|
912
|
+
throw new Error("Native iOS mode needs dist/native-screen.js. Compile a .stx screen with `stx native compile` first.");
|
|
698
913
|
}
|
|
699
|
-
} else if (nativeBundlePath) {
|
|
700
|
-
throw new Error('--native-bundle requires renderer: "native" in craft.config.json.');
|
|
914
|
+
} else if (nativeBundlePath && !isHybrid(config)) {
|
|
915
|
+
throw new Error('--native-bundle requires renderer: "native", or nativeScreens, in craft.config.json.');
|
|
701
916
|
}
|
|
917
|
+
const problems = hybridConfigProblems(config);
|
|
918
|
+
if (problems.length)
|
|
919
|
+
throw new Error(problems.join(`
|
|
920
|
+
`));
|
|
702
921
|
if (devServer) {
|
|
703
922
|
config.devServerURL = devServer;
|
|
704
923
|
const origin = new URL(devServer).origin;
|
|
@@ -710,6 +929,15 @@ async function build(options) {
|
|
|
710
929
|
syncWebAssets(htmlPath, output);
|
|
711
930
|
console.log(` Synced: ${htmlPath} \u2192 dist/`);
|
|
712
931
|
}
|
|
932
|
+
if (isHybrid(config)) {
|
|
933
|
+
const bundle = nativeBundlePath ?? config.nativeBundle;
|
|
934
|
+
if (bundle) {
|
|
935
|
+
installNativeBundle(bundle, output);
|
|
936
|
+
console.log(` Native screens: ${bundle} \u2192 dist/native-screen.js`);
|
|
937
|
+
} else if (!existsSync(join(output, "dist", "native-screen.js"))) {
|
|
938
|
+
console.warn(" \u26A0 nativeScreens is set but there is no native bundle: every path opens in the web view.");
|
|
939
|
+
}
|
|
940
|
+
}
|
|
713
941
|
await refreshRuntime(output, options.runtimeDir);
|
|
714
942
|
if (!generateProject)
|
|
715
943
|
return;
|
|
@@ -844,20 +1072,29 @@ export {
|
|
|
844
1072
|
renderRuntimeSettings,
|
|
845
1073
|
renderPrivacyManifest,
|
|
846
1074
|
renderOrientations,
|
|
1075
|
+
renderOfflinePage,
|
|
847
1076
|
renderLaunchScreen,
|
|
848
1077
|
renderEntitlements,
|
|
849
1078
|
renderDeviceFamilies,
|
|
1079
|
+
renderBackgroundTaskIdentifiers,
|
|
850
1080
|
renderBackgroundModes,
|
|
851
1081
|
renderAppearance,
|
|
852
1082
|
renderAppBoundDomains,
|
|
1083
|
+
readableTextOn,
|
|
853
1084
|
productName,
|
|
854
1085
|
pngHasAlpha,
|
|
855
1086
|
pickSimulator,
|
|
856
1087
|
orderSimulators,
|
|
857
1088
|
open,
|
|
1089
|
+
normalizeNativePath,
|
|
1090
|
+
matchNativePath,
|
|
1091
|
+
isHybrid,
|
|
858
1092
|
installRuntime,
|
|
859
1093
|
init,
|
|
1094
|
+
hybridConfigProblems,
|
|
860
1095
|
build,
|
|
861
1096
|
bootSimulator,
|
|
1097
|
+
backgroundRefreshIdentifier,
|
|
1098
|
+
associatedDomainEntries,
|
|
862
1099
|
LAUNCH_LOGO
|
|
863
1100
|
};
|