craft-native 0.0.104 → 0.0.105
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/cli.js +120 -28
- package/dist/index.cjs +115 -26
- package/dist/index.js +115 -26
- package/dist/package.d.ts +21 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ var __require = import.meta.require;
|
|
|
20
20
|
// src/package.ts
|
|
21
21
|
var exports_package = {};
|
|
22
22
|
__export(exports_package, {
|
|
23
|
+
windowsNotificationAppId: () => windowsNotificationAppId,
|
|
23
24
|
windowsExecutableName: () => windowsExecutableName,
|
|
24
25
|
windowsArchitecture: () => windowsArchitecture,
|
|
25
26
|
urlTypesEntry: () => urlTypesEntry,
|
|
@@ -32,11 +33,14 @@ __export(exports_package, {
|
|
|
32
33
|
pack: () => pack,
|
|
33
34
|
notarytoolArguments: () => notarytoolArguments,
|
|
34
35
|
macOSInfoPlist: () => macOSInfoPlist,
|
|
36
|
+
linuxDesktopEntry: () => linuxDesktopEntry,
|
|
35
37
|
formatPackagingCommandError: () => formatPackagingCommandError,
|
|
36
38
|
dmgCreateArguments: () => dmgCreateArguments,
|
|
37
39
|
dmgCapacityMegabytes: () => dmgCapacityMegabytes,
|
|
38
40
|
codesignArguments: () => codesignArguments,
|
|
39
|
-
candleArguments: () => candleArguments
|
|
41
|
+
candleArguments: () => candleArguments,
|
|
42
|
+
WINDOWS_NOTIFICATION_ID_FILE: () => WINDOWS_NOTIFICATION_ID_FILE,
|
|
43
|
+
DEFAULT_DEB_DEPENDENCIES: () => DEFAULT_DEB_DEPENDENCIES
|
|
40
44
|
});
|
|
41
45
|
import { spawn } from "child_process";
|
|
42
46
|
import { createHash } from "crypto";
|
|
@@ -277,6 +281,9 @@ async function packageWindows(config, outDir) {
|
|
|
277
281
|
outputPath: join(outDir, `${name}-${version2}.msi`),
|
|
278
282
|
manufacturer: config.author || "Unknown",
|
|
279
283
|
architecture: opts.architecture || windowsArchitecture(process.arch),
|
|
284
|
+
additionalFiles: opts.additionalFiles,
|
|
285
|
+
urlSchemes: opts.urlSchemes,
|
|
286
|
+
launchUrl: opts.launchUrl,
|
|
280
287
|
certificatePath: opts.certificatePath,
|
|
281
288
|
certificatePassword: opts.certificatePassword
|
|
282
289
|
});
|
|
@@ -292,7 +299,9 @@ async function packageWindows(config, outDir) {
|
|
|
292
299
|
const zipResult = await createZIP({
|
|
293
300
|
name,
|
|
294
301
|
version: version2,
|
|
302
|
+
manufacturer: config.author || "Unknown",
|
|
295
303
|
binaryPath: config.binaryPath,
|
|
304
|
+
additionalFiles: opts.additionalFiles,
|
|
296
305
|
outputPath: join(outDir, `${name}-${version2}-windows.zip`)
|
|
297
306
|
});
|
|
298
307
|
results.push({
|
|
@@ -317,7 +326,9 @@ async function packageLinux(config, outDir) {
|
|
|
317
326
|
outputPath: join(outDir, `${name}_${version2}_amd64.deb`),
|
|
318
327
|
description: config.description || "",
|
|
319
328
|
maintainer: config.author || "Unknown",
|
|
320
|
-
dependencies: opts.debDependencies || [
|
|
329
|
+
dependencies: opts.debDependencies || [...DEFAULT_DEB_DEPENDENCIES],
|
|
330
|
+
urlSchemes: opts.urlSchemes,
|
|
331
|
+
launchUrl: opts.launchUrl
|
|
321
332
|
});
|
|
322
333
|
results.push({
|
|
323
334
|
success: debResult.success,
|
|
@@ -363,17 +374,7 @@ async function packageLinux(config, outDir) {
|
|
|
363
374
|
return results;
|
|
364
375
|
}
|
|
365
376
|
function urlTypesEntry(bundleId, schemes) {
|
|
366
|
-
const
|
|
367
|
-
const unique = [];
|
|
368
|
-
for (const scheme of schemes) {
|
|
369
|
-
if (!URL_SCHEME.test(scheme))
|
|
370
|
-
throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
|
|
371
|
-
const key = scheme.toLowerCase();
|
|
372
|
-
if (seen.has(key))
|
|
373
|
-
continue;
|
|
374
|
-
seen.add(key);
|
|
375
|
-
unique.push(scheme);
|
|
376
|
-
}
|
|
377
|
+
const unique = uniqueUrlSchemes(schemes);
|
|
377
378
|
if (unique.length === 0)
|
|
378
379
|
return null;
|
|
379
380
|
const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
|
|
@@ -392,6 +393,46 @@ ${list}
|
|
|
392
393
|
</dict>
|
|
393
394
|
</array>`;
|
|
394
395
|
}
|
|
396
|
+
function uniqueUrlSchemes(schemes) {
|
|
397
|
+
const seen = new Set;
|
|
398
|
+
const unique = [];
|
|
399
|
+
for (const scheme of schemes) {
|
|
400
|
+
if (!URL_SCHEME.test(scheme))
|
|
401
|
+
throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
|
|
402
|
+
const key = scheme.toLowerCase();
|
|
403
|
+
if (seen.has(key))
|
|
404
|
+
continue;
|
|
405
|
+
seen.add(key);
|
|
406
|
+
unique.push(scheme);
|
|
407
|
+
}
|
|
408
|
+
return unique;
|
|
409
|
+
}
|
|
410
|
+
function launchPageUrl(value) {
|
|
411
|
+
let url;
|
|
412
|
+
try {
|
|
413
|
+
url = new URL(value);
|
|
414
|
+
} catch {
|
|
415
|
+
throw new Error(`Invalid packaged app launch URL: ${JSON.stringify(value)}`);
|
|
416
|
+
}
|
|
417
|
+
if (!["http:", "https:", "file:"].includes(url.protocol))
|
|
418
|
+
throw new Error(`Unsupported packaged app launch URL protocol: ${url.protocol}`);
|
|
419
|
+
return url.href;
|
|
420
|
+
}
|
|
421
|
+
function desktopExecArgument(value) {
|
|
422
|
+
return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`;
|
|
423
|
+
}
|
|
424
|
+
function linuxDesktopEntry(name, binaryName, urlSchemes = [], launchUrl) {
|
|
425
|
+
const schemes = uniqueUrlSchemes(urlSchemes);
|
|
426
|
+
const page = launchUrl ? ` --url ${desktopExecArgument(launchPageUrl(launchUrl))}` : "";
|
|
427
|
+
return `[Desktop Entry]
|
|
428
|
+
Type=Application
|
|
429
|
+
Name=${name}
|
|
430
|
+
Exec=/usr/bin/${binaryName}${page}${schemes.length ? " --deep-link %u" : ""}
|
|
431
|
+
Terminal=false
|
|
432
|
+
Categories=Utility;
|
|
433
|
+
${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${scheme.toLowerCase()};`).join("")}
|
|
434
|
+
` : ""}`;
|
|
435
|
+
}
|
|
395
436
|
function macOSInfoPlist(metadata) {
|
|
396
437
|
const entries = [
|
|
397
438
|
["CFBundleExecutable", metadata.name],
|
|
@@ -429,7 +470,7 @@ function codesignArguments(opts) {
|
|
|
429
470
|
"--force",
|
|
430
471
|
"--sign",
|
|
431
472
|
opts.identity,
|
|
432
|
-
"--timestamp",
|
|
473
|
+
...opts.identity === "-" ? [] : ["--timestamp"],
|
|
433
474
|
...opts.hardenedRuntime ? ["--options", "runtime"] : [],
|
|
434
475
|
...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
|
|
435
476
|
"--deep",
|
|
@@ -654,6 +695,9 @@ function deterministicGuid(value) {
|
|
|
654
695
|
const hex = digest.join("").toUpperCase();
|
|
655
696
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
656
697
|
}
|
|
698
|
+
function windowsNotificationAppId(name, manufacturer) {
|
|
699
|
+
return `Craft.${deterministicGuid(`${manufacturer.trim() || "Unknown"}/${name}`)}`;
|
|
700
|
+
}
|
|
657
701
|
function windowsArchitecture(architecture) {
|
|
658
702
|
if (architecture === "ia32" || architecture === "x86")
|
|
659
703
|
return "x86";
|
|
@@ -661,7 +705,7 @@ function windowsArchitecture(architecture) {
|
|
|
661
705
|
return architecture;
|
|
662
706
|
throw new Error(`Unsupported Windows package architecture: ${architecture}`);
|
|
663
707
|
}
|
|
664
|
-
function renderWixSource(opts, sourceName) {
|
|
708
|
+
function renderWixSource(opts, sourceName, additionalFileNames = []) {
|
|
665
709
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
|
|
666
710
|
throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
|
|
667
711
|
const id = wixIdentifier(opts.name);
|
|
@@ -669,6 +713,9 @@ function renderWixSource(opts, sourceName) {
|
|
|
669
713
|
const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
|
|
670
714
|
const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
|
|
671
715
|
const win64 = opts.architecture === "x86" ? "no" : "yes";
|
|
716
|
+
const notificationAppId = windowsNotificationAppId(opts.name, manufacturer);
|
|
717
|
+
const schemes = uniqueUrlSchemes(opts.urlSchemes || []);
|
|
718
|
+
const page = opts.launchUrl ? ` --url "${xml(launchPageUrl(opts.launchUrl))}"` : "";
|
|
672
719
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
673
720
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
|
674
721
|
<Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
|
|
@@ -679,18 +726,59 @@ function renderWixSource(opts, sourceName) {
|
|
|
679
726
|
<Directory Id="${programFilesFolder}">
|
|
680
727
|
<Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
|
|
681
728
|
<Component Id="${id}Executable" Guid="*" Win64="${win64}">
|
|
682
|
-
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes"
|
|
729
|
+
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes">
|
|
730
|
+
<Shortcut Id="${id}StartMenuShortcut" Directory="ProgramMenuFolder" Name="${xml(opts.name)}" WorkingDirectory="INSTALLFOLDER"${page ? ` Arguments="${page.trim()}"` : ""}>
|
|
731
|
+
<ShortcutProperty Key="System.AppUserModel.ID" Value="${notificationAppId}" />
|
|
732
|
+
</Shortcut>
|
|
733
|
+
</File>
|
|
734
|
+
</Component>
|
|
735
|
+
<Component Id="${id}NotificationIdentity" Guid="*" Win64="${win64}">
|
|
736
|
+
<File Id="${id}NotificationIdentityFile" Source="${WINDOWS_NOTIFICATION_ID_FILE}" KeyPath="yes" />
|
|
683
737
|
</Component>
|
|
738
|
+
${additionalFileNames.map((name, index) => ` <Component Id="${id}Extra${index}" Guid="*" Win64="${win64}">
|
|
739
|
+
<File Id="${id}ExtraFile${index}" Source="${xml(name)}" KeyPath="yes" />
|
|
740
|
+
</Component>`).join(`
|
|
741
|
+
`)}
|
|
742
|
+
${schemes.map((scheme, index) => ` <Component Id="${id}Protocol${index}" Guid="${deterministicGuid(`${manufacturer}/${opts.name}/protocol/${scheme.toLowerCase()}`)}" Win64="${win64}">
|
|
743
|
+
<RegistryKey Root="HKLM" Key="Software\\Classes\\${xml(scheme.toLowerCase())}" Action="createAndRemoveOnUninstall">
|
|
744
|
+
<RegistryValue Type="string" Value="URL:${xml(opts.name)} Protocol" KeyPath="yes" />
|
|
745
|
+
<RegistryValue Type="string" Name="URL Protocol" Value="" />
|
|
746
|
+
<RegistryKey Key="shell\\open\\command">
|
|
747
|
+
<RegistryValue Type="string" Value=""[#${id}File]"${page} --deep-link "%1"" />
|
|
748
|
+
</RegistryKey>
|
|
749
|
+
</RegistryKey>
|
|
750
|
+
</Component>`).join(`
|
|
751
|
+
`)}
|
|
684
752
|
</Directory>
|
|
685
753
|
</Directory>
|
|
754
|
+
<Directory Id="ProgramMenuFolder" />
|
|
686
755
|
</Directory>
|
|
687
756
|
<Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
|
|
688
757
|
<ComponentRef Id="${id}Executable" />
|
|
758
|
+
<ComponentRef Id="${id}NotificationIdentity" />
|
|
759
|
+
${additionalFileNames.map((_, index) => ` <ComponentRef Id="${id}Extra${index}" />`).join(`
|
|
760
|
+
`)}
|
|
761
|
+
${schemes.map((_, index) => ` <ComponentRef Id="${id}Protocol${index}" />`).join(`
|
|
762
|
+
`)}
|
|
689
763
|
</Feature>
|
|
690
764
|
</Product>
|
|
691
765
|
</Wix>
|
|
692
766
|
`;
|
|
693
767
|
}
|
|
768
|
+
function windowsCompanionFiles(paths, executableName) {
|
|
769
|
+
const seen = new Set([executableName.toLowerCase(), WINDOWS_NOTIFICATION_ID_FILE.toLowerCase()]);
|
|
770
|
+
return (paths || []).map((path2) => {
|
|
771
|
+
const name = basename(path2);
|
|
772
|
+
if (!name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name) || /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
|
|
773
|
+
throw new Error(`Invalid Windows companion file name: ${JSON.stringify(name)}`);
|
|
774
|
+
if (seen.has(name.toLowerCase()))
|
|
775
|
+
throw new Error(`Duplicate Windows package file: ${name}`);
|
|
776
|
+
seen.add(name.toLowerCase());
|
|
777
|
+
if (!existsSync3(path2) || lstatSync(path2).isDirectory())
|
|
778
|
+
throw new Error(`Windows companion file not found: ${path2}`);
|
|
779
|
+
return { path: path2, name };
|
|
780
|
+
});
|
|
781
|
+
}
|
|
694
782
|
function windowsExecutableName(name) {
|
|
695
783
|
if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
|
|
696
784
|
throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
|
|
@@ -712,11 +800,15 @@ async function createMSI(opts) {
|
|
|
712
800
|
const tempDir = mkdtempSync(join(tmpdir(), "craft-msi-"));
|
|
713
801
|
try {
|
|
714
802
|
const binaryName = windowsExecutableName(opts.name);
|
|
803
|
+
const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
|
|
715
804
|
const sourcePath = join(tempDir, binaryName);
|
|
716
805
|
const wxsPath = join(tempDir, "installer.wxs");
|
|
717
806
|
const wixobjPath = join(tempDir, "installer.wixobj");
|
|
718
807
|
copyFileSync(opts.binaryPath, sourcePath);
|
|
719
|
-
writeFileSync2(
|
|
808
|
+
writeFileSync2(join(tempDir, WINDOWS_NOTIFICATION_ID_FILE), windowsNotificationAppId(opts.name, opts.manufacturer));
|
|
809
|
+
for (const file of additionalFiles)
|
|
810
|
+
copyFileSync(file.path, join(tempDir, file.name));
|
|
811
|
+
writeFileSync2(wxsPath, renderWixSource(opts, binaryName, additionalFiles.map((file) => file.name)));
|
|
720
812
|
await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
|
|
721
813
|
await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
|
|
722
814
|
if (opts.certificatePath) {
|
|
@@ -738,8 +830,13 @@ async function createMSI(opts) {
|
|
|
738
830
|
}
|
|
739
831
|
async function createZIP(opts) {
|
|
740
832
|
try {
|
|
741
|
-
const
|
|
742
|
-
const
|
|
833
|
+
const binaryName = windowsExecutableName(opts.name);
|
|
834
|
+
const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
|
|
835
|
+
const entries = [{ name: binaryName, data: new Uint8Array(readFileSync2(opts.binaryPath)) }];
|
|
836
|
+
entries.push({ name: WINDOWS_NOTIFICATION_ID_FILE, data: new TextEncoder().encode(windowsNotificationAppId(opts.name, opts.manufacturer)) });
|
|
837
|
+
for (const file of additionalFiles)
|
|
838
|
+
entries.push({ name: file.name, data: new Uint8Array(readFileSync2(file.path)) });
|
|
839
|
+
const zip = buildZip(entries);
|
|
743
840
|
writeFileSync2(opts.outputPath, zip);
|
|
744
841
|
return { success: true, outputPath: opts.outputPath };
|
|
745
842
|
} catch (err) {
|
|
@@ -769,6 +866,7 @@ async function createDEB(opts) {
|
|
|
769
866
|
return;
|
|
770
867
|
}
|
|
771
868
|
}
|
|
869
|
+
const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
|
|
772
870
|
const tempDir = mkdtempSync(join(tmpdir(), "craft-deb-"));
|
|
773
871
|
const debianDir = join(tempDir, "DEBIAN");
|
|
774
872
|
const binDir = join(tempDir, "usr", "bin");
|
|
@@ -789,13 +887,6 @@ Maintainer: ${maintainer}
|
|
|
789
887
|
Description: ${description || opts.name}
|
|
790
888
|
`;
|
|
791
889
|
writeFileSync2(join(debianDir, "control"), controlContent);
|
|
792
|
-
const desktopContent = `[Desktop Entry]
|
|
793
|
-
Type=Application
|
|
794
|
-
Name=${opts.name}
|
|
795
|
-
Exec=/usr/bin/${binaryName}
|
|
796
|
-
Terminal=false
|
|
797
|
-
Categories=Utility;
|
|
798
|
-
`;
|
|
799
890
|
writeFileSync2(join(applicationsDir, `${binaryName}.desktop`), desktopContent);
|
|
800
891
|
const proc = spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
|
|
801
892
|
proc.on("close", (code) => {
|
|
@@ -1031,8 +1122,9 @@ async function pack(options) {
|
|
|
1031
1122
|
platforms: [detectPlatform()]
|
|
1032
1123
|
});
|
|
1033
1124
|
}
|
|
1034
|
-
var CRC32_TABLE, URL_SCHEME, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args);
|
|
1125
|
+
var DEFAULT_DEB_DEPENDENCIES, CRC32_TABLE, URL_SCHEME, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args), WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
|
|
1035
1126
|
var init_package = __esm(() => {
|
|
1127
|
+
DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
|
|
1036
1128
|
CRC32_TABLE = (() => {
|
|
1037
1129
|
const table = new Uint32Array(256);
|
|
1038
1130
|
for (let n = 0;n < 256; n++) {
|
|
@@ -4621,7 +4713,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
4621
4713
|
`);
|
|
4622
4714
|
}
|
|
4623
4715
|
// package.json
|
|
4624
|
-
var version = "0.0.
|
|
4716
|
+
var version = "0.0.105";
|
|
4625
4717
|
|
|
4626
4718
|
// bin/cli.ts
|
|
4627
4719
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
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.105";
|
|
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.105";
|
|
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/package.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Enables users to create installers for their Craft applications
|
|
5
5
|
* across all platforms (macOS, Windows, Linux)
|
|
6
6
|
*/
|
|
7
|
+
/** Runtime packages required by the default Linux DEB, including notify-send. */
|
|
8
|
+
export declare const DEFAULT_DEB_DEPENDENCIES: readonly ['libgtk-3-0', 'libwebkit2gtk-4.1-37', 'libnotify-bin'];
|
|
7
9
|
export interface PackageConfig {
|
|
8
10
|
/** Application name */
|
|
9
11
|
name: string;
|
|
@@ -95,6 +97,12 @@ export interface PackageConfig {
|
|
|
95
97
|
msi?: boolean;
|
|
96
98
|
/** Create ZIP archive */
|
|
97
99
|
zip?: boolean;
|
|
100
|
+
/** Files installed beside the executable (for example WebView2Loader.dll) */
|
|
101
|
+
additionalFiles?: string[];
|
|
102
|
+
/** Custom URI schemes registered by the MSI installer */
|
|
103
|
+
urlSchemes?: string[];
|
|
104
|
+
/** App page loaded when the MSI shortcut or a registered URI starts Craft. */
|
|
105
|
+
launchUrl?: string;
|
|
98
106
|
/** Architecture encoded in MSI metadata (defaults to the native host) */
|
|
99
107
|
architecture?: 'x86' | 'x64' | 'arm64';
|
|
100
108
|
/** Code signing certificate */
|
|
@@ -106,13 +114,17 @@ export interface PackageConfig {
|
|
|
106
114
|
linux?: {
|
|
107
115
|
/** Create DEB package */
|
|
108
116
|
deb?: boolean;
|
|
117
|
+
/** Custom URI schemes advertised by the DEB desktop entry */
|
|
118
|
+
urlSchemes?: string[];
|
|
119
|
+
/** App page loaded when the DEB desktop entry starts Craft. */
|
|
120
|
+
launchUrl?: string;
|
|
109
121
|
/** Create RPM package */
|
|
110
122
|
rpm?: boolean;
|
|
111
123
|
/** Create AppImage */
|
|
112
124
|
appImage?: boolean;
|
|
113
125
|
/** Desktop categories */
|
|
114
126
|
categories?: string[];
|
|
115
|
-
/** Dependencies (Debian) */
|
|
127
|
+
/** Dependencies (Debian); replaces the defaults, including libnotify-bin */
|
|
116
128
|
debDependencies?: string[];
|
|
117
129
|
/** Dependencies (RPM) */
|
|
118
130
|
rpmDependencies?: string[];
|
|
@@ -125,6 +137,9 @@ interface MSIOptions {
|
|
|
125
137
|
outputPath: string;
|
|
126
138
|
manufacturer: string;
|
|
127
139
|
architecture: 'x86' | 'x64' | 'arm64';
|
|
140
|
+
additionalFiles?: string[];
|
|
141
|
+
urlSchemes?: string[];
|
|
142
|
+
launchUrl?: string;
|
|
128
143
|
certificatePath?: string;
|
|
129
144
|
certificatePassword?: string;
|
|
130
145
|
}
|
|
@@ -164,6 +179,7 @@ export interface MacOSBundleMetadata {
|
|
|
164
179
|
* dicts would only invent distinctions LaunchServices does not care about.
|
|
165
180
|
*/
|
|
166
181
|
export declare function urlTypesEntry(bundleId: string, schemes: readonly string[]): string | null;
|
|
182
|
+
export declare function linuxDesktopEntry(name: string, binaryName: string, urlSchemes?: readonly string[], launchUrl?: string): string;
|
|
167
183
|
/**
|
|
168
184
|
* Render `Contents/Info.plist` for a macOS app bundle.
|
|
169
185
|
*
|
|
@@ -247,8 +263,11 @@ export declare function pkgbuildArguments(opts: {
|
|
|
247
263
|
outputPath: string;
|
|
248
264
|
installerIdentity?: string;
|
|
249
265
|
}): string[];
|
|
266
|
+
export declare const WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
|
|
267
|
+
/** Shared by the MSI shortcut and the installed executable's toast bridge. */
|
|
268
|
+
export declare function windowsNotificationAppId(name: string, manufacturer: string): string;
|
|
250
269
|
export declare function windowsArchitecture(architecture: string): 'x86' | 'x64' | 'arm64';
|
|
251
|
-
export declare function renderWixSource(opts: Pick<MSIOptions, 'name' | 'version' | 'manufacturer' | 'architecture'>, sourceName: string): string;
|
|
270
|
+
export declare function renderWixSource(opts: Pick<MSIOptions, 'name' | 'version' | 'manufacturer' | 'architecture' | 'urlSchemes' | 'launchUrl'>, sourceName: string, additionalFileNames?: string[]): string;
|
|
252
271
|
export declare function windowsExecutableName(name: string): string;
|
|
253
272
|
/**
|
|
254
273
|
* `candle.exe` arguments.
|