craft-native 0.0.272 → 0.0.278
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.js +1 -1
- package/dist/android/templates/CraftNativeMapLibre.kt.template +32 -1
- package/dist/android/templates/CraftNativeMutation.kt.template +35 -0
- package/dist/api/index.d.ts +2 -2
- package/dist/api/mobile.d.ts +25 -0
- package/dist/cli.js +29 -6
- package/dist/index.cjs +78 -5
- package/dist/index.js +79 -6
- package/dist/ios/src/index.js +7 -3
- package/dist/ios/templates/CraftHybrid.swift +5 -2
- package/dist/ios/templates/CraftNativeActions.swift +6 -0
- package/dist/ios/templates/CraftNativeFiles.swift +136 -0
- package/dist/ios/templates/CraftNativeMutation.swift +51 -0
- package/dist/ios/templates/CraftNativeScreen.swift +183 -14
- package/dist/ios/templates/CraftWatchApp.swift.template +303 -9
- package/dist/ios/templates/WatchExtension.Info.plist.template +1 -0
- package/dist/macos/templates/CraftNativeMutation.swift +51 -0
- package/dist/mobile.js +50 -0
- package/dist/package.d.ts +19 -3
- package/dist/types.d.ts +19 -0
- package/package.json +1 -1
|
@@ -338,7 +338,7 @@ async function createAndroidEmulator(options) {
|
|
|
338
338
|
const events = await run([adb, "-s", serial, "logcat", "-b", "events", "-d", "-t", "1"], undefined, probeTimeout(), true);
|
|
339
339
|
const home = await run([adb, "-s", serial, "shell", "cmd", "package", "resolve-activity", "--brief", "-a", "android.intent.action.MAIN", "-c", "android.intent.category.HOME"], undefined, probeTimeout(), true);
|
|
340
340
|
const homeActivity = home.stdout.trim().split(/\r?\n/).at(-1) ?? "";
|
|
341
|
-
const temporaryHome = homeActivity.startsWith("com.android.sdksetup/") || /^com\.android\.settings\/(?:\.|com\.android\.settings\.)FallbackHome$/.test(homeActivity) || /^android\/.*(?:Resolver|Chooser)Activity$/.test(homeActivity);
|
|
341
|
+
const temporaryHome = homeActivity.startsWith("com.android.sdksetup/") || homeActivity.startsWith("com.google.android.googlesdksetup/") || /^com\.android\.settings\/(?:\.|com\.android\.settings\.)FallbackHome$/.test(homeActivity) || /^android\/.*(?:Resolver|Chooser)Activity$/.test(homeActivity);
|
|
342
342
|
booted = settings.code === 0 && packages.code === 0 && systemUi.code === 0 && events.code === 0 && /^package:\/\S+/m.test(packages.stdout) && /^\d+(?:\s+\d+)*$/.test(systemUi.stdout.trim()) && (!requiresPlayStore || playStore?.code === 0 && /^package:\/\S+/m.test(playStore.stdout)) && home.code === 0 && /^[\w.]+\/[\w.$]+$/.test(homeActivity) && !temporaryHome;
|
|
343
343
|
}
|
|
344
344
|
}
|
|
@@ -16,6 +16,8 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
|
|
|
16
16
|
private var pendingChildren = JSONArray()
|
|
17
17
|
private var styleKey: String? = null
|
|
18
18
|
private var styleVersion = 0
|
|
19
|
+
/** The prefix of a style key that is a vector style's URL, not raster style JSON. */
|
|
20
|
+
private val vectorStylePrefix = "uri:"
|
|
19
21
|
private var readyStyle: Style? = null
|
|
20
22
|
private var initialCameraKey: String? = null
|
|
21
23
|
private var reportedRegion: LatLngBounds? = null
|
|
@@ -146,7 +148,10 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
|
|
|
146
148
|
styleKey = nextStyle
|
|
147
149
|
readyStyle = null
|
|
148
150
|
val version = ++styleVersion
|
|
149
|
-
|
|
151
|
+
// A vector style (the standard basemap) loads from its URL; the
|
|
152
|
+
// raster ones are composed here as style JSON.
|
|
153
|
+
val builder = if (nextStyle.startsWith(vectorStylePrefix)) Style.Builder().fromUri(nextStyle.removePrefix(vectorStylePrefix)) else Style.Builder().fromJson(nextStyle)
|
|
154
|
+
nativeMap.setStyle(builder) { loaded ->
|
|
150
155
|
if (!destroyed && version == styleVersion) {
|
|
151
156
|
readyStyle = loaded
|
|
152
157
|
installOverlays(loaded)
|
|
@@ -183,6 +188,20 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
|
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
private fun basemapStyle(): String {
|
|
191
|
+
// `basemap`: the named presets, drawn as well as each can be here.
|
|
192
|
+
// Standard is OpenFreeMap's vector style - sharp at any density, no
|
|
193
|
+
// key - where it was raster tiles from tile.openstreetmap.org, whose
|
|
194
|
+
// policy does not allow an app's traffic. Outdoors is topography with
|
|
195
|
+
// contours; satellite and hybrid are Esri imagery.
|
|
196
|
+
when (pendingProps.optString("basemap", "").lowercase()) {
|
|
197
|
+
"standard" -> {
|
|
198
|
+
attribution.text = "OpenFreeMap © OpenMapTiles · Data © OpenStreetMap contributors"
|
|
199
|
+
return vectorStylePrefix + "https://tiles.openfreemap.org/styles/liberty"
|
|
200
|
+
}
|
|
201
|
+
"outdoors" -> return rasterStyle("https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", "Topography: Esri, USGS, NOAA, OpenStreetMap contributors", labels = false)
|
|
202
|
+
"satellite" -> return rasterStyle("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", "Imagery © Esri, Maxar, Earthstar Geographics, and the GIS User Community", labels = false)
|
|
203
|
+
"hybrid" -> return rasterStyle("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", "Imagery © Esri, Maxar, Earthstar Geographics, and the GIS User Community", labels = true)
|
|
204
|
+
}
|
|
186
205
|
val custom = pendingProps.optJSONObject("tileOverlay")
|
|
187
206
|
val template = custom?.optString("urlTemplate").orEmpty()
|
|
188
207
|
val credit = custom?.optString("attribution").orEmpty()
|
|
@@ -212,6 +231,18 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
|
|
|
212
231
|
return JSONObject().put("version", 8).put("sources", sources).put("layers", layers).toString()
|
|
213
232
|
}
|
|
214
233
|
|
|
234
|
+
private fun rasterStyle(url: String, credit: String, labels: Boolean): String {
|
|
235
|
+
val sources = JSONObject().put("basemap", rasterSource(url, credit))
|
|
236
|
+
val layers = JSONArray().put(JSONObject().put("id", "basemap").put("type", "raster").put("source", "basemap"))
|
|
237
|
+
attribution.text = credit
|
|
238
|
+
if (labels) {
|
|
239
|
+
sources.put("labels", rasterSource("https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}", "Labels © Esri"))
|
|
240
|
+
layers.put(JSONObject().put("id", "labels").put("type", "raster").put("source", "labels"))
|
|
241
|
+
attribution.text = "$credit · Labels © Esri"
|
|
242
|
+
}
|
|
243
|
+
return JSONObject().put("version", 8).put("sources", sources).put("layers", layers).toString()
|
|
244
|
+
}
|
|
245
|
+
|
|
215
246
|
private fun rasterSource(url: String, credit: String): JSONObject = JSONObject()
|
|
216
247
|
.put("type", "raster").put("tiles", JSONArray().put(url)).put("tileSize", 256).put("maxzoom", 19).put("attribution", credit)
|
|
217
248
|
|
|
@@ -119,6 +119,10 @@ class CraftNativeMutationDocument {
|
|
|
119
119
|
// be reconciled so the host can move its existing view.
|
|
120
120
|
requiresFullRender = true
|
|
121
121
|
}
|
|
122
|
+
"replaceChildren" -> {
|
|
123
|
+
val parentId = replaceChildren(operation, candidateNodes)
|
|
124
|
+
if (parentId !in affectedNodeIds) affectedNodeIds.add(parentId)
|
|
125
|
+
}
|
|
122
126
|
"removeNode" -> {
|
|
123
127
|
val parent = candidateNodes[operation.optString("id")]?.parent
|
|
124
128
|
if (parent == null) requiresFullRender = true
|
|
@@ -257,6 +261,37 @@ class CraftNativeMutationDocument {
|
|
|
257
261
|
return previousParentId
|
|
258
262
|
}
|
|
259
263
|
|
|
264
|
+
/** Replaces a container's ordered child snapshot in one atomic mutation. */
|
|
265
|
+
private fun replaceChildren(operation: JSONObject, nodes: MutableMap<String, Node>): String {
|
|
266
|
+
val parentId = requiredId(operation, "parentId", "replaceChildren")
|
|
267
|
+
val parent = nodes[parentId] ?: fail("UNKNOWN_PARENT", "node $parentId does not exist")
|
|
268
|
+
if (!acceptsChildren(parent.type)) fail("INVALID_PARENT", "node $parentId cannot contain child nodes")
|
|
269
|
+
val values = operation.optJSONArray("children")
|
|
270
|
+
?: fail("INVALID_CHILDREN", "replaceChildren.children must be an array")
|
|
271
|
+
val childIds = mutableListOf<String>()
|
|
272
|
+
for (index in 0 until values.length()) {
|
|
273
|
+
val childId = values.opt(index) as? String
|
|
274
|
+
if (childId.isNullOrBlank()) {
|
|
275
|
+
fail("INVALID_CHILDREN", "replaceChildren.children must contain non-empty ids")
|
|
276
|
+
}
|
|
277
|
+
if (childId in childIds) fail("DUPLICATE_CHILD", "child $childId appears more than once")
|
|
278
|
+
val child = nodes[childId] ?: fail("UNKNOWN_NODE", "node $childId does not exist")
|
|
279
|
+
if (childId == parentId || parentId in descendants(childId, nodes)) {
|
|
280
|
+
fail("CYCLE", "attaching node $childId would create a cycle")
|
|
281
|
+
}
|
|
282
|
+
if (child.parent != null && child.parent != parentId) {
|
|
283
|
+
fail("NODE_ATTACHED", "node $childId already has a parent")
|
|
284
|
+
}
|
|
285
|
+
childIds.add(childId)
|
|
286
|
+
}
|
|
287
|
+
parent.children.filterNot { it in childIds }.forEach { childId ->
|
|
288
|
+
(descendants(childId, nodes) + childId).forEach { nodes.remove(it) }
|
|
289
|
+
}
|
|
290
|
+
parent.children = childIds
|
|
291
|
+
childIds.forEach { nodes[it]?.parent = parentId }
|
|
292
|
+
return parentId
|
|
293
|
+
}
|
|
294
|
+
|
|
260
295
|
private fun removeNode(
|
|
261
296
|
operation: JSONObject,
|
|
262
297
|
nodes: MutableMap<String, Node>,
|
package/dist/api/index.d.ts
CHANGED
|
@@ -17,8 +17,8 @@ export type { HttpClientOptions, RequestOptions, HttpResponse, WebSocketOptions
|
|
|
17
17
|
export { crypto, uuid, randomString, hmac, timingSafeEqual, hashPassword, verifyPassword } from './crypto.js';
|
|
18
18
|
export { env, getPlatform, isDesktop, isMobile, isCraft, getSystemInfo, exec, spawn, ChildProcess, cwd, homeDir, tempDir, exit, argv, open } from './process.js';
|
|
19
19
|
export type { Platform, SystemInfo, ExecOptions, ExecResult, SpawnOptions } from './process.js';
|
|
20
|
-
export { device, haptics, permissions, camera, biometrics, secureStorage, storage, location, share, lifecycle, notifications, notifications as notification } from './mobile.js';
|
|
21
|
-
export type { DeviceInfo, DeviceCapabilities, HapticStyle, HapticNotificationType, PermissionType, PermissionStatus, CameraOptions as MobileCameraOptions, PhotoResult, BiometricType, Location, LocationOptions, ShareOptions, AppState, NotificationOptions as MobileNotificationOptions } from './mobile.js';
|
|
20
|
+
export { device, haptics, permissions, camera, files, biometrics, secureStorage, storage, location, share, lifecycle, notifications, notifications as notification } from './mobile.js';
|
|
21
|
+
export type { DeviceInfo, DeviceCapabilities, HapticStyle, HapticNotificationType, PermissionType, PermissionStatus, CameraOptions as MobileCameraOptions, PhotoResult, FileInfo, PickFileOptions, BiometricType, Location, LocationOptions, ShareOptions, AppState, NotificationOptions as MobileNotificationOptions } from './mobile.js';
|
|
22
22
|
export { carplay, appClips, liveActivities, sharePlay, storeKit, appIntents, tipKit, focusFilters } from './ios-advanced.js';
|
|
23
23
|
export type { CarPlayTemplateType, CarPlayListItem, CarPlayGridItem, CarPlayTemplate, AppClipInvocation, LiveActivityContentState, LiveActivityAttributes, LiveActivityConfig, LiveActivityHandle, SharePlaySessionState, SharePlayParticipant, SharePlayActivity, ProductType, Product, Transaction, IntentParameterType, IntentParameter, AppIntent, TipDisplayFrequency, Tip, FocusStatus, FocusFilter } from './ios-advanced.js';
|
|
24
24
|
export { materialYou, photoPicker, workManager, foregroundService, predictiveBack, appLanguage, widgets as androidWidgets, playBilling } from './android-advanced.js';
|
package/dist/api/mobile.d.ts
CHANGED
|
@@ -314,6 +314,30 @@ export interface PhotoResult {
|
|
|
314
314
|
/** MIME type */
|
|
315
315
|
mimeType: string;
|
|
316
316
|
}
|
|
317
|
+
/** A file copied into the app-owned native file area. */
|
|
318
|
+
export interface FileInfo {
|
|
319
|
+
/** App-owned path suitable for `fetch` FormData and sharing. */
|
|
320
|
+
path: string;
|
|
321
|
+
/** Original (sanitized) file name. */
|
|
322
|
+
name: string;
|
|
323
|
+
/** Preferred MIME type, or `application/octet-stream` when unknown. */
|
|
324
|
+
type: string;
|
|
325
|
+
/** Number of bytes in the copied file. */
|
|
326
|
+
size: number;
|
|
327
|
+
}
|
|
328
|
+
/** General document picker options. */
|
|
329
|
+
export interface PickFileOptions {
|
|
330
|
+
/** MIME types, UTIs, or filename extensions (for example `pdf` or `public.data`). */
|
|
331
|
+
contentTypes?: string[];
|
|
332
|
+
/** Maximum number of documents; native hosts clamp this to 1–20. */
|
|
333
|
+
maxSelections?: number;
|
|
334
|
+
/** Maximum size of each document in bytes; native hosts default to 512 MiB. */
|
|
335
|
+
maxBytes?: number;
|
|
336
|
+
}
|
|
337
|
+
/** General document picker API. */
|
|
338
|
+
export declare const files: {
|
|
339
|
+
pickFile(options?: PickFileOptions): Promise<FileInfo[]>;
|
|
340
|
+
};
|
|
317
341
|
/**
|
|
318
342
|
* Camera and image picker API.
|
|
319
343
|
*
|
|
@@ -1835,6 +1859,7 @@ declare const mobile: {
|
|
|
1835
1859
|
haptics: typeof haptics;
|
|
1836
1860
|
permissions: typeof permissions;
|
|
1837
1861
|
camera: typeof camera;
|
|
1862
|
+
files: typeof files;
|
|
1838
1863
|
biometrics: typeof biometrics;
|
|
1839
1864
|
secureStorage: typeof secureStorage;
|
|
1840
1865
|
location: typeof location;
|
package/dist/cli.js
CHANGED
|
@@ -141,7 +141,7 @@ import {
|
|
|
141
141
|
writeFileSync as writeFileSync3
|
|
142
142
|
} from "fs";
|
|
143
143
|
import { homedir } from "os";
|
|
144
|
-
import { basename, join as join2 } from "path";
|
|
144
|
+
import { basename, join as join2, resolve } from "path";
|
|
145
145
|
import { deflateRawSync } from "zlib";
|
|
146
146
|
function crc32(data) {
|
|
147
147
|
let c = 4294967295;
|
|
@@ -244,7 +244,7 @@ async function packageMacOS(config, outDir) {
|
|
|
244
244
|
const opts = config.macos || {};
|
|
245
245
|
const hardenedRuntime = !opts.appStore;
|
|
246
246
|
const appBundlePath = join2(outDir, `${name}.app`);
|
|
247
|
-
const appBundle = createMacOSAppBundle({
|
|
247
|
+
const appBundle = opts.appBundlePath ? copyMacOSAppBundle(opts.appBundlePath, appBundlePath) : createMacOSAppBundle({
|
|
248
248
|
name,
|
|
249
249
|
version,
|
|
250
250
|
bundleId,
|
|
@@ -327,19 +327,21 @@ async function packageMacOS(config, outDir) {
|
|
|
327
327
|
async function notarizeIfRequested(opts, artifactPath, format) {
|
|
328
328
|
if (!opts.notarize)
|
|
329
329
|
return [];
|
|
330
|
-
if (!opts.appleId || !opts.applePassword || !opts.teamId) {
|
|
330
|
+
if (!opts.notaryKeychainProfile && !opts.notaryApiKey && (!opts.appleId || !opts.applePassword || !opts.teamId)) {
|
|
331
331
|
return [{
|
|
332
332
|
success: false,
|
|
333
333
|
platform: "macos",
|
|
334
334
|
format: `${format} (notarize)`,
|
|
335
|
-
error: "Notarization requires
|
|
335
|
+
error: "Notarization requires a keychain profile, API key, or Apple ID/password/team ID"
|
|
336
336
|
}];
|
|
337
337
|
}
|
|
338
338
|
const submitted = await runTool("xcrun", notarytoolArguments({
|
|
339
339
|
artifactPath,
|
|
340
340
|
appleId: opts.appleId,
|
|
341
341
|
applePassword: opts.applePassword,
|
|
342
|
-
teamId: opts.teamId
|
|
342
|
+
teamId: opts.teamId,
|
|
343
|
+
keychainProfile: opts.notaryKeychainProfile,
|
|
344
|
+
apiKey: opts.notaryApiKey
|
|
343
345
|
}));
|
|
344
346
|
if (!submitted.success)
|
|
345
347
|
return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
|
|
@@ -571,6 +573,13 @@ function productbuildArguments(opts) {
|
|
|
571
573
|
];
|
|
572
574
|
}
|
|
573
575
|
function notarytoolArguments(opts) {
|
|
576
|
+
const base = ["notarytool", "submit", opts.artifactPath, "--wait"];
|
|
577
|
+
if (opts.keychainProfile)
|
|
578
|
+
return [...base, "--keychain-profile", opts.keychainProfile];
|
|
579
|
+
if (opts.apiKey)
|
|
580
|
+
return [...base, "--key", opts.apiKey.path, "--key-id", opts.apiKey.keyId, "--issuer", opts.apiKey.issuer];
|
|
581
|
+
if (!opts.appleId || !opts.applePassword || !opts.teamId)
|
|
582
|
+
throw new Error("Notarization credentials are incomplete");
|
|
574
583
|
return [
|
|
575
584
|
"notarytool",
|
|
576
585
|
"submit",
|
|
@@ -584,6 +593,20 @@ function notarytoolArguments(opts) {
|
|
|
584
593
|
"--wait"
|
|
585
594
|
];
|
|
586
595
|
}
|
|
596
|
+
function copyMacOSAppBundle(source, destination) {
|
|
597
|
+
try {
|
|
598
|
+
const info = join2(source, "Contents", "Info.plist");
|
|
599
|
+
if (!existsSync4(info) || !existsSync4(join2(source, "Contents", "MacOS")))
|
|
600
|
+
throw new Error(`Invalid prebuilt macOS application: ${source}`);
|
|
601
|
+
if (resolve(source) !== resolve(destination)) {
|
|
602
|
+
rmSync2(destination, { recursive: true, force: true });
|
|
603
|
+
cpSync(source, destination, { recursive: true });
|
|
604
|
+
}
|
|
605
|
+
return { success: true };
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return { success: false, error: error.message };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
587
610
|
function createMacOSAppBundle(opts) {
|
|
588
611
|
try {
|
|
589
612
|
const { name, binaryPath, iconPath, provisioningProfile, additionalExecutables, outputPath } = opts;
|
|
@@ -2842,7 +2865,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
2842
2865
|
`);
|
|
2843
2866
|
}
|
|
2844
2867
|
// package.json
|
|
2845
|
-
var version = "0.0.
|
|
2868
|
+
var version = "0.0.278";
|
|
2846
2869
|
|
|
2847
2870
|
// bin/cli.ts
|
|
2848
2871
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
|
@@ -118,6 +118,7 @@ __export(exports_src, {
|
|
|
118
118
|
extractBundle: () => extractBundle,
|
|
119
119
|
extractBundleFromDmg: () => extractBundleFromDmg,
|
|
120
120
|
extractBundleFromZip: () => extractBundleFromZip,
|
|
121
|
+
files: () => files,
|
|
121
122
|
focus: () => focus,
|
|
122
123
|
focusFilters: () => focusFilters,
|
|
123
124
|
foregroundService: () => foregroundService,
|
|
@@ -373,7 +374,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
373
374
|
`);
|
|
374
375
|
}
|
|
375
376
|
// package.json
|
|
376
|
-
var version = "0.0.
|
|
377
|
+
var version = "0.0.278";
|
|
377
378
|
|
|
378
379
|
// src/sdk-version.ts
|
|
379
380
|
var SDK_VERSION = version;
|
|
@@ -611,7 +612,7 @@ async function packageMacOS(config, outDir) {
|
|
|
611
612
|
const opts = config.macos || {};
|
|
612
613
|
const hardenedRuntime = !opts.appStore;
|
|
613
614
|
const appBundlePath = import_path.join(outDir, `${name}.app`);
|
|
614
|
-
const appBundle = createMacOSAppBundle({
|
|
615
|
+
const appBundle = opts.appBundlePath ? copyMacOSAppBundle(opts.appBundlePath, appBundlePath) : createMacOSAppBundle({
|
|
615
616
|
name,
|
|
616
617
|
version,
|
|
617
618
|
bundleId,
|
|
@@ -694,19 +695,21 @@ async function packageMacOS(config, outDir) {
|
|
|
694
695
|
async function notarizeIfRequested(opts, artifactPath, format) {
|
|
695
696
|
if (!opts.notarize)
|
|
696
697
|
return [];
|
|
697
|
-
if (!opts.appleId || !opts.applePassword || !opts.teamId) {
|
|
698
|
+
if (!opts.notaryKeychainProfile && !opts.notaryApiKey && (!opts.appleId || !opts.applePassword || !opts.teamId)) {
|
|
698
699
|
return [{
|
|
699
700
|
success: false,
|
|
700
701
|
platform: "macos",
|
|
701
702
|
format: `${format} (notarize)`,
|
|
702
|
-
error: "Notarization requires
|
|
703
|
+
error: "Notarization requires a keychain profile, API key, or Apple ID/password/team ID"
|
|
703
704
|
}];
|
|
704
705
|
}
|
|
705
706
|
const submitted = await runTool("xcrun", notarytoolArguments({
|
|
706
707
|
artifactPath,
|
|
707
708
|
appleId: opts.appleId,
|
|
708
709
|
applePassword: opts.applePassword,
|
|
709
|
-
teamId: opts.teamId
|
|
710
|
+
teamId: opts.teamId,
|
|
711
|
+
keychainProfile: opts.notaryKeychainProfile,
|
|
712
|
+
apiKey: opts.notaryApiKey
|
|
710
713
|
}));
|
|
711
714
|
if (!submitted.success)
|
|
712
715
|
return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
|
|
@@ -939,6 +942,13 @@ function productbuildArguments(opts) {
|
|
|
939
942
|
];
|
|
940
943
|
}
|
|
941
944
|
function notarytoolArguments(opts) {
|
|
945
|
+
const base = ["notarytool", "submit", opts.artifactPath, "--wait"];
|
|
946
|
+
if (opts.keychainProfile)
|
|
947
|
+
return [...base, "--keychain-profile", opts.keychainProfile];
|
|
948
|
+
if (opts.apiKey)
|
|
949
|
+
return [...base, "--key", opts.apiKey.path, "--key-id", opts.apiKey.keyId, "--issuer", opts.apiKey.issuer];
|
|
950
|
+
if (!opts.appleId || !opts.applePassword || !opts.teamId)
|
|
951
|
+
throw new Error("Notarization credentials are incomplete");
|
|
942
952
|
return [
|
|
943
953
|
"notarytool",
|
|
944
954
|
"submit",
|
|
@@ -952,6 +962,20 @@ function notarytoolArguments(opts) {
|
|
|
952
962
|
"--wait"
|
|
953
963
|
];
|
|
954
964
|
}
|
|
965
|
+
function copyMacOSAppBundle(source, destination) {
|
|
966
|
+
try {
|
|
967
|
+
const info = import_path.join(source, "Contents", "Info.plist");
|
|
968
|
+
if (!import_fs.existsSync(info) || !import_fs.existsSync(import_path.join(source, "Contents", "MacOS")))
|
|
969
|
+
throw new Error(`Invalid prebuilt macOS application: ${source}`);
|
|
970
|
+
if (import_path.resolve(source) !== import_path.resolve(destination)) {
|
|
971
|
+
import_fs.rmSync(destination, { recursive: true, force: true });
|
|
972
|
+
import_fs.cpSync(source, destination, { recursive: true });
|
|
973
|
+
}
|
|
974
|
+
return { success: true };
|
|
975
|
+
} catch (error) {
|
|
976
|
+
return { success: false, error: error.message };
|
|
977
|
+
}
|
|
978
|
+
}
|
|
955
979
|
function createMacOSAppBundle(opts) {
|
|
956
980
|
try {
|
|
957
981
|
const { name, binaryPath, iconPath, provisioningProfile, additionalExecutables, outputPath } = opts;
|
|
@@ -5106,6 +5130,55 @@ var permissions = {
|
|
|
5106
5130
|
}
|
|
5107
5131
|
}
|
|
5108
5132
|
};
|
|
5133
|
+
var files = {
|
|
5134
|
+
async pickFile(options = {}) {
|
|
5135
|
+
const craft = getCraftMobile();
|
|
5136
|
+
if (typeof window !== "undefined" && craft?.files) {
|
|
5137
|
+
return craft.files.pickFile(options);
|
|
5138
|
+
}
|
|
5139
|
+
if (typeof document === "undefined") {
|
|
5140
|
+
throw new Error("File picker not available. Must run in Craft or a browser.");
|
|
5141
|
+
}
|
|
5142
|
+
return new Promise((resolve, reject) => {
|
|
5143
|
+
const input = document.createElement("input");
|
|
5144
|
+
input.type = "file";
|
|
5145
|
+
if (options.contentTypes?.length)
|
|
5146
|
+
input.accept = options.contentTypes.join(",");
|
|
5147
|
+
const requested = Number(options.maxSelections);
|
|
5148
|
+
const maxSelections = Number.isFinite(requested) && requested > 0 ? Math.min(20, Math.floor(requested)) : 1;
|
|
5149
|
+
input.multiple = maxSelections > 1;
|
|
5150
|
+
input.style.display = "none";
|
|
5151
|
+
let settled = false;
|
|
5152
|
+
const finish = (result) => {
|
|
5153
|
+
if (settled)
|
|
5154
|
+
return;
|
|
5155
|
+
settled = true;
|
|
5156
|
+
input.remove();
|
|
5157
|
+
result();
|
|
5158
|
+
};
|
|
5159
|
+
input.addEventListener("cancel", () => finish(() => resolve([])), { once: true });
|
|
5160
|
+
input.onchange = () => {
|
|
5161
|
+
const chosen = Array.from(input.files ?? []).slice(0, maxSelections);
|
|
5162
|
+
const limit = Number(options.maxBytes);
|
|
5163
|
+
if (Number.isFinite(limit) && limit > 0) {
|
|
5164
|
+
const oversized = chosen.find((file) => file.size > limit);
|
|
5165
|
+
if (oversized) {
|
|
5166
|
+
finish(() => reject(Object.assign(new Error("The selected file is too large"), { code: "FILE_TOO_LARGE" })));
|
|
5167
|
+
return;
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
5170
|
+
finish(() => resolve(chosen.map((file) => ({
|
|
5171
|
+
path: URL.createObjectURL(file),
|
|
5172
|
+
name: file.name,
|
|
5173
|
+
type: file.type || "application/octet-stream",
|
|
5174
|
+
size: file.size
|
|
5175
|
+
}))));
|
|
5176
|
+
};
|
|
5177
|
+
document.body.append(input);
|
|
5178
|
+
input.click();
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
5181
|
+
};
|
|
5109
5182
|
var camera = {
|
|
5110
5183
|
async takePicture(options = {}) {
|
|
5111
5184
|
const craft = getCraftMobile();
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
117
117
|
`);
|
|
118
118
|
}
|
|
119
119
|
// package.json
|
|
120
|
-
var version = "0.0.
|
|
120
|
+
var version = "0.0.278";
|
|
121
121
|
|
|
122
122
|
// src/sdk-version.ts
|
|
123
123
|
var SDK_VERSION = version;
|
|
@@ -252,7 +252,7 @@ import {
|
|
|
252
252
|
writeFileSync as writeFileSync2
|
|
253
253
|
} from "fs";
|
|
254
254
|
import { homedir } from "os";
|
|
255
|
-
import { basename, join as join2 } from "path";
|
|
255
|
+
import { basename, join as join2, resolve } from "path";
|
|
256
256
|
import { deflateRawSync } from "zlib";
|
|
257
257
|
var DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
|
|
258
258
|
var CRC32_TABLE = (() => {
|
|
@@ -366,7 +366,7 @@ async function packageMacOS(config, outDir) {
|
|
|
366
366
|
const opts = config.macos || {};
|
|
367
367
|
const hardenedRuntime = !opts.appStore;
|
|
368
368
|
const appBundlePath = join2(outDir, `${name}.app`);
|
|
369
|
-
const appBundle = createMacOSAppBundle({
|
|
369
|
+
const appBundle = opts.appBundlePath ? copyMacOSAppBundle(opts.appBundlePath, appBundlePath) : createMacOSAppBundle({
|
|
370
370
|
name,
|
|
371
371
|
version,
|
|
372
372
|
bundleId,
|
|
@@ -449,19 +449,21 @@ async function packageMacOS(config, outDir) {
|
|
|
449
449
|
async function notarizeIfRequested(opts, artifactPath, format) {
|
|
450
450
|
if (!opts.notarize)
|
|
451
451
|
return [];
|
|
452
|
-
if (!opts.appleId || !opts.applePassword || !opts.teamId) {
|
|
452
|
+
if (!opts.notaryKeychainProfile && !opts.notaryApiKey && (!opts.appleId || !opts.applePassword || !opts.teamId)) {
|
|
453
453
|
return [{
|
|
454
454
|
success: false,
|
|
455
455
|
platform: "macos",
|
|
456
456
|
format: `${format} (notarize)`,
|
|
457
|
-
error: "Notarization requires
|
|
457
|
+
error: "Notarization requires a keychain profile, API key, or Apple ID/password/team ID"
|
|
458
458
|
}];
|
|
459
459
|
}
|
|
460
460
|
const submitted = await runTool("xcrun", notarytoolArguments({
|
|
461
461
|
artifactPath,
|
|
462
462
|
appleId: opts.appleId,
|
|
463
463
|
applePassword: opts.applePassword,
|
|
464
|
-
teamId: opts.teamId
|
|
464
|
+
teamId: opts.teamId,
|
|
465
|
+
keychainProfile: opts.notaryKeychainProfile,
|
|
466
|
+
apiKey: opts.notaryApiKey
|
|
465
467
|
}));
|
|
466
468
|
if (!submitted.success)
|
|
467
469
|
return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
|
|
@@ -694,6 +696,13 @@ function productbuildArguments(opts) {
|
|
|
694
696
|
];
|
|
695
697
|
}
|
|
696
698
|
function notarytoolArguments(opts) {
|
|
699
|
+
const base = ["notarytool", "submit", opts.artifactPath, "--wait"];
|
|
700
|
+
if (opts.keychainProfile)
|
|
701
|
+
return [...base, "--keychain-profile", opts.keychainProfile];
|
|
702
|
+
if (opts.apiKey)
|
|
703
|
+
return [...base, "--key", opts.apiKey.path, "--key-id", opts.apiKey.keyId, "--issuer", opts.apiKey.issuer];
|
|
704
|
+
if (!opts.appleId || !opts.applePassword || !opts.teamId)
|
|
705
|
+
throw new Error("Notarization credentials are incomplete");
|
|
697
706
|
return [
|
|
698
707
|
"notarytool",
|
|
699
708
|
"submit",
|
|
@@ -707,6 +716,20 @@ function notarytoolArguments(opts) {
|
|
|
707
716
|
"--wait"
|
|
708
717
|
];
|
|
709
718
|
}
|
|
719
|
+
function copyMacOSAppBundle(source, destination) {
|
|
720
|
+
try {
|
|
721
|
+
const info = join2(source, "Contents", "Info.plist");
|
|
722
|
+
if (!existsSync3(info) || !existsSync3(join2(source, "Contents", "MacOS")))
|
|
723
|
+
throw new Error(`Invalid prebuilt macOS application: ${source}`);
|
|
724
|
+
if (resolve(source) !== resolve(destination)) {
|
|
725
|
+
rmSync2(destination, { recursive: true, force: true });
|
|
726
|
+
cpSync(source, destination, { recursive: true });
|
|
727
|
+
}
|
|
728
|
+
return { success: true };
|
|
729
|
+
} catch (error) {
|
|
730
|
+
return { success: false, error: error.message };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
710
733
|
function createMacOSAppBundle(opts) {
|
|
711
734
|
try {
|
|
712
735
|
const { name, binaryPath, iconPath, provisioningProfile, additionalExecutables, outputPath } = opts;
|
|
@@ -4861,6 +4884,55 @@ var permissions = {
|
|
|
4861
4884
|
}
|
|
4862
4885
|
}
|
|
4863
4886
|
};
|
|
4887
|
+
var files = {
|
|
4888
|
+
async pickFile(options = {}) {
|
|
4889
|
+
const craft = getCraftMobile();
|
|
4890
|
+
if (typeof window !== "undefined" && craft?.files) {
|
|
4891
|
+
return craft.files.pickFile(options);
|
|
4892
|
+
}
|
|
4893
|
+
if (typeof document === "undefined") {
|
|
4894
|
+
throw new Error("File picker not available. Must run in Craft or a browser.");
|
|
4895
|
+
}
|
|
4896
|
+
return new Promise((resolve, reject) => {
|
|
4897
|
+
const input = document.createElement("input");
|
|
4898
|
+
input.type = "file";
|
|
4899
|
+
if (options.contentTypes?.length)
|
|
4900
|
+
input.accept = options.contentTypes.join(",");
|
|
4901
|
+
const requested = Number(options.maxSelections);
|
|
4902
|
+
const maxSelections = Number.isFinite(requested) && requested > 0 ? Math.min(20, Math.floor(requested)) : 1;
|
|
4903
|
+
input.multiple = maxSelections > 1;
|
|
4904
|
+
input.style.display = "none";
|
|
4905
|
+
let settled = false;
|
|
4906
|
+
const finish = (result) => {
|
|
4907
|
+
if (settled)
|
|
4908
|
+
return;
|
|
4909
|
+
settled = true;
|
|
4910
|
+
input.remove();
|
|
4911
|
+
result();
|
|
4912
|
+
};
|
|
4913
|
+
input.addEventListener("cancel", () => finish(() => resolve([])), { once: true });
|
|
4914
|
+
input.onchange = () => {
|
|
4915
|
+
const chosen = Array.from(input.files ?? []).slice(0, maxSelections);
|
|
4916
|
+
const limit = Number(options.maxBytes);
|
|
4917
|
+
if (Number.isFinite(limit) && limit > 0) {
|
|
4918
|
+
const oversized = chosen.find((file) => file.size > limit);
|
|
4919
|
+
if (oversized) {
|
|
4920
|
+
finish(() => reject(Object.assign(new Error("The selected file is too large"), { code: "FILE_TOO_LARGE" })));
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
finish(() => resolve(chosen.map((file) => ({
|
|
4925
|
+
path: URL.createObjectURL(file),
|
|
4926
|
+
name: file.name,
|
|
4927
|
+
type: file.type || "application/octet-stream",
|
|
4928
|
+
size: file.size
|
|
4929
|
+
}))));
|
|
4930
|
+
};
|
|
4931
|
+
document.body.append(input);
|
|
4932
|
+
input.click();
|
|
4933
|
+
});
|
|
4934
|
+
}
|
|
4935
|
+
};
|
|
4864
4936
|
var camera = {
|
|
4865
4937
|
async takePicture(options = {}) {
|
|
4866
4938
|
const craft = getCraftMobile();
|
|
@@ -11403,6 +11475,7 @@ export {
|
|
|
11403
11475
|
extractBundle,
|
|
11404
11476
|
extractBundleFromDmg,
|
|
11405
11477
|
extractBundleFromZip,
|
|
11478
|
+
files,
|
|
11406
11479
|
focus,
|
|
11407
11480
|
focusFilters,
|
|
11408
11481
|
foregroundService,
|
package/dist/ios/src/index.js
CHANGED
|
@@ -776,12 +776,15 @@ function renderWatchEntitlements(config) {
|
|
|
776
776
|
<array>
|
|
777
777
|
${plistArray(config.appGroups)}
|
|
778
778
|
</array>
|
|
779
|
+
` : "";
|
|
780
|
+
const healthKit = config.enableHealthKit ? ` <key>com.apple.developer.healthkit</key>
|
|
781
|
+
<true/>
|
|
779
782
|
` : "";
|
|
780
783
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
781
784
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
782
785
|
<plist version="1.0">
|
|
783
786
|
<dict>
|
|
784
|
-
${appGroups}</dict>
|
|
787
|
+
${appGroups}${healthKit}</dict>
|
|
785
788
|
</plist>
|
|
786
789
|
`;
|
|
787
790
|
}
|
|
@@ -1195,7 +1198,7 @@ function writeShell(output, config) {
|
|
|
1195
1198
|
if (!enabled)
|
|
1196
1199
|
continue;
|
|
1197
1200
|
mkdirSync2(join3(output, folder), { recursive: true });
|
|
1198
|
-
const source = readFileSync2(join3(TEMPLATES_DIR, template), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
|
|
1201
|
+
const source = readFileSync2(join3(TEMPLATES_DIR, template), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{HEALTH_KIT_ENABLED\}\}/g, config.enableHealthKit ? "true" : "false");
|
|
1199
1202
|
writeFileSync2(join3(output, folder, filename), source);
|
|
1200
1203
|
}
|
|
1201
1204
|
}
|
|
@@ -1328,7 +1331,8 @@ ${nativeDependencies.join(`
|
|
|
1328
1331
|
const watchInfo = readFileSync2(join3(TEMPLATES_DIR, "WatchApp.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
1329
1332
|
writeFileSync2(join3(output, "WatchApp", "Info.plist"), watchInfo);
|
|
1330
1333
|
writeFileSync2(join3(output, "WatchApp", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
1331
|
-
const watchExtensionInfo = readFileSync2(join3(TEMPLATES_DIR, "WatchExtension.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId)
|
|
1334
|
+
const watchExtensionInfo = readFileSync2(join3(TEMPLATES_DIR, "WatchExtension.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{HEALTH_USAGE_DESCRIPTIONS\}\}/g, config.enableHealthKit ? `${plistString("NSHealthShareUsageDescription", `${displayName} reads health data you choose to share.`)}
|
|
1335
|
+
${plistString("NSHealthUpdateUsageDescription", `${displayName} writes health data only with your permission.`)}` : "");
|
|
1332
1336
|
writeFileSync2(join3(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
|
|
1333
1337
|
writeFileSync2(join3(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
|
|
1334
1338
|
}
|
|
@@ -357,6 +357,7 @@ final class CraftHybrid: NSObject {
|
|
|
357
357
|
isConfigured = true
|
|
358
358
|
self.config = config
|
|
359
359
|
CraftNativeScriptThread.isBackground = config.nativeScriptThread == "background"
|
|
360
|
+
if CraftNativeScriptThread.isBackground { CraftNativeBundle.prewarm() }
|
|
360
361
|
hasWeb = config.renderer != "native"
|
|
361
362
|
siteURL = config.devServerURL.flatMap(URL.init(string:)).flatMap { url in
|
|
362
363
|
["http", "https"].contains((url.scheme ?? "").lowercased()) ? url : nil
|
|
@@ -2295,13 +2296,15 @@ final class CraftFramePacing: NSObject {
|
|
|
2295
2296
|
}
|
|
2296
2297
|
}
|
|
2297
2298
|
|
|
2298
|
-
/// One
|
|
2299
|
+
/// One block, timed: a screen mount, a bundle evaluation. Labelled by the
|
|
2300
|
+
/// thread it ran on, since a screen's script now runs off the main thread
|
|
2301
|
+
/// and a bundle's time there costs no frames.
|
|
2299
2302
|
@discardableResult
|
|
2300
2303
|
func measure<T>(_ name: String, _ work: () -> T) -> T {
|
|
2301
2304
|
guard enabled else { return work() }
|
|
2302
2305
|
let started = CACurrentMediaTime()
|
|
2303
2306
|
let result = work()
|
|
2304
|
-
write(String(format: "%@
|
|
2307
|
+
write(String(format: "%@ %@=%.1fms", name, Thread.isMainThread ? "main" : "script", (CACurrentMediaTime() - started) * 1000))
|
|
2305
2308
|
return result
|
|
2306
2309
|
}
|
|
2307
2310
|
|
|
@@ -253,6 +253,12 @@ enum CraftNativeActions {
|
|
|
253
253
|
CraftNativeFiles.pickVideo(options) { complete(requestToken, $0, completion) }
|
|
254
254
|
}
|
|
255
255
|
return
|
|
256
|
+
case ("Files", "pickFile"):
|
|
257
|
+
let options = args.first as? [String: Any] ?? [:]
|
|
258
|
+
DispatchQueue.main.async {
|
|
259
|
+
CraftNativeFiles.pickFile(options) { complete(requestToken, $0, completion) }
|
|
260
|
+
}
|
|
261
|
+
return
|
|
256
262
|
case ("Files", "download"):
|
|
257
263
|
download(requestToken: requestToken, args: args, completion: completion)
|
|
258
264
|
return
|