craft-native 0.0.104 → 0.0.107
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 +2 -0
- package/dist/android/src/index.js +33 -6
- package/dist/android/templates/MainActivityNative.kt.template +390 -0
- package/dist/android/templates/build.gradle.kts.app.template +1 -0
- package/dist/api/window.d.ts +6 -11
- package/dist/cli.js +198 -1507
- package/dist/index.cjs +115 -26
- package/dist/index.js +115 -26
- package/dist/ios/src/index.d.ts +4 -0
- package/dist/ios/src/index.js +32 -3
- package/dist/ios/templates/CraftApp.swift +20 -92
- package/dist/ios/templates/CraftNativeActions.swift +93 -0
- package/dist/ios/templates/CraftNativeScreen.swift +381 -0
- package/dist/package.d.ts +21 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -310,7 +310,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
310
310
|
`);
|
|
311
311
|
}
|
|
312
312
|
// package.json
|
|
313
|
-
var version = "0.0.
|
|
313
|
+
var version = "0.0.107";
|
|
314
314
|
|
|
315
315
|
// src/sdk-version.ts
|
|
316
316
|
var SDK_VERSION = version;
|
|
@@ -322,6 +322,7 @@ var import_fs = require("fs");
|
|
|
322
322
|
var import_os = require("os");
|
|
323
323
|
var import_path = require("path");
|
|
324
324
|
var import_zlib = require("zlib");
|
|
325
|
+
var DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
|
|
325
326
|
var CRC32_TABLE = (() => {
|
|
326
327
|
const table = new Uint32Array(256);
|
|
327
328
|
for (let n = 0;n < 256; n++) {
|
|
@@ -553,6 +554,9 @@ async function packageWindows(config, outDir) {
|
|
|
553
554
|
outputPath: import_path.join(outDir, `${name}-${version2}.msi`),
|
|
554
555
|
manufacturer: config.author || "Unknown",
|
|
555
556
|
architecture: opts.architecture || windowsArchitecture(process.arch),
|
|
557
|
+
additionalFiles: opts.additionalFiles,
|
|
558
|
+
urlSchemes: opts.urlSchemes,
|
|
559
|
+
launchUrl: opts.launchUrl,
|
|
556
560
|
certificatePath: opts.certificatePath,
|
|
557
561
|
certificatePassword: opts.certificatePassword
|
|
558
562
|
});
|
|
@@ -568,7 +572,9 @@ async function packageWindows(config, outDir) {
|
|
|
568
572
|
const zipResult = await createZIP({
|
|
569
573
|
name,
|
|
570
574
|
version: version2,
|
|
575
|
+
manufacturer: config.author || "Unknown",
|
|
571
576
|
binaryPath: config.binaryPath,
|
|
577
|
+
additionalFiles: opts.additionalFiles,
|
|
572
578
|
outputPath: import_path.join(outDir, `${name}-${version2}-windows.zip`)
|
|
573
579
|
});
|
|
574
580
|
results.push({
|
|
@@ -593,7 +599,9 @@ async function packageLinux(config, outDir) {
|
|
|
593
599
|
outputPath: import_path.join(outDir, `${name}_${version2}_amd64.deb`),
|
|
594
600
|
description: config.description || "",
|
|
595
601
|
maintainer: config.author || "Unknown",
|
|
596
|
-
dependencies: opts.debDependencies || [
|
|
602
|
+
dependencies: opts.debDependencies || [...DEFAULT_DEB_DEPENDENCIES],
|
|
603
|
+
urlSchemes: opts.urlSchemes,
|
|
604
|
+
launchUrl: opts.launchUrl
|
|
597
605
|
});
|
|
598
606
|
results.push({
|
|
599
607
|
success: debResult.success,
|
|
@@ -640,17 +648,7 @@ async function packageLinux(config, outDir) {
|
|
|
640
648
|
}
|
|
641
649
|
var URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
|
|
642
650
|
function urlTypesEntry(bundleId, schemes) {
|
|
643
|
-
const
|
|
644
|
-
const unique = [];
|
|
645
|
-
for (const scheme of schemes) {
|
|
646
|
-
if (!URL_SCHEME.test(scheme))
|
|
647
|
-
throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
|
|
648
|
-
const key = scheme.toLowerCase();
|
|
649
|
-
if (seen.has(key))
|
|
650
|
-
continue;
|
|
651
|
-
seen.add(key);
|
|
652
|
-
unique.push(scheme);
|
|
653
|
-
}
|
|
651
|
+
const unique = uniqueUrlSchemes(schemes);
|
|
654
652
|
if (unique.length === 0)
|
|
655
653
|
return null;
|
|
656
654
|
const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
|
|
@@ -669,6 +667,46 @@ ${list}
|
|
|
669
667
|
</dict>
|
|
670
668
|
</array>`;
|
|
671
669
|
}
|
|
670
|
+
function uniqueUrlSchemes(schemes) {
|
|
671
|
+
const seen = new Set;
|
|
672
|
+
const unique = [];
|
|
673
|
+
for (const scheme of schemes) {
|
|
674
|
+
if (!URL_SCHEME.test(scheme))
|
|
675
|
+
throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
|
|
676
|
+
const key = scheme.toLowerCase();
|
|
677
|
+
if (seen.has(key))
|
|
678
|
+
continue;
|
|
679
|
+
seen.add(key);
|
|
680
|
+
unique.push(scheme);
|
|
681
|
+
}
|
|
682
|
+
return unique;
|
|
683
|
+
}
|
|
684
|
+
function launchPageUrl(value) {
|
|
685
|
+
let url;
|
|
686
|
+
try {
|
|
687
|
+
url = new URL(value);
|
|
688
|
+
} catch {
|
|
689
|
+
throw new Error(`Invalid packaged app launch URL: ${JSON.stringify(value)}`);
|
|
690
|
+
}
|
|
691
|
+
if (!["http:", "https:", "file:"].includes(url.protocol))
|
|
692
|
+
throw new Error(`Unsupported packaged app launch URL protocol: ${url.protocol}`);
|
|
693
|
+
return url.href;
|
|
694
|
+
}
|
|
695
|
+
function desktopExecArgument(value) {
|
|
696
|
+
return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`;
|
|
697
|
+
}
|
|
698
|
+
function linuxDesktopEntry(name, binaryName, urlSchemes = [], launchUrl) {
|
|
699
|
+
const schemes = uniqueUrlSchemes(urlSchemes);
|
|
700
|
+
const page = launchUrl ? ` --url ${desktopExecArgument(launchPageUrl(launchUrl))}` : "";
|
|
701
|
+
return `[Desktop Entry]
|
|
702
|
+
Type=Application
|
|
703
|
+
Name=${name}
|
|
704
|
+
Exec=/usr/bin/${binaryName}${page}${schemes.length ? " --deep-link %u" : ""}
|
|
705
|
+
Terminal=false
|
|
706
|
+
Categories=Utility;
|
|
707
|
+
${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${scheme.toLowerCase()};`).join("")}
|
|
708
|
+
` : ""}`;
|
|
709
|
+
}
|
|
672
710
|
function macOSInfoPlist(metadata) {
|
|
673
711
|
const entries = [
|
|
674
712
|
["CFBundleExecutable", metadata.name],
|
|
@@ -706,7 +744,7 @@ function codesignArguments(opts) {
|
|
|
706
744
|
"--force",
|
|
707
745
|
"--sign",
|
|
708
746
|
opts.identity,
|
|
709
|
-
"--timestamp",
|
|
747
|
+
...opts.identity === "-" ? [] : ["--timestamp"],
|
|
710
748
|
...opts.hardenedRuntime ? ["--options", "runtime"] : [],
|
|
711
749
|
...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
|
|
712
750
|
"--deep",
|
|
@@ -934,6 +972,10 @@ function deterministicGuid(value) {
|
|
|
934
972
|
const hex = digest.join("").toUpperCase();
|
|
935
973
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
936
974
|
}
|
|
975
|
+
var WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
|
|
976
|
+
function windowsNotificationAppId(name, manufacturer) {
|
|
977
|
+
return `Craft.${deterministicGuid(`${manufacturer.trim() || "Unknown"}/${name}`)}`;
|
|
978
|
+
}
|
|
937
979
|
function windowsArchitecture(architecture) {
|
|
938
980
|
if (architecture === "ia32" || architecture === "x86")
|
|
939
981
|
return "x86";
|
|
@@ -941,7 +983,7 @@ function windowsArchitecture(architecture) {
|
|
|
941
983
|
return architecture;
|
|
942
984
|
throw new Error(`Unsupported Windows package architecture: ${architecture}`);
|
|
943
985
|
}
|
|
944
|
-
function renderWixSource(opts, sourceName) {
|
|
986
|
+
function renderWixSource(opts, sourceName, additionalFileNames = []) {
|
|
945
987
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
|
|
946
988
|
throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
|
|
947
989
|
const id = wixIdentifier(opts.name);
|
|
@@ -949,6 +991,9 @@ function renderWixSource(opts, sourceName) {
|
|
|
949
991
|
const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
|
|
950
992
|
const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
|
|
951
993
|
const win64 = opts.architecture === "x86" ? "no" : "yes";
|
|
994
|
+
const notificationAppId = windowsNotificationAppId(opts.name, manufacturer);
|
|
995
|
+
const schemes = uniqueUrlSchemes(opts.urlSchemes || []);
|
|
996
|
+
const page = opts.launchUrl ? ` --url "${xml(launchPageUrl(opts.launchUrl))}"` : "";
|
|
952
997
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
953
998
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
|
954
999
|
<Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
|
|
@@ -959,18 +1004,59 @@ function renderWixSource(opts, sourceName) {
|
|
|
959
1004
|
<Directory Id="${programFilesFolder}">
|
|
960
1005
|
<Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
|
|
961
1006
|
<Component Id="${id}Executable" Guid="*" Win64="${win64}">
|
|
962
|
-
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes"
|
|
1007
|
+
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes">
|
|
1008
|
+
<Shortcut Id="${id}StartMenuShortcut" Directory="ProgramMenuFolder" Name="${xml(opts.name)}" WorkingDirectory="INSTALLFOLDER"${page ? ` Arguments="${page.trim()}"` : ""}>
|
|
1009
|
+
<ShortcutProperty Key="System.AppUserModel.ID" Value="${notificationAppId}" />
|
|
1010
|
+
</Shortcut>
|
|
1011
|
+
</File>
|
|
1012
|
+
</Component>
|
|
1013
|
+
<Component Id="${id}NotificationIdentity" Guid="*" Win64="${win64}">
|
|
1014
|
+
<File Id="${id}NotificationIdentityFile" Source="${WINDOWS_NOTIFICATION_ID_FILE}" KeyPath="yes" />
|
|
963
1015
|
</Component>
|
|
1016
|
+
${additionalFileNames.map((name, index) => ` <Component Id="${id}Extra${index}" Guid="*" Win64="${win64}">
|
|
1017
|
+
<File Id="${id}ExtraFile${index}" Source="${xml(name)}" KeyPath="yes" />
|
|
1018
|
+
</Component>`).join(`
|
|
1019
|
+
`)}
|
|
1020
|
+
${schemes.map((scheme, index) => ` <Component Id="${id}Protocol${index}" Guid="${deterministicGuid(`${manufacturer}/${opts.name}/protocol/${scheme.toLowerCase()}`)}" Win64="${win64}">
|
|
1021
|
+
<RegistryKey Root="HKLM" Key="Software\\Classes\\${xml(scheme.toLowerCase())}" Action="createAndRemoveOnUninstall">
|
|
1022
|
+
<RegistryValue Type="string" Value="URL:${xml(opts.name)} Protocol" KeyPath="yes" />
|
|
1023
|
+
<RegistryValue Type="string" Name="URL Protocol" Value="" />
|
|
1024
|
+
<RegistryKey Key="shell\\open\\command">
|
|
1025
|
+
<RegistryValue Type="string" Value=""[#${id}File]"${page} --deep-link "%1"" />
|
|
1026
|
+
</RegistryKey>
|
|
1027
|
+
</RegistryKey>
|
|
1028
|
+
</Component>`).join(`
|
|
1029
|
+
`)}
|
|
964
1030
|
</Directory>
|
|
965
1031
|
</Directory>
|
|
1032
|
+
<Directory Id="ProgramMenuFolder" />
|
|
966
1033
|
</Directory>
|
|
967
1034
|
<Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
|
|
968
1035
|
<ComponentRef Id="${id}Executable" />
|
|
1036
|
+
<ComponentRef Id="${id}NotificationIdentity" />
|
|
1037
|
+
${additionalFileNames.map((_, index) => ` <ComponentRef Id="${id}Extra${index}" />`).join(`
|
|
1038
|
+
`)}
|
|
1039
|
+
${schemes.map((_, index) => ` <ComponentRef Id="${id}Protocol${index}" />`).join(`
|
|
1040
|
+
`)}
|
|
969
1041
|
</Feature>
|
|
970
1042
|
</Product>
|
|
971
1043
|
</Wix>
|
|
972
1044
|
`;
|
|
973
1045
|
}
|
|
1046
|
+
function windowsCompanionFiles(paths, executableName) {
|
|
1047
|
+
const seen = new Set([executableName.toLowerCase(), WINDOWS_NOTIFICATION_ID_FILE.toLowerCase()]);
|
|
1048
|
+
return (paths || []).map((path) => {
|
|
1049
|
+
const name = import_path.basename(path);
|
|
1050
|
+
if (!name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name) || /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
|
|
1051
|
+
throw new Error(`Invalid Windows companion file name: ${JSON.stringify(name)}`);
|
|
1052
|
+
if (seen.has(name.toLowerCase()))
|
|
1053
|
+
throw new Error(`Duplicate Windows package file: ${name}`);
|
|
1054
|
+
seen.add(name.toLowerCase());
|
|
1055
|
+
if (!import_fs.existsSync(path) || import_fs.lstatSync(path).isDirectory())
|
|
1056
|
+
throw new Error(`Windows companion file not found: ${path}`);
|
|
1057
|
+
return { path, name };
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
974
1060
|
function windowsExecutableName(name) {
|
|
975
1061
|
if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
|
|
976
1062
|
throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
|
|
@@ -992,11 +1078,15 @@ async function createMSI(opts) {
|
|
|
992
1078
|
const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-msi-"));
|
|
993
1079
|
try {
|
|
994
1080
|
const binaryName = windowsExecutableName(opts.name);
|
|
1081
|
+
const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
|
|
995
1082
|
const sourcePath = import_path.join(tempDir, binaryName);
|
|
996
1083
|
const wxsPath = import_path.join(tempDir, "installer.wxs");
|
|
997
1084
|
const wixobjPath = import_path.join(tempDir, "installer.wixobj");
|
|
998
1085
|
import_fs.copyFileSync(opts.binaryPath, sourcePath);
|
|
999
|
-
import_fs.writeFileSync(
|
|
1086
|
+
import_fs.writeFileSync(import_path.join(tempDir, WINDOWS_NOTIFICATION_ID_FILE), windowsNotificationAppId(opts.name, opts.manufacturer));
|
|
1087
|
+
for (const file of additionalFiles)
|
|
1088
|
+
import_fs.copyFileSync(file.path, import_path.join(tempDir, file.name));
|
|
1089
|
+
import_fs.writeFileSync(wxsPath, renderWixSource(opts, binaryName, additionalFiles.map((file) => file.name)));
|
|
1000
1090
|
await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
|
|
1001
1091
|
await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
|
|
1002
1092
|
if (opts.certificatePath) {
|
|
@@ -1018,8 +1108,13 @@ async function createMSI(opts) {
|
|
|
1018
1108
|
}
|
|
1019
1109
|
async function createZIP(opts) {
|
|
1020
1110
|
try {
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1111
|
+
const binaryName = windowsExecutableName(opts.name);
|
|
1112
|
+
const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
|
|
1113
|
+
const entries = [{ name: binaryName, data: new Uint8Array(import_fs.readFileSync(opts.binaryPath)) }];
|
|
1114
|
+
entries.push({ name: WINDOWS_NOTIFICATION_ID_FILE, data: new TextEncoder().encode(windowsNotificationAppId(opts.name, opts.manufacturer)) });
|
|
1115
|
+
for (const file of additionalFiles)
|
|
1116
|
+
entries.push({ name: file.name, data: new Uint8Array(import_fs.readFileSync(file.path)) });
|
|
1117
|
+
const zip = buildZip(entries);
|
|
1023
1118
|
import_fs.writeFileSync(opts.outputPath, zip);
|
|
1024
1119
|
return { success: true, outputPath: opts.outputPath };
|
|
1025
1120
|
} catch (err) {
|
|
@@ -1049,6 +1144,7 @@ async function createDEB(opts) {
|
|
|
1049
1144
|
return;
|
|
1050
1145
|
}
|
|
1051
1146
|
}
|
|
1147
|
+
const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
|
|
1052
1148
|
const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-deb-"));
|
|
1053
1149
|
const debianDir = import_path.join(tempDir, "DEBIAN");
|
|
1054
1150
|
const binDir = import_path.join(tempDir, "usr", "bin");
|
|
@@ -1069,13 +1165,6 @@ Maintainer: ${maintainer}
|
|
|
1069
1165
|
Description: ${description || opts.name}
|
|
1070
1166
|
`;
|
|
1071
1167
|
import_fs.writeFileSync(import_path.join(debianDir, "control"), controlContent);
|
|
1072
|
-
const desktopContent = `[Desktop Entry]
|
|
1073
|
-
Type=Application
|
|
1074
|
-
Name=${opts.name}
|
|
1075
|
-
Exec=/usr/bin/${binaryName}
|
|
1076
|
-
Terminal=false
|
|
1077
|
-
Categories=Utility;
|
|
1078
|
-
`;
|
|
1079
1168
|
import_fs.writeFileSync(import_path.join(applicationsDir, `${binaryName}.desktop`), desktopContent);
|
|
1080
1169
|
const proc = import_child_process.spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
|
|
1081
1170
|
proc.on("close", (code) => {
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
58
58
|
`);
|
|
59
59
|
}
|
|
60
60
|
// package.json
|
|
61
|
-
var version = "0.0.
|
|
61
|
+
var version = "0.0.107";
|
|
62
62
|
|
|
63
63
|
// src/sdk-version.ts
|
|
64
64
|
var SDK_VERSION = version;
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
import { homedir, tmpdir } from "os";
|
|
83
83
|
import { basename, join } from "path";
|
|
84
84
|
import { deflateRawSync } from "zlib";
|
|
85
|
+
var DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
|
|
85
86
|
var CRC32_TABLE = (() => {
|
|
86
87
|
const table = new Uint32Array(256);
|
|
87
88
|
for (let n = 0;n < 256; n++) {
|
|
@@ -313,6 +314,9 @@ async function packageWindows(config, outDir) {
|
|
|
313
314
|
outputPath: join(outDir, `${name}-${version2}.msi`),
|
|
314
315
|
manufacturer: config.author || "Unknown",
|
|
315
316
|
architecture: opts.architecture || windowsArchitecture(process.arch),
|
|
317
|
+
additionalFiles: opts.additionalFiles,
|
|
318
|
+
urlSchemes: opts.urlSchemes,
|
|
319
|
+
launchUrl: opts.launchUrl,
|
|
316
320
|
certificatePath: opts.certificatePath,
|
|
317
321
|
certificatePassword: opts.certificatePassword
|
|
318
322
|
});
|
|
@@ -328,7 +332,9 @@ async function packageWindows(config, outDir) {
|
|
|
328
332
|
const zipResult = await createZIP({
|
|
329
333
|
name,
|
|
330
334
|
version: version2,
|
|
335
|
+
manufacturer: config.author || "Unknown",
|
|
331
336
|
binaryPath: config.binaryPath,
|
|
337
|
+
additionalFiles: opts.additionalFiles,
|
|
332
338
|
outputPath: join(outDir, `${name}-${version2}-windows.zip`)
|
|
333
339
|
});
|
|
334
340
|
results.push({
|
|
@@ -353,7 +359,9 @@ async function packageLinux(config, outDir) {
|
|
|
353
359
|
outputPath: join(outDir, `${name}_${version2}_amd64.deb`),
|
|
354
360
|
description: config.description || "",
|
|
355
361
|
maintainer: config.author || "Unknown",
|
|
356
|
-
dependencies: opts.debDependencies || [
|
|
362
|
+
dependencies: opts.debDependencies || [...DEFAULT_DEB_DEPENDENCIES],
|
|
363
|
+
urlSchemes: opts.urlSchemes,
|
|
364
|
+
launchUrl: opts.launchUrl
|
|
357
365
|
});
|
|
358
366
|
results.push({
|
|
359
367
|
success: debResult.success,
|
|
@@ -400,17 +408,7 @@ async function packageLinux(config, outDir) {
|
|
|
400
408
|
}
|
|
401
409
|
var URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
|
|
402
410
|
function urlTypesEntry(bundleId, schemes) {
|
|
403
|
-
const
|
|
404
|
-
const unique = [];
|
|
405
|
-
for (const scheme of schemes) {
|
|
406
|
-
if (!URL_SCHEME.test(scheme))
|
|
407
|
-
throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
|
|
408
|
-
const key = scheme.toLowerCase();
|
|
409
|
-
if (seen.has(key))
|
|
410
|
-
continue;
|
|
411
|
-
seen.add(key);
|
|
412
|
-
unique.push(scheme);
|
|
413
|
-
}
|
|
411
|
+
const unique = uniqueUrlSchemes(schemes);
|
|
414
412
|
if (unique.length === 0)
|
|
415
413
|
return null;
|
|
416
414
|
const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
|
|
@@ -429,6 +427,46 @@ ${list}
|
|
|
429
427
|
</dict>
|
|
430
428
|
</array>`;
|
|
431
429
|
}
|
|
430
|
+
function uniqueUrlSchemes(schemes) {
|
|
431
|
+
const seen = new Set;
|
|
432
|
+
const unique = [];
|
|
433
|
+
for (const scheme of schemes) {
|
|
434
|
+
if (!URL_SCHEME.test(scheme))
|
|
435
|
+
throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
|
|
436
|
+
const key = scheme.toLowerCase();
|
|
437
|
+
if (seen.has(key))
|
|
438
|
+
continue;
|
|
439
|
+
seen.add(key);
|
|
440
|
+
unique.push(scheme);
|
|
441
|
+
}
|
|
442
|
+
return unique;
|
|
443
|
+
}
|
|
444
|
+
function launchPageUrl(value) {
|
|
445
|
+
let url;
|
|
446
|
+
try {
|
|
447
|
+
url = new URL(value);
|
|
448
|
+
} catch {
|
|
449
|
+
throw new Error(`Invalid packaged app launch URL: ${JSON.stringify(value)}`);
|
|
450
|
+
}
|
|
451
|
+
if (!["http:", "https:", "file:"].includes(url.protocol))
|
|
452
|
+
throw new Error(`Unsupported packaged app launch URL protocol: ${url.protocol}`);
|
|
453
|
+
return url.href;
|
|
454
|
+
}
|
|
455
|
+
function desktopExecArgument(value) {
|
|
456
|
+
return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`;
|
|
457
|
+
}
|
|
458
|
+
function linuxDesktopEntry(name, binaryName, urlSchemes = [], launchUrl) {
|
|
459
|
+
const schemes = uniqueUrlSchemes(urlSchemes);
|
|
460
|
+
const page = launchUrl ? ` --url ${desktopExecArgument(launchPageUrl(launchUrl))}` : "";
|
|
461
|
+
return `[Desktop Entry]
|
|
462
|
+
Type=Application
|
|
463
|
+
Name=${name}
|
|
464
|
+
Exec=/usr/bin/${binaryName}${page}${schemes.length ? " --deep-link %u" : ""}
|
|
465
|
+
Terminal=false
|
|
466
|
+
Categories=Utility;
|
|
467
|
+
${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${scheme.toLowerCase()};`).join("")}
|
|
468
|
+
` : ""}`;
|
|
469
|
+
}
|
|
432
470
|
function macOSInfoPlist(metadata) {
|
|
433
471
|
const entries = [
|
|
434
472
|
["CFBundleExecutable", metadata.name],
|
|
@@ -466,7 +504,7 @@ function codesignArguments(opts) {
|
|
|
466
504
|
"--force",
|
|
467
505
|
"--sign",
|
|
468
506
|
opts.identity,
|
|
469
|
-
"--timestamp",
|
|
507
|
+
...opts.identity === "-" ? [] : ["--timestamp"],
|
|
470
508
|
...opts.hardenedRuntime ? ["--options", "runtime"] : [],
|
|
471
509
|
...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
|
|
472
510
|
"--deep",
|
|
@@ -694,6 +732,10 @@ function deterministicGuid(value) {
|
|
|
694
732
|
const hex = digest.join("").toUpperCase();
|
|
695
733
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
696
734
|
}
|
|
735
|
+
var WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
|
|
736
|
+
function windowsNotificationAppId(name, manufacturer) {
|
|
737
|
+
return `Craft.${deterministicGuid(`${manufacturer.trim() || "Unknown"}/${name}`)}`;
|
|
738
|
+
}
|
|
697
739
|
function windowsArchitecture(architecture) {
|
|
698
740
|
if (architecture === "ia32" || architecture === "x86")
|
|
699
741
|
return "x86";
|
|
@@ -701,7 +743,7 @@ function windowsArchitecture(architecture) {
|
|
|
701
743
|
return architecture;
|
|
702
744
|
throw new Error(`Unsupported Windows package architecture: ${architecture}`);
|
|
703
745
|
}
|
|
704
|
-
function renderWixSource(opts, sourceName) {
|
|
746
|
+
function renderWixSource(opts, sourceName, additionalFileNames = []) {
|
|
705
747
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
|
|
706
748
|
throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
|
|
707
749
|
const id = wixIdentifier(opts.name);
|
|
@@ -709,6 +751,9 @@ function renderWixSource(opts, sourceName) {
|
|
|
709
751
|
const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
|
|
710
752
|
const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
|
|
711
753
|
const win64 = opts.architecture === "x86" ? "no" : "yes";
|
|
754
|
+
const notificationAppId = windowsNotificationAppId(opts.name, manufacturer);
|
|
755
|
+
const schemes = uniqueUrlSchemes(opts.urlSchemes || []);
|
|
756
|
+
const page = opts.launchUrl ? ` --url "${xml(launchPageUrl(opts.launchUrl))}"` : "";
|
|
712
757
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
713
758
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
|
714
759
|
<Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
|
|
@@ -719,18 +764,59 @@ function renderWixSource(opts, sourceName) {
|
|
|
719
764
|
<Directory Id="${programFilesFolder}">
|
|
720
765
|
<Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
|
|
721
766
|
<Component Id="${id}Executable" Guid="*" Win64="${win64}">
|
|
722
|
-
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes"
|
|
767
|
+
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes">
|
|
768
|
+
<Shortcut Id="${id}StartMenuShortcut" Directory="ProgramMenuFolder" Name="${xml(opts.name)}" WorkingDirectory="INSTALLFOLDER"${page ? ` Arguments="${page.trim()}"` : ""}>
|
|
769
|
+
<ShortcutProperty Key="System.AppUserModel.ID" Value="${notificationAppId}" />
|
|
770
|
+
</Shortcut>
|
|
771
|
+
</File>
|
|
772
|
+
</Component>
|
|
773
|
+
<Component Id="${id}NotificationIdentity" Guid="*" Win64="${win64}">
|
|
774
|
+
<File Id="${id}NotificationIdentityFile" Source="${WINDOWS_NOTIFICATION_ID_FILE}" KeyPath="yes" />
|
|
723
775
|
</Component>
|
|
776
|
+
${additionalFileNames.map((name, index) => ` <Component Id="${id}Extra${index}" Guid="*" Win64="${win64}">
|
|
777
|
+
<File Id="${id}ExtraFile${index}" Source="${xml(name)}" KeyPath="yes" />
|
|
778
|
+
</Component>`).join(`
|
|
779
|
+
`)}
|
|
780
|
+
${schemes.map((scheme, index) => ` <Component Id="${id}Protocol${index}" Guid="${deterministicGuid(`${manufacturer}/${opts.name}/protocol/${scheme.toLowerCase()}`)}" Win64="${win64}">
|
|
781
|
+
<RegistryKey Root="HKLM" Key="Software\\Classes\\${xml(scheme.toLowerCase())}" Action="createAndRemoveOnUninstall">
|
|
782
|
+
<RegistryValue Type="string" Value="URL:${xml(opts.name)} Protocol" KeyPath="yes" />
|
|
783
|
+
<RegistryValue Type="string" Name="URL Protocol" Value="" />
|
|
784
|
+
<RegistryKey Key="shell\\open\\command">
|
|
785
|
+
<RegistryValue Type="string" Value=""[#${id}File]"${page} --deep-link "%1"" />
|
|
786
|
+
</RegistryKey>
|
|
787
|
+
</RegistryKey>
|
|
788
|
+
</Component>`).join(`
|
|
789
|
+
`)}
|
|
724
790
|
</Directory>
|
|
725
791
|
</Directory>
|
|
792
|
+
<Directory Id="ProgramMenuFolder" />
|
|
726
793
|
</Directory>
|
|
727
794
|
<Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
|
|
728
795
|
<ComponentRef Id="${id}Executable" />
|
|
796
|
+
<ComponentRef Id="${id}NotificationIdentity" />
|
|
797
|
+
${additionalFileNames.map((_, index) => ` <ComponentRef Id="${id}Extra${index}" />`).join(`
|
|
798
|
+
`)}
|
|
799
|
+
${schemes.map((_, index) => ` <ComponentRef Id="${id}Protocol${index}" />`).join(`
|
|
800
|
+
`)}
|
|
729
801
|
</Feature>
|
|
730
802
|
</Product>
|
|
731
803
|
</Wix>
|
|
732
804
|
`;
|
|
733
805
|
}
|
|
806
|
+
function windowsCompanionFiles(paths, executableName) {
|
|
807
|
+
const seen = new Set([executableName.toLowerCase(), WINDOWS_NOTIFICATION_ID_FILE.toLowerCase()]);
|
|
808
|
+
return (paths || []).map((path) => {
|
|
809
|
+
const name = basename(path);
|
|
810
|
+
if (!name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name) || /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
|
|
811
|
+
throw new Error(`Invalid Windows companion file name: ${JSON.stringify(name)}`);
|
|
812
|
+
if (seen.has(name.toLowerCase()))
|
|
813
|
+
throw new Error(`Duplicate Windows package file: ${name}`);
|
|
814
|
+
seen.add(name.toLowerCase());
|
|
815
|
+
if (!existsSync2(path) || lstatSync(path).isDirectory())
|
|
816
|
+
throw new Error(`Windows companion file not found: ${path}`);
|
|
817
|
+
return { path, name };
|
|
818
|
+
});
|
|
819
|
+
}
|
|
734
820
|
function windowsExecutableName(name) {
|
|
735
821
|
if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
|
|
736
822
|
throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
|
|
@@ -752,11 +838,15 @@ async function createMSI(opts) {
|
|
|
752
838
|
const tempDir = mkdtempSync(join(tmpdir(), "craft-msi-"));
|
|
753
839
|
try {
|
|
754
840
|
const binaryName = windowsExecutableName(opts.name);
|
|
841
|
+
const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
|
|
755
842
|
const sourcePath = join(tempDir, binaryName);
|
|
756
843
|
const wxsPath = join(tempDir, "installer.wxs");
|
|
757
844
|
const wixobjPath = join(tempDir, "installer.wixobj");
|
|
758
845
|
copyFileSync(opts.binaryPath, sourcePath);
|
|
759
|
-
writeFileSync(
|
|
846
|
+
writeFileSync(join(tempDir, WINDOWS_NOTIFICATION_ID_FILE), windowsNotificationAppId(opts.name, opts.manufacturer));
|
|
847
|
+
for (const file of additionalFiles)
|
|
848
|
+
copyFileSync(file.path, join(tempDir, file.name));
|
|
849
|
+
writeFileSync(wxsPath, renderWixSource(opts, binaryName, additionalFiles.map((file) => file.name)));
|
|
760
850
|
await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
|
|
761
851
|
await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
|
|
762
852
|
if (opts.certificatePath) {
|
|
@@ -778,8 +868,13 @@ async function createMSI(opts) {
|
|
|
778
868
|
}
|
|
779
869
|
async function createZIP(opts) {
|
|
780
870
|
try {
|
|
781
|
-
const
|
|
782
|
-
const
|
|
871
|
+
const binaryName = windowsExecutableName(opts.name);
|
|
872
|
+
const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
|
|
873
|
+
const entries = [{ name: binaryName, data: new Uint8Array(readFileSync(opts.binaryPath)) }];
|
|
874
|
+
entries.push({ name: WINDOWS_NOTIFICATION_ID_FILE, data: new TextEncoder().encode(windowsNotificationAppId(opts.name, opts.manufacturer)) });
|
|
875
|
+
for (const file of additionalFiles)
|
|
876
|
+
entries.push({ name: file.name, data: new Uint8Array(readFileSync(file.path)) });
|
|
877
|
+
const zip = buildZip(entries);
|
|
783
878
|
writeFileSync(opts.outputPath, zip);
|
|
784
879
|
return { success: true, outputPath: opts.outputPath };
|
|
785
880
|
} catch (err) {
|
|
@@ -809,6 +904,7 @@ async function createDEB(opts) {
|
|
|
809
904
|
return;
|
|
810
905
|
}
|
|
811
906
|
}
|
|
907
|
+
const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
|
|
812
908
|
const tempDir = mkdtempSync(join(tmpdir(), "craft-deb-"));
|
|
813
909
|
const debianDir = join(tempDir, "DEBIAN");
|
|
814
910
|
const binDir = join(tempDir, "usr", "bin");
|
|
@@ -829,13 +925,6 @@ Maintainer: ${maintainer}
|
|
|
829
925
|
Description: ${description || opts.name}
|
|
830
926
|
`;
|
|
831
927
|
writeFileSync(join(debianDir, "control"), controlContent);
|
|
832
|
-
const desktopContent = `[Desktop Entry]
|
|
833
|
-
Type=Application
|
|
834
|
-
Name=${opts.name}
|
|
835
|
-
Exec=/usr/bin/${binaryName}
|
|
836
|
-
Terminal=false
|
|
837
|
-
Categories=Utility;
|
|
838
|
-
`;
|
|
839
928
|
writeFileSync(join(applicationsDir, `${binaryName}.desktop`), desktopContent);
|
|
840
929
|
const proc = spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
|
|
841
930
|
proc.on("close", (code) => {
|
package/dist/ios/src/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type CraftAppearance = 'light' | 'dark' | 'system';
|
|
|
7
7
|
export interface CraftConfig {
|
|
8
8
|
appName: string;
|
|
9
9
|
bundleId: string;
|
|
10
|
+
/** Opt in to UIKit + JavaScriptCore instead of the default WebView. */
|
|
11
|
+
renderer?: 'web' | 'native';
|
|
10
12
|
version?: string;
|
|
11
13
|
buildNumber?: string;
|
|
12
14
|
darkMode?: boolean;
|
|
@@ -108,6 +110,8 @@ export interface InitOptions {
|
|
|
108
110
|
}
|
|
109
111
|
export interface BuildOptions {
|
|
110
112
|
htmlPath?: string;
|
|
113
|
+
/** JavaScript bundle emitted by `stx-native compile Screen.stx --format bundle`. */
|
|
114
|
+
nativeBundlePath?: string;
|
|
111
115
|
devServer?: string;
|
|
112
116
|
output: string;
|
|
113
117
|
generateProject?: boolean;
|
package/dist/ios/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join, resolve } from "path";
|
|
|
5
5
|
var {$ } = globalThis.Bun;
|
|
6
6
|
var TEMPLATES_DIR = join(dirname(import.meta.dir), "templates");
|
|
7
7
|
var DEFAULT_CONFIG = {
|
|
8
|
+
renderer: "web",
|
|
8
9
|
version: "1.0.0",
|
|
9
10
|
buildNumber: "1",
|
|
10
11
|
darkMode: true,
|
|
@@ -393,12 +394,17 @@ async function init(options) {
|
|
|
393
394
|
teamId: teamId || "",
|
|
394
395
|
...given
|
|
395
396
|
};
|
|
397
|
+
if (config.renderer !== "web" && config.renderer !== "native") {
|
|
398
|
+
throw new Error(`Unknown iOS renderer: ${config.renderer}. Expected web or native.`);
|
|
399
|
+
}
|
|
396
400
|
if (config.enableBackgroundLocation)
|
|
397
401
|
config.enableGeolocation = true;
|
|
398
402
|
writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
399
403
|
const swiftTemplate = readFileSync(join(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
400
404
|
const swiftSource = swiftTemplate.replace(/CraftApp/g, `${name}App`).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
|
|
401
405
|
writeFileSync(join(output, "Sources", `${name}App.swift`), swiftSource);
|
|
406
|
+
cpSync(join(TEMPLATES_DIR, "CraftNativeScreen.swift"), join(output, "Sources", "CraftNativeScreen.swift"));
|
|
407
|
+
cpSync(join(TEMPLATES_DIR, "CraftNativeActions.swift"), join(output, "Sources", "CraftNativeActions.swift"));
|
|
402
408
|
const infoPlistTemplate = readFileSync(join(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
403
409
|
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, xmlEscape(displayName)).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, renderAppearance(config).interfaceStyle).replace(/\{\{STATUS_BAR_STYLE\}\}/g, renderAppearance(config).statusBarStyle).replace(/\{\{ORIENTATIONS\}\}/g, renderOrientations(config)).replace(/\{\{USAGE_DESCRIPTIONS\}\}/g, renderUsageDescriptions(config)).replace(/\{\{URL_TYPES\}\}/g, renderUrlTypes(config)).replace(/\{\{BACKGROUND_MODES\}\}/g, renderBackgroundModes(config)).replace(/\{\{LIVE_ACTIVITY_SUPPORT\}\}/g, config.enableLiveActivities ? ` <key>NSSupportsLiveActivities</key>
|
|
404
410
|
<true/>
|
|
@@ -533,13 +539,18 @@ ${nativeDependencies.join(`
|
|
|
533
539
|
console.log("");
|
|
534
540
|
console.log("Next steps:");
|
|
535
541
|
console.log(` 1. cd ${output}`);
|
|
536
|
-
|
|
537
|
-
|
|
542
|
+
if (config.renderer === "native") {
|
|
543
|
+
console.log(" 2. Compile your screen: stx-native compile Screen.stx --format bundle --output screen.js");
|
|
544
|
+
console.log(" 3. Run: craft ios build --native-bundle screen.js");
|
|
545
|
+
} else {
|
|
546
|
+
console.log(" 2. Add your web content to dist/index.html");
|
|
547
|
+
console.log(" 3. Run: craft ios build");
|
|
548
|
+
}
|
|
538
549
|
console.log(" 4. Run: craft ios open");
|
|
539
550
|
console.log("");
|
|
540
551
|
}
|
|
541
552
|
async function build(options) {
|
|
542
|
-
const { htmlPath, devServer, output, generateProject = true } = options;
|
|
553
|
+
const { htmlPath, nativeBundlePath, devServer, output, generateProject = true } = options;
|
|
543
554
|
console.log(`
|
|
544
555
|
\uD83D\uDCE6 Building Craft iOS project...`);
|
|
545
556
|
const configPath = join(output, "craft.config.json");
|
|
@@ -547,6 +558,24 @@ async function build(options) {
|
|
|
547
558
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft ios init' first.`);
|
|
548
559
|
}
|
|
549
560
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
561
|
+
if (config.renderer !== undefined && config.renderer !== "web" && config.renderer !== "native") {
|
|
562
|
+
throw new Error(`Unknown iOS renderer: ${config.renderer}. Expected web or native.`);
|
|
563
|
+
}
|
|
564
|
+
if (config.renderer === "native") {
|
|
565
|
+
if (htmlPath || devServer) {
|
|
566
|
+
throw new Error("A native iOS screen cannot use --html-path or --dev-server.");
|
|
567
|
+
}
|
|
568
|
+
if (nativeBundlePath) {
|
|
569
|
+
const destination = join(output, "dist", "native-screen.js");
|
|
570
|
+
if (resolve(nativeBundlePath) !== resolve(destination))
|
|
571
|
+
cpSync(nativeBundlePath, destination);
|
|
572
|
+
}
|
|
573
|
+
if (!existsSync(join(output, "dist", "native-screen.js"))) {
|
|
574
|
+
throw new Error("Native iOS mode needs dist/native-screen.js. Compile a .stx screen with stx-native first.");
|
|
575
|
+
}
|
|
576
|
+
} else if (nativeBundlePath) {
|
|
577
|
+
throw new Error('--native-bundle requires renderer: "native" in craft.config.json.');
|
|
578
|
+
}
|
|
550
579
|
if (devServer) {
|
|
551
580
|
config.devServerURL = devServer;
|
|
552
581
|
const origin = new URL(devServer).origin;
|