craft-native 0.0.278 → 0.0.290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/android/src/emulator.d.ts +4 -0
- package/dist/android/src/index.js +14 -5
- package/dist/android/templates/CraftNativeShell.kt.template +12 -5
- package/dist/android/templates/CraftSnapshotFile.kt.template +26 -0
- package/dist/android/templates/MainActivityNative.kt.template +16 -0
- package/dist/browser/storage.d.ts +5 -3
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +31 -7
- package/dist/cli.js +103 -23
- package/dist/index.cjs +100 -23
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +103 -23
- package/dist/ios/templates/CraftApp.swift +44 -5
- package/dist/ios/templates/CraftHybrid.swift +21 -0
- package/dist/ios/templates/CraftNativeScreen.swift +116 -8
- package/dist/package.d.ts +10 -0
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -374,7 +374,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
374
374
|
`);
|
|
375
375
|
}
|
|
376
376
|
// package.json
|
|
377
|
-
var version = "0.0.
|
|
377
|
+
var version = "0.0.290";
|
|
378
378
|
|
|
379
379
|
// src/sdk-version.ts
|
|
380
380
|
var SDK_VERSION = version;
|
|
@@ -611,6 +611,8 @@ async function packageMacOS(config, outDir) {
|
|
|
611
611
|
const { name, version, bundleId = `com.myapp.${name.toLowerCase()}` } = config;
|
|
612
612
|
const opts = config.macos || {};
|
|
613
613
|
const hardenedRuntime = !opts.appStore;
|
|
614
|
+
if (opts.appStore && (!opts.entitlements || !opts.provisioningProfile))
|
|
615
|
+
return [{ success: false, platform: "macos", format: "app", error: "App Store packaging requires macos.entitlements and macos.provisioningProfile" }];
|
|
614
616
|
const appBundlePath = import_path.join(outDir, `${name}.app`);
|
|
615
617
|
const appBundle = opts.appBundlePath ? copyMacOSAppBundle(opts.appBundlePath, appBundlePath) : createMacOSAppBundle({
|
|
616
618
|
name,
|
|
@@ -636,25 +638,42 @@ async function packageMacOS(config, outDir) {
|
|
|
636
638
|
});
|
|
637
639
|
return results;
|
|
638
640
|
}
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
641
|
+
try {
|
|
642
|
+
const contents = import_path.join(appBundlePath, "Contents");
|
|
643
|
+
if (opts.provisioningProfile) {
|
|
644
|
+
const profile = import_path.join(contents, "embedded.provisionprofile");
|
|
645
|
+
import_fs.copyFileSync(opts.provisioningProfile, profile);
|
|
646
|
+
import_fs.chmodSync(profile, 420);
|
|
647
|
+
}
|
|
648
|
+
if (opts.privacyManifest)
|
|
649
|
+
import_fs.copyFileSync(opts.privacyManifest, import_path.join(contents, "Resources", "PrivacyInfo.xcprivacy"));
|
|
650
|
+
if (opts.appBundlePath) {
|
|
651
|
+
const metadata = {
|
|
652
|
+
CFBundleVersion: opts.buildNumber,
|
|
653
|
+
LSApplicationCategoryType: opts.category,
|
|
654
|
+
LSMinimumSystemVersion: opts.minimumSystemVersion,
|
|
655
|
+
LSUIElement: opts.menuBarOnly
|
|
656
|
+
};
|
|
657
|
+
if (config.iconPath) {
|
|
658
|
+
const icon = import_path.basename(config.iconPath);
|
|
659
|
+
import_fs.copyFileSync(config.iconPath, import_path.join(contents, "Resources", icon));
|
|
660
|
+
metadata.CFBundleIconFile = icon;
|
|
661
|
+
}
|
|
662
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
663
|
+
if (value === undefined)
|
|
664
|
+
continue;
|
|
665
|
+
const result = await runTool("plutil", ["-replace", key, typeof value === "boolean" ? "-bool" : "-string", String(value), import_path.join(contents, "Info.plist")]);
|
|
666
|
+
if (!result.success)
|
|
667
|
+
throw new Error(result.error);
|
|
668
|
+
}
|
|
649
669
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
657
|
-
return results;
|
|
670
|
+
if (opts.signIdentity) {
|
|
671
|
+
await signMacOSAppBundle(appBundlePath, bundleId, opts, hardenedRuntime);
|
|
672
|
+
} else if (opts.appStore) {
|
|
673
|
+
throw new Error('App Store packaging requires macos.signIdentity (a "3rd Party Mac Developer Application" identity)');
|
|
674
|
+
}
|
|
675
|
+
} catch (error) {
|
|
676
|
+
return [{ success: false, platform: "macos", format: "app", error: error.message }];
|
|
658
677
|
}
|
|
659
678
|
results.push({ success: true, platform: "macos", format: "app", outputPath: appBundlePath });
|
|
660
679
|
if (opts.dmg !== false) {
|
|
@@ -678,7 +697,8 @@ async function packageMacOS(config, outDir) {
|
|
|
678
697
|
identifier: bundleId,
|
|
679
698
|
version,
|
|
680
699
|
appStore: opts.appStore === true,
|
|
681
|
-
installerIdentity: opts.installerIdentity
|
|
700
|
+
installerIdentity: opts.installerIdentity,
|
|
701
|
+
signingKeychain: opts.signingKeychain
|
|
682
702
|
});
|
|
683
703
|
results.push({
|
|
684
704
|
success: pkgResult.success,
|
|
@@ -919,6 +939,59 @@ ${body}
|
|
|
919
939
|
</dict>
|
|
920
940
|
</plist>`;
|
|
921
941
|
}
|
|
942
|
+
async function signMacOSAppBundle(app, bundleId, opts, hardenedRuntime) {
|
|
943
|
+
const scratch = createTempDir("craft-sign-");
|
|
944
|
+
try {
|
|
945
|
+
let helperEntitlements = opts.helperEntitlements;
|
|
946
|
+
if (opts.appStore && !helperEntitlements) {
|
|
947
|
+
helperEntitlements = import_path.join(scratch, "inherit.plist");
|
|
948
|
+
import_fs.writeFileSync(helperEntitlements, '<?xml version="1.0"?><plist version="1.0"><dict><key>com.apple.security.app-sandbox</key><true/><key>com.apple.security.inherit</key><true/></dict></plist>');
|
|
949
|
+
}
|
|
950
|
+
const extracted = await runTool("plutil", ["-extract", "CFBundleExecutable", "raw", "-o", "-", import_path.join(app, "Contents", "Info.plist")]);
|
|
951
|
+
if (!extracted.success)
|
|
952
|
+
throw new Error(extracted.error);
|
|
953
|
+
const main = import_path.join(app, "Contents", "MacOS", extracted.stdout.trim());
|
|
954
|
+
const sign = async (path, entitlements, identifier) => {
|
|
955
|
+
const signed = await runTool("codesign", codesignArguments({ path, identity: opts.signIdentity, entitlements, hardenedRuntime, identifier, keychain: opts.signingKeychain, deep: false }));
|
|
956
|
+
if (!signed.success)
|
|
957
|
+
throw new Error(signed.error);
|
|
958
|
+
};
|
|
959
|
+
const walk = async (directory) => {
|
|
960
|
+
for (const entry of import_fs.readdirSync(directory)) {
|
|
961
|
+
const path = import_path.join(directory, entry);
|
|
962
|
+
const stat = import_fs.lstatSync(path);
|
|
963
|
+
if (stat.isSymbolicLink())
|
|
964
|
+
continue;
|
|
965
|
+
if (stat.isDirectory()) {
|
|
966
|
+
await walk(path);
|
|
967
|
+
if (/\.(?:app|framework|xpc|appex|bundle)$/.test(entry))
|
|
968
|
+
await sign(path, /\.(?:app|xpc|appex)$/.test(entry) ? helperEntitlements : undefined);
|
|
969
|
+
} else if (path !== main && stat.isFile() && isMachO(path)) {
|
|
970
|
+
const executable = (stat.mode & 73) !== 0 && !/\.(?:dylib|so)$/.test(entry);
|
|
971
|
+
await sign(path, executable ? helperEntitlements : undefined, `${bundleId}.${entry}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
await walk(import_path.join(app, "Contents"));
|
|
976
|
+
await sign(app, opts.entitlements);
|
|
977
|
+
const verified = await runTool("codesign", ["--verify", "--deep", "--strict", app]);
|
|
978
|
+
if (!verified.success)
|
|
979
|
+
throw new Error(verified.error);
|
|
980
|
+
} finally {
|
|
981
|
+
removeTempDir(scratch);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function isMachO(path) {
|
|
985
|
+
const fd = import_fs.openSync(path, "r");
|
|
986
|
+
try {
|
|
987
|
+
const magic = Buffer.alloc(4);
|
|
988
|
+
if (import_fs.readSync(fd, magic, 0, 4, 0) !== 4)
|
|
989
|
+
return false;
|
|
990
|
+
return [4277009102, 3472551422, 4277009103, 3489328638, 3405691582, 3199925962, 3405691583, 3216703178].includes(magic.readUInt32BE());
|
|
991
|
+
} finally {
|
|
992
|
+
import_fs.closeSync(fd);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
922
995
|
function codesignArguments(opts) {
|
|
923
996
|
return [
|
|
924
997
|
"--force",
|
|
@@ -927,7 +1000,9 @@ function codesignArguments(opts) {
|
|
|
927
1000
|
...opts.identity === "-" ? [] : ["--timestamp"],
|
|
928
1001
|
...opts.hardenedRuntime ? ["--options", "runtime"] : [],
|
|
929
1002
|
...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
|
|
930
|
-
"--
|
|
1003
|
+
...opts.identifier ? ["--identifier", opts.identifier] : [],
|
|
1004
|
+
...opts.keychain ? ["--keychain", opts.keychain] : [],
|
|
1005
|
+
...opts.deep === false ? [] : ["--deep"],
|
|
931
1006
|
opts.path
|
|
932
1007
|
];
|
|
933
1008
|
}
|
|
@@ -938,6 +1013,7 @@ function productbuildArguments(opts) {
|
|
|
938
1013
|
"/Applications",
|
|
939
1014
|
"--sign",
|
|
940
1015
|
opts.identity,
|
|
1016
|
+
...opts.keychain ? ["--keychain", opts.keychain] : [],
|
|
941
1017
|
opts.outputPath
|
|
942
1018
|
];
|
|
943
1019
|
}
|
|
@@ -1057,7 +1133,7 @@ function runTool(tool, args) {
|
|
|
1057
1133
|
});
|
|
1058
1134
|
proc.on("close", (code) => {
|
|
1059
1135
|
if (code === 0)
|
|
1060
|
-
resolve({ success: true });
|
|
1136
|
+
resolve({ success: true, stdout });
|
|
1061
1137
|
else
|
|
1062
1138
|
resolve({ success: false, error: formatPackagingCommandError(tool, code, stdout, stderr) });
|
|
1063
1139
|
});
|
|
@@ -1132,7 +1208,8 @@ async function createPKG(opts) {
|
|
|
1132
1208
|
const built = await runTool("productbuild", productbuildArguments({
|
|
1133
1209
|
appBundlePath: opts.appBundlePath,
|
|
1134
1210
|
outputPath: opts.outputPath,
|
|
1135
|
-
identity: opts.installerIdentity
|
|
1211
|
+
identity: opts.installerIdentity,
|
|
1212
|
+
keychain: opts.signingKeychain
|
|
1136
1213
|
}));
|
|
1137
1214
|
return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
|
|
1138
1215
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -85,4 +85,4 @@ export declare function show(html: string, options?: WindowOptions): Promise<voi
|
|
|
85
85
|
* Quick helper to load a URL
|
|
86
86
|
*/
|
|
87
87
|
export declare function loadURL(url: string, options?: WindowOptions): Promise<void>;
|
|
88
|
-
export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
|
|
88
|
+
export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, CraftTimersAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -85,4 +85,4 @@ export declare function show(html: string, options?: WindowOptions): Promise<voi
|
|
|
85
85
|
* Quick helper to load a URL
|
|
86
86
|
*/
|
|
87
87
|
export declare function loadURL(url: string, options?: WindowOptions): Promise<void>;
|
|
88
|
-
export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
|
|
88
|
+
export type { WindowOptions, CraftSidebarAPI, CraftNativeUIAPI, CraftContextMenuItem, CraftContextMenuResult, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, CraftTimersAPI, Permission, HapticType, CraftHapticsAPI, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftFsWatchEvent, CraftFsWatchHandle, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
|
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.290";
|
|
121
121
|
|
|
122
122
|
// src/sdk-version.ts
|
|
123
123
|
var SDK_VERSION = version;
|
|
@@ -241,12 +241,15 @@ import { spawn } from "child_process";
|
|
|
241
241
|
import { createHash } from "crypto";
|
|
242
242
|
import {
|
|
243
243
|
chmodSync,
|
|
244
|
+
closeSync,
|
|
244
245
|
copyFileSync,
|
|
245
246
|
cpSync,
|
|
246
247
|
existsSync as existsSync3,
|
|
247
248
|
lstatSync,
|
|
248
249
|
mkdirSync,
|
|
250
|
+
openSync,
|
|
249
251
|
readFileSync,
|
|
252
|
+
readSync,
|
|
250
253
|
readdirSync as readdirSync2,
|
|
251
254
|
rmSync as rmSync2,
|
|
252
255
|
writeFileSync as writeFileSync2
|
|
@@ -365,6 +368,8 @@ async function packageMacOS(config, outDir) {
|
|
|
365
368
|
const { name, version, bundleId = `com.myapp.${name.toLowerCase()}` } = config;
|
|
366
369
|
const opts = config.macos || {};
|
|
367
370
|
const hardenedRuntime = !opts.appStore;
|
|
371
|
+
if (opts.appStore && (!opts.entitlements || !opts.provisioningProfile))
|
|
372
|
+
return [{ success: false, platform: "macos", format: "app", error: "App Store packaging requires macos.entitlements and macos.provisioningProfile" }];
|
|
368
373
|
const appBundlePath = join2(outDir, `${name}.app`);
|
|
369
374
|
const appBundle = opts.appBundlePath ? copyMacOSAppBundle(opts.appBundlePath, appBundlePath) : createMacOSAppBundle({
|
|
370
375
|
name,
|
|
@@ -390,25 +395,42 @@ async function packageMacOS(config, outDir) {
|
|
|
390
395
|
});
|
|
391
396
|
return results;
|
|
392
397
|
}
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
398
|
+
try {
|
|
399
|
+
const contents = join2(appBundlePath, "Contents");
|
|
400
|
+
if (opts.provisioningProfile) {
|
|
401
|
+
const profile = join2(contents, "embedded.provisionprofile");
|
|
402
|
+
copyFileSync(opts.provisioningProfile, profile);
|
|
403
|
+
chmodSync(profile, 420);
|
|
404
|
+
}
|
|
405
|
+
if (opts.privacyManifest)
|
|
406
|
+
copyFileSync(opts.privacyManifest, join2(contents, "Resources", "PrivacyInfo.xcprivacy"));
|
|
407
|
+
if (opts.appBundlePath) {
|
|
408
|
+
const metadata = {
|
|
409
|
+
CFBundleVersion: opts.buildNumber,
|
|
410
|
+
LSApplicationCategoryType: opts.category,
|
|
411
|
+
LSMinimumSystemVersion: opts.minimumSystemVersion,
|
|
412
|
+
LSUIElement: opts.menuBarOnly
|
|
413
|
+
};
|
|
414
|
+
if (config.iconPath) {
|
|
415
|
+
const icon = basename(config.iconPath);
|
|
416
|
+
copyFileSync(config.iconPath, join2(contents, "Resources", icon));
|
|
417
|
+
metadata.CFBundleIconFile = icon;
|
|
418
|
+
}
|
|
419
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
420
|
+
if (value === undefined)
|
|
421
|
+
continue;
|
|
422
|
+
const result = await runTool("plutil", ["-replace", key, typeof value === "boolean" ? "-bool" : "-string", String(value), join2(contents, "Info.plist")]);
|
|
423
|
+
if (!result.success)
|
|
424
|
+
throw new Error(result.error);
|
|
425
|
+
}
|
|
403
426
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
return results;
|
|
427
|
+
if (opts.signIdentity) {
|
|
428
|
+
await signMacOSAppBundle(appBundlePath, bundleId, opts, hardenedRuntime);
|
|
429
|
+
} else if (opts.appStore) {
|
|
430
|
+
throw new Error('App Store packaging requires macos.signIdentity (a "3rd Party Mac Developer Application" identity)');
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
return [{ success: false, platform: "macos", format: "app", error: error.message }];
|
|
412
434
|
}
|
|
413
435
|
results.push({ success: true, platform: "macos", format: "app", outputPath: appBundlePath });
|
|
414
436
|
if (opts.dmg !== false) {
|
|
@@ -432,7 +454,8 @@ async function packageMacOS(config, outDir) {
|
|
|
432
454
|
identifier: bundleId,
|
|
433
455
|
version,
|
|
434
456
|
appStore: opts.appStore === true,
|
|
435
|
-
installerIdentity: opts.installerIdentity
|
|
457
|
+
installerIdentity: opts.installerIdentity,
|
|
458
|
+
signingKeychain: opts.signingKeychain
|
|
436
459
|
});
|
|
437
460
|
results.push({
|
|
438
461
|
success: pkgResult.success,
|
|
@@ -673,6 +696,59 @@ ${body}
|
|
|
673
696
|
</dict>
|
|
674
697
|
</plist>`;
|
|
675
698
|
}
|
|
699
|
+
async function signMacOSAppBundle(app, bundleId, opts, hardenedRuntime) {
|
|
700
|
+
const scratch = createTempDir("craft-sign-");
|
|
701
|
+
try {
|
|
702
|
+
let helperEntitlements = opts.helperEntitlements;
|
|
703
|
+
if (opts.appStore && !helperEntitlements) {
|
|
704
|
+
helperEntitlements = join2(scratch, "inherit.plist");
|
|
705
|
+
writeFileSync2(helperEntitlements, '<?xml version="1.0"?><plist version="1.0"><dict><key>com.apple.security.app-sandbox</key><true/><key>com.apple.security.inherit</key><true/></dict></plist>');
|
|
706
|
+
}
|
|
707
|
+
const extracted = await runTool("plutil", ["-extract", "CFBundleExecutable", "raw", "-o", "-", join2(app, "Contents", "Info.plist")]);
|
|
708
|
+
if (!extracted.success)
|
|
709
|
+
throw new Error(extracted.error);
|
|
710
|
+
const main = join2(app, "Contents", "MacOS", extracted.stdout.trim());
|
|
711
|
+
const sign = async (path, entitlements, identifier) => {
|
|
712
|
+
const signed = await runTool("codesign", codesignArguments({ path, identity: opts.signIdentity, entitlements, hardenedRuntime, identifier, keychain: opts.signingKeychain, deep: false }));
|
|
713
|
+
if (!signed.success)
|
|
714
|
+
throw new Error(signed.error);
|
|
715
|
+
};
|
|
716
|
+
const walk = async (directory) => {
|
|
717
|
+
for (const entry of readdirSync2(directory)) {
|
|
718
|
+
const path = join2(directory, entry);
|
|
719
|
+
const stat = lstatSync(path);
|
|
720
|
+
if (stat.isSymbolicLink())
|
|
721
|
+
continue;
|
|
722
|
+
if (stat.isDirectory()) {
|
|
723
|
+
await walk(path);
|
|
724
|
+
if (/\.(?:app|framework|xpc|appex|bundle)$/.test(entry))
|
|
725
|
+
await sign(path, /\.(?:app|xpc|appex)$/.test(entry) ? helperEntitlements : undefined);
|
|
726
|
+
} else if (path !== main && stat.isFile() && isMachO(path)) {
|
|
727
|
+
const executable = (stat.mode & 73) !== 0 && !/\.(?:dylib|so)$/.test(entry);
|
|
728
|
+
await sign(path, executable ? helperEntitlements : undefined, `${bundleId}.${entry}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
await walk(join2(app, "Contents"));
|
|
733
|
+
await sign(app, opts.entitlements);
|
|
734
|
+
const verified = await runTool("codesign", ["--verify", "--deep", "--strict", app]);
|
|
735
|
+
if (!verified.success)
|
|
736
|
+
throw new Error(verified.error);
|
|
737
|
+
} finally {
|
|
738
|
+
removeTempDir(scratch);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function isMachO(path) {
|
|
742
|
+
const fd = openSync(path, "r");
|
|
743
|
+
try {
|
|
744
|
+
const magic = Buffer.alloc(4);
|
|
745
|
+
if (readSync(fd, magic, 0, 4, 0) !== 4)
|
|
746
|
+
return false;
|
|
747
|
+
return [4277009102, 3472551422, 4277009103, 3489328638, 3405691582, 3199925962, 3405691583, 3216703178].includes(magic.readUInt32BE());
|
|
748
|
+
} finally {
|
|
749
|
+
closeSync(fd);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
676
752
|
function codesignArguments(opts) {
|
|
677
753
|
return [
|
|
678
754
|
"--force",
|
|
@@ -681,7 +757,9 @@ function codesignArguments(opts) {
|
|
|
681
757
|
...opts.identity === "-" ? [] : ["--timestamp"],
|
|
682
758
|
...opts.hardenedRuntime ? ["--options", "runtime"] : [],
|
|
683
759
|
...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
|
|
684
|
-
"--
|
|
760
|
+
...opts.identifier ? ["--identifier", opts.identifier] : [],
|
|
761
|
+
...opts.keychain ? ["--keychain", opts.keychain] : [],
|
|
762
|
+
...opts.deep === false ? [] : ["--deep"],
|
|
685
763
|
opts.path
|
|
686
764
|
];
|
|
687
765
|
}
|
|
@@ -692,6 +770,7 @@ function productbuildArguments(opts) {
|
|
|
692
770
|
"/Applications",
|
|
693
771
|
"--sign",
|
|
694
772
|
opts.identity,
|
|
773
|
+
...opts.keychain ? ["--keychain", opts.keychain] : [],
|
|
695
774
|
opts.outputPath
|
|
696
775
|
];
|
|
697
776
|
}
|
|
@@ -811,7 +890,7 @@ function runTool(tool, args) {
|
|
|
811
890
|
});
|
|
812
891
|
proc.on("close", (code) => {
|
|
813
892
|
if (code === 0)
|
|
814
|
-
resolve({ success: true });
|
|
893
|
+
resolve({ success: true, stdout });
|
|
815
894
|
else
|
|
816
895
|
resolve({ success: false, error: formatPackagingCommandError(tool, code, stdout, stderr) });
|
|
817
896
|
});
|
|
@@ -886,7 +965,8 @@ async function createPKG(opts) {
|
|
|
886
965
|
const built = await runTool("productbuild", productbuildArguments({
|
|
887
966
|
appBundlePath: opts.appBundlePath,
|
|
888
967
|
outputPath: opts.outputPath,
|
|
889
|
-
identity: opts.installerIdentity
|
|
968
|
+
identity: opts.installerIdentity,
|
|
969
|
+
keychain: opts.signingKeychain
|
|
890
970
|
}));
|
|
891
971
|
return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
|
|
892
972
|
}
|
|
@@ -903,6 +903,9 @@ final class CraftChrome: ObservableObject {
|
|
|
903
903
|
func tap(_ tab: Tab) {
|
|
904
904
|
let reselect = tab.id == selected
|
|
905
905
|
if !reselect {
|
|
906
|
+
// A tab's screen not made yet is made first, so the pill moves on
|
|
907
|
+
// a free main thread rather than stalling behind its mount.
|
|
908
|
+
CraftHybrid.shared.mountTabBeforeSelecting(tab.id)
|
|
906
909
|
withAnimation(Self.motion(.spring(response: 0.32, dampingFraction: 0.86))) { selected = tab.id }
|
|
907
910
|
}
|
|
908
911
|
// A hybrid app shows the tab's own stack first; the page hears the
|
|
@@ -1162,11 +1165,7 @@ struct CraftTabBarView: View {
|
|
|
1162
1165
|
VStack(spacing: 3) {
|
|
1163
1166
|
ZStack(alignment: .topTrailing) {
|
|
1164
1167
|
if let image = tab.image, let url = URL(string: image) {
|
|
1165
|
-
|
|
1166
|
-
image.resizable().scaledToFill()
|
|
1167
|
-
} placeholder: {
|
|
1168
|
-
Image(systemName: symbol(tab.symbol, selected: isSelected))
|
|
1169
|
-
}
|
|
1168
|
+
CraftTabPicture(url: url, placeholder: symbol(tab.symbol, selected: isSelected))
|
|
1170
1169
|
.frame(width: iconSize + 4, height: iconSize + 4)
|
|
1171
1170
|
.clipShape(Circle())
|
|
1172
1171
|
.frame(height: iconSize + 6)
|
|
@@ -1258,6 +1257,46 @@ struct CraftTabBarView: View {
|
|
|
1258
1257
|
}
|
|
1259
1258
|
}
|
|
1260
1259
|
|
|
1260
|
+
/// A tab's picture (HQ's Me: the account's photo). It is kept once loaded,
|
|
1261
|
+
/// so a tab item drawn again (its screen mounting, the bar coming back) shows
|
|
1262
|
+
/// it at once: AsyncImage keeps nothing, so the photo went back to the
|
|
1263
|
+
/// person symbol, loaded again and arrived inside whatever animation was
|
|
1264
|
+
/// running, sliding in from the side on the first visit to Me.
|
|
1265
|
+
private struct CraftTabPicture: View {
|
|
1266
|
+
let url: URL
|
|
1267
|
+
let placeholder: String
|
|
1268
|
+
@State private var image: UIImage?
|
|
1269
|
+
|
|
1270
|
+
private static let cache: NSCache<NSURL, UIImage> = {
|
|
1271
|
+
let cache = NSCache<NSURL, UIImage>()
|
|
1272
|
+
cache.countLimit = 16
|
|
1273
|
+
return cache
|
|
1274
|
+
}()
|
|
1275
|
+
|
|
1276
|
+
var body: some View {
|
|
1277
|
+
Group {
|
|
1278
|
+
if let shown = image ?? Self.cache.object(forKey: url as NSURL) {
|
|
1279
|
+
Image(uiImage: shown).resizable().scaledToFill()
|
|
1280
|
+
} else {
|
|
1281
|
+
Image(systemName: placeholder)
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
// A picture arriving never animates, whatever transaction it lands in.
|
|
1285
|
+
.transaction { $0.animation = nil }
|
|
1286
|
+
.task(id: url) {
|
|
1287
|
+
if let cached = Self.cache.object(forKey: url as NSURL) {
|
|
1288
|
+
image = cached
|
|
1289
|
+
return
|
|
1290
|
+
}
|
|
1291
|
+
guard let (data, response) = try? await URLSession.shared.data(from: url),
|
|
1292
|
+
(response as? HTTPURLResponse).map({ (200..<300).contains($0.statusCode) }) ?? true,
|
|
1293
|
+
let loaded = UIImage(data: data) else { return }
|
|
1294
|
+
Self.cache.setObject(loaded, forKey: url as NSURL)
|
|
1295
|
+
image = loaded
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1261
1300
|
private struct CraftTabSelectionShape: Shape {
|
|
1262
1301
|
let cornerRadius: CGFloat?
|
|
1263
1302
|
|
|
@@ -880,6 +880,13 @@ final class CraftHybrid: NSObject {
|
|
|
880
880
|
return controller.tabTapped(id, reselect: reselect)
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
+
/// A native tab not prepared yet mounts its screen now, hidden, before
|
|
884
|
+
/// the bar's selection moves (CraftChrome.tap).
|
|
885
|
+
func mountTabBeforeSelecting(_ id: String) {
|
|
886
|
+
guard isEnabled else { return }
|
|
887
|
+
controller?.mountTabBeforeSelecting(id)
|
|
888
|
+
}
|
|
889
|
+
|
|
883
890
|
/// The page chose a tab itself (its `selectTab`, or a tab link).
|
|
884
891
|
func pageSelectedTab(_ id: String) {
|
|
885
892
|
guard isEnabled else { return }
|
|
@@ -1731,6 +1738,20 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1731
1738
|
|
|
1732
1739
|
private var preparation: Timer?
|
|
1733
1740
|
|
|
1741
|
+
/// A native tab tapped before the shell prepared it (HQ's Me, prepared
|
|
1742
|
+
/// last, tapped right after launch) mounts its screen hidden first. Mounted
|
|
1743
|
+
/// in the tap's own turn after the selection began, it held the main
|
|
1744
|
+
/// thread while SwiftUI's spring should have moved the pill, which then
|
|
1745
|
+
/// stalled and jumped in from the side.
|
|
1746
|
+
func mountTabBeforeSelecting(_ id: String) {
|
|
1747
|
+
guard stacks[id] == nil, id != currentTab, activeTabSwipeTarget == nil,
|
|
1748
|
+
hybrid.tabIds.contains(id), hybrid.routes.match(id) != nil, view.window != nil else { return }
|
|
1749
|
+
let nav = makeStack(tab: id, rootPath: id, hidden: true)
|
|
1750
|
+
nav.view.layoutIfNeeded()
|
|
1751
|
+
Self.drawContents(of: nav.view.layer)
|
|
1752
|
+
(visibleStack?.topViewController as? CraftNativeScreenController)?.screenVisibilityChanged(true)
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1734
1755
|
/// When the shell last moved between screens: a tab, a push or a pop.
|
|
1735
1756
|
private var lastNavigation: CFTimeInterval = 0
|
|
1736
1757
|
|