electrobun 0.0.19-beta.74 → 0.0.19-beta.76
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/package.json +3 -1
- package/src/cli/index.ts +155 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electrobun",
|
|
3
|
-
"version": "0.0.19-beta.
|
|
3
|
+
"version": "0.0.19-beta.76",
|
|
4
4
|
"description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blackboard Technologies Inc.",
|
|
@@ -48,10 +48,12 @@
|
|
|
48
48
|
"build:push:artifacts": "bun scripts/build-and-upload-artifacts.js"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
+
"@types/archiver": "^6.0.3",
|
|
51
52
|
"@types/bun": "1.1.9"
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"@oneidentity/zstd-js": "^1.0.3",
|
|
56
|
+
"archiver": "^7.0.1",
|
|
55
57
|
"rpc-anywhere": "1.5.0",
|
|
56
58
|
"tar": "^6.2.1"
|
|
57
59
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -11,9 +11,12 @@ import {
|
|
|
11
11
|
readdirSync,
|
|
12
12
|
rmSync,
|
|
13
13
|
symlinkSync,
|
|
14
|
+
statSync,
|
|
15
|
+
copyFileSync,
|
|
14
16
|
} from "fs";
|
|
15
17
|
import { execSync } from "child_process";
|
|
16
18
|
import tar from "tar";
|
|
19
|
+
import archiver from "archiver";
|
|
17
20
|
import { ZstdInit } from "@oneidentity/zstd-js/wasm";
|
|
18
21
|
import { OS, ARCH } from '../shared/platform';
|
|
19
22
|
import { getTemplate, getTemplateNames } from './templates/embedded';
|
|
@@ -22,7 +25,7 @@ import { getTemplate, getTemplateNames } from './templates/embedded';
|
|
|
22
25
|
const MAX_CHUNK_SIZE = 1024 * 2;
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
const binExt = OS === 'win' ? '.exe' : '';
|
|
28
|
+
// const binExt = OS === 'win' ? '.exe' : '';
|
|
26
29
|
|
|
27
30
|
// this when run as an npm script this will be where the folder where package.json is.
|
|
28
31
|
const projectRoot = process.cwd();
|
|
@@ -611,6 +614,7 @@ const buildSubFolder = `${buildEnvironment}-${currentTarget.os}-${currentTarget.
|
|
|
611
614
|
// Use target OS/ARCH for build logic (instead of current machine's OS/ARCH)
|
|
612
615
|
const targetOS = currentTarget.os;
|
|
613
616
|
const targetARCH = currentTarget.arch;
|
|
617
|
+
const targetBinExt = targetOS === 'win' ? '.exe' : '';
|
|
614
618
|
|
|
615
619
|
const buildFolder = join(projectRoot, config.build.buildFolder, buildSubFolder);
|
|
616
620
|
|
|
@@ -846,7 +850,7 @@ if (commandArg === "init") {
|
|
|
846
850
|
// Only copy launcher for non-dev builds
|
|
847
851
|
if (buildEnvironment !== "dev") {
|
|
848
852
|
const bunCliLauncherBinarySource = targetPaths.LAUNCHER_RELEASE;
|
|
849
|
-
const bunCliLauncherDestination = join(appBundleMacOSPath, "launcher") +
|
|
853
|
+
const bunCliLauncherDestination = join(appBundleMacOSPath, "launcher") + targetBinExt;
|
|
850
854
|
const destLauncherFolder = dirname(bunCliLauncherDestination);
|
|
851
855
|
if (!existsSync(destLauncherFolder)) {
|
|
852
856
|
// console.info('creating folder: ', destFolder);
|
|
@@ -866,7 +870,7 @@ if (commandArg === "init") {
|
|
|
866
870
|
const bunBinarySourcePath = targetPaths.BUN_BINARY;
|
|
867
871
|
// Note: .bin/bun binary in node_modules is a symlink to the versioned one in another place
|
|
868
872
|
// in node_modules, so we have to dereference here to get the actual binary in the bundle.
|
|
869
|
-
const bunBinaryDestInBundlePath = join(appBundleMacOSPath, "bun") +
|
|
873
|
+
const bunBinaryDestInBundlePath = join(appBundleMacOSPath, "bun") + targetBinExt;
|
|
870
874
|
const destFolder2 = dirname(bunBinaryDestInBundlePath);
|
|
871
875
|
if (!existsSync(destFolder2)) {
|
|
872
876
|
// console.info('creating folder: ', destFolder);
|
|
@@ -1172,7 +1176,7 @@ if (commandArg === "init") {
|
|
|
1172
1176
|
|
|
1173
1177
|
// copy native bindings
|
|
1174
1178
|
const bsPatchSource = targetPaths.BSPATCH;
|
|
1175
|
-
const bsPatchDestination = join(appBundleMacOSPath, "bspatch") +
|
|
1179
|
+
const bsPatchDestination = join(appBundleMacOSPath, "bspatch") + targetBinExt;
|
|
1176
1180
|
const bsPatchDestFolder = dirname(bsPatchDestination);
|
|
1177
1181
|
if (!existsSync(bsPatchDestFolder)) {
|
|
1178
1182
|
mkdirSync(bsPatchDestFolder, { recursive: true });
|
|
@@ -1501,7 +1505,13 @@ if (commandArg === "init") {
|
|
|
1501
1505
|
targetPaths,
|
|
1502
1506
|
buildEnvironment
|
|
1503
1507
|
);
|
|
1504
|
-
|
|
1508
|
+
|
|
1509
|
+
// Wrap Windows installer files in zip for distribution
|
|
1510
|
+
const wrappedExePath = await wrapWindowsInstallerInZip(selfExtractingExePath, buildFolder);
|
|
1511
|
+
artifactsToUpload.push(wrappedExePath);
|
|
1512
|
+
|
|
1513
|
+
// Also keep the raw exe for backwards compatibility (optional)
|
|
1514
|
+
// artifactsToUpload.push(selfExtractingExePath);
|
|
1505
1515
|
} else if (targetOS === 'linux') {
|
|
1506
1516
|
// Create desktop file for Linux
|
|
1507
1517
|
const desktopFileContent = `[Desktop Entry]
|
|
@@ -1554,7 +1564,13 @@ exec "\$LAUNCHER_BINARY" "\$@"
|
|
|
1554
1564
|
targetPaths,
|
|
1555
1565
|
buildEnvironment
|
|
1556
1566
|
);
|
|
1557
|
-
|
|
1567
|
+
|
|
1568
|
+
// Wrap Linux .run file in tar.gz to preserve permissions
|
|
1569
|
+
const wrappedRunPath = await wrapInArchive(selfExtractingLinuxPath, buildFolder, 'tar.gz');
|
|
1570
|
+
artifactsToUpload.push(wrappedRunPath);
|
|
1571
|
+
|
|
1572
|
+
// Also keep the raw .run for backwards compatibility (optional)
|
|
1573
|
+
// artifactsToUpload.push(selfExtractingLinuxPath);
|
|
1558
1574
|
|
|
1559
1575
|
// On Linux, create a tar.gz of the bundle
|
|
1560
1576
|
const linuxTarPath = join(buildFolder, `${appFileName}.tar.gz`);
|
|
@@ -1864,7 +1880,7 @@ async function createWindowsSelfExtractingExe(
|
|
|
1864
1880
|
targetPaths: any,
|
|
1865
1881
|
buildEnvironment: string
|
|
1866
1882
|
): Promise<string> {
|
|
1867
|
-
console.log("Creating
|
|
1883
|
+
console.log("Creating Windows installer with separate archive...");
|
|
1868
1884
|
|
|
1869
1885
|
// Format: MyApp-Setup.exe (stable) or MyApp-Setup-canary.exe (non-stable)
|
|
1870
1886
|
const setupFileName = buildEnvironment === "stable"
|
|
@@ -1873,43 +1889,40 @@ async function createWindowsSelfExtractingExe(
|
|
|
1873
1889
|
|
|
1874
1890
|
const outputExePath = join(buildFolder, setupFileName);
|
|
1875
1891
|
|
|
1876
|
-
//
|
|
1892
|
+
// Copy the extractor exe
|
|
1877
1893
|
const extractorExe = readFileSync(targetPaths.EXTRACTOR);
|
|
1894
|
+
writeFileSync(outputExePath, extractorExe);
|
|
1878
1895
|
|
|
1879
|
-
//
|
|
1880
|
-
const compressedArchive = readFileSync(compressedTarPath);
|
|
1881
|
-
|
|
1882
|
-
// Create metadata JSON
|
|
1896
|
+
// Create metadata JSON file
|
|
1883
1897
|
const metadata = {
|
|
1884
1898
|
identifier: config.app.identifier,
|
|
1885
1899
|
name: config.app.name,
|
|
1886
1900
|
channel: buildEnvironment
|
|
1887
1901
|
};
|
|
1888
|
-
const metadataJson = JSON.stringify(metadata);
|
|
1889
|
-
const
|
|
1902
|
+
const metadataJson = JSON.stringify(metadata, null, 2);
|
|
1903
|
+
const metadataFileName = setupFileName.replace('.exe', '.metadata.json');
|
|
1904
|
+
const metadataPath = join(buildFolder, metadataFileName);
|
|
1905
|
+
writeFileSync(metadataPath, metadataJson);
|
|
1890
1906
|
|
|
1891
|
-
//
|
|
1892
|
-
const
|
|
1893
|
-
const
|
|
1907
|
+
// Copy the compressed archive with matching name
|
|
1908
|
+
const archiveFileName = setupFileName.replace('.exe', '.tar.zst');
|
|
1909
|
+
const archivePath = join(buildFolder, archiveFileName);
|
|
1910
|
+
copyFileSync(compressedTarPath, archivePath);
|
|
1894
1911
|
|
|
1895
|
-
//
|
|
1896
|
-
const combinedBuffer = Buffer.concat([
|
|
1897
|
-
extractorExe,
|
|
1898
|
-
metadataMarker,
|
|
1899
|
-
metadataBuffer,
|
|
1900
|
-
archiveMarker,
|
|
1901
|
-
compressedArchive
|
|
1902
|
-
]);
|
|
1903
|
-
|
|
1904
|
-
// Write the self-extracting exe
|
|
1905
|
-
writeFileSync(outputExePath, combinedBuffer);
|
|
1906
|
-
|
|
1907
|
-
// Make it executable (though Windows doesn't need chmod)
|
|
1912
|
+
// Make the exe executable (though Windows doesn't need chmod)
|
|
1908
1913
|
if (OS !== 'win') {
|
|
1909
1914
|
execSync(`chmod +x ${escapePathForTerminal(outputExePath)}`);
|
|
1910
1915
|
}
|
|
1911
1916
|
|
|
1912
|
-
|
|
1917
|
+
const exeSize = statSync(outputExePath).size;
|
|
1918
|
+
const archiveSize = statSync(archivePath).size;
|
|
1919
|
+
const totalSize = exeSize + archiveSize;
|
|
1920
|
+
|
|
1921
|
+
console.log(`Created Windows installer:`);
|
|
1922
|
+
console.log(` - Extractor: ${outputExePath} (${(exeSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
1923
|
+
console.log(` - Archive: ${archivePath} (${(archiveSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
1924
|
+
console.log(` - Metadata: ${metadataPath}`);
|
|
1925
|
+
console.log(` - Total size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
|
1913
1926
|
|
|
1914
1927
|
return outputExePath;
|
|
1915
1928
|
}
|
|
@@ -1969,6 +1982,117 @@ async function createLinuxSelfExtractingBinary(
|
|
|
1969
1982
|
return outputPath;
|
|
1970
1983
|
}
|
|
1971
1984
|
|
|
1985
|
+
async function wrapWindowsInstallerInZip(exePath: string, buildFolder: string): Promise<string> {
|
|
1986
|
+
const exeName = basename(exePath);
|
|
1987
|
+
const exeStem = exeName.replace('.exe', '');
|
|
1988
|
+
|
|
1989
|
+
// Derive the paths for metadata and archive files
|
|
1990
|
+
const metadataPath = join(buildFolder, `${exeStem}.metadata.json`);
|
|
1991
|
+
const archivePath = join(buildFolder, `${exeStem}.tar.zst`);
|
|
1992
|
+
const zipPath = join(buildFolder, `${exeStem}.zip`);
|
|
1993
|
+
|
|
1994
|
+
// Verify all files exist
|
|
1995
|
+
if (!existsSync(exePath)) {
|
|
1996
|
+
throw new Error(`Installer exe not found: ${exePath}`);
|
|
1997
|
+
}
|
|
1998
|
+
if (!existsSync(metadataPath)) {
|
|
1999
|
+
throw new Error(`Metadata file not found: ${metadataPath}`);
|
|
2000
|
+
}
|
|
2001
|
+
if (!existsSync(archivePath)) {
|
|
2002
|
+
throw new Error(`Archive file not found: ${archivePath}`);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// Create zip archive
|
|
2006
|
+
const output = createWriteStream(zipPath);
|
|
2007
|
+
const archive = archiver('zip', {
|
|
2008
|
+
zlib: { level: 9 } // Maximum compression
|
|
2009
|
+
});
|
|
2010
|
+
|
|
2011
|
+
return new Promise((resolve, reject) => {
|
|
2012
|
+
output.on('close', () => {
|
|
2013
|
+
console.log(`Created Windows installer package: ${zipPath} (${(archive.pointer() / 1024 / 1024).toFixed(2)} MB)`);
|
|
2014
|
+
resolve(zipPath);
|
|
2015
|
+
});
|
|
2016
|
+
|
|
2017
|
+
archive.on('error', (err) => {
|
|
2018
|
+
reject(err);
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
archive.pipe(output);
|
|
2022
|
+
|
|
2023
|
+
// Add all three files to the archive
|
|
2024
|
+
archive.file(exePath, { name: basename(exePath) });
|
|
2025
|
+
archive.file(metadataPath, { name: basename(metadataPath) });
|
|
2026
|
+
archive.file(archivePath, { name: basename(archivePath) });
|
|
2027
|
+
|
|
2028
|
+
archive.finalize();
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
async function wrapInArchive(filePath: string, buildFolder: string, archiveType: 'tar.gz' | 'zip'): Promise<string> {
|
|
2033
|
+
const fileName = basename(filePath);
|
|
2034
|
+
const fileDir = dirname(filePath);
|
|
2035
|
+
|
|
2036
|
+
if (archiveType === 'tar.gz') {
|
|
2037
|
+
// Output filename: Setup.exe -> Setup.exe.tar.gz or Setup.run -> Setup.run.tar.gz
|
|
2038
|
+
const archivePath = filePath + '.tar.gz';
|
|
2039
|
+
|
|
2040
|
+
// For Linux files, ensure they have executable permissions before archiving
|
|
2041
|
+
if (fileName.endsWith('.run')) {
|
|
2042
|
+
try {
|
|
2043
|
+
// Try to set executable permissions (will only work on Unix-like systems)
|
|
2044
|
+
execSync(`chmod +x ${escapePathForTerminal(filePath)}`, { stdio: 'ignore' });
|
|
2045
|
+
} catch {
|
|
2046
|
+
// Ignore errors on Windows hosts
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// Create tar.gz archive preserving permissions
|
|
2051
|
+
// Using the tar package for cross-platform compatibility
|
|
2052
|
+
await tar.c(
|
|
2053
|
+
{
|
|
2054
|
+
gzip: true,
|
|
2055
|
+
file: archivePath,
|
|
2056
|
+
cwd: fileDir,
|
|
2057
|
+
portable: true, // Ensures consistent behavior across platforms
|
|
2058
|
+
preservePaths: false,
|
|
2059
|
+
// The tar package should preserve file modes when creating archives
|
|
2060
|
+
},
|
|
2061
|
+
[fileName]
|
|
2062
|
+
);
|
|
2063
|
+
|
|
2064
|
+
console.log(`Created archive: ${archivePath} (preserving executable permissions)`);
|
|
2065
|
+
return archivePath;
|
|
2066
|
+
} else if (archiveType === 'zip') {
|
|
2067
|
+
// Output filename: Setup.exe -> Setup.zip
|
|
2068
|
+
const archivePath = filePath.replace(/\.[^.]+$/, '.zip');
|
|
2069
|
+
|
|
2070
|
+
// Create zip archive
|
|
2071
|
+
const output = createWriteStream(archivePath);
|
|
2072
|
+
const archive = archiver('zip', {
|
|
2073
|
+
zlib: { level: 9 } // Maximum compression
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
return new Promise((resolve, reject) => {
|
|
2077
|
+
output.on('close', () => {
|
|
2078
|
+
console.log(`Created archive: ${archivePath} (${(archive.pointer() / 1024 / 1024).toFixed(2)} MB)`);
|
|
2079
|
+
resolve(archivePath);
|
|
2080
|
+
});
|
|
2081
|
+
|
|
2082
|
+
archive.on('error', (err) => {
|
|
2083
|
+
reject(err);
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
archive.pipe(output);
|
|
2087
|
+
|
|
2088
|
+
// Add the file to the archive
|
|
2089
|
+
archive.file(filePath, { name: fileName });
|
|
2090
|
+
|
|
2091
|
+
archive.finalize();
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
|
|
1972
2096
|
async function createAppImage(buildFolder: string, appBundlePath: string, appFileName: string, config: any): Promise<string | null> {
|
|
1973
2097
|
try {
|
|
1974
2098
|
console.log("Creating AppImage...");
|