electrobun 0.13.0-beta.4 → 0.13.0-beta.5
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/api/bun/core/Updater.ts +6 -14
- package/dist/api/shared/naming.test.ts +285 -0
- package/dist/api/shared/naming.ts +131 -0
- package/package.json +3 -2
- package/src/cli/index.ts +19 -30
|
@@ -5,6 +5,7 @@ import { execSync } from "child_process";
|
|
|
5
5
|
import tar from "tar";
|
|
6
6
|
import { ZstdInit } from "@oneidentity/zstd-js/wasm";
|
|
7
7
|
import { OS as currentOS, ARCH as currentArch } from '../../shared/platform';
|
|
8
|
+
import { getPlatformFolder, getTarballFileName } from '../../shared/naming';
|
|
8
9
|
import { native } from '../proc/native';
|
|
9
10
|
|
|
10
11
|
// setTimeout(async () => {
|
|
@@ -142,7 +143,7 @@ const Updater = {
|
|
|
142
143
|
|
|
143
144
|
const channelBucketUrl = await Updater.channelBucketUrl();
|
|
144
145
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
145
|
-
const platformFolder =
|
|
146
|
+
const platformFolder = getPlatformFolder(localInfo.channel, currentOS, currentArch);
|
|
146
147
|
const updateInfoUrl = join(localInfo.bucketUrl, platformFolder, `update.json?${cacheBuster}`);
|
|
147
148
|
|
|
148
149
|
try {
|
|
@@ -210,7 +211,7 @@ const Updater = {
|
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
// check if there's a patch file for it
|
|
213
|
-
const platformFolder =
|
|
214
|
+
const platformFolder = getPlatformFolder(localInfo.channel, currentOS, currentArch);
|
|
214
215
|
const patchResponse = await fetch(
|
|
215
216
|
join(localInfo.bucketUrl, platformFolder, `${currentHash}.patch`)
|
|
216
217
|
);
|
|
@@ -329,17 +330,8 @@ const Updater = {
|
|
|
329
330
|
// then just download it and unpack it
|
|
330
331
|
if (currentHash !== latestHash) {
|
|
331
332
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
332
|
-
const platformFolder =
|
|
333
|
-
|
|
334
|
-
let tarballName: string;
|
|
335
|
-
if (currentOS === 'macos') {
|
|
336
|
-
tarballName = `${appFileName}.app.tar.zst`;
|
|
337
|
-
} else if (currentOS === 'win') {
|
|
338
|
-
tarballName = `${appFileName}.tar.zst`;
|
|
339
|
-
} else {
|
|
340
|
-
tarballName = `${appFileName}.tar.zst`;
|
|
341
|
-
}
|
|
342
|
-
|
|
333
|
+
const platformFolder = getPlatformFolder(localInfo.channel, currentOS, currentArch);
|
|
334
|
+
const tarballName = getTarballFileName(appFileName, currentOS);
|
|
343
335
|
const urlToLatestTarball = join(
|
|
344
336
|
localInfo.bucketUrl,
|
|
345
337
|
platformFolder,
|
|
@@ -694,7 +686,7 @@ start "" launcher.exe
|
|
|
694
686
|
|
|
695
687
|
channelBucketUrl: async () => {
|
|
696
688
|
await Updater.getLocallocalInfo();
|
|
697
|
-
const platformFolder =
|
|
689
|
+
const platformFolder = getPlatformFolder(localInfo.channel, currentOS, currentArch);
|
|
698
690
|
return join(localInfo.bucketUrl, platformFolder);
|
|
699
691
|
},
|
|
700
692
|
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
sanitizeAppName,
|
|
4
|
+
getAppFileName,
|
|
5
|
+
getBundleFileName,
|
|
6
|
+
getPlatformFolder,
|
|
7
|
+
getTarballFileName,
|
|
8
|
+
getWindowsSetupFileName,
|
|
9
|
+
getLinuxSetupFileName,
|
|
10
|
+
getLinuxAppImageBaseName,
|
|
11
|
+
getLinuxAppImageFileName,
|
|
12
|
+
sanitizeVolumeNameForHdiutil,
|
|
13
|
+
getDmgVolumeName,
|
|
14
|
+
getUpdateInfoUrl,
|
|
15
|
+
getPatchFileUrl,
|
|
16
|
+
getTarballUrl,
|
|
17
|
+
} from './naming';
|
|
18
|
+
|
|
19
|
+
describe('sanitizeAppName', () => {
|
|
20
|
+
it('removes spaces from app name', () => {
|
|
21
|
+
expect(sanitizeAppName('My App')).toBe('MyApp');
|
|
22
|
+
expect(sanitizeAppName('My Multi Spaced App')).toBe('MyMultiSpacedApp');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('preserves names without spaces', () => {
|
|
26
|
+
expect(sanitizeAppName('MyApp')).toBe('MyApp');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles empty string', () => {
|
|
30
|
+
expect(sanitizeAppName('')).toBe('');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getAppFileName', () => {
|
|
35
|
+
it('returns sanitized name without suffix for stable builds', () => {
|
|
36
|
+
expect(getAppFileName('My App', 'stable')).toBe('MyApp');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('appends channel suffix for canary builds', () => {
|
|
40
|
+
expect(getAppFileName('My App', 'canary')).toBe('MyApp-canary');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('appends channel suffix for dev builds', () => {
|
|
44
|
+
expect(getAppFileName('My App', 'dev')).toBe('MyApp-dev');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('handles custom channels', () => {
|
|
48
|
+
expect(getAppFileName('My App', 'beta')).toBe('MyApp-beta');
|
|
49
|
+
expect(getAppFileName('My App', 'nightly')).toBe('MyApp-nightly');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('getBundleFileName', () => {
|
|
54
|
+
describe('macOS', () => {
|
|
55
|
+
it('adds .app extension for stable builds', () => {
|
|
56
|
+
expect(getBundleFileName('My App', 'stable', 'macos')).toBe('MyApp.app');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('adds .app extension for canary builds', () => {
|
|
60
|
+
expect(getBundleFileName('My App', 'canary', 'macos')).toBe('MyApp-canary.app');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('Windows', () => {
|
|
65
|
+
it('returns plain name for stable builds', () => {
|
|
66
|
+
expect(getBundleFileName('My App', 'stable', 'win')).toBe('MyApp');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns plain name with suffix for canary builds', () => {
|
|
70
|
+
expect(getBundleFileName('My App', 'canary', 'win')).toBe('MyApp-canary');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Linux', () => {
|
|
75
|
+
it('returns plain name for stable builds', () => {
|
|
76
|
+
expect(getBundleFileName('My App', 'stable', 'linux')).toBe('MyApp');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns plain name with suffix for canary builds', () => {
|
|
80
|
+
expect(getBundleFileName('My App', 'canary', 'linux')).toBe('MyApp-canary');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('getPlatformFolder', () => {
|
|
86
|
+
it('constructs correct folder format for all platform combinations', () => {
|
|
87
|
+
expect(getPlatformFolder('stable', 'macos', 'arm64')).toBe('stable-macos-arm64');
|
|
88
|
+
expect(getPlatformFolder('stable', 'macos', 'x64')).toBe('stable-macos-x64');
|
|
89
|
+
expect(getPlatformFolder('canary', 'win', 'x64')).toBe('canary-win-x64');
|
|
90
|
+
expect(getPlatformFolder('dev', 'linux', 'arm64')).toBe('dev-linux-arm64');
|
|
91
|
+
expect(getPlatformFolder('dev', 'linux', 'x64')).toBe('dev-linux-x64');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('getTarballFileName', () => {
|
|
96
|
+
describe('macOS', () => {
|
|
97
|
+
it('uses .app.tar.zst extension', () => {
|
|
98
|
+
expect(getTarballFileName('MyApp', 'macos')).toBe('MyApp.app.tar.zst');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('preserves channel suffix in filename', () => {
|
|
102
|
+
expect(getTarballFileName('MyApp-canary', 'macos')).toBe('MyApp-canary.app.tar.zst');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('Windows', () => {
|
|
107
|
+
it('uses .tar.zst extension', () => {
|
|
108
|
+
expect(getTarballFileName('MyApp', 'win')).toBe('MyApp.tar.zst');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('preserves channel suffix in filename', () => {
|
|
112
|
+
expect(getTarballFileName('MyApp-canary', 'win')).toBe('MyApp-canary.tar.zst');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Linux', () => {
|
|
117
|
+
it('uses .tar.zst extension', () => {
|
|
118
|
+
expect(getTarballFileName('MyApp', 'linux')).toBe('MyApp.tar.zst');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('preserves channel suffix in filename', () => {
|
|
122
|
+
expect(getTarballFileName('MyApp-canary', 'linux')).toBe('MyApp-canary.tar.zst');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('getWindowsSetupFileName', () => {
|
|
128
|
+
it('returns AppName-Setup.exe for stable builds', () => {
|
|
129
|
+
expect(getWindowsSetupFileName('MyApp', 'stable')).toBe('MyApp-Setup.exe');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('includes channel suffix for canary builds', () => {
|
|
133
|
+
expect(getWindowsSetupFileName('MyApp', 'canary')).toBe('MyApp-Setup-canary.exe');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('includes channel suffix for dev builds', () => {
|
|
137
|
+
expect(getWindowsSetupFileName('MyApp', 'dev')).toBe('MyApp-Setup-dev.exe');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('getLinuxSetupFileName', () => {
|
|
142
|
+
it('returns AppName-Setup.run for stable builds', () => {
|
|
143
|
+
expect(getLinuxSetupFileName('MyApp', 'stable')).toBe('MyApp-Setup.run');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('includes channel suffix for canary builds', () => {
|
|
147
|
+
expect(getLinuxSetupFileName('MyApp', 'canary')).toBe('MyApp-Setup-canary.run');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('includes channel suffix for dev builds', () => {
|
|
151
|
+
expect(getLinuxSetupFileName('MyApp', 'dev')).toBe('MyApp-Setup-dev.run');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('getLinuxAppImageBaseName', () => {
|
|
156
|
+
it('returns AppName-Setup for stable builds', () => {
|
|
157
|
+
expect(getLinuxAppImageBaseName('MyApp', 'stable')).toBe('MyApp-Setup');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('includes channel suffix for canary builds', () => {
|
|
161
|
+
expect(getLinuxAppImageBaseName('MyApp', 'canary')).toBe('MyApp-Setup-canary');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('getLinuxAppImageFileName', () => {
|
|
166
|
+
it('returns full filename with .AppImage extension for stable', () => {
|
|
167
|
+
expect(getLinuxAppImageFileName('MyApp', 'stable')).toBe('MyApp-Setup.AppImage');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('returns full filename with .AppImage extension for canary', () => {
|
|
171
|
+
expect(getLinuxAppImageFileName('MyApp', 'canary')).toBe('MyApp-Setup-canary.AppImage');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('sanitizeVolumeNameForHdiutil', () => {
|
|
176
|
+
it('removes special characters', () => {
|
|
177
|
+
expect(sanitizeVolumeNameForHdiutil('My-App_v1.0')).toBe('MyAppv10');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('removes parentheses and other punctuation', () => {
|
|
181
|
+
expect(sanitizeVolumeNameForHdiutil('My App (Beta)')).toBe('My App Beta');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('preserves spaces and alphanumerics', () => {
|
|
185
|
+
expect(sanitizeVolumeNameForHdiutil('My App 2024')).toBe('My App 2024');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('trims leading and trailing whitespace', () => {
|
|
189
|
+
expect(sanitizeVolumeNameForHdiutil(' My App ')).toBe('My App');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('handles names with only special characters', () => {
|
|
193
|
+
expect(sanitizeVolumeNameForHdiutil('---')).toBe('');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('getDmgVolumeName', () => {
|
|
198
|
+
it('adds -stable suffix for stable builds (to avoid CI volume conflicts)', () => {
|
|
199
|
+
expect(getDmgVolumeName('MyApp', 'stable')).toBe('MyApp-stable');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('returns sanitized name for canary builds (already has suffix)', () => {
|
|
203
|
+
expect(getDmgVolumeName('MyApp-canary', 'canary')).toBe('MyAppcanary');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('sanitizes special characters', () => {
|
|
207
|
+
expect(getDmgVolumeName('My-App', 'stable')).toBe('MyApp-stable');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('URL construction functions', () => {
|
|
212
|
+
const bucketUrl = 'https://storage.example.com/releases';
|
|
213
|
+
const platformFolder = 'canary-macos-arm64';
|
|
214
|
+
|
|
215
|
+
describe('getUpdateInfoUrl', () => {
|
|
216
|
+
it('constructs correct URL', () => {
|
|
217
|
+
expect(getUpdateInfoUrl(bucketUrl, platformFolder))
|
|
218
|
+
.toBe('https://storage.example.com/releases/canary-macos-arm64/update.json');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('handles bucket URLs with trailing content', () => {
|
|
222
|
+
expect(getUpdateInfoUrl('https://example.com/bucket', 'stable-win-x64'))
|
|
223
|
+
.toBe('https://example.com/bucket/stable-win-x64/update.json');
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('getPatchFileUrl', () => {
|
|
228
|
+
it('constructs correct URL with hash', () => {
|
|
229
|
+
expect(getPatchFileUrl(bucketUrl, platformFolder, 'abc123def456'))
|
|
230
|
+
.toBe('https://storage.example.com/releases/canary-macos-arm64/abc123def456.patch');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('getTarballUrl', () => {
|
|
235
|
+
it('constructs correct URL for macOS tarball', () => {
|
|
236
|
+
expect(getTarballUrl(bucketUrl, platformFolder, 'MyApp.app.tar.zst'))
|
|
237
|
+
.toBe('https://storage.example.com/releases/canary-macos-arm64/MyApp.app.tar.zst');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('constructs correct URL for Windows tarball', () => {
|
|
241
|
+
expect(getTarballUrl(bucketUrl, 'stable-win-x64', 'MyApp.tar.zst'))
|
|
242
|
+
.toBe('https://storage.example.com/releases/stable-win-x64/MyApp.tar.zst');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Integration tests that verify CLI and Updater would produce matching values
|
|
248
|
+
describe('CLI and Updater consistency', () => {
|
|
249
|
+
it('produces matching platform folders', () => {
|
|
250
|
+
// CLI uses: `${buildEnvironment}-${currentTarget.os}-${currentTarget.arch}`
|
|
251
|
+
// Updater uses: `${localInfo.channel}-${currentOS}-${currentArch}`
|
|
252
|
+
// Both should use getPlatformFolder()
|
|
253
|
+
const cliResult = getPlatformFolder('canary', 'macos', 'arm64');
|
|
254
|
+
const updaterResult = getPlatformFolder('canary', 'macos', 'arm64');
|
|
255
|
+
expect(cliResult).toBe(updaterResult);
|
|
256
|
+
expect(cliResult).toBe('canary-macos-arm64');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('produces matching tarball names for macOS', () => {
|
|
260
|
+
const appFileName = getAppFileName('My App', 'canary');
|
|
261
|
+
const tarballName = getTarballFileName(appFileName, 'macos');
|
|
262
|
+
expect(tarballName).toBe('MyApp-canary.app.tar.zst');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('produces matching tarball names for Windows', () => {
|
|
266
|
+
const appFileName = getAppFileName('My App', 'stable');
|
|
267
|
+
const tarballName = getTarballFileName(appFileName, 'win');
|
|
268
|
+
expect(tarballName).toBe('MyApp.tar.zst');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('produces matching tarball names for Linux', () => {
|
|
272
|
+
const appFileName = getAppFileName('My App', 'dev');
|
|
273
|
+
const tarballName = getTarballFileName(appFileName, 'linux');
|
|
274
|
+
expect(tarballName).toBe('MyApp-dev.tar.zst');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('stable builds have no channel suffix in artifact names', () => {
|
|
278
|
+
// This is the regression test for the -stable bug
|
|
279
|
+
expect(getAppFileName('MyApp', 'stable')).toBe('MyApp');
|
|
280
|
+
expect(getAppFileName('MyApp', 'stable')).not.toContain('-stable');
|
|
281
|
+
expect(getTarballFileName(getAppFileName('MyApp', 'stable'), 'macos')).toBe('MyApp.app.tar.zst');
|
|
282
|
+
expect(getWindowsSetupFileName('MyApp', 'stable')).toBe('MyApp-Setup.exe');
|
|
283
|
+
expect(getLinuxSetupFileName('MyApp', 'stable')).toBe('MyApp-Setup.run');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { SupportedOS, SupportedArch } from './platform';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build environment/channel types.
|
|
5
|
+
* "stable" is special - it produces artifacts without a channel suffix.
|
|
6
|
+
*/
|
|
7
|
+
export type BuildEnvironment = 'stable' | 'canary' | 'dev' | (string & {});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sanitizes an app name by removing spaces.
|
|
11
|
+
* Used as the base for all artifact naming.
|
|
12
|
+
*/
|
|
13
|
+
export function sanitizeAppName(appName: string): string {
|
|
14
|
+
return appName.replace(/ /g, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates the app file name based on build environment.
|
|
19
|
+
* Format: "AppName" (stable) or "AppName-channel" (non-stable)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* getAppFileName("My App", "stable") // "MyApp"
|
|
23
|
+
* getAppFileName("My App", "canary") // "MyApp-canary"
|
|
24
|
+
*/
|
|
25
|
+
export function getAppFileName(appName: string, buildEnvironment: BuildEnvironment): string {
|
|
26
|
+
const sanitized = sanitizeAppName(appName);
|
|
27
|
+
return buildEnvironment === 'stable' ? sanitized : `${sanitized}-${buildEnvironment}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates the bundle file name (with platform-specific extension).
|
|
32
|
+
* macOS: "AppName.app" or "AppName-channel.app"
|
|
33
|
+
* Others: "AppName" or "AppName-channel"
|
|
34
|
+
*/
|
|
35
|
+
export function getBundleFileName(appName: string, buildEnvironment: BuildEnvironment, os: SupportedOS): string {
|
|
36
|
+
const appFileName = getAppFileName(appName, buildEnvironment);
|
|
37
|
+
return os === 'macos' ? `${appFileName}.app` : appFileName;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generates the platform folder name for artifacts.
|
|
42
|
+
* Format: "channel-os-arch" (e.g., "stable-macos-arm64", "canary-win-x64")
|
|
43
|
+
*/
|
|
44
|
+
export function getPlatformFolder(buildEnvironment: BuildEnvironment, os: SupportedOS, arch: SupportedArch): string {
|
|
45
|
+
return `${buildEnvironment}-${os}-${arch}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generates the tarball file name for update distribution.
|
|
50
|
+
* macOS: "AppFileName.app.tar.zst"
|
|
51
|
+
* Others: "AppFileName.tar.zst"
|
|
52
|
+
*/
|
|
53
|
+
export function getTarballFileName(appFileName: string, os: SupportedOS): string {
|
|
54
|
+
return os === 'macos' ? `${appFileName}.app.tar.zst` : `${appFileName}.tar.zst`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generates the Windows installer setup file name.
|
|
59
|
+
* Format: "AppName-Setup.exe" (stable) or "AppName-Setup-channel.exe" (non-stable)
|
|
60
|
+
*/
|
|
61
|
+
export function getWindowsSetupFileName(appName: string, buildEnvironment: BuildEnvironment): string {
|
|
62
|
+
return buildEnvironment === 'stable'
|
|
63
|
+
? `${appName}-Setup.exe`
|
|
64
|
+
: `${appName}-Setup-${buildEnvironment}.exe`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generates the Linux self-extracting binary file name.
|
|
69
|
+
* Format: "AppName-Setup.run" (stable) or "AppName-Setup-channel.run" (non-stable)
|
|
70
|
+
*/
|
|
71
|
+
export function getLinuxSetupFileName(appName: string, buildEnvironment: BuildEnvironment): string {
|
|
72
|
+
return buildEnvironment === 'stable'
|
|
73
|
+
? `${appName}-Setup.run`
|
|
74
|
+
: `${appName}-Setup-${buildEnvironment}.run`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generates the Linux AppImage wrapper name (without extension).
|
|
79
|
+
* Format: "AppName-Setup" (stable) or "AppName-Setup-channel" (non-stable)
|
|
80
|
+
*/
|
|
81
|
+
export function getLinuxAppImageBaseName(appName: string, buildEnvironment: BuildEnvironment): string {
|
|
82
|
+
return buildEnvironment === 'stable'
|
|
83
|
+
? `${appName}-Setup`
|
|
84
|
+
: `${appName}-Setup-${buildEnvironment}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generates the full Linux AppImage file name.
|
|
89
|
+
*/
|
|
90
|
+
export function getLinuxAppImageFileName(appName: string, buildEnvironment: BuildEnvironment): string {
|
|
91
|
+
return `${getLinuxAppImageBaseName(appName, buildEnvironment)}.AppImage`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Sanitizes a volume name for hdiutil (macOS DMG creation).
|
|
96
|
+
* Removes all non-alphanumeric characters except spaces.
|
|
97
|
+
*/
|
|
98
|
+
export function sanitizeVolumeNameForHdiutil(name: string): string {
|
|
99
|
+
return name.replace(/[^a-zA-Z0-9 ]/g, '').trim();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generates the DMG volume name for macOS during creation.
|
|
104
|
+
* Uses sanitized name with "-stable" suffix for stable builds to avoid
|
|
105
|
+
* CI volume mounting conflicts. The DMG is renamed after creation.
|
|
106
|
+
*/
|
|
107
|
+
export function getDmgVolumeName(appFileName: string, buildEnvironment: BuildEnvironment): string {
|
|
108
|
+
const baseName = sanitizeVolumeNameForHdiutil(appFileName);
|
|
109
|
+
return buildEnvironment === 'stable' ? `${baseName}-stable` : baseName;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Constructs the full URL for the update.json file.
|
|
114
|
+
*/
|
|
115
|
+
export function getUpdateInfoUrl(bucketUrl: string, platformFolder: string): string {
|
|
116
|
+
return `${bucketUrl}/${platformFolder}/update.json`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Constructs the full URL for a patch file.
|
|
121
|
+
*/
|
|
122
|
+
export function getPatchFileUrl(bucketUrl: string, platformFolder: string, hash: string): string {
|
|
123
|
+
return `${bucketUrl}/${platformFolder}/${hash}.patch`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Constructs the full URL for a tarball.
|
|
128
|
+
*/
|
|
129
|
+
export function getTarballUrl(bucketUrl: string, platformFolder: string, tarballFileName: string): string {
|
|
130
|
+
return `${bucketUrl}/${platformFolder}/${tarballFileName}`;
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electrobun",
|
|
3
|
-
"version": "0.13.0-beta.
|
|
3
|
+
"version": "0.13.0-beta.5",
|
|
4
4
|
"description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blackboard Technologies Inc.",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"push:major": "bun scripts/push-version.js major",
|
|
47
47
|
"push:stable": "bun scripts/push-version.js stable",
|
|
48
48
|
"build:push:artifacts": "bun scripts/build-and-upload-artifacts.js",
|
|
49
|
-
"test": "bun install && bun build:dev && bun build:cli && cd ../tests && bun install && bun build:dev && bun start"
|
|
49
|
+
"test": "bun install && bun build:dev && bun build:cli && cd ../tests && bun install && bun build:dev && bun start",
|
|
50
|
+
"test:unit": "bun test src/shared"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/archiver": "^6.0.3",
|
package/src/cli/index.ts
CHANGED
|
@@ -22,6 +22,17 @@ import tar from "tar";
|
|
|
22
22
|
import archiver from "archiver";
|
|
23
23
|
import { ZstdInit } from "@oneidentity/zstd-js/wasm";
|
|
24
24
|
import { OS, ARCH } from '../shared/platform';
|
|
25
|
+
import {
|
|
26
|
+
getAppFileName,
|
|
27
|
+
getBundleFileName,
|
|
28
|
+
getPlatformFolder,
|
|
29
|
+
getTarballFileName,
|
|
30
|
+
getWindowsSetupFileName,
|
|
31
|
+
getLinuxSetupFileName,
|
|
32
|
+
getLinuxAppImageBaseName,
|
|
33
|
+
sanitizeVolumeNameForHdiutil,
|
|
34
|
+
getDmgVolumeName,
|
|
35
|
+
} from '../shared/naming';
|
|
25
36
|
import { getTemplate, getTemplateNames } from './templates/embedded';
|
|
26
37
|
// import { loadBsdiff, loadBspatch } from 'bsdiff-wasm';
|
|
27
38
|
// MacOS named pipes hang at around 4KB
|
|
@@ -1036,13 +1047,10 @@ if (commandArg === "init") {
|
|
|
1036
1047
|
const targetOS = currentTarget.os;
|
|
1037
1048
|
const targetARCH = currentTarget.arch;
|
|
1038
1049
|
const targetBinExt = targetOS === 'win' ? '.exe' : '';
|
|
1039
|
-
|
|
1040
|
-
const
|
|
1041
|
-
? config.app.name.replace(/ /g, "")
|
|
1042
|
-
: `${config.app.name.replace(/ /g, "")}-${buildEnvironment}`;
|
|
1043
|
-
const buildSubFolder = `${buildEnvironment}-${currentTarget.os}-${currentTarget.arch}`;
|
|
1050
|
+
const appFileName = getAppFileName(config.app.name, buildEnvironment);
|
|
1051
|
+
const buildSubFolder = getPlatformFolder(buildEnvironment, currentTarget.os, currentTarget.arch);
|
|
1044
1052
|
const buildFolder = join(projectRoot, config.build.buildFolder, buildSubFolder);
|
|
1045
|
-
const bundleFileName =
|
|
1053
|
+
const bundleFileName = getBundleFileName(config.app.name, buildEnvironment, targetOS);
|
|
1046
1054
|
const artifactFolder = join(projectRoot, config.build.artifactFolder, buildSubFolder);
|
|
1047
1055
|
|
|
1048
1056
|
// Ensure core binaries are available for the target platform before starting build
|
|
@@ -1050,11 +1058,6 @@ if (commandArg === "init") {
|
|
|
1050
1058
|
|
|
1051
1059
|
// Get platform-specific paths for the current target
|
|
1052
1060
|
const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);
|
|
1053
|
-
|
|
1054
|
-
// Helper functions
|
|
1055
|
-
const sanitizeVolumeNameForHdiutil = (name: string) => {
|
|
1056
|
-
return name.replace(/[^a-zA-Z0-9 ]/g, '').trim();
|
|
1057
|
-
};
|
|
1058
1061
|
|
|
1059
1062
|
// Helper to run lifecycle hook scripts
|
|
1060
1063
|
const runHook = (hookName: keyof typeof config.scripts, extraEnv: Record<string, string> = {}) => {
|
|
@@ -2257,11 +2260,7 @@ if (commandArg === "init") {
|
|
|
2257
2260
|
buildEnvironment === "stable"
|
|
2258
2261
|
? join(buildFolder, `${appFileName}-stable.dmg`)
|
|
2259
2262
|
: finalDmgPath;
|
|
2260
|
-
const
|
|
2261
|
-
const dmgVolumeName =
|
|
2262
|
-
buildEnvironment === "stable"
|
|
2263
|
-
? `${baseVolumeName}-stable`
|
|
2264
|
-
: baseVolumeName;
|
|
2263
|
+
const dmgVolumeName = getDmgVolumeName(appFileName, buildEnvironment);
|
|
2265
2264
|
|
|
2266
2265
|
// Create a staging directory for DMG contents (app + Applications shortcut)
|
|
2267
2266
|
const dmgStagingDir = join(buildFolder, '.dmg-staging');
|
|
@@ -2730,11 +2729,7 @@ async function createWindowsSelfExtractingExe(
|
|
|
2730
2729
|
): Promise<string> {
|
|
2731
2730
|
console.log("Creating Windows installer with separate archive...");
|
|
2732
2731
|
|
|
2733
|
-
|
|
2734
|
-
const setupFileName = buildEnvironment === "stable"
|
|
2735
|
-
? `${config.app.name}-Setup.exe`
|
|
2736
|
-
: `${config.app.name}-Setup-${buildEnvironment}.exe`;
|
|
2737
|
-
|
|
2732
|
+
const setupFileName = getWindowsSetupFileName(config.app.name, buildEnvironment);
|
|
2738
2733
|
const outputExePath = join(buildFolder, setupFileName);
|
|
2739
2734
|
|
|
2740
2735
|
// Copy the extractor exe
|
|
@@ -2825,11 +2820,8 @@ async function createLinuxSelfExtractingBinary(
|
|
|
2825
2820
|
config: Awaited<ReturnType<typeof getConfig>>
|
|
2826
2821
|
): Promise<string> {
|
|
2827
2822
|
console.log("Creating self-extracting Linux binary...");
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
const setupFileName = buildEnvironment === "stable"
|
|
2831
|
-
? `${config.app.name}-Setup.run`
|
|
2832
|
-
: `${config.app.name}-Setup-${buildEnvironment}.run`;
|
|
2823
|
+
|
|
2824
|
+
const setupFileName = getLinuxSetupFileName(config.app.name, buildEnvironment);
|
|
2833
2825
|
|
|
2834
2826
|
const outputPath = join(buildFolder, setupFileName);
|
|
2835
2827
|
|
|
@@ -2996,10 +2988,7 @@ async function createLinuxSelfExtractingAppImage(
|
|
|
2996
2988
|
console.log('Creating Linux AppImage wrapper...');
|
|
2997
2989
|
|
|
2998
2990
|
// Create wrapper AppImage filename
|
|
2999
|
-
const wrapperName = buildEnvironment
|
|
3000
|
-
? `${config.app.name}-Setup`
|
|
3001
|
-
: `${config.app.name}-Setup-${buildEnvironment}`;
|
|
3002
|
-
|
|
2991
|
+
const wrapperName = getLinuxAppImageBaseName(config.app.name, buildEnvironment);
|
|
3003
2992
|
const wrapperAppImagePath = join(buildFolder, `${wrapperName}.AppImage`);
|
|
3004
2993
|
const wrapperAppDirPath = join(buildFolder, `${wrapperName}.AppDir`);
|
|
3005
2994
|
|