craft-native 0.0.55 → 0.0.72
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/src/index.d.ts +73 -0
- package/dist/android/src/index.js +177 -37
- package/dist/android/templates/AndroidManifest.xml.template +10 -36
- package/dist/android/templates/CraftBridge.kt.template +192 -60
- package/dist/android/templates/CraftHealthConnect.kt.template +200 -0
- package/dist/android/templates/CraftHealthConnectStub.kt.template +28 -0
- package/dist/android/templates/LocationRecordingService.kt.template +160 -0
- package/dist/android/templates/MainActivity.kt.template +140 -19
- package/dist/android/templates/build.gradle.kts.app.template +23 -1
- package/dist/android/templates/build.gradle.kts.project.template +3 -2
- package/dist/android/templates/proguard-rules.pro.template +9 -0
- package/dist/api/android-advanced.d.ts +5 -6
- package/dist/api/app.d.ts +0 -1
- package/dist/api/clipboard.d.ts +0 -1
- package/dist/api/crypto.d.ts +1 -2
- package/dist/api/db.d.ts +1 -2
- package/dist/api/dialog.d.ts +0 -1
- package/dist/api/fs.d.ts +1 -2
- package/dist/api/gestures.d.ts +66 -0
- package/dist/api/http.d.ts +1 -2
- package/dist/api/index.d.ts +37 -34
- package/dist/api/ios-advanced.d.ts +6 -7
- package/dist/api/macos-advanced.d.ts +2 -3
- package/dist/api/media.d.ts +0 -1
- package/dist/api/mobile.d.ts +109 -1
- package/dist/api/process.d.ts +0 -1
- package/dist/api/sidebar.d.ts +3 -20
- package/dist/api/spaces.d.ts +83 -0
- package/dist/api/tray.d.ts +2 -3
- package/dist/api/window.d.ts +1 -2
- package/dist/api/windows-advanced.d.ts +6 -7
- package/dist/assets/index.d.ts +0 -1
- package/dist/binary-resolver.d.ts +23 -1
- package/dist/bridge/android.d.ts +0 -1
- package/dist/bridge/core.d.ts +0 -38
- package/dist/bridge/ids.d.ts +0 -1
- package/dist/bridge/ios.d.ts +0 -1
- package/dist/bridge/webkit-pending.d.ts +0 -1
- package/dist/build/bundle-optimizer.d.ts +0 -1
- package/dist/cli.js +666 -90
- package/dist/components/index.d.ts +6 -7
- package/dist/components/native.d.ts +0 -1
- package/dist/components/sidebar.d.ts +2 -81
- package/dist/dev/devtools.d.ts +0 -1
- package/dist/dev/error-overlay.d.ts +0 -1
- package/dist/dev/hot-reload.d.ts +0 -1
- package/dist/index.cjs +113 -3
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +112 -4
- package/dist/ios/src/index.d.ts +172 -0
- package/dist/ios/src/index.js +440 -42
- package/dist/ios/templates/CraftActivityAttributes.swift +14 -0
- package/dist/ios/templates/CraftApp.swift +1001 -73
- package/dist/ios/templates/CraftLiveActivityWidget.swift.template +62 -0
- package/dist/ios/templates/CraftWatchApp.swift.template +155 -0
- package/dist/ios/templates/Info.plist.template +10 -9
- package/dist/ios/templates/WatchApp.Info.plist.template +26 -0
- package/dist/ios/templates/WidgetExtension.Info.plist +31 -0
- package/dist/ios/templates/project.yml.template +12 -1
- package/dist/logging/index.d.ts +0 -1
- package/dist/mobile.d.ts +8 -0
- package/dist/mobile.js +755 -0
- package/dist/optimizations/index.d.ts +3 -4
- package/dist/package.d.ts +11 -1
- package/dist/performance/runtime.d.ts +0 -1
- package/dist/performance/startup.d.ts +0 -1
- package/dist/plugins/index.d.ts +0 -1
- package/dist/security/index.d.ts +0 -1
- package/dist/sidebar-types.d.ts +102 -0
- package/dist/signing/index.d.ts +0 -1
- package/dist/styles/headwind.d.ts +0 -1
- package/dist/styles/index.d.ts +9 -10
- package/dist/styles/sidebar-templates.d.ts +0 -1
- package/dist/styles/sidebars.d.ts +0 -1
- package/dist/types.d.ts +131 -1
- package/dist/updater/index.d.ts +0 -1
- package/dist/utils/audio.d.ts +0 -1
- package/dist/utils/index.d.ts +4 -5
- package/dist/utils/native.d.ts +1 -2
- package/dist/utils/react.d.ts +0 -1
- package/dist/utils/storage.d.ts +0 -1
- package/dist/utils/svelte.d.ts +0 -1
- package/dist/utils/timer.d.ts +0 -1
- package/dist/utils/vue.d.ts +0 -1
- package/package.json +32 -4
- package/dist/android/src/index.js.map +0 -10
- package/dist/api/android-advanced.d.ts.map +0 -1
- package/dist/api/app.d.ts.map +0 -1
- package/dist/api/clipboard.d.ts.map +0 -1
- package/dist/api/crypto.d.ts.map +0 -1
- package/dist/api/db.d.ts.map +0 -1
- package/dist/api/dialog.d.ts.map +0 -1
- package/dist/api/fs.d.ts.map +0 -1
- package/dist/api/http.d.ts.map +0 -1
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/ios-advanced.d.ts.map +0 -1
- package/dist/api/macos-advanced.d.ts.map +0 -1
- package/dist/api/media.d.ts.map +0 -1
- package/dist/api/mobile.d.ts.map +0 -1
- package/dist/api/process.d.ts.map +0 -1
- package/dist/api/sidebar.d.ts.map +0 -1
- package/dist/api/tray.d.ts.map +0 -1
- package/dist/api/window.d.ts.map +0 -1
- package/dist/api/windows-advanced.d.ts.map +0 -1
- package/dist/assets/index.d.ts.map +0 -1
- package/dist/binary-resolver.d.ts.map +0 -1
- package/dist/bridge/android.d.ts.map +0 -1
- package/dist/bridge/core.d.ts.map +0 -1
- package/dist/bridge/ids.d.ts.map +0 -1
- package/dist/bridge/ios.d.ts.map +0 -1
- package/dist/bridge/webkit-pending.d.ts.map +0 -1
- package/dist/build/bundle-optimizer.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/native.d.ts.map +0 -1
- package/dist/components/sidebar.d.ts.map +0 -1
- package/dist/dev/devtools.d.ts.map +0 -1
- package/dist/dev/error-overlay.d.ts.map +0 -1
- package/dist/dev/hot-reload.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -45
- package/dist/ios/src/index.js.map +0 -10
- package/dist/logging/index.d.ts.map +0 -1
- package/dist/optimizations/index.d.ts.map +0 -1
- package/dist/package.d.ts.map +0 -1
- package/dist/package.test.d.ts +0 -2
- package/dist/package.test.d.ts.map +0 -1
- package/dist/performance/runtime.d.ts.map +0 -1
- package/dist/performance/startup.d.ts.map +0 -1
- package/dist/plugins/index.d.ts.map +0 -1
- package/dist/security/index.d.ts.map +0 -1
- package/dist/signing/index.d.ts.map +0 -1
- package/dist/styles/headwind.d.ts.map +0 -1
- package/dist/styles/index.d.ts.map +0 -1
- package/dist/styles/sidebar-templates.d.ts.map +0 -1
- package/dist/styles/sidebars.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/updater/index.d.ts.map +0 -1
- package/dist/utils/audio.d.ts.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/native.d.ts.map +0 -1
- package/dist/utils/react.d.ts.map +0 -1
- package/dist/utils/storage.d.ts.map +0 -1
- package/dist/utils/svelte.d.ts.map +0 -1
- package/dist/utils/timer.d.ts.map +0 -1
- package/dist/utils/vue.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -32,7 +32,8 @@ __export(exports_package, {
|
|
|
32
32
|
formatPackagingCommandError: () => formatPackagingCommandError,
|
|
33
33
|
dmgCreateArguments: () => dmgCreateArguments,
|
|
34
34
|
dmgCapacityMegabytes: () => dmgCapacityMegabytes,
|
|
35
|
-
codesignArguments: () => codesignArguments
|
|
35
|
+
codesignArguments: () => codesignArguments,
|
|
36
|
+
candleArguments: () => candleArguments
|
|
36
37
|
});
|
|
37
38
|
import { spawn } from "child_process";
|
|
38
39
|
import { createHash } from "crypto";
|
|
@@ -622,6 +623,9 @@ function windowsExecutableName(name) {
|
|
|
622
623
|
throw new Error(`Reserved Windows application name: ${JSON.stringify(name)}`);
|
|
623
624
|
return `${name}.exe`;
|
|
624
625
|
}
|
|
626
|
+
function candleArguments(architecture, wixobjPath, wxsPath) {
|
|
627
|
+
return ["-nologo", "-arch", architecture, "-out", wixobjPath, wxsPath];
|
|
628
|
+
}
|
|
625
629
|
function runCommand(command, args, cwd) {
|
|
626
630
|
return new Promise((resolve, reject) => {
|
|
627
631
|
const child = spawn(command, args, { cwd, stdio: "inherit", windowsHide: true });
|
|
@@ -638,7 +642,7 @@ async function createMSI(opts) {
|
|
|
638
642
|
const wixobjPath = join(tempDir, "installer.wixobj");
|
|
639
643
|
copyFileSync(opts.binaryPath, sourcePath);
|
|
640
644
|
writeFileSync(wxsPath, renderWixSource(opts, binaryName));
|
|
641
|
-
await runCommand("candle.exe",
|
|
645
|
+
await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
|
|
642
646
|
await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
|
|
643
647
|
if (opts.certificatePath) {
|
|
644
648
|
const signArgs = ["sign", "/fd", "sha256", "/tr", "https://timestamp.digicert.com", "/td", "sha256", "/f", opts.certificatePath];
|
|
@@ -970,20 +974,261 @@ var init_package = __esm(() => {
|
|
|
970
974
|
// dist/ios/src/index.js
|
|
971
975
|
var exports_src = {};
|
|
972
976
|
__export(exports_src, {
|
|
977
|
+
syncWebAssets: () => syncWebAssets,
|
|
978
|
+
showSimulator: () => showSimulator,
|
|
973
979
|
run: () => run,
|
|
980
|
+
renderWatchEntitlements: () => renderWatchEntitlements,
|
|
981
|
+
renderUsageDescriptions: () => renderUsageDescriptions,
|
|
982
|
+
renderUrlTypes: () => renderUrlTypes,
|
|
983
|
+
renderPrivacyManifest: () => renderPrivacyManifest,
|
|
984
|
+
renderOrientations: () => renderOrientations,
|
|
985
|
+
renderEntitlements: () => renderEntitlements,
|
|
986
|
+
renderDeviceFamilies: () => renderDeviceFamilies,
|
|
987
|
+
renderBackgroundModes: () => renderBackgroundModes,
|
|
988
|
+
pickSimulator: () => pickSimulator,
|
|
989
|
+
orderSimulators: () => orderSimulators,
|
|
974
990
|
open: () => open,
|
|
975
991
|
init: () => init,
|
|
976
|
-
build: () => build
|
|
992
|
+
build: () => build,
|
|
993
|
+
bootSimulator: () => bootSimulator
|
|
977
994
|
});
|
|
978
|
-
import {
|
|
979
|
-
import { join as join2,
|
|
995
|
+
import { cpSync as cpSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
996
|
+
import { dirname, join as join2, resolve } from "path";
|
|
997
|
+
function renderDeviceFamilies(config) {
|
|
998
|
+
const values = new Set(((config.deviceFamilies?.length) ? config.deviceFamilies : ["iphone", "ipad"]).map((family) => family === "ipad" ? "2" : "1"));
|
|
999
|
+
return [...values].join(",");
|
|
1000
|
+
}
|
|
1001
|
+
function xmlEscape(value) {
|
|
1002
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll(/\u0027/g, "'");
|
|
1003
|
+
}
|
|
1004
|
+
function plistString(key, value) {
|
|
1005
|
+
return ` <key>${key}</key>
|
|
1006
|
+
<string>${xmlEscape(value)}</string>`;
|
|
1007
|
+
}
|
|
1008
|
+
function renderUsageDescriptions(config) {
|
|
1009
|
+
const entries = [
|
|
1010
|
+
[config.enableSpeechRecognition, "NSSpeechRecognitionUsageDescription", `${config.appName} uses speech recognition for voice input.`],
|
|
1011
|
+
[config.enableSpeechRecognition || config.enableAudioRecording, "NSMicrophoneUsageDescription", `${config.appName} uses the microphone to record audio.`],
|
|
1012
|
+
[config.enableCamera || config.enableVideoRecording || config.enableQRScanner || config.enableAR, "NSCameraUsageDescription", `${config.appName} uses the camera for photos, video, scanning, and augmented reality.`],
|
|
1013
|
+
[config.enableCamera || config.enableVideoRecording, "NSPhotoLibraryUsageDescription", `${config.appName} lets you choose photos and videos from your library.`],
|
|
1014
|
+
[config.enableGeolocation || config.enableBackgroundLocation, "NSLocationWhenInUseUsageDescription", `${config.appName} uses your location while you record an activity.`],
|
|
1015
|
+
[config.enableBackgroundLocation, "NSLocationAlwaysAndWhenInUseUsageDescription", `${config.appName} continues recording your route when the screen is locked or the app is in the background.`],
|
|
1016
|
+
[config.enableContacts, "NSContactsUsageDescription", `${config.appName} accesses contacts only when you choose a contact feature.`],
|
|
1017
|
+
[config.enableCalendar, "NSCalendarsUsageDescription", `${config.appName} accesses your calendar only when you choose a calendar feature.`],
|
|
1018
|
+
[config.enableBluetooth, "NSBluetoothAlwaysUsageDescription", `${config.appName} uses Bluetooth to connect to nearby devices.`],
|
|
1019
|
+
[config.enableMotionSensors, "NSMotionUsageDescription", `${config.appName} uses motion data for activity features.`],
|
|
1020
|
+
[config.enableNFC, "NFCReaderUsageDescription", `${config.appName} reads NFC tags when you start a scan.`],
|
|
1021
|
+
[config.enableHealthKit, "NSHealthShareUsageDescription", `${config.appName} reads health data you choose to share.`],
|
|
1022
|
+
[config.enableHealthKit, "NSHealthUpdateUsageDescription", `${config.appName} writes health data only with your permission.`],
|
|
1023
|
+
[config.enableBiometric, "NSFaceIDUsageDescription", `${config.appName} uses Face ID to protect your account.`]
|
|
1024
|
+
];
|
|
1025
|
+
return entries.filter(([enabled2]) => enabled2).map(([, key, value]) => plistString(key, value)).join(`
|
|
1026
|
+
`);
|
|
1027
|
+
}
|
|
1028
|
+
function renderOrientations(config) {
|
|
1029
|
+
const names = {
|
|
1030
|
+
portrait: "UIInterfaceOrientationPortrait",
|
|
1031
|
+
"landscape-left": "UIInterfaceOrientationLandscapeLeft",
|
|
1032
|
+
"landscape-right": "UIInterfaceOrientationLandscapeRight",
|
|
1033
|
+
"portrait-upside-down": "UIInterfaceOrientationPortraitUpsideDown"
|
|
1034
|
+
};
|
|
1035
|
+
const values = config.orientations?.length ? config.orientations : ["portrait"];
|
|
1036
|
+
return values.map((value) => ` <string>${names[value]}</string>`).join(`
|
|
1037
|
+
`);
|
|
1038
|
+
}
|
|
1039
|
+
function renderUrlTypes(config) {
|
|
1040
|
+
const schemes = [...new Set(config.urlSchemes?.map((value) => value.trim()).filter(Boolean) ?? [])];
|
|
1041
|
+
if (!schemes.length)
|
|
1042
|
+
return "";
|
|
1043
|
+
return ` <key>CFBundleURLTypes</key>
|
|
1044
|
+
<array>
|
|
1045
|
+
<dict>
|
|
1046
|
+
<key>CFBundleURLSchemes</key>
|
|
1047
|
+
<array>
|
|
1048
|
+
${schemes.map((value) => ` <string>${xmlEscape(value)}</string>`).join(`
|
|
1049
|
+
`)}
|
|
1050
|
+
</array>
|
|
1051
|
+
</dict>
|
|
1052
|
+
</array>`;
|
|
1053
|
+
}
|
|
1054
|
+
function plistArray(values, indent = 2) {
|
|
1055
|
+
const padding = " ".repeat(indent);
|
|
1056
|
+
return values.map((value) => `${padding}<string>${xmlEscape(value)}</string>`).join(`
|
|
1057
|
+
`);
|
|
1058
|
+
}
|
|
1059
|
+
function renderBackgroundModes(config) {
|
|
1060
|
+
const modes = new Set;
|
|
1061
|
+
if (config.enableBackgroundLocation)
|
|
1062
|
+
modes.add("location");
|
|
1063
|
+
if (config.enableBackgroundTasks)
|
|
1064
|
+
modes.add("processing");
|
|
1065
|
+
if (config.enablePushNotifications)
|
|
1066
|
+
modes.add("remote-notification");
|
|
1067
|
+
if (!modes.size)
|
|
1068
|
+
return "";
|
|
1069
|
+
return ` <key>UIBackgroundModes</key>
|
|
1070
|
+
<array>
|
|
1071
|
+
${plistArray([...modes])}
|
|
1072
|
+
</array>`;
|
|
1073
|
+
}
|
|
1074
|
+
function renderEntitlements(config) {
|
|
1075
|
+
const entries = [];
|
|
1076
|
+
if (config.associatedDomains?.length) {
|
|
1077
|
+
entries.push(` <key>com.apple.developer.associated-domains</key>
|
|
1078
|
+
<array>
|
|
1079
|
+
${plistArray(config.associatedDomains)}
|
|
1080
|
+
</array>`);
|
|
1081
|
+
}
|
|
1082
|
+
if (config.appGroups?.length) {
|
|
1083
|
+
entries.push(` <key>com.apple.security.application-groups</key>
|
|
1084
|
+
<array>
|
|
1085
|
+
${plistArray(config.appGroups)}
|
|
1086
|
+
</array>`);
|
|
1087
|
+
}
|
|
1088
|
+
if (config.enableHealthKit) {
|
|
1089
|
+
entries.push(` <key>com.apple.developer.healthkit</key>
|
|
1090
|
+
<true/>`);
|
|
1091
|
+
}
|
|
1092
|
+
if (config.enablePushNotifications) {
|
|
1093
|
+
entries.push(` <key>aps-environment</key>
|
|
1094
|
+
<string>development</string>`);
|
|
1095
|
+
}
|
|
1096
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1097
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1098
|
+
<plist version="1.0">
|
|
1099
|
+
<dict>
|
|
1100
|
+
${entries.join(`
|
|
1101
|
+
`)}
|
|
1102
|
+
</dict>
|
|
1103
|
+
</plist>
|
|
1104
|
+
`;
|
|
1105
|
+
}
|
|
1106
|
+
function renderWatchEntitlements(config) {
|
|
1107
|
+
const appGroups = config.appGroups?.length ? ` <key>com.apple.security.application-groups</key>
|
|
1108
|
+
<array>
|
|
1109
|
+
${plistArray(config.appGroups)}
|
|
1110
|
+
</array>
|
|
1111
|
+
` : "";
|
|
1112
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1113
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1114
|
+
<plist version="1.0">
|
|
1115
|
+
<dict>
|
|
1116
|
+
${appGroups}</dict>
|
|
1117
|
+
</plist>
|
|
1118
|
+
`;
|
|
1119
|
+
}
|
|
1120
|
+
function renderPrivacyManifest(config) {
|
|
1121
|
+
const privacy = config.privacy ?? {};
|
|
1122
|
+
const collected = privacy.collectedDataTypes ?? [];
|
|
1123
|
+
const accessed = privacy.accessedApiTypes ?? [];
|
|
1124
|
+
const collectedXml = collected.map((item) => ` <dict>
|
|
1125
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
1126
|
+
<string>${xmlEscape(item.type)}</string>
|
|
1127
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
1128
|
+
<${item.linked ? "true" : "false"}/>
|
|
1129
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
1130
|
+
<${item.tracking ? "true" : "false"}/>
|
|
1131
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
1132
|
+
<array>
|
|
1133
|
+
${plistArray(item.purposes, 4)}
|
|
1134
|
+
</array>
|
|
1135
|
+
</dict>`).join(`
|
|
1136
|
+
`);
|
|
1137
|
+
const accessedXml = accessed.map((item) => ` <dict>
|
|
1138
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
1139
|
+
<string>${xmlEscape(item.type)}</string>
|
|
1140
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
1141
|
+
<array>
|
|
1142
|
+
${plistArray(item.reasons, 4)}
|
|
1143
|
+
</array>
|
|
1144
|
+
</dict>`).join(`
|
|
1145
|
+
`);
|
|
1146
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1147
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1148
|
+
<plist version="1.0">
|
|
1149
|
+
<dict>
|
|
1150
|
+
<key>NSPrivacyTracking</key>
|
|
1151
|
+
<${privacy.tracking ? "true" : "false"}/>
|
|
1152
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
1153
|
+
<array>
|
|
1154
|
+
${plistArray(privacy.trackingDomains ?? [])}
|
|
1155
|
+
</array>
|
|
1156
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
1157
|
+
<array>
|
|
1158
|
+
${collectedXml}
|
|
1159
|
+
</array>
|
|
1160
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
1161
|
+
<array>
|
|
1162
|
+
${accessedXml}
|
|
1163
|
+
</array>
|
|
1164
|
+
</dict>
|
|
1165
|
+
</plist>
|
|
1166
|
+
`;
|
|
1167
|
+
}
|
|
1168
|
+
function renderAssetCatalog(output, config) {
|
|
1169
|
+
const catalog = join2(output, "Assets.xcassets");
|
|
1170
|
+
const appIcon = join2(catalog, "AppIcon.appiconset");
|
|
1171
|
+
const launchBackground = join2(catalog, "LaunchBackground.colorset");
|
|
1172
|
+
mkdirSync2(appIcon, { recursive: true });
|
|
1173
|
+
mkdirSync2(launchBackground, { recursive: true });
|
|
1174
|
+
writeFileSync2(join2(catalog, "Contents.json"), `${JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2)}
|
|
1175
|
+
`);
|
|
1176
|
+
const iconFilename = config.appIconPath ? "AppIcon-1024.png" : undefined;
|
|
1177
|
+
if (config.appIconPath) {
|
|
1178
|
+
if (!existsSync4(config.appIconPath))
|
|
1179
|
+
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
1180
|
+
cpSync2(config.appIconPath, join2(appIcon, iconFilename));
|
|
1181
|
+
}
|
|
1182
|
+
writeFileSync2(join2(appIcon, "Contents.json"), `${JSON.stringify({
|
|
1183
|
+
images: iconFilename ? [{
|
|
1184
|
+
filename: iconFilename,
|
|
1185
|
+
idiom: "universal",
|
|
1186
|
+
platform: "ios",
|
|
1187
|
+
size: "1024x1024"
|
|
1188
|
+
}] : [],
|
|
1189
|
+
info: { author: "xcode", version: 1 }
|
|
1190
|
+
}, null, 2)}
|
|
1191
|
+
`);
|
|
1192
|
+
const color = config.backgroundColor?.replace(/^#/, "") || "000000";
|
|
1193
|
+
const normalized = color.length === 3 ? [...color].map((value) => `${value}${value}`).join("") : color.padEnd(6, "0").slice(0, 6);
|
|
1194
|
+
const components = [0, 2, 4].map((index) => (Number.parseInt(normalized.slice(index, index + 2), 16) / 255).toFixed(3));
|
|
1195
|
+
writeFileSync2(join2(launchBackground, "Contents.json"), `${JSON.stringify({
|
|
1196
|
+
colors: [{
|
|
1197
|
+
color: {
|
|
1198
|
+
"color-space": "srgb",
|
|
1199
|
+
components: { alpha: "1.000", blue: components[2], green: components[1], red: components[0] }
|
|
1200
|
+
},
|
|
1201
|
+
idiom: "universal"
|
|
1202
|
+
}],
|
|
1203
|
+
info: { author: "xcode", version: 1 }
|
|
1204
|
+
}, null, 2)}
|
|
1205
|
+
`);
|
|
1206
|
+
}
|
|
1207
|
+
function syncWebAssets(source, output) {
|
|
1208
|
+
const sourcePath = resolve(source);
|
|
1209
|
+
if (!existsSync4(sourcePath))
|
|
1210
|
+
throw new Error(`Web asset path not found: ${source}`);
|
|
1211
|
+
const distDir = join2(output, "dist");
|
|
1212
|
+
rmSync2(distDir, { recursive: true, force: true });
|
|
1213
|
+
mkdirSync2(distDir, { recursive: true });
|
|
1214
|
+
if (statSync(sourcePath).isDirectory()) {
|
|
1215
|
+
cpSync2(sourcePath, distDir, { recursive: true });
|
|
1216
|
+
} else {
|
|
1217
|
+
cpSync2(sourcePath, join2(distDir, "index.html"));
|
|
1218
|
+
}
|
|
1219
|
+
if (!existsSync4(join2(distDir, "index.html"))) {
|
|
1220
|
+
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
980
1223
|
async function init(options) {
|
|
981
1224
|
const { name, bundleId, teamId, output } = options;
|
|
982
1225
|
console.log(`
|
|
983
1226
|
\u26A1 Initializing Craft iOS project: ${name}`);
|
|
984
1227
|
console.log(` Output: ${output}
|
|
985
1228
|
`);
|
|
986
|
-
const dirs = [output, join2(output, "Sources"), join2(output, "dist")];
|
|
1229
|
+
const dirs = [output, join2(output, "Sources"), join2(output, "Shared"), join2(output, "dist")];
|
|
1230
|
+
if (options.config?.enableWatchApp)
|
|
1231
|
+
dirs.push(join2(output, "WatchApp"));
|
|
987
1232
|
for (const dir of dirs) {
|
|
988
1233
|
if (!existsSync4(dir)) {
|
|
989
1234
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -992,27 +1237,84 @@ async function init(options) {
|
|
|
992
1237
|
const finalBundleId = bundleId || `com.craft.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
993
1238
|
const bundleIdPrefix = finalBundleId.split(".").slice(0, -1).join(".");
|
|
994
1239
|
const config = {
|
|
1240
|
+
...DEFAULT_CONFIG,
|
|
995
1241
|
appName: name,
|
|
996
1242
|
bundleId: finalBundleId,
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
darkMode: true,
|
|
1000
|
-
backgroundColor: "#1a1a2e",
|
|
1001
|
-
enableSpeechRecognition: true,
|
|
1002
|
-
enableHaptics: true,
|
|
1003
|
-
enableShare: true,
|
|
1004
|
-
iosVersion: "15.0",
|
|
1005
|
-
teamId: teamId || ""
|
|
1243
|
+
teamId: teamId || "",
|
|
1244
|
+
...options.config
|
|
1006
1245
|
};
|
|
1246
|
+
if (config.enableBackgroundLocation)
|
|
1247
|
+
config.enableGeolocation = true;
|
|
1007
1248
|
writeFileSync2(join2(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1008
1249
|
const swiftTemplate = readFileSync2(join2(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
1009
|
-
|
|
1250
|
+
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
1251
|
+
writeFileSync2(join2(output, "Sources", `${name}App.swift`), swiftSource);
|
|
1010
1252
|
const infoPlistTemplate = readFileSync2(join2(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
1011
|
-
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, config.darkMode ? "Dark" : "Light")
|
|
1253
|
+
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, config.darkMode ? "Dark" : "Light").replace(/\{\{ORIENTATIONS\}\}/g, renderOrientations(config)).replace(/\{\{USAGE_DESCRIPTIONS\}\}/g, renderUsageDescriptions(config)).replace(/\{\{URL_TYPES\}\}/g, renderUrlTypes(config)).replace(/\{\{BACKGROUND_MODES\}\}/g, renderBackgroundModes(config)).replace(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
1254
|
+
<true/>
|
|
1255
|
+
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
1256
|
+
<true/>` : "");
|
|
1012
1257
|
writeFileSync2(join2(output, "Info.plist"), infoPlist);
|
|
1013
1258
|
const projectYmlTemplate = readFileSync2(join2(TEMPLATES_DIR, "project.yml.template"), "utf-8");
|
|
1014
|
-
const
|
|
1259
|
+
const nativeDependencies = [];
|
|
1260
|
+
if (config.enableLiveActivities)
|
|
1261
|
+
nativeDependencies.push(` - target: ${name}LiveActivity`);
|
|
1262
|
+
if (config.enableWatchApp)
|
|
1263
|
+
nativeDependencies.push(` - target: ${name}Watch
|
|
1264
|
+
embed: true`);
|
|
1265
|
+
const nativeTargets = [];
|
|
1266
|
+
if (config.enableLiveActivities) {
|
|
1267
|
+
nativeTargets.push(` ${name}LiveActivity:
|
|
1268
|
+
type: app-extension
|
|
1269
|
+
platform: iOS
|
|
1270
|
+
deploymentTarget: "16.2"
|
|
1271
|
+
sources:
|
|
1272
|
+
- WidgetExtension
|
|
1273
|
+
- Shared
|
|
1274
|
+
settings:
|
|
1275
|
+
INFOPLIST_FILE: WidgetExtension/Info.plist
|
|
1276
|
+
PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.liveactivity
|
|
1277
|
+
SWIFT_VERSION: "5.0"
|
|
1278
|
+
APPLICATION_EXTENSION_API_ONLY: YES
|
|
1279
|
+
SKIP_INSTALL: YES`);
|
|
1280
|
+
}
|
|
1281
|
+
if (config.enableWatchApp) {
|
|
1282
|
+
nativeTargets.push(` ${name}Watch:
|
|
1283
|
+
type: application
|
|
1284
|
+
platform: watchOS
|
|
1285
|
+
deploymentTarget: "${config.watchosVersion || "9.0"}"
|
|
1286
|
+
sources:
|
|
1287
|
+
- WatchApp
|
|
1288
|
+
settings:
|
|
1289
|
+
INFOPLIST_FILE: WatchApp/Info.plist
|
|
1290
|
+
CODE_SIGN_ENTITLEMENTS: WatchApp/Watch.entitlements
|
|
1291
|
+
PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp
|
|
1292
|
+
SWIFT_VERSION: "5.0"
|
|
1293
|
+
SKIP_INSTALL: YES`);
|
|
1294
|
+
}
|
|
1295
|
+
const projectYml = projectYmlTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{BUNDLE_ID_PREFIX\}\}/g, bundleIdPrefix).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{IOS_VERSION\}\}/g, config.iosVersion || "15.0").replace(/\{\{DEVICE_FAMILIES\}\}/g, renderDeviceFamilies(config)).replace(/\{\{TEAM_ID\}\}/g, teamId || "").replace(/\{\{NATIVE_DEPENDENCIES\}\}/g, nativeDependencies.length ? ` dependencies:
|
|
1296
|
+
${nativeDependencies.join(`
|
|
1297
|
+
`)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
|
|
1298
|
+
`));
|
|
1015
1299
|
writeFileSync2(join2(output, "project.yml"), projectYml);
|
|
1300
|
+
writeFileSync2(join2(output, "Craft.entitlements"), renderEntitlements(config));
|
|
1301
|
+
writeFileSync2(join2(output, "PrivacyInfo.xcprivacy"), renderPrivacyManifest(config));
|
|
1302
|
+
renderAssetCatalog(output, config);
|
|
1303
|
+
cpSync2(join2(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join2(output, "Shared", "CraftActivityAttributes.swift"));
|
|
1304
|
+
if (config.enableLiveActivities) {
|
|
1305
|
+
mkdirSync2(join2(output, "WidgetExtension"), { recursive: true });
|
|
1306
|
+
const widgetSource = readFileSync2(join2(TEMPLATES_DIR, "CraftLiveActivityWidget.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
1307
|
+
writeFileSync2(join2(output, "WidgetExtension", `${name}LiveActivity.swift`), widgetSource);
|
|
1308
|
+
const widgetInfo = readFileSync2(join2(TEMPLATES_DIR, "WidgetExtension.Info.plist"), "utf8");
|
|
1309
|
+
writeFileSync2(join2(output, "WidgetExtension", "Info.plist"), widgetInfo);
|
|
1310
|
+
}
|
|
1311
|
+
if (config.enableWatchApp) {
|
|
1312
|
+
const watchSource = readFileSync2(join2(TEMPLATES_DIR, "CraftWatchApp.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
1313
|
+
writeFileSync2(join2(output, "WatchApp", `${name}WatchApp.swift`), watchSource);
|
|
1314
|
+
const watchInfo = readFileSync2(join2(TEMPLATES_DIR, "WatchApp.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
1315
|
+
writeFileSync2(join2(output, "WatchApp", "Info.plist"), watchInfo);
|
|
1316
|
+
writeFileSync2(join2(output, "WatchApp", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
1317
|
+
}
|
|
1016
1318
|
const placeholderHtml = `<!DOCTYPE html>
|
|
1017
1319
|
<html>
|
|
1018
1320
|
<head>
|
|
@@ -1062,7 +1364,7 @@ async function init(options) {
|
|
|
1062
1364
|
console.log("");
|
|
1063
1365
|
}
|
|
1064
1366
|
async function build(options) {
|
|
1065
|
-
const { htmlPath, devServer, output } = options;
|
|
1367
|
+
const { htmlPath, devServer, output, generateProject = true } = options;
|
|
1066
1368
|
console.log(`
|
|
1067
1369
|
\uD83D\uDCE6 Building Craft iOS project...`);
|
|
1068
1370
|
const configPath = join2(output, "craft.config.json");
|
|
@@ -1072,25 +1374,17 @@ async function build(options) {
|
|
|
1072
1374
|
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
1073
1375
|
if (devServer) {
|
|
1074
1376
|
config.devServerURL = devServer;
|
|
1377
|
+
const origin = new URL(devServer).origin;
|
|
1378
|
+
config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], origin])];
|
|
1075
1379
|
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
1076
1380
|
console.log(` Dev server: ${devServer}`);
|
|
1077
1381
|
}
|
|
1078
1382
|
if (htmlPath) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
mkdirSync2(distDir, { recursive: true });
|
|
1082
|
-
}
|
|
1083
|
-
if (existsSync4(htmlPath)) {
|
|
1084
|
-
const stat = await Bun.file(htmlPath).exists();
|
|
1085
|
-
if (stat) {
|
|
1086
|
-
const html = readFileSync2(htmlPath, "utf-8");
|
|
1087
|
-
writeFileSync2(join2(distDir, "index.html"), html);
|
|
1088
|
-
console.log(` Copied: ${htmlPath} \u2192 dist/index.html`);
|
|
1089
|
-
}
|
|
1090
|
-
} else {
|
|
1091
|
-
throw new Error(`HTML path not found: ${htmlPath}`);
|
|
1092
|
-
}
|
|
1383
|
+
syncWebAssets(htmlPath, output);
|
|
1384
|
+
console.log(` Synced: ${htmlPath} \u2192 dist/`);
|
|
1093
1385
|
}
|
|
1386
|
+
if (!generateProject)
|
|
1387
|
+
return;
|
|
1094
1388
|
try {
|
|
1095
1389
|
const result = await $`which xcodegen`.quiet();
|
|
1096
1390
|
if (result.exitCode === 0) {
|
|
@@ -1100,10 +1394,10 @@ async function build(options) {
|
|
|
1100
1394
|
} else {
|
|
1101
1395
|
throw new Error("xcodegen not found");
|
|
1102
1396
|
}
|
|
1103
|
-
} catch {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
throw new Error("Unable to generate the Xcode project. Install xcodegen with `brew install xcodegen`.", {
|
|
1399
|
+
cause: error
|
|
1400
|
+
});
|
|
1107
1401
|
}
|
|
1108
1402
|
console.log("");
|
|
1109
1403
|
}
|
|
@@ -1118,6 +1412,51 @@ async function open(options) {
|
|
|
1118
1412
|
console.log(`\uD83D\uDE80 Opening ${xcodeproj}...`);
|
|
1119
1413
|
await $`open ${projectPath}`;
|
|
1120
1414
|
}
|
|
1415
|
+
function orderSimulators(devices) {
|
|
1416
|
+
return [...devices].sort((left, right) => {
|
|
1417
|
+
const booted = Number(right.state === "Booted") - Number(left.state === "Booted");
|
|
1418
|
+
if (booted !== 0)
|
|
1419
|
+
return booted;
|
|
1420
|
+
const phone = Number(right.name.startsWith("iPhone")) - Number(left.name.startsWith("iPhone"));
|
|
1421
|
+
if (phone !== 0)
|
|
1422
|
+
return phone;
|
|
1423
|
+
return right.runtime.localeCompare(left.runtime);
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
async function pickSimulator() {
|
|
1427
|
+
const listed = await $`xcrun simctl list devices available --json`.quiet().nothrow();
|
|
1428
|
+
if (listed.exitCode !== 0)
|
|
1429
|
+
return null;
|
|
1430
|
+
let parsed;
|
|
1431
|
+
try {
|
|
1432
|
+
parsed = JSON.parse(listed.stdout.toString());
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
const devices = [];
|
|
1437
|
+
for (const [runtime, list] of Object.entries(parsed.devices ?? {})) {
|
|
1438
|
+
for (const device of list)
|
|
1439
|
+
devices.push({ ...device, runtime: runtime.split(".").pop() ?? runtime });
|
|
1440
|
+
}
|
|
1441
|
+
return orderSimulators(devices)[0] ?? null;
|
|
1442
|
+
}
|
|
1443
|
+
async function bootSimulator(device) {
|
|
1444
|
+
if (device.state !== "Booted")
|
|
1445
|
+
await $`xcrun simctl boot ${device.udid}`.quiet().nothrow();
|
|
1446
|
+
await $`xcrun simctl bootstatus ${device.udid}`.quiet().nothrow();
|
|
1447
|
+
}
|
|
1448
|
+
async function showSimulator() {
|
|
1449
|
+
const selected = await $`xcode-select -p`.quiet().nothrow();
|
|
1450
|
+
const developer = selected.exitCode === 0 ? selected.stdout.toString().trim() : "";
|
|
1451
|
+
if (developer) {
|
|
1452
|
+
const app = join2(developer, "Applications", "Simulator.app");
|
|
1453
|
+
if (existsSync4(app)) {
|
|
1454
|
+
await $`open ${app}`.quiet().nothrow();
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
await $`open -a Simulator`.quiet().nothrow();
|
|
1459
|
+
}
|
|
1121
1460
|
async function run(options) {
|
|
1122
1461
|
const { simulator, output } = options;
|
|
1123
1462
|
await build({ output });
|
|
@@ -1128,15 +1467,31 @@ async function run(options) {
|
|
|
1128
1467
|
}
|
|
1129
1468
|
const projectPath = join2(output, xcodeproj);
|
|
1130
1469
|
const appName = xcodeproj.replace(".xcodeproj", "");
|
|
1470
|
+
const configPath = join2(output, "craft.config.json");
|
|
1471
|
+
if (!existsSync4(configPath))
|
|
1472
|
+
throw new Error(`No craft.config.json found in ${output}. Run 'craft-ios init' first.`);
|
|
1473
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
1131
1474
|
if (simulator) {
|
|
1132
1475
|
console.log("\uD83D\uDCF1 Building and running on simulator...");
|
|
1476
|
+
const device = await pickSimulator();
|
|
1477
|
+
if (!device) {
|
|
1478
|
+
throw new Error("No iOS simulator is available. Install a runtime with " + "`xcodebuild -downloadPlatform iOS`, then try again.");
|
|
1479
|
+
}
|
|
1480
|
+
console.log(` Device: ${device.name} (${device.runtime})`);
|
|
1133
1481
|
try {
|
|
1134
|
-
await
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1482
|
+
await bootSimulator(device);
|
|
1483
|
+
const derivedData = join2(output, "build");
|
|
1484
|
+
const product = join2(derivedData, "Build", "Products", "Debug-iphonesimulator", `${appName}.app`);
|
|
1485
|
+
await $`xcodebuild -project ${projectPath} -scheme ${appName} -configuration Debug -destination ${`id=${device.udid}`} -derivedDataPath ${derivedData} build`;
|
|
1486
|
+
if (!existsSync4(product)) {
|
|
1487
|
+
throw new Error(`xcodebuild reported success but ${product} does not exist. ` + "The scheme may build a different product name than the project.");
|
|
1488
|
+
}
|
|
1489
|
+
await $`xcrun simctl install ${device.udid} ${product}`;
|
|
1490
|
+
await showSimulator();
|
|
1491
|
+
await $`xcrun simctl launch ${device.udid} ${config.bundleId}`;
|
|
1492
|
+
console.log(`\u2705 ${config.appName} is running on ${device.name}`);
|
|
1138
1493
|
} catch (error) {
|
|
1139
|
-
|
|
1494
|
+
throw new Error(`iOS simulator run failed for ${projectPath}`, { cause: error });
|
|
1140
1495
|
}
|
|
1141
1496
|
} else {
|
|
1142
1497
|
console.log("\uD83D\uDCF1 Opening Xcode for device deployment...");
|
|
@@ -1149,22 +1504,140 @@ async function run(options) {
|
|
|
1149
1504
|
console.log(" 4. Click Run (\u25B6\uFE0F)");
|
|
1150
1505
|
}
|
|
1151
1506
|
}
|
|
1152
|
-
var $, TEMPLATES_DIR;
|
|
1507
|
+
var $, TEMPLATES_DIR, DEFAULT_CONFIG;
|
|
1153
1508
|
var init_src = __esm(() => {
|
|
1154
1509
|
({ $ } = globalThis.Bun);
|
|
1155
1510
|
TEMPLATES_DIR = join2(dirname(import.meta.dir), "templates");
|
|
1511
|
+
DEFAULT_CONFIG = {
|
|
1512
|
+
version: "1.0.0",
|
|
1513
|
+
buildNumber: "1",
|
|
1514
|
+
darkMode: true,
|
|
1515
|
+
backgroundColor: "#0b1712",
|
|
1516
|
+
enableSpeechRecognition: false,
|
|
1517
|
+
enableHaptics: false,
|
|
1518
|
+
enableShare: false,
|
|
1519
|
+
enableCamera: false,
|
|
1520
|
+
enableBiometric: false,
|
|
1521
|
+
enablePushNotifications: false,
|
|
1522
|
+
enableSecureStorage: false,
|
|
1523
|
+
enableGeolocation: false,
|
|
1524
|
+
enableClipboard: false,
|
|
1525
|
+
enableContacts: false,
|
|
1526
|
+
enableCalendar: false,
|
|
1527
|
+
enableLocalNotifications: false,
|
|
1528
|
+
enableInAppPurchase: false,
|
|
1529
|
+
enableKeepAwake: false,
|
|
1530
|
+
enableOrientationLock: false,
|
|
1531
|
+
enableDeepLinks: false,
|
|
1532
|
+
enableQRScanner: false,
|
|
1533
|
+
enableFilePicker: false,
|
|
1534
|
+
enableFileDownload: false,
|
|
1535
|
+
enableSocialAuth: false,
|
|
1536
|
+
enableAudioRecording: false,
|
|
1537
|
+
enableVideoRecording: false,
|
|
1538
|
+
enableMotionSensors: false,
|
|
1539
|
+
enableLocalDatabase: false,
|
|
1540
|
+
enableBluetooth: false,
|
|
1541
|
+
enableNFC: false,
|
|
1542
|
+
enableHealthKit: false,
|
|
1543
|
+
enableLiveActivities: false,
|
|
1544
|
+
enableWatchApp: false,
|
|
1545
|
+
enableBackgroundLocation: false,
|
|
1546
|
+
enableBackgroundTasks: false,
|
|
1547
|
+
enableScreenCapture: false,
|
|
1548
|
+
enablePDFViewer: false,
|
|
1549
|
+
enableAR: false,
|
|
1550
|
+
enableMLKit: false,
|
|
1551
|
+
iosVersion: "16.0",
|
|
1552
|
+
watchosVersion: "9.0",
|
|
1553
|
+
teamId: "",
|
|
1554
|
+
trustedOrigins: [],
|
|
1555
|
+
associatedDomains: [],
|
|
1556
|
+
appGroups: [],
|
|
1557
|
+
orientations: ["portrait"],
|
|
1558
|
+
deviceFamilies: ["iphone", "ipad"]
|
|
1559
|
+
};
|
|
1156
1560
|
});
|
|
1157
1561
|
|
|
1158
1562
|
// dist/android/src/index.js
|
|
1159
1563
|
var exports_src2 = {};
|
|
1160
1564
|
__export(exports_src2, {
|
|
1565
|
+
syncAndroidWebAssets: () => syncAndroidWebAssets,
|
|
1161
1566
|
run: () => run2,
|
|
1567
|
+
renderAndroidPermissions: () => renderAndroidPermissions,
|
|
1568
|
+
renderAndroidDeepLinks: () => renderAndroidDeepLinks,
|
|
1162
1569
|
open: () => open2,
|
|
1163
1570
|
init: () => init2,
|
|
1164
1571
|
build: () => build2
|
|
1165
1572
|
});
|
|
1166
|
-
import {
|
|
1167
|
-
import { join as join3,
|
|
1573
|
+
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1574
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
1575
|
+
function syncAndroidWebAssets(source, output) {
|
|
1576
|
+
const sourcePath = resolve2(source);
|
|
1577
|
+
if (!existsSync5(sourcePath))
|
|
1578
|
+
throw new Error(`Web asset path not found: ${source}`);
|
|
1579
|
+
const assetsDir = join3(output, "app/src/main/assets");
|
|
1580
|
+
const configPath = join3(assetsDir, "craft.config.json");
|
|
1581
|
+
const config = existsSync5(configPath) ? readFileSync3(configPath) : undefined;
|
|
1582
|
+
rmSync3(assetsDir, { recursive: true, force: true });
|
|
1583
|
+
mkdirSync3(assetsDir, { recursive: true });
|
|
1584
|
+
if (statSync2(sourcePath).isDirectory())
|
|
1585
|
+
cpSync3(sourcePath, assetsDir, { recursive: true });
|
|
1586
|
+
else
|
|
1587
|
+
cpSync3(sourcePath, join3(assetsDir, "index.html"));
|
|
1588
|
+
if (!existsSync5(join3(assetsDir, "index.html")))
|
|
1589
|
+
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
1590
|
+
if (config)
|
|
1591
|
+
writeFileSync3(configPath, config);
|
|
1592
|
+
}
|
|
1593
|
+
function renderAndroidPermissions(config) {
|
|
1594
|
+
const permissions = new Set(["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"]);
|
|
1595
|
+
if (config.enableSpeechRecognition)
|
|
1596
|
+
permissions.add("android.permission.RECORD_AUDIO");
|
|
1597
|
+
if (config.enableCamera)
|
|
1598
|
+
permissions.add("android.permission.CAMERA");
|
|
1599
|
+
if (config.enableHaptics)
|
|
1600
|
+
permissions.add("android.permission.VIBRATE");
|
|
1601
|
+
if (config.enableBiometric)
|
|
1602
|
+
permissions.add("android.permission.USE_BIOMETRIC");
|
|
1603
|
+
if (config.enableGeolocation || config.enableBackgroundLocation) {
|
|
1604
|
+
permissions.add("android.permission.ACCESS_FINE_LOCATION");
|
|
1605
|
+
permissions.add("android.permission.ACCESS_COARSE_LOCATION");
|
|
1606
|
+
}
|
|
1607
|
+
if (config.enableBackgroundLocation) {
|
|
1608
|
+
permissions.add("android.permission.ACCESS_BACKGROUND_LOCATION");
|
|
1609
|
+
permissions.add("android.permission.FOREGROUND_SERVICE");
|
|
1610
|
+
permissions.add("android.permission.FOREGROUND_SERVICE_LOCATION");
|
|
1611
|
+
}
|
|
1612
|
+
if (config.enablePushNotifications)
|
|
1613
|
+
permissions.add("android.permission.POST_NOTIFICATIONS");
|
|
1614
|
+
if (config.enableKeepAwake)
|
|
1615
|
+
permissions.add("android.permission.WAKE_LOCK");
|
|
1616
|
+
if (config.enableHealthConnect) {
|
|
1617
|
+
permissions.add("android.permission.health.READ_STEPS");
|
|
1618
|
+
permissions.add("android.permission.health.READ_HEART_RATE");
|
|
1619
|
+
permissions.add("android.permission.health.READ_ACTIVE_CALORIES_BURNED");
|
|
1620
|
+
permissions.add("android.permission.health.READ_DISTANCE");
|
|
1621
|
+
permissions.add("android.permission.health.READ_EXERCISE");
|
|
1622
|
+
permissions.add("android.permission.health.WRITE_EXERCISE");
|
|
1623
|
+
permissions.add("android.permission.health.WRITE_ACTIVE_CALORIES_BURNED");
|
|
1624
|
+
permissions.add("android.permission.health.WRITE_DISTANCE");
|
|
1625
|
+
}
|
|
1626
|
+
return [...permissions].map((permission) => ` <uses-permission android:name="${permission}" />`).join(`
|
|
1627
|
+
`);
|
|
1628
|
+
}
|
|
1629
|
+
function renderAndroidDeepLinks(config) {
|
|
1630
|
+
if (!config.enableDeepLinks)
|
|
1631
|
+
return "";
|
|
1632
|
+
const schemes = [...new Set(config.urlSchemes?.map((value) => value.trim()).filter((value) => /^[a-z][a-z0-9+.-]*$/i.test(value)) ?? [])];
|
|
1633
|
+
return schemes.map((scheme) => ` <intent-filter>
|
|
1634
|
+
<action android:name="android.intent.action.VIEW" />
|
|
1635
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
1636
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
1637
|
+
<data android:scheme="${scheme}" />
|
|
1638
|
+
</intent-filter>`).join(`
|
|
1639
|
+
`);
|
|
1640
|
+
}
|
|
1168
1641
|
async function init2(options) {
|
|
1169
1642
|
const { name, packageName, output } = options;
|
|
1170
1643
|
console.log(`
|
|
@@ -1188,36 +1661,73 @@ async function init2(options) {
|
|
|
1188
1661
|
}
|
|
1189
1662
|
}
|
|
1190
1663
|
const config = {
|
|
1664
|
+
...DEFAULT_CONFIG2,
|
|
1191
1665
|
appName: name,
|
|
1192
1666
|
packageName: finalPackageName,
|
|
1193
|
-
|
|
1194
|
-
versionCode: 1,
|
|
1195
|
-
darkMode: true,
|
|
1196
|
-
backgroundColor: "#1a1a2e",
|
|
1197
|
-
enableSpeechRecognition: true,
|
|
1198
|
-
enableHaptics: true,
|
|
1199
|
-
enableShare: true,
|
|
1200
|
-
enableCamera: true,
|
|
1201
|
-
enableBiometric: true,
|
|
1202
|
-
enablePushNotifications: false,
|
|
1203
|
-
minSdk: 24,
|
|
1204
|
-
targetSdk: 34
|
|
1667
|
+
...options.config
|
|
1205
1668
|
};
|
|
1669
|
+
if (config.enableBackgroundLocation)
|
|
1670
|
+
config.enableGeolocation = true;
|
|
1206
1671
|
writeFileSync3(join3(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1672
|
+
writeFileSync3(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
1673
|
+
const hasGoogleServices = Boolean(config.googleServicesFile);
|
|
1674
|
+
if (config.googleServicesFile) {
|
|
1675
|
+
if (!existsSync5(config.googleServicesFile))
|
|
1676
|
+
throw new Error(`Google services file not found: ${config.googleServicesFile}`);
|
|
1677
|
+
cpSync3(config.googleServicesFile, join3(output, "app/google-services.json"));
|
|
1678
|
+
}
|
|
1207
1679
|
const mainActivityTemplate = readFileSync3(join3(TEMPLATES_DIR2, "MainActivity.kt.template"), "utf-8");
|
|
1208
1680
|
const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
|
|
1209
1681
|
writeFileSync3(join3(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
|
|
1210
1682
|
const craftBridgeTemplate = readFileSync3(join3(TEMPLATES_DIR2, "CraftBridge.kt.template"), "utf-8");
|
|
1211
|
-
const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName)
|
|
1683
|
+
const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{ENABLE_SPEECH\}\}/g, String(Boolean(config.enableSpeechRecognition))).replace(/\{\{ENABLE_HAPTICS\}\}/g, String(Boolean(config.enableHaptics))).replace(/\{\{ENABLE_SHARE\}\}/g, String(Boolean(config.enableShare))).replace(/\{\{ENABLE_CAMERA\}\}/g, String(Boolean(config.enableCamera))).replace(/\{\{ENABLE_BIOMETRIC\}\}/g, String(Boolean(config.enableBiometric))).replace(/\{\{ENABLE_PUSH\}\}/g, String(Boolean(config.enablePushNotifications))).replace(/\{\{ENABLE_SECURE_STORAGE\}\}/g, String(Boolean(config.enableSecureStorage))).replace(/\{\{ENABLE_GEOLOCATION\}\}/g, String(Boolean(config.enableGeolocation))).replace(/\{\{ENABLE_BACKGROUND_LOCATION\}\}/g, String(Boolean(config.enableBackgroundLocation))).replace(/\{\{ENABLE_KEEP_AWAKE\}\}/g, String(Boolean(config.enableKeepAwake))).replace(/\{\{ENABLE_DEEP_LINKS\}\}/g, String(Boolean(config.enableDeepLinks))).replace(/\{\{ENABLE_HEALTH_CONNECT\}\}/g, String(Boolean(config.enableHealthConnect))).replace(/\{\{FIREBASE_IMPORT\}\}/g, config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace(/\{\{REGISTER_PUSH_IMPLEMENTATION\}\}/g, config.enablePushNotifications ? `activity.runOnUiThread {
|
|
1684
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
1685
|
+
&& ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
|
1686
|
+
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)
|
|
1687
|
+
}
|
|
1688
|
+
try {
|
|
1689
|
+
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
|
|
1690
|
+
val token = if (task.isSuccessful) task.result else null
|
|
1691
|
+
val callback = if (token.isNullOrBlank()) "window._craftPushReject" else "window._craftPushResolve"
|
|
1692
|
+
val payload = JSONObject.quote(token ?: task.exception?.message ?: "Firebase Cloud Messaging is not configured")
|
|
1693
|
+
webView.evaluateJavascript("$callback && $callback($payload)", null)
|
|
1694
|
+
}
|
|
1695
|
+
} catch (error: Exception) {
|
|
1696
|
+
val payload = JSONObject.quote(error.message ?: "Firebase Cloud Messaging is not configured")
|
|
1697
|
+
webView.evaluateJavascript("window._craftPushReject && window._craftPushReject($payload)", null)
|
|
1698
|
+
}
|
|
1699
|
+
}` : `activity.runOnUiThread {
|
|
1700
|
+
webView.evaluateJavascript(
|
|
1701
|
+
"window._craftPushReject && window._craftPushReject('Push notifications are disabled')",
|
|
1702
|
+
null
|
|
1703
|
+
)
|
|
1704
|
+
}`);
|
|
1212
1705
|
writeFileSync3(join3(output, "app/src/main/java", packagePath, "CraftBridge.kt"), craftBridge);
|
|
1706
|
+
const healthTemplate = readFileSync3(join3(TEMPLATES_DIR2, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
|
|
1707
|
+
writeFileSync3(join3(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
1708
|
+
if (config.enableBackgroundLocation) {
|
|
1709
|
+
const serviceTemplate = readFileSync3(join3(TEMPLATES_DIR2, "LocationRecordingService.kt.template"), "utf-8");
|
|
1710
|
+
writeFileSync3(join3(output, "app/src/main/java", packagePath, "LocationRecordingService.kt"), serviceTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
1711
|
+
}
|
|
1213
1712
|
const manifestTemplate = readFileSync3(join3(TEMPLATES_DIR2, "AndroidManifest.xml.template"), "utf-8");
|
|
1214
|
-
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name)
|
|
1713
|
+
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name=".LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
|
|
1714
|
+
<package android:name="com.google.android.apps.healthdata" />
|
|
1715
|
+
</queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
|
|
1716
|
+
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
|
1717
|
+
</intent-filter>
|
|
1718
|
+
<intent-filter>
|
|
1719
|
+
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
|
1720
|
+
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
|
1721
|
+
</intent-filter>` : "");
|
|
1215
1722
|
writeFileSync3(join3(output, "app/src/main/AndroidManifest.xml"), manifest);
|
|
1216
1723
|
const projectGradleTemplate = readFileSync3(join3(TEMPLATES_DIR2, "build.gradle.kts.project.template"), "utf-8");
|
|
1217
|
-
writeFileSync3(join3(output, "build.gradle.kts"), projectGradleTemplate);
|
|
1724
|
+
writeFileSync3(join3(output, "build.gradle.kts"), projectGradleTemplate.replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services") version "4.4.2" apply false' : ""));
|
|
1218
1725
|
const appGradleTemplate = readFileSync3(join3(TEMPLATES_DIR2, "build.gradle.kts.app.template"), "utf-8");
|
|
1219
|
-
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk ||
|
|
1726
|
+
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{COMPILE_SDK\}\}/g, String(Math.max(config.compileSdk || 36, config.enableHealthConnect ? 36 : 1))).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
|
|
1727
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "");
|
|
1220
1728
|
writeFileSync3(join3(output, "app/build.gradle.kts"), appGradle);
|
|
1729
|
+
const proguardTemplate = readFileSync3(join3(TEMPLATES_DIR2, "proguard-rules.pro.template"), "utf-8");
|
|
1730
|
+
writeFileSync3(join3(output, "app/proguard-rules.pro"), proguardTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
1221
1731
|
const settingsTemplate = readFileSync3(join3(TEMPLATES_DIR2, "settings.gradle.kts.template"), "utf-8");
|
|
1222
1732
|
const settings2 = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, name);
|
|
1223
1733
|
writeFileSync3(join3(output, "settings.gradle.kts"), settings2);
|
|
@@ -1231,7 +1741,7 @@ android.nonTransitiveRClass=true
|
|
|
1231
1741
|
`);
|
|
1232
1742
|
const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
|
|
1233
1743
|
distributionPath=wrapper/dists
|
|
1234
|
-
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.
|
|
1744
|
+
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
|
1235
1745
|
networkTimeout=10000
|
|
1236
1746
|
validateDistributionUrl=true
|
|
1237
1747
|
zipStoreBase=GRADLE_USER_HOME
|
|
@@ -1253,6 +1763,23 @@ zipStorePath=wrapper/dists
|
|
|
1253
1763
|
</resources>
|
|
1254
1764
|
`;
|
|
1255
1765
|
writeFileSync3(join3(output, "app/src/main/res/values/colors.xml"), colorsXml);
|
|
1766
|
+
const appIconXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1767
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1768
|
+
android:width="108dp"
|
|
1769
|
+
android:height="108dp"
|
|
1770
|
+
android:viewportWidth="108"
|
|
1771
|
+
android:viewportHeight="108">
|
|
1772
|
+
<path android:fillColor="${config.backgroundColor}" android:pathData="M0,0h108v108h-108z" />
|
|
1773
|
+
<path android:fillColor="#10B981" android:pathData="M18,78L42,42l12,18l12,-12l24,30z" />
|
|
1774
|
+
</vector>
|
|
1775
|
+
`;
|
|
1776
|
+
if (config.appIconPath) {
|
|
1777
|
+
if (!existsSync5(config.appIconPath))
|
|
1778
|
+
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
1779
|
+
cpSync3(config.appIconPath, join3(output, "app/src/main/res/drawable/craft_app_icon.png"));
|
|
1780
|
+
} else {
|
|
1781
|
+
writeFileSync3(join3(output, "app/src/main/res/drawable/craft_app_icon.xml"), appIconXml);
|
|
1782
|
+
}
|
|
1256
1783
|
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1257
1784
|
<resources>
|
|
1258
1785
|
<style name="Theme.CraftApp" parent="Theme.Material3.DayNight.NoActionBar">
|
|
@@ -1326,7 +1853,7 @@ zipStorePath=wrapper/dists
|
|
|
1326
1853
|
console.log("");
|
|
1327
1854
|
}
|
|
1328
1855
|
async function build2(options) {
|
|
1329
|
-
const { htmlPath, devServer, output, release } = options;
|
|
1856
|
+
const { htmlPath, devServer, output, release, compile = true } = options;
|
|
1330
1857
|
console.log(`
|
|
1331
1858
|
\uD83D\uDCE6 Building Craft Android project...`);
|
|
1332
1859
|
const configPath = join3(output, "craft.config.json");
|
|
@@ -1336,31 +1863,27 @@ async function build2(options) {
|
|
|
1336
1863
|
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1337
1864
|
if (devServer) {
|
|
1338
1865
|
config.devServerURL = devServer;
|
|
1866
|
+
config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], new URL(devServer).origin])];
|
|
1339
1867
|
writeFileSync3(configPath, JSON.stringify(config, null, 2));
|
|
1868
|
+
writeFileSync3(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
1340
1869
|
console.log(` Dev server: ${devServer}`);
|
|
1341
1870
|
}
|
|
1342
1871
|
if (htmlPath) {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
const html = readFileSync3(htmlPath, "utf-8");
|
|
1349
|
-
writeFileSync3(join3(assetsDir, "index.html"), html);
|
|
1350
|
-
console.log(` Copied: ${htmlPath} \u2192 assets/index.html`);
|
|
1351
|
-
} else {
|
|
1352
|
-
throw new Error(`HTML file not found or is a directory: ${htmlPath}`);
|
|
1353
|
-
}
|
|
1872
|
+
syncAndroidWebAssets(htmlPath, output);
|
|
1873
|
+
config.hasBundledFallback = Boolean(devServer);
|
|
1874
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2));
|
|
1875
|
+
writeFileSync3(join3(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
1876
|
+
console.log(` Synced: ${htmlPath} \u2192 assets/`);
|
|
1354
1877
|
}
|
|
1878
|
+
if (!compile)
|
|
1879
|
+
return;
|
|
1355
1880
|
const gradlewPath = join3(output, "gradlew");
|
|
1356
1881
|
if (!existsSync5(gradlewPath)) {
|
|
1357
1882
|
console.log(" Generating Gradle wrapper...");
|
|
1358
1883
|
try {
|
|
1359
1884
|
await $2`cd ${output} && gradle wrapper`.quiet();
|
|
1360
|
-
} catch {
|
|
1361
|
-
|
|
1362
|
-
console.log(" Download: https://developer.android.com/studio");
|
|
1363
|
-
return;
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
throw new Error("Gradle is unavailable. Install Android Studio or Gradle before building Android.", { cause: error });
|
|
1364
1887
|
}
|
|
1365
1888
|
}
|
|
1366
1889
|
const buildType = release ? "assembleRelease" : "assembleDebug";
|
|
@@ -1419,10 +1942,33 @@ async function run2(options) {
|
|
|
1419
1942
|
throw error;
|
|
1420
1943
|
}
|
|
1421
1944
|
}
|
|
1422
|
-
var $2, TEMPLATES_DIR2;
|
|
1945
|
+
var $2, TEMPLATES_DIR2, DEFAULT_CONFIG2;
|
|
1423
1946
|
var init_src2 = __esm(() => {
|
|
1424
1947
|
({ $: $2 } = globalThis.Bun);
|
|
1425
1948
|
TEMPLATES_DIR2 = join3(dirname2(import.meta.dir), "templates");
|
|
1949
|
+
DEFAULT_CONFIG2 = {
|
|
1950
|
+
version: "1.0.0",
|
|
1951
|
+
versionCode: 1,
|
|
1952
|
+
darkMode: true,
|
|
1953
|
+
backgroundColor: "#0b1712",
|
|
1954
|
+
enableSpeechRecognition: false,
|
|
1955
|
+
enableHaptics: false,
|
|
1956
|
+
enableShare: false,
|
|
1957
|
+
enableCamera: false,
|
|
1958
|
+
enableBiometric: false,
|
|
1959
|
+
enablePushNotifications: false,
|
|
1960
|
+
enableSecureStorage: false,
|
|
1961
|
+
enableGeolocation: false,
|
|
1962
|
+
enableBackgroundLocation: false,
|
|
1963
|
+
enableHealthConnect: false,
|
|
1964
|
+
enableKeepAwake: false,
|
|
1965
|
+
enableDeepLinks: false,
|
|
1966
|
+
urlSchemes: [],
|
|
1967
|
+
trustedOrigins: [],
|
|
1968
|
+
minSdk: 26,
|
|
1969
|
+
compileSdk: 36,
|
|
1970
|
+
targetSdk: 35
|
|
1971
|
+
};
|
|
1426
1972
|
});
|
|
1427
1973
|
|
|
1428
1974
|
// ../../node_modules/@stacksjs/clapp/dist/index.js
|
|
@@ -3487,6 +4033,28 @@ function resolveCraftBinary(explicit) {
|
|
|
3487
4033
|
}
|
|
3488
4034
|
return "craft";
|
|
3489
4035
|
}
|
|
4036
|
+
var CRAFT_CLI_SPAWN_MARKER = "CRAFT_CLI_SPAWNED_FROM";
|
|
4037
|
+
function craftBinaryIsCliShimMessage(spawnedFrom) {
|
|
4038
|
+
return [
|
|
4039
|
+
`"${spawnedFrom}" on PATH is the Craft CLI, not the native binary.`,
|
|
4040
|
+
"",
|
|
4041
|
+
"The CLI spawned it expecting the native build, and re-entered itself \u2014",
|
|
4042
|
+
"which is why an option you never typed (--url) came back as unknown.",
|
|
4043
|
+
"",
|
|
4044
|
+
"Fix it in one of two ways:",
|
|
4045
|
+
"",
|
|
4046
|
+
" 1. Install the native binary and make sure it comes first on PATH:",
|
|
4047
|
+
"",
|
|
4048
|
+
" pantry install craft",
|
|
4049
|
+
"",
|
|
4050
|
+
" 2. Point CRAFT_BIN at a build directly:",
|
|
4051
|
+
"",
|
|
4052
|
+
" CRAFT_BIN=/path/to/craft craft <url>",
|
|
4053
|
+
"",
|
|
4054
|
+
"For monorepo development, CRAFT_BIN is the supported way in."
|
|
4055
|
+
].join(`
|
|
4056
|
+
`);
|
|
4057
|
+
}
|
|
3490
4058
|
function craftBinaryNotFoundMessage(triedPath) {
|
|
3491
4059
|
return [
|
|
3492
4060
|
`Craft native binary not found (tried "${triedPath}").`,
|
|
@@ -3504,17 +4072,25 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
3504
4072
|
`);
|
|
3505
4073
|
}
|
|
3506
4074
|
// package.json
|
|
3507
|
-
var version = "0.0.
|
|
4075
|
+
var version = "0.0.72";
|
|
3508
4076
|
|
|
3509
4077
|
// bin/cli.ts
|
|
4078
|
+
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
|
4079
|
+
if (spawnedFrom) {
|
|
4080
|
+
console.error(craftBinaryIsCliShimMessage(spawnedFrom));
|
|
4081
|
+
process6.exit(1);
|
|
4082
|
+
}
|
|
3510
4083
|
var cli = new CLI("craft");
|
|
3511
4084
|
async function runCraftBinary(args) {
|
|
3512
4085
|
const craftPath = resolveCraftBinary();
|
|
3513
|
-
return new Promise((
|
|
3514
|
-
const proc = spawn2(craftPath, args, {
|
|
4086
|
+
return new Promise((resolve3, reject) => {
|
|
4087
|
+
const proc = spawn2(craftPath, args, {
|
|
4088
|
+
stdio: "inherit",
|
|
4089
|
+
env: { ...process6.env, [CRAFT_CLI_SPAWN_MARKER]: craftPath }
|
|
4090
|
+
});
|
|
3515
4091
|
proc.on("exit", (code) => {
|
|
3516
4092
|
if (code === 0 || code === null)
|
|
3517
|
-
|
|
4093
|
+
resolve3();
|
|
3518
4094
|
else
|
|
3519
4095
|
reject(new Error(`Craft exited with code ${code}`));
|
|
3520
4096
|
});
|
|
@@ -3621,7 +4197,7 @@ cli.command("build", "Build the Craft application").option("--release", "Build i
|
|
|
3621
4197
|
const iosDir = "./ios";
|
|
3622
4198
|
if (existsSync7(iosDir)) {
|
|
3623
4199
|
const buildType = options?.release ? "Release" : "Debug";
|
|
3624
|
-
await new Promise((
|
|
4200
|
+
await new Promise((resolve3, reject) => {
|
|
3625
4201
|
const proc = spawn3("xcodebuild", [
|
|
3626
4202
|
"-project",
|
|
3627
4203
|
`${iosDir}/App.xcodeproj`,
|
|
@@ -3633,7 +4209,7 @@ cli.command("build", "Build the Craft application").option("--release", "Build i
|
|
|
3633
4209
|
"iphoneos",
|
|
3634
4210
|
"build"
|
|
3635
4211
|
], { stdio: "inherit" });
|
|
3636
|
-
proc.on("exit", (code) => code === 0 ?
|
|
4212
|
+
proc.on("exit", (code) => code === 0 ? resolve3() : reject(new Error(`Exit ${code}`)));
|
|
3637
4213
|
proc.on("error", reject);
|
|
3638
4214
|
});
|
|
3639
4215
|
console.log(` \u2705 iOS ${buildType} build complete`);
|
|
@@ -3644,12 +4220,12 @@ cli.command("build", "Build the Craft application").option("--release", "Build i
|
|
|
3644
4220
|
const androidDir = "./android";
|
|
3645
4221
|
if (existsSync7(androidDir)) {
|
|
3646
4222
|
const task = options?.release ? "assembleRelease" : "assembleDebug";
|
|
3647
|
-
await new Promise((
|
|
4223
|
+
await new Promise((resolve3, reject) => {
|
|
3648
4224
|
const proc = spawn3("./gradlew", [task], {
|
|
3649
4225
|
cwd: androidDir,
|
|
3650
4226
|
stdio: "inherit"
|
|
3651
4227
|
});
|
|
3652
|
-
proc.on("exit", (code) => code === 0 ?
|
|
4228
|
+
proc.on("exit", (code) => code === 0 ? resolve3() : reject(new Error(`Exit ${code}`)));
|
|
3653
4229
|
proc.on("error", reject);
|
|
3654
4230
|
});
|
|
3655
4231
|
console.log(` \u2705 Android ${options?.release ? "Release" : "Debug"} build complete`);
|
|
@@ -3659,7 +4235,7 @@ cli.command("build", "Build the Craft application").option("--release", "Build i
|
|
|
3659
4235
|
} else if (platform === "macos" || platform === "windows" || platform === "linux") {
|
|
3660
4236
|
const zigDir = existsSync7("./packages/zig") ? "./packages/zig" : ".";
|
|
3661
4237
|
const optimizeFlag = options?.release ? "-Doptimize=ReleaseSafe" : "";
|
|
3662
|
-
await new Promise((
|
|
4238
|
+
await new Promise((resolve3, reject) => {
|
|
3663
4239
|
const args = ["build"];
|
|
3664
4240
|
if (optimizeFlag)
|
|
3665
4241
|
args.push(optimizeFlag);
|
|
@@ -3667,7 +4243,7 @@ cli.command("build", "Build the Craft application").option("--release", "Build i
|
|
|
3667
4243
|
cwd: zigDir,
|
|
3668
4244
|
stdio: "inherit"
|
|
3669
4245
|
});
|
|
3670
|
-
proc.on("exit", (code) => code === 0 ?
|
|
4246
|
+
proc.on("exit", (code) => code === 0 ? resolve3() : reject(new Error(`Exit ${code}`)));
|
|
3671
4247
|
proc.on("error", reject);
|
|
3672
4248
|
});
|
|
3673
4249
|
console.log(` \u2705 ${platform} build complete`);
|
|
@@ -3990,7 +4566,7 @@ cli.command("init <name>", "Initialize a new Craft project").option("--template
|
|
|
3990
4566
|
const template = options?.template || "blank";
|
|
3991
4567
|
const bundleId = options?.bundleId || `com.example.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
3992
4568
|
const appNameSlug = name.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
3993
|
-
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4, existsSync: existsSync7, readdirSync: readdirSync3, readFileSync: readFileSync4, cpSync:
|
|
4569
|
+
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4, existsSync: existsSync7, readdirSync: readdirSync3, readFileSync: readFileSync4, cpSync: cpSync4 } = await import("fs");
|
|
3994
4570
|
const { join: join4, dirname: dirname3 } = await import("path");
|
|
3995
4571
|
const replaceVars = (content) => {
|
|
3996
4572
|
return content.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{APP_NAME_SLUG\}\}/g, appNameSlug).replace(/\{\{BUNDLE_ID\}\}/g, bundleId).replace(/\{\{AUTHOR\}\}/g, "Developer");
|