craft-native 0.0.123 → 0.0.124
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 +17 -0
- package/dist/android/templates/AndroidManifest.xml.template +1 -1
- package/dist/android/templates/CraftNativeFlatList.kt.template +113 -6
- package/dist/android/templates/MainActivity.kt.template +17 -0
- package/dist/android/templates/MainActivityNative.kt.template +826 -68
- package/dist/api/mobile.d.ts +207 -2
- package/dist/cli.js +1 -1
- package/dist/index.cjs +22 -1
- package/dist/index.js +22 -1
- package/dist/ios/src/index.d.ts +76 -2
- package/dist/ios/src/index.js +134 -43
- package/dist/ios/templates/CraftApp.swift +1086 -67
- package/dist/ios/templates/CraftNativeActions.swift +97 -9
- package/dist/ios/templates/CraftNativeFlatList.swift +92 -12
- package/dist/ios/templates/CraftNativeScreen.swift +876 -64
- package/dist/ios/templates/CraftNativeUI.swift +562 -0
- package/dist/ios/templates/Info.plist.template +2 -1
- package/dist/mobile.js +138 -0
- package/package.json +1 -1
package/dist/ios/src/index.js
CHANGED
|
@@ -4,6 +4,13 @@ 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
|
+
})();
|
|
7
14
|
var DEFAULT_CONFIG = {
|
|
8
15
|
renderer: "web",
|
|
9
16
|
version: "1.0.0",
|
|
@@ -171,6 +178,8 @@ function plistArray(values, indent = 2) {
|
|
|
171
178
|
}
|
|
172
179
|
function renderBackgroundModes(config) {
|
|
173
180
|
const modes = new Set;
|
|
181
|
+
if (config.backgroundRefresh?.enabled)
|
|
182
|
+
modes.add("fetch");
|
|
174
183
|
if (config.enableBackgroundLocation)
|
|
175
184
|
modes.add("location");
|
|
176
185
|
if (config.enableBackgroundTasks)
|
|
@@ -184,12 +193,30 @@ function renderBackgroundModes(config) {
|
|
|
184
193
|
${plistArray([...modes])}
|
|
185
194
|
</array>`;
|
|
186
195
|
}
|
|
196
|
+
function backgroundRefreshIdentifier(config) {
|
|
197
|
+
if (!config.backgroundRefresh?.enabled)
|
|
198
|
+
return null;
|
|
199
|
+
return config.backgroundRefresh.identifier?.trim() || `${config.bundleId}.refresh`;
|
|
200
|
+
}
|
|
201
|
+
function renderBackgroundTaskIdentifiers(config) {
|
|
202
|
+
const identifier = backgroundRefreshIdentifier(config);
|
|
203
|
+
if (!identifier)
|
|
204
|
+
return "";
|
|
205
|
+
return ` <key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
206
|
+
<array>
|
|
207
|
+
${plistArray([identifier])}
|
|
208
|
+
</array>`;
|
|
209
|
+
}
|
|
210
|
+
function associatedDomainEntries(domains) {
|
|
211
|
+
return [...new Set((domains ?? []).map((value) => value.trim()).filter(Boolean).map((value) => /^[a-z]+:/i.test(value) ? value : `applinks:${value}`))];
|
|
212
|
+
}
|
|
187
213
|
function renderEntitlements(config) {
|
|
188
214
|
const entries = [];
|
|
189
|
-
|
|
215
|
+
const associatedDomains = associatedDomainEntries(config.associatedDomains);
|
|
216
|
+
if (associatedDomains.length) {
|
|
190
217
|
entries.push(` <key>com.apple.developer.associated-domains</key>
|
|
191
218
|
<array>
|
|
192
|
-
${plistArray(
|
|
219
|
+
${plistArray(associatedDomains)}
|
|
193
220
|
</array>`);
|
|
194
221
|
}
|
|
195
222
|
if (config.appGroups?.length) {
|
|
@@ -414,6 +441,102 @@ function renderLaunchScreen(config) {
|
|
|
414
441
|
return ` <key>UIColorName</key>
|
|
415
442
|
<string>LaunchBackground</string>${image}`;
|
|
416
443
|
}
|
|
444
|
+
function hexChannels(hex) {
|
|
445
|
+
const value = hex?.trim().replace(/^#/, "") ?? "";
|
|
446
|
+
const full = value.length === 3 ? [...value].map((digit) => digit + digit).join("") : value.slice(0, 6);
|
|
447
|
+
if (!/^[0-9a-f]{6}$/i.test(full))
|
|
448
|
+
return null;
|
|
449
|
+
return [0, 2, 4].map((index) => Number.parseInt(full.slice(index, index + 2), 16));
|
|
450
|
+
}
|
|
451
|
+
function readableTextOn(background) {
|
|
452
|
+
const channels = hexChannels(background);
|
|
453
|
+
if (!channels)
|
|
454
|
+
return "#0f172a";
|
|
455
|
+
const [r, g, b] = channels.map((channel) => {
|
|
456
|
+
const c = channel / 255;
|
|
457
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
458
|
+
});
|
|
459
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
460
|
+
return luminance > 0.4 ? "#0f172a" : "#f8fafc";
|
|
461
|
+
}
|
|
462
|
+
function renderOfflinePage(config) {
|
|
463
|
+
const light = hexChannels(config.backgroundColor) ? config.backgroundColor : "#ffffff";
|
|
464
|
+
const dark = hexChannels(config.backgroundColorDark) ? config.backgroundColorDark : light;
|
|
465
|
+
const name = xmlEscape(config.appName);
|
|
466
|
+
return `<!DOCTYPE html>
|
|
467
|
+
<html lang="en">
|
|
468
|
+
<head>
|
|
469
|
+
<meta charset="UTF-8">
|
|
470
|
+
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
471
|
+
<meta name="color-scheme" content="light dark">
|
|
472
|
+
<title>${name}</title>
|
|
473
|
+
<style>
|
|
474
|
+
:root { --background: ${light}; --text: ${readableTextOn(light)}; }
|
|
475
|
+
@media (prefers-color-scheme: dark) {
|
|
476
|
+
:root { --background: ${dark}; --text: ${readableTextOn(dark)}; }
|
|
477
|
+
}
|
|
478
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
479
|
+
html, body { height: 100%; }
|
|
480
|
+
body {
|
|
481
|
+
font: -apple-system-body;
|
|
482
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
483
|
+
background: var(--background);
|
|
484
|
+
color: var(--text);
|
|
485
|
+
display: flex;
|
|
486
|
+
align-items: center;
|
|
487
|
+
justify-content: center;
|
|
488
|
+
text-align: center;
|
|
489
|
+
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);
|
|
490
|
+
-webkit-user-select: none;
|
|
491
|
+
user-select: none;
|
|
492
|
+
-webkit-tap-highlight-color: transparent;
|
|
493
|
+
}
|
|
494
|
+
main { max-width: 22rem; }
|
|
495
|
+
.app { font: -apple-system-subheadline; font-weight: 600; opacity: 0.6; margin-bottom: 0.5rem; }
|
|
496
|
+
h1 { font: -apple-system-title1; font-weight: 700; margin-bottom: 0.75rem; }
|
|
497
|
+
p { opacity: 0.72; line-height: 1.4; }
|
|
498
|
+
button {
|
|
499
|
+
font: -apple-system-headline;
|
|
500
|
+
margin-top: 1.75rem;
|
|
501
|
+
min-width: 9rem;
|
|
502
|
+
min-height: 44px;
|
|
503
|
+
padding: 0.75rem 1.75rem;
|
|
504
|
+
border: 0;
|
|
505
|
+
border-radius: 999px;
|
|
506
|
+
background: var(--text);
|
|
507
|
+
color: var(--background);
|
|
508
|
+
cursor: pointer;
|
|
509
|
+
}
|
|
510
|
+
button:active { opacity: 0.7; }
|
|
511
|
+
button:disabled { opacity: 0.5; }
|
|
512
|
+
</style>
|
|
513
|
+
</head>
|
|
514
|
+
<body>
|
|
515
|
+
<main>
|
|
516
|
+
<div class="app">${name}</div>
|
|
517
|
+
<h1>You're offline</h1>
|
|
518
|
+
<p>${name} could not reach the internet. Check your connection, then try again.</p>
|
|
519
|
+
<button id="retry" type="button">Retry</button>
|
|
520
|
+
</main>
|
|
521
|
+
<script>
|
|
522
|
+
(function () {
|
|
523
|
+
var button = document.getElementById('retry');
|
|
524
|
+
function retry() {
|
|
525
|
+
button.disabled = true;
|
|
526
|
+
button.textContent = 'Retrying';
|
|
527
|
+
var handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.craft;
|
|
528
|
+
if (handler) handler.postMessage({ action: 'retryRemote' });
|
|
529
|
+
else location.reload();
|
|
530
|
+
// Still here a while later means the page is still out of reach.
|
|
531
|
+
setTimeout(function () { button.disabled = false; button.textContent = 'Retry'; }, 12000);
|
|
532
|
+
}
|
|
533
|
+
button.addEventListener('click', retry);
|
|
534
|
+
})();
|
|
535
|
+
</script>
|
|
536
|
+
</body>
|
|
537
|
+
</html>
|
|
538
|
+
`;
|
|
539
|
+
}
|
|
417
540
|
function syncWebAssets(source, output) {
|
|
418
541
|
const sourcePath = resolve(source);
|
|
419
542
|
if (!existsSync(sourcePath))
|
|
@@ -522,14 +645,15 @@ async function init(options) {
|
|
|
522
645
|
config.enableGeolocation = true;
|
|
523
646
|
writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
524
647
|
const swiftTemplate = readFileSync(join(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
525
|
-
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
648
|
+
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{CRAFT_VERSION\}\}/g, CRAFT_VERSION);
|
|
526
649
|
writeFileSync(join(output, "Sources", `${name}App.swift`), swiftSource);
|
|
527
650
|
cpSync(join(TEMPLATES_DIR, "CraftNativeScreen.swift"), join(output, "Sources", "CraftNativeScreen.swift"));
|
|
528
651
|
cpSync(join(TEMPLATES_DIR, "CraftNativeFlatList.swift"), join(output, "Sources", "CraftNativeFlatList.swift"));
|
|
529
652
|
cpSync(join(TEMPLATES_DIR, "CraftNativeActions.swift"), join(output, "Sources", "CraftNativeActions.swift"));
|
|
530
653
|
cpSync(join(TEMPLATES_DIR, "CraftNativeMutation.swift"), join(output, "Sources", "CraftNativeMutation.swift"));
|
|
654
|
+
cpSync(join(TEMPLATES_DIR, "CraftNativeUI.swift"), join(output, "Sources", "CraftNativeUI.swift"));
|
|
531
655
|
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>
|
|
656
|
+
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
657
|
<true/>
|
|
534
658
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
535
659
|
<true/>` : "");
|
|
@@ -619,45 +743,7 @@ ${nativeDependencies.join(`
|
|
|
619
743
|
writeFileSync(join(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
620
744
|
writeFileSync(join(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
621
745
|
}
|
|
622
|
-
|
|
623
|
-
<html>
|
|
624
|
-
<head>
|
|
625
|
-
<meta charset="UTF-8">
|
|
626
|
-
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
627
|
-
<title>${xmlEscape(displayName)}</title>
|
|
628
|
-
<style>
|
|
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);
|
|
746
|
+
writeFileSync(join(output, "dist", "index.html"), renderOfflinePage(config));
|
|
661
747
|
console.log("\u2705 Project initialized");
|
|
662
748
|
console.log("");
|
|
663
749
|
console.log("Next steps:");
|
|
@@ -844,12 +930,15 @@ export {
|
|
|
844
930
|
renderRuntimeSettings,
|
|
845
931
|
renderPrivacyManifest,
|
|
846
932
|
renderOrientations,
|
|
933
|
+
renderOfflinePage,
|
|
847
934
|
renderLaunchScreen,
|
|
848
935
|
renderEntitlements,
|
|
849
936
|
renderDeviceFamilies,
|
|
937
|
+
renderBackgroundTaskIdentifiers,
|
|
850
938
|
renderBackgroundModes,
|
|
851
939
|
renderAppearance,
|
|
852
940
|
renderAppBoundDomains,
|
|
941
|
+
readableTextOn,
|
|
853
942
|
productName,
|
|
854
943
|
pngHasAlpha,
|
|
855
944
|
pickSimulator,
|
|
@@ -859,5 +948,7 @@ export {
|
|
|
859
948
|
init,
|
|
860
949
|
build,
|
|
861
950
|
bootSimulator,
|
|
951
|
+
backgroundRefreshIdentifier,
|
|
952
|
+
associatedDomainEntries,
|
|
862
953
|
LAUNCH_LOGO
|
|
863
954
|
};
|