@loongdotjs/electron-winstaller 5.4.0
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/.circleci/config.yml +95 -0
- package/.editorconfig +9 -0
- package/.eslintrc.json +30 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/dependabot.yml +6 -0
- package/.github/workflows/add-to-project.yml +29 -0
- package/.github/workflows/semantic.yml +26 -0
- package/.gitignore +18 -0
- package/.releaserc.json +9 -0
- package/LICENSE +20 -0
- package/README.md +189 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +350 -0
- package/lib/index.js.map +1 -0
- package/lib/options.d.ts +219 -0
- package/lib/options.js +6 -0
- package/lib/options.js.map +1 -0
- package/lib/sign.d.ts +15 -0
- package/lib/sign.js +189 -0
- package/lib/sign.js.map +1 -0
- package/lib/spawn-promise.d.ts +3 -0
- package/lib/spawn-promise.js +54 -0
- package/lib/spawn-promise.js.map +1 -0
- package/lib/temp-utils.d.ts +3 -0
- package/lib/temp-utils.js +32 -0
- package/lib/temp-utils.js.map +1 -0
- package/package.json +69 -0
- package/resources/install-spinner.gif +0 -0
- package/script/select-7z-arch.js +33 -0
- package/spec/convert-version-spec.ts +14 -0
- package/spec/fixtures/app/LICENSE +0 -0
- package/spec/fixtures/app/chromiumcontent.dll +0 -0
- package/spec/fixtures/app/d3dcompiler_47.dll +0 -0
- package/spec/fixtures/app/ffmpegsumo.dll +0 -0
- package/spec/fixtures/app/icudtl.dat +0 -0
- package/spec/fixtures/app/locales/en.pak +0 -0
- package/spec/fixtures/app/msvcp120.dll +0 -0
- package/spec/fixtures/app/msvcr120.dll +0 -0
- package/spec/fixtures/app/myapp.exe +0 -0
- package/spec/fixtures/app/natives_blob.bin +0 -0
- package/spec/fixtures/app/resources/app/package.json +7 -0
- package/spec/fixtures/app/snapshot_blob.bin +0 -0
- package/spec/fixtures/app/swiftshader/libEGL.dll +0 -0
- package/spec/fixtures/app/swiftshader/libGLESv2.dll +0 -0
- package/spec/fixtures/app/vccorlib120.dll +0 -0
- package/spec/fixtures/app/vk_swiftshader.dll +0 -0
- package/spec/fixtures/app/vk_swiftshader_icd.json +0 -0
- package/spec/fixtures/app/xinput1_3.dll +0 -0
- package/spec/helpers/helpers.ts +12 -0
- package/spec/helpers/windowsSignHook.js +8 -0
- package/spec/installer-spec.ts +70 -0
- package/spec/sign-spec.ts +48 -0
- package/src/index.ts +271 -0
- package/src/options.ts +228 -0
- package/src/sign.ts +89 -0
- package/src/spawn-promise.ts +55 -0
- package/src/temp-utils.ts +9 -0
- package/template.nuspectemplate +30 -0
- package/tsconfig.json +21 -0
- package/typedoc.json +4 -0
- package/vendor/7z-arm64.dll +0 -0
- package/vendor/7z-arm64.exe +0 -0
- package/vendor/7z-x64.dll +0 -0
- package/vendor/7z-x64.exe +0 -0
- package/vendor/Microsoft.Deployment.Resources.dll +0 -0
- package/vendor/Microsoft.Deployment.WindowsInstaller.dll +0 -0
- package/vendor/Setup.exe +0 -0
- package/vendor/Setup.pdb +0 -0
- package/vendor/Squirrel-Mono.exe +0 -0
- package/vendor/Squirrel-Mono.pdb +0 -0
- package/vendor/Squirrel.com +0 -0
- package/vendor/Squirrel.exe +0 -0
- package/vendor/Squirrel.pdb +0 -0
- package/vendor/StubExecutable.exe +0 -0
- package/vendor/SyncReleases.exe +0 -0
- package/vendor/SyncReleases.pdb +0 -0
- package/vendor/WixNetFxExtension.dll +0 -0
- package/vendor/WriteZipToSetup.exe +0 -0
- package/vendor/WriteZipToSetup.pdb +0 -0
- package/vendor/candle.exe +0 -0
- package/vendor/candle.exe.config +18 -0
- package/vendor/darice.cub +0 -0
- package/vendor/light.exe +0 -0
- package/vendor/light.exe.config +18 -0
- package/vendor/nuget.exe +0 -0
- package/vendor/rcedit.exe +0 -0
- package/vendor/signtool.exe +0 -0
- package/vendor/template.wxs +39 -0
- package/vendor/wconsole.dll +0 -0
- package/vendor/winterop.dll +0 -0
- package/vendor/wix.dll +0 -0
- package/yarn.lock +2008 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import * as asar from '@electron/asar';
|
|
2
|
+
import { createTempDir } from './temp-utils';
|
|
3
|
+
import * as fs from 'fs-extra';
|
|
4
|
+
import { Metadata, SquirrelWindowsOptions, PersonMetadata } from './options';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
import spawn from './spawn-promise';
|
|
9
|
+
import { template } from 'lodash';
|
|
10
|
+
import { createSignTool, resetSignTool } from './sign';
|
|
11
|
+
|
|
12
|
+
export { SquirrelWindowsOptions } from './options';
|
|
13
|
+
export { SquirrelWindowsOptions as Options} from './options';
|
|
14
|
+
|
|
15
|
+
const log = require('debug')('electron-windows-installer:main');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A utility function to convert SemVer version strings into NuGet-compatible
|
|
19
|
+
* version strings.
|
|
20
|
+
* @param version A SemVer version string
|
|
21
|
+
* @returns A NuGet-compatible version string
|
|
22
|
+
* @see {@link https://semver.org/ | Semantic Versioning specification}
|
|
23
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/concepts/package-versioning?tabs=semver20sort | NuGet versioning specification}
|
|
24
|
+
*/
|
|
25
|
+
export function convertVersion(version: string): string {
|
|
26
|
+
const parts = version.split('+')[0].split('-');
|
|
27
|
+
const mainVersion = parts.shift();
|
|
28
|
+
|
|
29
|
+
if (parts.length > 0) {
|
|
30
|
+
return [mainVersion, parts.join('-').replace(/\./g, '')].join('-');
|
|
31
|
+
} else {
|
|
32
|
+
return mainVersion as string;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function checkIfCommandExists(command: string): Promise<boolean> {
|
|
37
|
+
const checkCommand = os.platform() === 'win32' ? 'where' : 'which';
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
exec(`${checkCommand} ${command}`, (error) => {
|
|
40
|
+
resolve(error ? false : true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* This package's main function, which creates a Squirrel.Windows executable
|
|
47
|
+
* installer and optionally code-signs the output.
|
|
48
|
+
*
|
|
49
|
+
* @param options Options for installer generation and signing
|
|
50
|
+
* @see {@link https://github.com/Squirrel/Squirrel.Windows | Squirrel.Windows}
|
|
51
|
+
*/
|
|
52
|
+
export async function createWindowsInstaller(options: SquirrelWindowsOptions): Promise<void> {
|
|
53
|
+
let useMono = false;
|
|
54
|
+
|
|
55
|
+
const monoExe = 'mono';
|
|
56
|
+
const wineExe = ['arm64', 'x64'].includes(process.arch) ? 'wine64' : 'wine';
|
|
57
|
+
|
|
58
|
+
if (process.platform !== 'win32') {
|
|
59
|
+
useMono = true;
|
|
60
|
+
const [hasWine, hasMono] = await Promise.all([
|
|
61
|
+
checkIfCommandExists(wineExe),
|
|
62
|
+
checkIfCommandExists(monoExe)
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
if (!hasWine || !hasMono) {
|
|
66
|
+
throw new Error('You must install both Mono and Wine on non-Windows');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log(`Using Mono: '${monoExe}'`);
|
|
70
|
+
log(`Using Wine: '${wineExe}'`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line prefer-const
|
|
74
|
+
let { appDirectory, outputDirectory, loadingGif } = options;
|
|
75
|
+
outputDirectory = path.resolve(outputDirectory || 'installer');
|
|
76
|
+
|
|
77
|
+
const vendorPath = options.vendorDirectory || path.join(__dirname, '..', 'vendor');
|
|
78
|
+
const vendorUpdate = path.join(vendorPath, 'Squirrel.exe');
|
|
79
|
+
const appUpdate = path.join(appDirectory, 'Squirrel.exe');
|
|
80
|
+
|
|
81
|
+
await fs.copy(vendorUpdate, appUpdate);
|
|
82
|
+
if (options.setupIcon && (options.skipUpdateIcon !== true)) {
|
|
83
|
+
let cmd = path.join(vendorPath, 'rcedit.exe');
|
|
84
|
+
const args = [
|
|
85
|
+
appUpdate,
|
|
86
|
+
'--set-icon', options.setupIcon
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
if (useMono) {
|
|
90
|
+
args.unshift(cmd);
|
|
91
|
+
cmd = wineExe;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await spawn(cmd, args);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const defaultLoadingGif = path.join(__dirname, '..', 'resources', 'install-spinner.gif');
|
|
98
|
+
loadingGif = loadingGif ? path.resolve(loadingGif) : defaultLoadingGif;
|
|
99
|
+
|
|
100
|
+
const { certificateFile, certificatePassword, remoteReleases, signWithParams, remoteToken, windowsSign } = options;
|
|
101
|
+
|
|
102
|
+
const metadata: Metadata = {
|
|
103
|
+
description: '',
|
|
104
|
+
iconUrl: 'https://raw.githubusercontent.com/electron/electron/main/shell/browser/resources/win/electron.ico'
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (options.usePackageJson !== false) {
|
|
108
|
+
const appResources = path.join(appDirectory, 'resources');
|
|
109
|
+
const asarFile = path.join(appResources, 'app.asar');
|
|
110
|
+
let appMetadata;
|
|
111
|
+
|
|
112
|
+
if (await fs.pathExists(asarFile)) {
|
|
113
|
+
appMetadata = JSON.parse(asar.extractFile(asarFile, 'package.json').toString());
|
|
114
|
+
} else {
|
|
115
|
+
appMetadata = await fs.readJson(path.join(appResources, 'app', 'package.json'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
Object.assign(metadata, {
|
|
119
|
+
exe: `${appMetadata.name}.exe`,
|
|
120
|
+
title: appMetadata.productName || appMetadata.name
|
|
121
|
+
}, appMetadata);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Object.assign(metadata, options);
|
|
125
|
+
|
|
126
|
+
if (!metadata.authors) {
|
|
127
|
+
if (typeof (metadata.author) === 'string') {
|
|
128
|
+
metadata.authors = metadata.author;
|
|
129
|
+
} else {
|
|
130
|
+
metadata.authors = (metadata.author || ({} as PersonMetadata)).name || '';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
metadata.owners = metadata.owners || metadata.authors;
|
|
135
|
+
metadata.version = convertVersion(metadata.version as string);
|
|
136
|
+
metadata.copyright = metadata.copyright ||
|
|
137
|
+
`Copyright © ${new Date().getFullYear()} ${metadata.authors || metadata.owners}`;
|
|
138
|
+
metadata.additionalFiles = metadata.additionalFiles || [];
|
|
139
|
+
|
|
140
|
+
if (await fs.pathExists(path.join(appDirectory, 'swiftshader'))) {
|
|
141
|
+
metadata.additionalFiles.push({ src: 'swiftshader\\**', target: 'lib\\net45\\swiftshader' });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (await fs.pathExists(path.join(appDirectory, 'vk_swiftshader_icd.json'))) {
|
|
145
|
+
metadata.additionalFiles.push({ src: 'vk_swiftshader_icd.json', target: 'lib\\net45' });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const templatePath = options.nuspecTemplate || path.join(__dirname, '..', 'template.nuspectemplate');
|
|
149
|
+
let templateData = await fs.readFile(templatePath, 'utf8');
|
|
150
|
+
if (path.sep === '/') {
|
|
151
|
+
templateData = templateData.replace(/\\/g, '/');
|
|
152
|
+
for (const f of metadata.additionalFiles) {
|
|
153
|
+
f.src = f.src.replace(/\\/g, '/');
|
|
154
|
+
f.target = f.target.replace(/\\/g, '/');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const nuspecContent = template(templateData)(metadata);
|
|
158
|
+
|
|
159
|
+
log(`Created NuSpec file:\n${nuspecContent}`);
|
|
160
|
+
|
|
161
|
+
const nugetOutput = await createTempDir('si-');
|
|
162
|
+
const targetNuspecPath = path.join(nugetOutput, metadata.name + '.nuspec');
|
|
163
|
+
|
|
164
|
+
await fs.writeFile(targetNuspecPath, nuspecContent);
|
|
165
|
+
|
|
166
|
+
let cmd = path.join(vendorPath, 'nuget.exe');
|
|
167
|
+
let args = [
|
|
168
|
+
'pack', targetNuspecPath,
|
|
169
|
+
'-BasePath', appDirectory,
|
|
170
|
+
'-OutputDirectory', nugetOutput,
|
|
171
|
+
'-NoDefaultExcludes'
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
if (useMono) {
|
|
175
|
+
args.unshift(cmd);
|
|
176
|
+
cmd = monoExe;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Call NuGet to create our package
|
|
180
|
+
log(await spawn(cmd, args));
|
|
181
|
+
const nupkgPath = path.join(nugetOutput, `${metadata.name}.${metadata.version}.nupkg`);
|
|
182
|
+
|
|
183
|
+
if (remoteReleases) {
|
|
184
|
+
cmd = path.join(vendorPath, 'SyncReleases.exe');
|
|
185
|
+
args = ['-u', remoteReleases, '-r', outputDirectory];
|
|
186
|
+
|
|
187
|
+
if (useMono) {
|
|
188
|
+
args.unshift(cmd);
|
|
189
|
+
cmd = monoExe;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (remoteToken) {
|
|
193
|
+
args.push('-t', remoteToken);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
log(await spawn(cmd, args));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
cmd = path.join(vendorPath, 'Squirrel.exe');
|
|
200
|
+
args = [
|
|
201
|
+
'--releasify', nupkgPath,
|
|
202
|
+
'--releaseDir', outputDirectory,
|
|
203
|
+
'--loadingGif', loadingGif
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
if (useMono) {
|
|
207
|
+
args.unshift(path.join(vendorPath, 'Squirrel-Mono.exe'));
|
|
208
|
+
cmd = monoExe;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Legacy codesign options
|
|
212
|
+
await resetSignTool();
|
|
213
|
+
if (signWithParams) {
|
|
214
|
+
args.push('--signWithParams');
|
|
215
|
+
if (!signWithParams.includes('/f') && !signWithParams.includes('/p') && certificateFile && certificatePassword) {
|
|
216
|
+
args.push(`${signWithParams} /a /f "${path.resolve(certificateFile)}" /p "${certificatePassword}"`);
|
|
217
|
+
} else {
|
|
218
|
+
args.push(signWithParams);
|
|
219
|
+
}
|
|
220
|
+
} else if (certificateFile && certificatePassword) {
|
|
221
|
+
args.push('--signWithParams');
|
|
222
|
+
args.push(`/a /f "${path.resolve(certificateFile)}" /p "${certificatePassword}"`);
|
|
223
|
+
// @electron/windows-sign options
|
|
224
|
+
} else if (windowsSign) {
|
|
225
|
+
args.push('--signWithParams');
|
|
226
|
+
args.push('windows-sign');
|
|
227
|
+
await createSignTool(options);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (options.setupIcon) {
|
|
231
|
+
args.push('--setupIcon');
|
|
232
|
+
args.push(path.resolve(options.setupIcon));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (options.noMsi) {
|
|
236
|
+
args.push('--no-msi');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options.noDelta) {
|
|
240
|
+
args.push('--no-delta');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (options.frameworkVersion) {
|
|
244
|
+
args.push('--framework-version');
|
|
245
|
+
args.push(options.frameworkVersion);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
log(await spawn(cmd, args));
|
|
249
|
+
|
|
250
|
+
if (options.fixUpPaths !== false) {
|
|
251
|
+
log('Fixing up paths');
|
|
252
|
+
|
|
253
|
+
if (metadata.productName || options.setupExe) {
|
|
254
|
+
const setupPath = path.join(outputDirectory, options.setupExe || `${metadata.productName}Setup.exe`);
|
|
255
|
+
const unfixedSetupPath = path.join(outputDirectory, 'Setup.exe');
|
|
256
|
+
log(`Renaming ${unfixedSetupPath} => ${setupPath}`);
|
|
257
|
+
await fs.rename(unfixedSetupPath, setupPath);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (metadata.productName || options.setupMsi) {
|
|
261
|
+
const msiPath = path.join(outputDirectory, options.setupMsi || `${metadata.productName}Setup.msi`);
|
|
262
|
+
const unfixedMsiPath = path.join(outputDirectory, 'Setup.msi');
|
|
263
|
+
if (await fs.pathExists(unfixedMsiPath)) {
|
|
264
|
+
log(`Renaming ${unfixedMsiPath} => ${msiPath}`);
|
|
265
|
+
await fs.rename(unfixedMsiPath, msiPath);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await resetSignTool();
|
|
271
|
+
}
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Originally from @types/electron-winstaller@2.6.2
|
|
2
|
+
// Original definitions by: Brendan Forster <https://github.com/shiftkey>, Daniel Perez Alvarez <https://github.com/unindented>
|
|
3
|
+
// Original definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
4
|
+
|
|
5
|
+
import { SignToolOptions } from '@electron/windows-sign';
|
|
6
|
+
|
|
7
|
+
export interface SquirrelWindowsOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The folder path of your Electron app
|
|
10
|
+
*/
|
|
11
|
+
appDirectory: string;
|
|
12
|
+
/**
|
|
13
|
+
* The folder path of Squirrel windows
|
|
14
|
+
*/
|
|
15
|
+
vendorDirectory?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The folder path to create the .exe installer in.
|
|
18
|
+
*
|
|
19
|
+
* @defaultValue an `installer` folder at the project root.
|
|
20
|
+
*/
|
|
21
|
+
outputDirectory?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The path to the .nuspectemplate file used by Squirrel.exe.
|
|
24
|
+
*
|
|
25
|
+
* @defaultValue the bundled {@link https://github.com/electron/windows-installer/blob/main/template.nuspectemplate | template.nuspectemplate}.
|
|
26
|
+
*/
|
|
27
|
+
nuspecTemplate?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The local path to a `.gif` file to display during install.
|
|
30
|
+
*
|
|
31
|
+
* @defaultValue the bundled {@link https://github.com/electron/windows-installer/blob/main/resources/install-spinner.gif | install-spinner.gif}
|
|
32
|
+
*/
|
|
33
|
+
loadingGif?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The `authors` value for the NuGet package metadata.
|
|
36
|
+
*
|
|
37
|
+
* @defaultValue the `author` field from your app's package.json file
|
|
38
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
39
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/reference/nuspec | the Microsoft .nuspec reference}.
|
|
40
|
+
*/
|
|
41
|
+
authors?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The `owners` value for the NuGet package metadata.
|
|
44
|
+
*
|
|
45
|
+
* @defaultValue the `authors` field from your app's package.json file
|
|
46
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
47
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/reference/nuspec | the Microsoft .nuspec reference}.
|
|
48
|
+
*/
|
|
49
|
+
owners?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The `copyright` value for the NuGet package metadata.
|
|
52
|
+
*
|
|
53
|
+
* @defaultValue a generated copyright with {@link SquirrelWindowsOptions.authors | authors}
|
|
54
|
+
* or {@link SquirrelWindowsOptions.owners | owners}.
|
|
55
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/reference/nuspec | the Microsoft .nuspec reference}.
|
|
56
|
+
*/
|
|
57
|
+
copyright?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The name of your app's main `.exe` file.
|
|
60
|
+
*
|
|
61
|
+
* @defaultValue the `name` field in your app's package.json file with an added `.exe` extension
|
|
62
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
63
|
+
*/
|
|
64
|
+
exe?: string;
|
|
65
|
+
/**
|
|
66
|
+
* The `description` value for the NuGet package metadata.
|
|
67
|
+
*
|
|
68
|
+
* @defaultValue the `description` field from your app's package.json file
|
|
69
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
70
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/reference/nuspec | the Microsoft .nuspec reference}.
|
|
71
|
+
*/
|
|
72
|
+
description?: string;
|
|
73
|
+
/**
|
|
74
|
+
* The `version` value for the Nuget package metadata.
|
|
75
|
+
*
|
|
76
|
+
* @defaultValue the `version` field from your app's package.json file
|
|
77
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
78
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/reference/nuspec | the Microsoft .nuspec reference}.
|
|
79
|
+
*/
|
|
80
|
+
version?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The `title` value for the nuget package metadata.
|
|
83
|
+
*
|
|
84
|
+
* @defaultValue the `productName` field and then the `name` field from your app's package.json
|
|
85
|
+
* file unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
86
|
+
* @see {@link https://learn.microsoft.com/en-us/nuget/reference/nuspec | the Microsoft .nuspec reference}.
|
|
87
|
+
*/
|
|
88
|
+
title?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Windows Application Model ID (appId).
|
|
91
|
+
*
|
|
92
|
+
* @defaultValue the `name` field in your app's package.json file
|
|
93
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
94
|
+
* @see {@link https://learn.microsoft.com/en-us/windows/win32/shell/appids | Microsoft's Application User Model IDs documentation}.
|
|
95
|
+
*/
|
|
96
|
+
name?: string;
|
|
97
|
+
/**
|
|
98
|
+
* The path to an Authenticode Code Signing Certificate.
|
|
99
|
+
*
|
|
100
|
+
* This is a legacy parameter provided for backwards compatibility.
|
|
101
|
+
* For more comprehensive support of various codesigning scenarios
|
|
102
|
+
* like EV certificates, see the
|
|
103
|
+
* {@link SquirrelWindowsOptions.windowsSign | windowsSign} parameter.
|
|
104
|
+
*/
|
|
105
|
+
certificateFile?: string;
|
|
106
|
+
/**
|
|
107
|
+
* The password to decrypt the certificate given in `certificateFile`
|
|
108
|
+
*
|
|
109
|
+
* This is a legacy parameter provided for backwards compatibility.
|
|
110
|
+
* For more comprehensive support of various codesigning scenarios
|
|
111
|
+
* like EV certificates, see the
|
|
112
|
+
* {@link SquirrelWindowsOptions.windowsSign | windowsSign} parameter.
|
|
113
|
+
*/
|
|
114
|
+
certificatePassword?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Params to pass to signtool.
|
|
117
|
+
*
|
|
118
|
+
* Overrides {@link SquirrelWindowsOptions.certificateFile | certificateFile}
|
|
119
|
+
* and {@link SquirrelWindowsOptions.certificatePassword | certificatePassword}`.
|
|
120
|
+
*
|
|
121
|
+
* This is a legacy parameter provided for backwards compatibility.
|
|
122
|
+
* For more comprehensive support of various codesigning scenarios
|
|
123
|
+
* like EV certificates, see the
|
|
124
|
+
* {@link SquirrelWindowsOptions.windowsSign | windowsSign} parameter.
|
|
125
|
+
*/
|
|
126
|
+
signWithParams?: string;
|
|
127
|
+
/**
|
|
128
|
+
* A publicly accessible, fully qualified HTTP(S) URL to an ICO file, used as the application icon
|
|
129
|
+
* displayed in Control Panel ➡ Programs and Features. The icon is retrieved at install time.
|
|
130
|
+
* Example: http://example.com/favicon.ico
|
|
131
|
+
*
|
|
132
|
+
* Does not accept `file:` URLs.
|
|
133
|
+
*
|
|
134
|
+
* @defaultValue the Electron icon.
|
|
135
|
+
*/
|
|
136
|
+
iconUrl?: string;
|
|
137
|
+
/**
|
|
138
|
+
* The ICO file to use as the icon for the generated Setup.exe
|
|
139
|
+
*/
|
|
140
|
+
setupIcon?: string;
|
|
141
|
+
/**
|
|
142
|
+
* The name to use for the generated Setup.exe file
|
|
143
|
+
* @defaultValue the `productName` field from your app's package.json file
|
|
144
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
145
|
+
*/
|
|
146
|
+
setupExe?: string;
|
|
147
|
+
/**
|
|
148
|
+
* The name to use for the generated Setup.msi file
|
|
149
|
+
* @defaultValue the `productName` field from your app's package.json file
|
|
150
|
+
* unless {@link SquirrelWindowsOptions.usePackageJson | usePackageJson} is false.
|
|
151
|
+
*/
|
|
152
|
+
setupMsi?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Enable this flag to prevent Squirrel.Windows from creating an MSI installer.
|
|
155
|
+
* @defaultValue false
|
|
156
|
+
*/
|
|
157
|
+
noMsi?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Enable this flag to prevent Squirrel.Windows from creating delta packages (disable only if necessary, they are a Good Thing).
|
|
160
|
+
* @defaultValue false
|
|
161
|
+
*/
|
|
162
|
+
noDelta?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* A URL to your existing updates. If given, these will be downloaded to create delta updates.
|
|
165
|
+
*/
|
|
166
|
+
remoteReleases?: string;
|
|
167
|
+
/**
|
|
168
|
+
* Authentication token for remote updates using {@link SquirrelWindowsOptions.remoteReleases | remoteReleases}
|
|
169
|
+
*/
|
|
170
|
+
remoteToken?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Whether or not to infer metadata options from your app's package.json file.
|
|
173
|
+
* @defaultValue true
|
|
174
|
+
*/
|
|
175
|
+
usePackageJson?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Set the required .NET framework version (e.g. `net461`).
|
|
178
|
+
*/
|
|
179
|
+
frameworkVersion?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Attempt to create more descriptive installer names using metadata parameters.
|
|
182
|
+
* @defaultValue false
|
|
183
|
+
*/
|
|
184
|
+
fixUpPaths?: boolean;
|
|
185
|
+
/**
|
|
186
|
+
* Enable this flag to skip setting a custom icon for `Update.exe`
|
|
187
|
+
* @defaultValue false
|
|
188
|
+
*/
|
|
189
|
+
skipUpdateIcon?: boolean;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Requires Node.js 18 or newer.
|
|
193
|
+
*
|
|
194
|
+
* Sign your app with `@electron/windows-sign`, allowing for full customization
|
|
195
|
+
* of the code-signing process - and supports more complicated scenarios like
|
|
196
|
+
* cloud-hosted EV certificates, custom sign pipelines, and per-file overrides.
|
|
197
|
+
* It also supports all existing "simple" codesigning scenarios, including
|
|
198
|
+
* just passing a certificate file and password.
|
|
199
|
+
*
|
|
200
|
+
* @see {@link https://github.com/electron/windows-sign | @electron/windows-sign documentation}
|
|
201
|
+
* for all possible configuration options.
|
|
202
|
+
*/
|
|
203
|
+
windowsSign?: SignToolOptions;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface PersonMetadata {
|
|
207
|
+
name: string;
|
|
208
|
+
email?: string;
|
|
209
|
+
url?: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface AdditionalFile {
|
|
213
|
+
src: string;
|
|
214
|
+
target: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface Metadata {
|
|
218
|
+
name?: string;
|
|
219
|
+
productName?: string;
|
|
220
|
+
version?: string;
|
|
221
|
+
copyright?: string;
|
|
222
|
+
author?: string | PersonMetadata;
|
|
223
|
+
authors?: string | PersonMetadata[];
|
|
224
|
+
owners?: string | PersonMetadata[];
|
|
225
|
+
description?: string;
|
|
226
|
+
iconUrl?: string;
|
|
227
|
+
additionalFiles?: AdditionalFile[];
|
|
228
|
+
}
|
package/src/sign.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { createSeaSignTool as createSeaSignToolType } from '@electron/windows-sign';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
|
|
6
|
+
import { SquirrelWindowsOptions } from './options';
|
|
7
|
+
|
|
8
|
+
let VENDOR_PATH: string;
|
|
9
|
+
let ORIGINAL_SIGN_TOOL_PATH: string;
|
|
10
|
+
let BACKUP_SIGN_TOOL_PATH: string;
|
|
11
|
+
let SIGN_LOG_PATH: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This method uses @electron/windows-sign to create a fake signtool.exe
|
|
15
|
+
* that can be called by Squirrel - but then just calls @electron/windows-sign
|
|
16
|
+
* to actually perform the signing.
|
|
17
|
+
*
|
|
18
|
+
* That's useful for users who need a high degree of customization of the signing
|
|
19
|
+
* process but still want to use @electron/windows-installer.
|
|
20
|
+
*/
|
|
21
|
+
export async function createSignTool(options: SquirrelWindowsOptions): Promise<void> {
|
|
22
|
+
if (!options.windowsSign) {
|
|
23
|
+
throw new Error('Signtool should only be created if windowsSign options are set');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
VENDOR_PATH = options.vendorDirectory || path.join(__dirname, '..', 'vendor');
|
|
27
|
+
ORIGINAL_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool.exe');
|
|
28
|
+
BACKUP_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool-original.exe');
|
|
29
|
+
SIGN_LOG_PATH = path.join(VENDOR_PATH, 'electron-windows-sign.log');
|
|
30
|
+
|
|
31
|
+
const createSeaSignTool = await getCreateSeaSignTool();
|
|
32
|
+
|
|
33
|
+
await resetSignTool();
|
|
34
|
+
await fs.remove(SIGN_LOG_PATH);
|
|
35
|
+
|
|
36
|
+
// Make a backup of signtool.exe
|
|
37
|
+
await fs.copy(ORIGINAL_SIGN_TOOL_PATH, BACKUP_SIGN_TOOL_PATH, { overwrite: true });
|
|
38
|
+
|
|
39
|
+
// Create a new signtool.exe using @electron/windows-sign
|
|
40
|
+
await createSeaSignTool({
|
|
41
|
+
path: ORIGINAL_SIGN_TOOL_PATH,
|
|
42
|
+
windowsSign: options.windowsSign
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Ensure that signtool.exe is actually the "real" signtool.exe, not our
|
|
48
|
+
* fake substitute.
|
|
49
|
+
*/
|
|
50
|
+
export async function resetSignTool() {
|
|
51
|
+
if (fs.existsSync(BACKUP_SIGN_TOOL_PATH)) {
|
|
52
|
+
// Reset the backup of signtool.exe
|
|
53
|
+
await fs.copy(BACKUP_SIGN_TOOL_PATH, ORIGINAL_SIGN_TOOL_PATH, { overwrite: true });
|
|
54
|
+
await fs.remove(BACKUP_SIGN_TOOL_PATH);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @electron/windows-installer only requires Node.js >= 8.0.0.
|
|
60
|
+
* @electron/windows-sign requires Node.js >= 16.0.0.
|
|
61
|
+
* @electron/windows-sign's "fake signtool.exe" feature requires
|
|
62
|
+
* Node.js >= 20.0.0, the first version to contain the "single
|
|
63
|
+
* executable" feature with proper support.
|
|
64
|
+
*
|
|
65
|
+
* Since this is overall a very niche feature and only benefits
|
|
66
|
+
* consumers with rather advanced codesigning needs, we did not
|
|
67
|
+
* want to make Node.js v18 a hard requirement for @electron/windows-installer.
|
|
68
|
+
*
|
|
69
|
+
* Instead, @electron/windows-sign is an optional dependency - and
|
|
70
|
+
* if it didn't install, we'll throw a useful error here.
|
|
71
|
+
*
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
74
|
+
async function getCreateSeaSignTool(): Promise<typeof createSeaSignToolType> {
|
|
75
|
+
try {
|
|
76
|
+
const { createSeaSignTool } = await import('@electron/windows-sign');
|
|
77
|
+
return createSeaSignTool;
|
|
78
|
+
} catch(error) {
|
|
79
|
+
let message = 'In order to use windowsSign options, @electron/windows-sign must be installed as a dependency.';
|
|
80
|
+
|
|
81
|
+
if (semver.lte(process.version, '20.0.0')) {
|
|
82
|
+
message += ` You are currently using Node.js ${process.version}. Please upgrade to Node.js 19 or later and reinstall all dependencies to ensure that @electron/windows-sign is available.`;
|
|
83
|
+
} else {
|
|
84
|
+
message += ` ${error}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error(message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawn as spawnOg, SpawnOptionsWithoutStdio } from 'child_process';
|
|
2
|
+
|
|
3
|
+
const d = require('debug')('electron-windows-installer:spawn');
|
|
4
|
+
|
|
5
|
+
// Public: Maps a process's output into an {Observable}
|
|
6
|
+
//
|
|
7
|
+
// exe - The program to execute
|
|
8
|
+
// params - Arguments passed to the process
|
|
9
|
+
// opts - Options that will be passed to child_process.spawn
|
|
10
|
+
//
|
|
11
|
+
// Returns an {Observable} with a single value, that is the output of the
|
|
12
|
+
// spawned process
|
|
13
|
+
export default function spawn(exe: string, params: string[], opts?: SpawnOptionsWithoutStdio): Promise<string> {
|
|
14
|
+
return new Promise((resolve, reject): void => {
|
|
15
|
+
let proc = null;
|
|
16
|
+
|
|
17
|
+
d(`Spawning ${exe} ${params.join(' ')}`);
|
|
18
|
+
if (!opts) {
|
|
19
|
+
proc = spawnOg(exe, params);
|
|
20
|
+
} else {
|
|
21
|
+
proc = spawnOg(exe, params, opts);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// We need to wait until all three events have happened:
|
|
25
|
+
// * stdout's pipe is closed
|
|
26
|
+
// * stderr's pipe is closed
|
|
27
|
+
// * We've got an exit code
|
|
28
|
+
let rejected = false;
|
|
29
|
+
let refCount = 3;
|
|
30
|
+
let stdout = '';
|
|
31
|
+
|
|
32
|
+
const release = (): void => {
|
|
33
|
+
if (--refCount <= 0 && !rejected) resolve(stdout);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const bufHandler = (chunk: string): void => {
|
|
37
|
+
stdout += chunk;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
proc.stdout.setEncoding('utf8').on('data', bufHandler);
|
|
41
|
+
proc.stdout.once('close', release);
|
|
42
|
+
proc.stderr.setEncoding('utf8').on('data', bufHandler);
|
|
43
|
+
proc.stderr.once('close', release);
|
|
44
|
+
proc.on('error', (e: Error): void => reject(e));
|
|
45
|
+
|
|
46
|
+
proc.on('close', (code: number): void => {
|
|
47
|
+
if (code === 0) {
|
|
48
|
+
release();
|
|
49
|
+
} else {
|
|
50
|
+
rejected = true;
|
|
51
|
+
reject(new Error(`Failed with exit code: ${code}\nOutput:\n${stdout}`));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
|
3
|
+
<metadata>
|
|
4
|
+
<id><%- name %></id>
|
|
5
|
+
<title><%- title %></title>
|
|
6
|
+
<version><%- version %></version>
|
|
7
|
+
<authors><%- authors %></authors>
|
|
8
|
+
<owners><%- owners %></owners>
|
|
9
|
+
<iconUrl><%- iconUrl %></iconUrl>
|
|
10
|
+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
|
11
|
+
<description><%- description %></description>
|
|
12
|
+
<copyright><%- copyright %></copyright>
|
|
13
|
+
</metadata>
|
|
14
|
+
<files>
|
|
15
|
+
<file src="locales\**" target="lib\net45\locales" />
|
|
16
|
+
<file src="resources\**" target="lib\net45\resources" />
|
|
17
|
+
<file src="*.bin" target="lib\net45" />
|
|
18
|
+
<file src="*.dll" target="lib\net45" />
|
|
19
|
+
<file src="*.pak" target="lib\net45" />
|
|
20
|
+
<file src="*.exe.config" target="lib\net45" />
|
|
21
|
+
<file src="*.exe.sig" target="lib\net45" />
|
|
22
|
+
<file src="icudtl.dat" target="lib\net45\icudtl.dat" />
|
|
23
|
+
<file src="Squirrel.exe" target="lib\net45\squirrel.exe" />
|
|
24
|
+
<file src="LICENSE" target="lib\net45\LICENSE" />
|
|
25
|
+
<file src="<%- exe %>" target="lib\net45\<%- exe %>" />
|
|
26
|
+
<% additionalFiles.forEach(function(f) { %>
|
|
27
|
+
<file src="<%- f.src %>" target="<%- f.target %>" />
|
|
28
|
+
<% }); %>
|
|
29
|
+
</files>
|
|
30
|
+
</package>
|