craft-native 0.0.120 → 0.0.122
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/dist/android/templates/MainActivityNative.kt.template +378 -54
- package/dist/cli.js +1 -1
- package/dist/components/index.d.ts +9 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/src/index.d.ts +12 -0
- package/dist/ios/src/index.js +56 -3
- package/dist/ios/templates/CraftNativeScreen.swift +414 -52
- package/dist/ios/templates/Info.plist.template +2 -0
- package/dist/ios/templates/project.yml.template +17 -0
- package/package.json +1 -1
package/dist/ios/src/index.js
CHANGED
|
@@ -79,6 +79,18 @@ function renderAppearance(config) {
|
|
|
79
79
|
return { interfaceStyle: "Light", statusBarStyle: "UIStatusBarStyleDarkContent" };
|
|
80
80
|
return { interfaceStyle: "Dark", statusBarStyle: "UIStatusBarStyleLightContent" };
|
|
81
81
|
}
|
|
82
|
+
var LINKED_PURPOSES = {
|
|
83
|
+
NSSpeechRecognitionUsageDescription: "speech recognition",
|
|
84
|
+
NSMicrophoneUsageDescription: "the microphone",
|
|
85
|
+
NSCameraUsageDescription: "the camera",
|
|
86
|
+
NSPhotoLibraryUsageDescription: "your photo library",
|
|
87
|
+
NSLocationWhenInUseUsageDescription: "your location",
|
|
88
|
+
NSContactsUsageDescription: "your contacts",
|
|
89
|
+
NSCalendarsUsageDescription: "your calendar",
|
|
90
|
+
NSBluetoothAlwaysUsageDescription: "Bluetooth",
|
|
91
|
+
NSMotionUsageDescription: "motion data",
|
|
92
|
+
NSFaceIDUsageDescription: "Face ID"
|
|
93
|
+
};
|
|
82
94
|
function renderUsageDescriptions(config) {
|
|
83
95
|
const entries = [
|
|
84
96
|
[config.enableSpeechRecognition, "NSSpeechRecognitionUsageDescription", `${config.appName} uses speech recognition for voice input.`],
|
|
@@ -97,7 +109,12 @@ function renderUsageDescriptions(config) {
|
|
|
97
109
|
[config.enableHealthKit, "NSHealthUpdateUsageDescription", `${config.appName} writes health data only with your permission.`],
|
|
98
110
|
[config.enableBiometric, "NSFaceIDUsageDescription", `${config.appName} uses Face ID to protect your account.`]
|
|
99
111
|
];
|
|
100
|
-
return entries.
|
|
112
|
+
return entries.map(([enabled, key, value]) => {
|
|
113
|
+
if (enabled)
|
|
114
|
+
return [key, value];
|
|
115
|
+
const linked = LINKED_PURPOSES[key];
|
|
116
|
+
return linked ? [key, `${config.appName} asks for ${linked} only in a feature that needs it.`] : null;
|
|
117
|
+
}).filter((entry) => entry !== null).map(([key, value]) => plistString(key, value)).join(`
|
|
101
118
|
`);
|
|
102
119
|
}
|
|
103
120
|
function renderOrientations(config) {
|
|
@@ -275,6 +292,41 @@ ${accessedXml}
|
|
|
275
292
|
</plist>
|
|
276
293
|
`;
|
|
277
294
|
}
|
|
295
|
+
function pngHasAlpha(bytes) {
|
|
296
|
+
const signature = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
297
|
+
if (bytes.length < 33 || signature.some((byte, index) => bytes[index] !== byte))
|
|
298
|
+
return false;
|
|
299
|
+
const colourType = bytes[25];
|
|
300
|
+
if (colourType === 4 || colourType === 6)
|
|
301
|
+
return true;
|
|
302
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
303
|
+
for (let offset = 8;offset + 8 <= bytes.length; ) {
|
|
304
|
+
const length = view.getUint32(offset);
|
|
305
|
+
const type = String.fromCharCode(bytes[offset + 4], bytes[offset + 5], bytes[offset + 6], bytes[offset + 7]);
|
|
306
|
+
if (type === "tRNS")
|
|
307
|
+
return true;
|
|
308
|
+
if (type === "IDAT" || type === "IEND")
|
|
309
|
+
return false;
|
|
310
|
+
offset += 12 + length;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function writeOpaqueIcon(source, target) {
|
|
315
|
+
const bytes = new Uint8Array(readFileSync(source));
|
|
316
|
+
if (!pngHasAlpha(bytes) || process.platform !== "darwin") {
|
|
317
|
+
cpSync(source, target);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const flattened = `${target}.flatten.jpg`;
|
|
321
|
+
const run = (args) => Bun.spawnSync(["sips", ...args], { stdout: "ignore", stderr: "pipe" });
|
|
322
|
+
const toJpeg = run(["-s", "format", "jpeg", "-s", "formatOptions", "100", source, "--out", flattened]);
|
|
323
|
+
const toPng = toJpeg.exitCode === 0 ? run(["-s", "format", "png", flattened, "--out", target]) : toJpeg;
|
|
324
|
+
rmSync(flattened, { force: true });
|
|
325
|
+
if (toPng.exitCode !== 0) {
|
|
326
|
+
cpSync(source, target);
|
|
327
|
+
console.warn(`[craft] Could not remove the app icon's alpha channel; App Store Connect will refuse it: ${toPng.stderr.toString().trim()}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
278
330
|
function renderAssetCatalog(output, config) {
|
|
279
331
|
const catalog = join(output, "Assets.xcassets");
|
|
280
332
|
const appIcon = join(catalog, "AppIcon.appiconset");
|
|
@@ -287,7 +339,7 @@ function renderAssetCatalog(output, config) {
|
|
|
287
339
|
if (config.appIconPath) {
|
|
288
340
|
if (!existsSync(config.appIconPath))
|
|
289
341
|
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
290
|
-
|
|
342
|
+
writeOpaqueIcon(config.appIconPath, join(appIcon, iconFilename));
|
|
291
343
|
}
|
|
292
344
|
writeFileSync(join(appIcon, "Contents.json"), `${JSON.stringify({
|
|
293
345
|
images: iconFilename ? [{
|
|
@@ -437,7 +489,7 @@ async function init(options) {
|
|
|
437
489
|
cpSync(join(TEMPLATES_DIR, "CraftNativeActions.swift"), join(output, "Sources", "CraftNativeActions.swift"));
|
|
438
490
|
cpSync(join(TEMPLATES_DIR, "CraftNativeMutation.swift"), join(output, "Sources", "CraftNativeMutation.swift"));
|
|
439
491
|
const infoPlistTemplate = readFileSync(join(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
440
|
-
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(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
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>
|
|
441
493
|
<true/>
|
|
442
494
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
443
495
|
<true/>` : "");
|
|
@@ -758,6 +810,7 @@ export {
|
|
|
758
810
|
renderAppearance,
|
|
759
811
|
renderAppBoundDomains,
|
|
760
812
|
productName,
|
|
813
|
+
pngHasAlpha,
|
|
761
814
|
pickSimulator,
|
|
762
815
|
orderSimulators,
|
|
763
816
|
open,
|