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/ios/src/index.js
CHANGED
|
@@ -1,16 +1,293 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../ios/src/index.ts
|
|
3
|
-
import {
|
|
4
|
-
import { join,
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
|
|
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 DEFAULT_CONFIG = {
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
buildNumber: "1",
|
|
10
|
+
darkMode: true,
|
|
11
|
+
backgroundColor: "#0b1712",
|
|
12
|
+
enableSpeechRecognition: false,
|
|
13
|
+
enableHaptics: false,
|
|
14
|
+
enableShare: false,
|
|
15
|
+
enableCamera: false,
|
|
16
|
+
enableBiometric: false,
|
|
17
|
+
enablePushNotifications: false,
|
|
18
|
+
enableSecureStorage: false,
|
|
19
|
+
enableGeolocation: false,
|
|
20
|
+
enableClipboard: false,
|
|
21
|
+
enableContacts: false,
|
|
22
|
+
enableCalendar: false,
|
|
23
|
+
enableLocalNotifications: false,
|
|
24
|
+
enableInAppPurchase: false,
|
|
25
|
+
enableKeepAwake: false,
|
|
26
|
+
enableOrientationLock: false,
|
|
27
|
+
enableDeepLinks: false,
|
|
28
|
+
enableQRScanner: false,
|
|
29
|
+
enableFilePicker: false,
|
|
30
|
+
enableFileDownload: false,
|
|
31
|
+
enableSocialAuth: false,
|
|
32
|
+
enableAudioRecording: false,
|
|
33
|
+
enableVideoRecording: false,
|
|
34
|
+
enableMotionSensors: false,
|
|
35
|
+
enableLocalDatabase: false,
|
|
36
|
+
enableBluetooth: false,
|
|
37
|
+
enableNFC: false,
|
|
38
|
+
enableHealthKit: false,
|
|
39
|
+
enableLiveActivities: false,
|
|
40
|
+
enableWatchApp: false,
|
|
41
|
+
enableBackgroundLocation: false,
|
|
42
|
+
enableBackgroundTasks: false,
|
|
43
|
+
enableScreenCapture: false,
|
|
44
|
+
enablePDFViewer: false,
|
|
45
|
+
enableAR: false,
|
|
46
|
+
enableMLKit: false,
|
|
47
|
+
iosVersion: "16.0",
|
|
48
|
+
watchosVersion: "9.0",
|
|
49
|
+
teamId: "",
|
|
50
|
+
trustedOrigins: [],
|
|
51
|
+
associatedDomains: [],
|
|
52
|
+
appGroups: [],
|
|
53
|
+
orientations: ["portrait"],
|
|
54
|
+
deviceFamilies: ["iphone", "ipad"]
|
|
55
|
+
};
|
|
56
|
+
function renderDeviceFamilies(config) {
|
|
57
|
+
const values = new Set(((config.deviceFamilies?.length) ? config.deviceFamilies : ["iphone", "ipad"]).map((family) => family === "ipad" ? "2" : "1"));
|
|
58
|
+
return [...values].join(",");
|
|
59
|
+
}
|
|
60
|
+
function xmlEscape(value) {
|
|
61
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll(/\u0027/g, "'");
|
|
62
|
+
}
|
|
63
|
+
function plistString(key, value) {
|
|
64
|
+
return ` <key>${key}</key>
|
|
65
|
+
<string>${xmlEscape(value)}</string>`;
|
|
66
|
+
}
|
|
67
|
+
function renderUsageDescriptions(config) {
|
|
68
|
+
const entries = [
|
|
69
|
+
[config.enableSpeechRecognition, "NSSpeechRecognitionUsageDescription", `${config.appName} uses speech recognition for voice input.`],
|
|
70
|
+
[config.enableSpeechRecognition || config.enableAudioRecording, "NSMicrophoneUsageDescription", `${config.appName} uses the microphone to record audio.`],
|
|
71
|
+
[config.enableCamera || config.enableVideoRecording || config.enableQRScanner || config.enableAR, "NSCameraUsageDescription", `${config.appName} uses the camera for photos, video, scanning, and augmented reality.`],
|
|
72
|
+
[config.enableCamera || config.enableVideoRecording, "NSPhotoLibraryUsageDescription", `${config.appName} lets you choose photos and videos from your library.`],
|
|
73
|
+
[config.enableGeolocation || config.enableBackgroundLocation, "NSLocationWhenInUseUsageDescription", `${config.appName} uses your location while you record an activity.`],
|
|
74
|
+
[config.enableBackgroundLocation, "NSLocationAlwaysAndWhenInUseUsageDescription", `${config.appName} continues recording your route when the screen is locked or the app is in the background.`],
|
|
75
|
+
[config.enableContacts, "NSContactsUsageDescription", `${config.appName} accesses contacts only when you choose a contact feature.`],
|
|
76
|
+
[config.enableCalendar, "NSCalendarsUsageDescription", `${config.appName} accesses your calendar only when you choose a calendar feature.`],
|
|
77
|
+
[config.enableBluetooth, "NSBluetoothAlwaysUsageDescription", `${config.appName} uses Bluetooth to connect to nearby devices.`],
|
|
78
|
+
[config.enableMotionSensors, "NSMotionUsageDescription", `${config.appName} uses motion data for activity features.`],
|
|
79
|
+
[config.enableNFC, "NFCReaderUsageDescription", `${config.appName} reads NFC tags when you start a scan.`],
|
|
80
|
+
[config.enableHealthKit, "NSHealthShareUsageDescription", `${config.appName} reads health data you choose to share.`],
|
|
81
|
+
[config.enableHealthKit, "NSHealthUpdateUsageDescription", `${config.appName} writes health data only with your permission.`],
|
|
82
|
+
[config.enableBiometric, "NSFaceIDUsageDescription", `${config.appName} uses Face ID to protect your account.`]
|
|
83
|
+
];
|
|
84
|
+
return entries.filter(([enabled]) => enabled).map(([, key, value]) => plistString(key, value)).join(`
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
function renderOrientations(config) {
|
|
88
|
+
const names = {
|
|
89
|
+
portrait: "UIInterfaceOrientationPortrait",
|
|
90
|
+
"landscape-left": "UIInterfaceOrientationLandscapeLeft",
|
|
91
|
+
"landscape-right": "UIInterfaceOrientationLandscapeRight",
|
|
92
|
+
"portrait-upside-down": "UIInterfaceOrientationPortraitUpsideDown"
|
|
93
|
+
};
|
|
94
|
+
const values = config.orientations?.length ? config.orientations : ["portrait"];
|
|
95
|
+
return values.map((value) => ` <string>${names[value]}</string>`).join(`
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
function renderUrlTypes(config) {
|
|
99
|
+
const schemes = [...new Set(config.urlSchemes?.map((value) => value.trim()).filter(Boolean) ?? [])];
|
|
100
|
+
if (!schemes.length)
|
|
101
|
+
return "";
|
|
102
|
+
return ` <key>CFBundleURLTypes</key>
|
|
103
|
+
<array>
|
|
104
|
+
<dict>
|
|
105
|
+
<key>CFBundleURLSchemes</key>
|
|
106
|
+
<array>
|
|
107
|
+
${schemes.map((value) => ` <string>${xmlEscape(value)}</string>`).join(`
|
|
108
|
+
`)}
|
|
109
|
+
</array>
|
|
110
|
+
</dict>
|
|
111
|
+
</array>`;
|
|
112
|
+
}
|
|
113
|
+
function plistArray(values, indent = 2) {
|
|
114
|
+
const padding = " ".repeat(indent);
|
|
115
|
+
return values.map((value) => `${padding}<string>${xmlEscape(value)}</string>`).join(`
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
function renderBackgroundModes(config) {
|
|
119
|
+
const modes = new Set;
|
|
120
|
+
if (config.enableBackgroundLocation)
|
|
121
|
+
modes.add("location");
|
|
122
|
+
if (config.enableBackgroundTasks)
|
|
123
|
+
modes.add("processing");
|
|
124
|
+
if (config.enablePushNotifications)
|
|
125
|
+
modes.add("remote-notification");
|
|
126
|
+
if (!modes.size)
|
|
127
|
+
return "";
|
|
128
|
+
return ` <key>UIBackgroundModes</key>
|
|
129
|
+
<array>
|
|
130
|
+
${plistArray([...modes])}
|
|
131
|
+
</array>`;
|
|
132
|
+
}
|
|
133
|
+
function renderEntitlements(config) {
|
|
134
|
+
const entries = [];
|
|
135
|
+
if (config.associatedDomains?.length) {
|
|
136
|
+
entries.push(` <key>com.apple.developer.associated-domains</key>
|
|
137
|
+
<array>
|
|
138
|
+
${plistArray(config.associatedDomains)}
|
|
139
|
+
</array>`);
|
|
140
|
+
}
|
|
141
|
+
if (config.appGroups?.length) {
|
|
142
|
+
entries.push(` <key>com.apple.security.application-groups</key>
|
|
143
|
+
<array>
|
|
144
|
+
${plistArray(config.appGroups)}
|
|
145
|
+
</array>`);
|
|
146
|
+
}
|
|
147
|
+
if (config.enableHealthKit) {
|
|
148
|
+
entries.push(` <key>com.apple.developer.healthkit</key>
|
|
149
|
+
<true/>`);
|
|
150
|
+
}
|
|
151
|
+
if (config.enablePushNotifications) {
|
|
152
|
+
entries.push(` <key>aps-environment</key>
|
|
153
|
+
<string>development</string>`);
|
|
154
|
+
}
|
|
155
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
156
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
157
|
+
<plist version="1.0">
|
|
158
|
+
<dict>
|
|
159
|
+
${entries.join(`
|
|
160
|
+
`)}
|
|
161
|
+
</dict>
|
|
162
|
+
</plist>
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
function renderWatchEntitlements(config) {
|
|
166
|
+
const appGroups = config.appGroups?.length ? ` <key>com.apple.security.application-groups</key>
|
|
167
|
+
<array>
|
|
168
|
+
${plistArray(config.appGroups)}
|
|
169
|
+
</array>
|
|
170
|
+
` : "";
|
|
171
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
172
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
173
|
+
<plist version="1.0">
|
|
174
|
+
<dict>
|
|
175
|
+
${appGroups}</dict>
|
|
176
|
+
</plist>
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
function renderPrivacyManifest(config) {
|
|
180
|
+
const privacy = config.privacy ?? {};
|
|
181
|
+
const collected = privacy.collectedDataTypes ?? [];
|
|
182
|
+
const accessed = privacy.accessedApiTypes ?? [];
|
|
183
|
+
const collectedXml = collected.map((item) => ` <dict>
|
|
184
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
185
|
+
<string>${xmlEscape(item.type)}</string>
|
|
186
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
187
|
+
<${item.linked ? "true" : "false"}/>
|
|
188
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
189
|
+
<${item.tracking ? "true" : "false"}/>
|
|
190
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
191
|
+
<array>
|
|
192
|
+
${plistArray(item.purposes, 4)}
|
|
193
|
+
</array>
|
|
194
|
+
</dict>`).join(`
|
|
195
|
+
`);
|
|
196
|
+
const accessedXml = accessed.map((item) => ` <dict>
|
|
197
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
198
|
+
<string>${xmlEscape(item.type)}</string>
|
|
199
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
200
|
+
<array>
|
|
201
|
+
${plistArray(item.reasons, 4)}
|
|
202
|
+
</array>
|
|
203
|
+
</dict>`).join(`
|
|
204
|
+
`);
|
|
205
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
206
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
207
|
+
<plist version="1.0">
|
|
208
|
+
<dict>
|
|
209
|
+
<key>NSPrivacyTracking</key>
|
|
210
|
+
<${privacy.tracking ? "true" : "false"}/>
|
|
211
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
212
|
+
<array>
|
|
213
|
+
${plistArray(privacy.trackingDomains ?? [])}
|
|
214
|
+
</array>
|
|
215
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
216
|
+
<array>
|
|
217
|
+
${collectedXml}
|
|
218
|
+
</array>
|
|
219
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
220
|
+
<array>
|
|
221
|
+
${accessedXml}
|
|
222
|
+
</array>
|
|
223
|
+
</dict>
|
|
224
|
+
</plist>
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
function renderAssetCatalog(output, config) {
|
|
228
|
+
const catalog = join(output, "Assets.xcassets");
|
|
229
|
+
const appIcon = join(catalog, "AppIcon.appiconset");
|
|
230
|
+
const launchBackground = join(catalog, "LaunchBackground.colorset");
|
|
231
|
+
mkdirSync(appIcon, { recursive: true });
|
|
232
|
+
mkdirSync(launchBackground, { recursive: true });
|
|
233
|
+
writeFileSync(join(catalog, "Contents.json"), `${JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2)}
|
|
234
|
+
`);
|
|
235
|
+
const iconFilename = config.appIconPath ? "AppIcon-1024.png" : undefined;
|
|
236
|
+
if (config.appIconPath) {
|
|
237
|
+
if (!existsSync(config.appIconPath))
|
|
238
|
+
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
239
|
+
cpSync(config.appIconPath, join(appIcon, iconFilename));
|
|
240
|
+
}
|
|
241
|
+
writeFileSync(join(appIcon, "Contents.json"), `${JSON.stringify({
|
|
242
|
+
images: iconFilename ? [{
|
|
243
|
+
filename: iconFilename,
|
|
244
|
+
idiom: "universal",
|
|
245
|
+
platform: "ios",
|
|
246
|
+
size: "1024x1024"
|
|
247
|
+
}] : [],
|
|
248
|
+
info: { author: "xcode", version: 1 }
|
|
249
|
+
}, null, 2)}
|
|
250
|
+
`);
|
|
251
|
+
const color = config.backgroundColor?.replace(/^#/, "") || "000000";
|
|
252
|
+
const normalized = color.length === 3 ? [...color].map((value) => `${value}${value}`).join("") : color.padEnd(6, "0").slice(0, 6);
|
|
253
|
+
const components = [0, 2, 4].map((index) => (Number.parseInt(normalized.slice(index, index + 2), 16) / 255).toFixed(3));
|
|
254
|
+
writeFileSync(join(launchBackground, "Contents.json"), `${JSON.stringify({
|
|
255
|
+
colors: [{
|
|
256
|
+
color: {
|
|
257
|
+
"color-space": "srgb",
|
|
258
|
+
components: { alpha: "1.000", blue: components[2], green: components[1], red: components[0] }
|
|
259
|
+
},
|
|
260
|
+
idiom: "universal"
|
|
261
|
+
}],
|
|
262
|
+
info: { author: "xcode", version: 1 }
|
|
263
|
+
}, null, 2)}
|
|
264
|
+
`);
|
|
265
|
+
}
|
|
266
|
+
function syncWebAssets(source, output) {
|
|
267
|
+
const sourcePath = resolve(source);
|
|
268
|
+
if (!existsSync(sourcePath))
|
|
269
|
+
throw new Error(`Web asset path not found: ${source}`);
|
|
270
|
+
const distDir = join(output, "dist");
|
|
271
|
+
rmSync(distDir, { recursive: true, force: true });
|
|
272
|
+
mkdirSync(distDir, { recursive: true });
|
|
273
|
+
if (statSync(sourcePath).isDirectory()) {
|
|
274
|
+
cpSync(sourcePath, distDir, { recursive: true });
|
|
275
|
+
} else {
|
|
276
|
+
cpSync(sourcePath, join(distDir, "index.html"));
|
|
277
|
+
}
|
|
278
|
+
if (!existsSync(join(distDir, "index.html"))) {
|
|
279
|
+
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
7
282
|
async function init(options) {
|
|
8
283
|
const { name, bundleId, teamId, output } = options;
|
|
9
284
|
console.log(`
|
|
10
285
|
\u26A1 Initializing Craft iOS project: ${name}`);
|
|
11
286
|
console.log(` Output: ${output}
|
|
12
287
|
`);
|
|
13
|
-
const dirs = [output, join(output, "Sources"), join(output, "dist")];
|
|
288
|
+
const dirs = [output, join(output, "Sources"), join(output, "Shared"), join(output, "dist")];
|
|
289
|
+
if (options.config?.enableWatchApp)
|
|
290
|
+
dirs.push(join(output, "WatchApp"));
|
|
14
291
|
for (const dir of dirs) {
|
|
15
292
|
if (!existsSync(dir)) {
|
|
16
293
|
mkdirSync(dir, { recursive: true });
|
|
@@ -19,27 +296,84 @@ async function init(options) {
|
|
|
19
296
|
const finalBundleId = bundleId || `com.craft.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
20
297
|
const bundleIdPrefix = finalBundleId.split(".").slice(0, -1).join(".");
|
|
21
298
|
const config = {
|
|
299
|
+
...DEFAULT_CONFIG,
|
|
22
300
|
appName: name,
|
|
23
301
|
bundleId: finalBundleId,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
darkMode: true,
|
|
27
|
-
backgroundColor: "#1a1a2e",
|
|
28
|
-
enableSpeechRecognition: true,
|
|
29
|
-
enableHaptics: true,
|
|
30
|
-
enableShare: true,
|
|
31
|
-
iosVersion: "15.0",
|
|
32
|
-
teamId: teamId || ""
|
|
302
|
+
teamId: teamId || "",
|
|
303
|
+
...options.config
|
|
33
304
|
};
|
|
305
|
+
if (config.enableBackgroundLocation)
|
|
306
|
+
config.enableGeolocation = true;
|
|
34
307
|
writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
35
308
|
const swiftTemplate = readFileSync(join(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
36
|
-
|
|
309
|
+
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
310
|
+
writeFileSync(join(output, "Sources", `${name}App.swift`), swiftSource);
|
|
37
311
|
const infoPlistTemplate = readFileSync(join(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
38
|
-
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")
|
|
312
|
+
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>
|
|
313
|
+
<true/>
|
|
314
|
+
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
315
|
+
<true/>` : "");
|
|
39
316
|
writeFileSync(join(output, "Info.plist"), infoPlist);
|
|
40
317
|
const projectYmlTemplate = readFileSync(join(TEMPLATES_DIR, "project.yml.template"), "utf-8");
|
|
41
|
-
const
|
|
318
|
+
const nativeDependencies = [];
|
|
319
|
+
if (config.enableLiveActivities)
|
|
320
|
+
nativeDependencies.push(` - target: ${name}LiveActivity`);
|
|
321
|
+
if (config.enableWatchApp)
|
|
322
|
+
nativeDependencies.push(` - target: ${name}Watch
|
|
323
|
+
embed: true`);
|
|
324
|
+
const nativeTargets = [];
|
|
325
|
+
if (config.enableLiveActivities) {
|
|
326
|
+
nativeTargets.push(` ${name}LiveActivity:
|
|
327
|
+
type: app-extension
|
|
328
|
+
platform: iOS
|
|
329
|
+
deploymentTarget: "16.2"
|
|
330
|
+
sources:
|
|
331
|
+
- WidgetExtension
|
|
332
|
+
- Shared
|
|
333
|
+
settings:
|
|
334
|
+
INFOPLIST_FILE: WidgetExtension/Info.plist
|
|
335
|
+
PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.liveactivity
|
|
336
|
+
SWIFT_VERSION: "5.0"
|
|
337
|
+
APPLICATION_EXTENSION_API_ONLY: YES
|
|
338
|
+
SKIP_INSTALL: YES`);
|
|
339
|
+
}
|
|
340
|
+
if (config.enableWatchApp) {
|
|
341
|
+
nativeTargets.push(` ${name}Watch:
|
|
342
|
+
type: application
|
|
343
|
+
platform: watchOS
|
|
344
|
+
deploymentTarget: "${config.watchosVersion || "9.0"}"
|
|
345
|
+
sources:
|
|
346
|
+
- WatchApp
|
|
347
|
+
settings:
|
|
348
|
+
INFOPLIST_FILE: WatchApp/Info.plist
|
|
349
|
+
CODE_SIGN_ENTITLEMENTS: WatchApp/Watch.entitlements
|
|
350
|
+
PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp
|
|
351
|
+
SWIFT_VERSION: "5.0"
|
|
352
|
+
SKIP_INSTALL: YES`);
|
|
353
|
+
}
|
|
354
|
+
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:
|
|
355
|
+
${nativeDependencies.join(`
|
|
356
|
+
`)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
|
|
357
|
+
`));
|
|
42
358
|
writeFileSync(join(output, "project.yml"), projectYml);
|
|
359
|
+
writeFileSync(join(output, "Craft.entitlements"), renderEntitlements(config));
|
|
360
|
+
writeFileSync(join(output, "PrivacyInfo.xcprivacy"), renderPrivacyManifest(config));
|
|
361
|
+
renderAssetCatalog(output, config);
|
|
362
|
+
cpSync(join(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join(output, "Shared", "CraftActivityAttributes.swift"));
|
|
363
|
+
if (config.enableLiveActivities) {
|
|
364
|
+
mkdirSync(join(output, "WidgetExtension"), { recursive: true });
|
|
365
|
+
const widgetSource = readFileSync(join(TEMPLATES_DIR, "CraftLiveActivityWidget.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
366
|
+
writeFileSync(join(output, "WidgetExtension", `${name}LiveActivity.swift`), widgetSource);
|
|
367
|
+
const widgetInfo = readFileSync(join(TEMPLATES_DIR, "WidgetExtension.Info.plist"), "utf8");
|
|
368
|
+
writeFileSync(join(output, "WidgetExtension", "Info.plist"), widgetInfo);
|
|
369
|
+
}
|
|
370
|
+
if (config.enableWatchApp) {
|
|
371
|
+
const watchSource = readFileSync(join(TEMPLATES_DIR, "CraftWatchApp.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
372
|
+
writeFileSync(join(output, "WatchApp", `${name}WatchApp.swift`), watchSource);
|
|
373
|
+
const watchInfo = readFileSync(join(TEMPLATES_DIR, "WatchApp.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
374
|
+
writeFileSync(join(output, "WatchApp", "Info.plist"), watchInfo);
|
|
375
|
+
writeFileSync(join(output, "WatchApp", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
376
|
+
}
|
|
43
377
|
const placeholderHtml = `<!DOCTYPE html>
|
|
44
378
|
<html>
|
|
45
379
|
<head>
|
|
@@ -89,7 +423,7 @@ async function init(options) {
|
|
|
89
423
|
console.log("");
|
|
90
424
|
}
|
|
91
425
|
async function build(options) {
|
|
92
|
-
const { htmlPath, devServer, output } = options;
|
|
426
|
+
const { htmlPath, devServer, output, generateProject = true } = options;
|
|
93
427
|
console.log(`
|
|
94
428
|
\uD83D\uDCE6 Building Craft iOS project...`);
|
|
95
429
|
const configPath = join(output, "craft.config.json");
|
|
@@ -99,25 +433,17 @@ async function build(options) {
|
|
|
99
433
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
100
434
|
if (devServer) {
|
|
101
435
|
config.devServerURL = devServer;
|
|
436
|
+
const origin = new URL(devServer).origin;
|
|
437
|
+
config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], origin])];
|
|
102
438
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
103
439
|
console.log(` Dev server: ${devServer}`);
|
|
104
440
|
}
|
|
105
441
|
if (htmlPath) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
mkdirSync(distDir, { recursive: true });
|
|
109
|
-
}
|
|
110
|
-
if (existsSync(htmlPath)) {
|
|
111
|
-
const stat = await Bun.file(htmlPath).exists();
|
|
112
|
-
if (stat) {
|
|
113
|
-
const html = readFileSync(htmlPath, "utf-8");
|
|
114
|
-
writeFileSync(join(distDir, "index.html"), html);
|
|
115
|
-
console.log(` Copied: ${htmlPath} \u2192 dist/index.html`);
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
throw new Error(`HTML path not found: ${htmlPath}`);
|
|
119
|
-
}
|
|
442
|
+
syncWebAssets(htmlPath, output);
|
|
443
|
+
console.log(` Synced: ${htmlPath} \u2192 dist/`);
|
|
120
444
|
}
|
|
445
|
+
if (!generateProject)
|
|
446
|
+
return;
|
|
121
447
|
try {
|
|
122
448
|
const result = await $`which xcodegen`.quiet();
|
|
123
449
|
if (result.exitCode === 0) {
|
|
@@ -127,10 +453,10 @@ async function build(options) {
|
|
|
127
453
|
} else {
|
|
128
454
|
throw new Error("xcodegen not found");
|
|
129
455
|
}
|
|
130
|
-
} catch {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
456
|
+
} catch (error) {
|
|
457
|
+
throw new Error("Unable to generate the Xcode project. Install xcodegen with `brew install xcodegen`.", {
|
|
458
|
+
cause: error
|
|
459
|
+
});
|
|
134
460
|
}
|
|
135
461
|
console.log("");
|
|
136
462
|
}
|
|
@@ -145,6 +471,51 @@ async function open(options) {
|
|
|
145
471
|
console.log(`\uD83D\uDE80 Opening ${xcodeproj}...`);
|
|
146
472
|
await $`open ${projectPath}`;
|
|
147
473
|
}
|
|
474
|
+
function orderSimulators(devices) {
|
|
475
|
+
return [...devices].sort((left, right) => {
|
|
476
|
+
const booted = Number(right.state === "Booted") - Number(left.state === "Booted");
|
|
477
|
+
if (booted !== 0)
|
|
478
|
+
return booted;
|
|
479
|
+
const phone = Number(right.name.startsWith("iPhone")) - Number(left.name.startsWith("iPhone"));
|
|
480
|
+
if (phone !== 0)
|
|
481
|
+
return phone;
|
|
482
|
+
return right.runtime.localeCompare(left.runtime);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
async function pickSimulator() {
|
|
486
|
+
const listed = await $`xcrun simctl list devices available --json`.quiet().nothrow();
|
|
487
|
+
if (listed.exitCode !== 0)
|
|
488
|
+
return null;
|
|
489
|
+
let parsed;
|
|
490
|
+
try {
|
|
491
|
+
parsed = JSON.parse(listed.stdout.toString());
|
|
492
|
+
} catch {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const devices = [];
|
|
496
|
+
for (const [runtime, list] of Object.entries(parsed.devices ?? {})) {
|
|
497
|
+
for (const device of list)
|
|
498
|
+
devices.push({ ...device, runtime: runtime.split(".").pop() ?? runtime });
|
|
499
|
+
}
|
|
500
|
+
return orderSimulators(devices)[0] ?? null;
|
|
501
|
+
}
|
|
502
|
+
async function bootSimulator(device) {
|
|
503
|
+
if (device.state !== "Booted")
|
|
504
|
+
await $`xcrun simctl boot ${device.udid}`.quiet().nothrow();
|
|
505
|
+
await $`xcrun simctl bootstatus ${device.udid}`.quiet().nothrow();
|
|
506
|
+
}
|
|
507
|
+
async function showSimulator() {
|
|
508
|
+
const selected = await $`xcode-select -p`.quiet().nothrow();
|
|
509
|
+
const developer = selected.exitCode === 0 ? selected.stdout.toString().trim() : "";
|
|
510
|
+
if (developer) {
|
|
511
|
+
const app = join(developer, "Applications", "Simulator.app");
|
|
512
|
+
if (existsSync(app)) {
|
|
513
|
+
await $`open ${app}`.quiet().nothrow();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
await $`open -a Simulator`.quiet().nothrow();
|
|
518
|
+
}
|
|
148
519
|
async function run(options) {
|
|
149
520
|
const { simulator, output } = options;
|
|
150
521
|
await build({ output });
|
|
@@ -155,15 +526,31 @@ async function run(options) {
|
|
|
155
526
|
}
|
|
156
527
|
const projectPath = join(output, xcodeproj);
|
|
157
528
|
const appName = xcodeproj.replace(".xcodeproj", "");
|
|
529
|
+
const configPath = join(output, "craft.config.json");
|
|
530
|
+
if (!existsSync(configPath))
|
|
531
|
+
throw new Error(`No craft.config.json found in ${output}. Run 'craft-ios init' first.`);
|
|
532
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
158
533
|
if (simulator) {
|
|
159
534
|
console.log("\uD83D\uDCF1 Building and running on simulator...");
|
|
535
|
+
const device = await pickSimulator();
|
|
536
|
+
if (!device) {
|
|
537
|
+
throw new Error("No iOS simulator is available. Install a runtime with " + "`xcodebuild -downloadPlatform iOS`, then try again.");
|
|
538
|
+
}
|
|
539
|
+
console.log(` Device: ${device.name} (${device.runtime})`);
|
|
160
540
|
try {
|
|
161
|
-
await
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
541
|
+
await bootSimulator(device);
|
|
542
|
+
const derivedData = join(output, "build");
|
|
543
|
+
const product = join(derivedData, "Build", "Products", "Debug-iphonesimulator", `${appName}.app`);
|
|
544
|
+
await $`xcodebuild -project ${projectPath} -scheme ${appName} -configuration Debug -destination ${`id=${device.udid}`} -derivedDataPath ${derivedData} build`;
|
|
545
|
+
if (!existsSync(product)) {
|
|
546
|
+
throw new Error(`xcodebuild reported success but ${product} does not exist. ` + "The scheme may build a different product name than the project.");
|
|
547
|
+
}
|
|
548
|
+
await $`xcrun simctl install ${device.udid} ${product}`;
|
|
549
|
+
await showSimulator();
|
|
550
|
+
await $`xcrun simctl launch ${device.udid} ${config.bundleId}`;
|
|
551
|
+
console.log(`\u2705 ${config.appName} is running on ${device.name}`);
|
|
165
552
|
} catch (error) {
|
|
166
|
-
|
|
553
|
+
throw new Error(`iOS simulator run failed for ${projectPath}`, { cause: error });
|
|
167
554
|
}
|
|
168
555
|
} else {
|
|
169
556
|
console.log("\uD83D\uDCF1 Opening Xcode for device deployment...");
|
|
@@ -177,10 +564,21 @@ async function run(options) {
|
|
|
177
564
|
}
|
|
178
565
|
}
|
|
179
566
|
export {
|
|
567
|
+
syncWebAssets,
|
|
568
|
+
showSimulator,
|
|
180
569
|
run,
|
|
570
|
+
renderWatchEntitlements,
|
|
571
|
+
renderUsageDescriptions,
|
|
572
|
+
renderUrlTypes,
|
|
573
|
+
renderPrivacyManifest,
|
|
574
|
+
renderOrientations,
|
|
575
|
+
renderEntitlements,
|
|
576
|
+
renderDeviceFamilies,
|
|
577
|
+
renderBackgroundModes,
|
|
578
|
+
pickSimulator,
|
|
579
|
+
orderSimulators,
|
|
181
580
|
open,
|
|
182
581
|
init,
|
|
183
|
-
build
|
|
582
|
+
build,
|
|
583
|
+
bootSimulator
|
|
184
584
|
};
|
|
185
|
-
|
|
186
|
-
//# debugId=357E4FC7A8DD0D6264756E2164756E21
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ActivityKit
|
|
2
|
+
|
|
3
|
+
@available(iOS 16.1, *)
|
|
4
|
+
struct CraftActivityAttributes: ActivityAttributes {
|
|
5
|
+
struct ContentState: Codable, Hashable {
|
|
6
|
+
let status: String
|
|
7
|
+
let distanceMeters: Double
|
|
8
|
+
let durationSeconds: Double
|
|
9
|
+
let progress: Double
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let activityId: String
|
|
13
|
+
let title: String
|
|
14
|
+
}
|