craft-native 0.0.122 → 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 +251 -0
- 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 +90 -2
- package/dist/ios/src/index.js +177 -44
- package/dist/ios/templates/CraftApp.swift +1341 -51
- 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 +3 -3
- package/dist/mobile.js +187 -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) {
|
|
@@ -373,6 +400,142 @@ function renderAssetCatalog(output, config) {
|
|
|
373
400
|
info: { author: "xcode", version: 1 }
|
|
374
401
|
}, null, 2)}
|
|
375
402
|
`);
|
|
403
|
+
renderLaunchLogo(catalog, config);
|
|
404
|
+
}
|
|
405
|
+
var LAUNCH_LOGO = "LaunchLogo";
|
|
406
|
+
function renderLaunchLogo(catalog, config) {
|
|
407
|
+
const imageset = join(catalog, `${LAUNCH_LOGO}.imageset`);
|
|
408
|
+
rmSync(imageset, { recursive: true, force: true });
|
|
409
|
+
if (!config.splashImagePath)
|
|
410
|
+
return;
|
|
411
|
+
mkdirSync(imageset, { recursive: true });
|
|
412
|
+
const images = [];
|
|
413
|
+
let vector = false;
|
|
414
|
+
for (const [source, dark] of [[config.splashImagePath, false], [config.splashImagePathDark, true]]) {
|
|
415
|
+
if (!source)
|
|
416
|
+
continue;
|
|
417
|
+
if (!existsSync(source))
|
|
418
|
+
throw new Error(`Splash image not found: ${source}`);
|
|
419
|
+
const extension = source.split(".").pop().toLowerCase();
|
|
420
|
+
if (extension === "svg" || extension === "pdf")
|
|
421
|
+
vector = true;
|
|
422
|
+
const filename = `${LAUNCH_LOGO}${dark ? "-dark" : ""}.${extension}`;
|
|
423
|
+
cpSync(source, join(imageset, filename));
|
|
424
|
+
images.push({
|
|
425
|
+
...dark ? { appearances: [{ appearance: "luminosity", value: "dark" }] } : {},
|
|
426
|
+
filename,
|
|
427
|
+
idiom: "universal"
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
writeFileSync(join(imageset, "Contents.json"), `${JSON.stringify({
|
|
431
|
+
images,
|
|
432
|
+
info: { author: "xcode", version: 1 },
|
|
433
|
+
...vector ? { properties: { "preserves-vector-representation": true } } : {}
|
|
434
|
+
}, null, 2)}
|
|
435
|
+
`);
|
|
436
|
+
}
|
|
437
|
+
function renderLaunchScreen(config) {
|
|
438
|
+
const image = config.splashImagePath ? `
|
|
439
|
+
<key>UIImageName</key>
|
|
440
|
+
<string>${LAUNCH_LOGO}</string>` : "";
|
|
441
|
+
return ` <key>UIColorName</key>
|
|
442
|
+
<string>LaunchBackground</string>${image}`;
|
|
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
|
+
`;
|
|
376
539
|
}
|
|
377
540
|
function syncWebAssets(source, output) {
|
|
378
541
|
const sourcePath = resolve(source);
|
|
@@ -482,14 +645,15 @@ async function init(options) {
|
|
|
482
645
|
config.enableGeolocation = true;
|
|
483
646
|
writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
484
647
|
const swiftTemplate = readFileSync(join(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
485
|
-
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);
|
|
486
649
|
writeFileSync(join(output, "Sources", `${name}App.swift`), swiftSource);
|
|
487
650
|
cpSync(join(TEMPLATES_DIR, "CraftNativeScreen.swift"), join(output, "Sources", "CraftNativeScreen.swift"));
|
|
488
651
|
cpSync(join(TEMPLATES_DIR, "CraftNativeFlatList.swift"), join(output, "Sources", "CraftNativeFlatList.swift"));
|
|
489
652
|
cpSync(join(TEMPLATES_DIR, "CraftNativeActions.swift"), join(output, "Sources", "CraftNativeActions.swift"));
|
|
490
653
|
cpSync(join(TEMPLATES_DIR, "CraftNativeMutation.swift"), join(output, "Sources", "CraftNativeMutation.swift"));
|
|
654
|
+
cpSync(join(TEMPLATES_DIR, "CraftNativeUI.swift"), join(output, "Sources", "CraftNativeUI.swift"));
|
|
491
655
|
const infoPlistTemplate = readFileSync(join(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
492
|
-
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(/\{\{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>
|
|
493
657
|
<true/>
|
|
494
658
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
495
659
|
<true/>` : "");
|
|
@@ -579,45 +743,7 @@ ${nativeDependencies.join(`
|
|
|
579
743
|
writeFileSync(join(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
580
744
|
writeFileSync(join(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
581
745
|
}
|
|
582
|
-
|
|
583
|
-
<html>
|
|
584
|
-
<head>
|
|
585
|
-
<meta charset="UTF-8">
|
|
586
|
-
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
587
|
-
<title>${xmlEscape(displayName)}</title>
|
|
588
|
-
<style>
|
|
589
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
590
|
-
body {
|
|
591
|
-
font-family: -apple-system, system-ui, sans-serif;
|
|
592
|
-
background: ${config.backgroundColor};
|
|
593
|
-
color: white;
|
|
594
|
-
min-height: 100vh;
|
|
595
|
-
display: flex;
|
|
596
|
-
justify-content: center;
|
|
597
|
-
align-items: center;
|
|
598
|
-
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
|
599
|
-
}
|
|
600
|
-
.container { text-align: center; padding: 2rem; }
|
|
601
|
-
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
602
|
-
p { opacity: 0.7; }
|
|
603
|
-
.ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }
|
|
604
|
-
</style>
|
|
605
|
-
</head>
|
|
606
|
-
<body>
|
|
607
|
-
<div class="container">
|
|
608
|
-
<h1>\u26A1 ${xmlEscape(displayName)}</h1>
|
|
609
|
-
<p>Built with Craft iOS</p>
|
|
610
|
-
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
611
|
-
</div>
|
|
612
|
-
<script>
|
|
613
|
-
window.addEventListener('craftReady', (e) => {
|
|
614
|
-
document.getElementById('status').textContent = \`\u2713 Craft bridge ready (platform: \${e.detail.platform})\`;
|
|
615
|
-
console.log('Craft capabilities:', e.detail.capabilities);
|
|
616
|
-
});
|
|
617
|
-
</script>
|
|
618
|
-
</body>
|
|
619
|
-
</html>`;
|
|
620
|
-
writeFileSync(join(output, "dist", "index.html"), placeholderHtml);
|
|
746
|
+
writeFileSync(join(output, "dist", "index.html"), renderOfflinePage(config));
|
|
621
747
|
console.log("\u2705 Project initialized");
|
|
622
748
|
console.log("");
|
|
623
749
|
console.log("Next steps:");
|
|
@@ -804,11 +930,15 @@ export {
|
|
|
804
930
|
renderRuntimeSettings,
|
|
805
931
|
renderPrivacyManifest,
|
|
806
932
|
renderOrientations,
|
|
933
|
+
renderOfflinePage,
|
|
934
|
+
renderLaunchScreen,
|
|
807
935
|
renderEntitlements,
|
|
808
936
|
renderDeviceFamilies,
|
|
937
|
+
renderBackgroundTaskIdentifiers,
|
|
809
938
|
renderBackgroundModes,
|
|
810
939
|
renderAppearance,
|
|
811
940
|
renderAppBoundDomains,
|
|
941
|
+
readableTextOn,
|
|
812
942
|
productName,
|
|
813
943
|
pngHasAlpha,
|
|
814
944
|
pickSimulator,
|
|
@@ -817,5 +947,8 @@ export {
|
|
|
817
947
|
installRuntime,
|
|
818
948
|
init,
|
|
819
949
|
build,
|
|
820
|
-
bootSimulator
|
|
950
|
+
bootSimulator,
|
|
951
|
+
backgroundRefreshIdentifier,
|
|
952
|
+
associatedDomainEntries,
|
|
953
|
+
LAUNCH_LOGO
|
|
821
954
|
};
|