@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.
Files changed (92) hide show
  1. package/.circleci/config.yml +95 -0
  2. package/.editorconfig +9 -0
  3. package/.eslintrc.json +30 -0
  4. package/.github/CODEOWNERS +1 -0
  5. package/.github/dependabot.yml +6 -0
  6. package/.github/workflows/add-to-project.yml +29 -0
  7. package/.github/workflows/semantic.yml +26 -0
  8. package/.gitignore +18 -0
  9. package/.releaserc.json +9 -0
  10. package/LICENSE +20 -0
  11. package/README.md +189 -0
  12. package/lib/index.d.ts +20 -0
  13. package/lib/index.js +350 -0
  14. package/lib/index.js.map +1 -0
  15. package/lib/options.d.ts +219 -0
  16. package/lib/options.js +6 -0
  17. package/lib/options.js.map +1 -0
  18. package/lib/sign.d.ts +15 -0
  19. package/lib/sign.js +189 -0
  20. package/lib/sign.js.map +1 -0
  21. package/lib/spawn-promise.d.ts +3 -0
  22. package/lib/spawn-promise.js +54 -0
  23. package/lib/spawn-promise.js.map +1 -0
  24. package/lib/temp-utils.d.ts +3 -0
  25. package/lib/temp-utils.js +32 -0
  26. package/lib/temp-utils.js.map +1 -0
  27. package/package.json +69 -0
  28. package/resources/install-spinner.gif +0 -0
  29. package/script/select-7z-arch.js +33 -0
  30. package/spec/convert-version-spec.ts +14 -0
  31. package/spec/fixtures/app/LICENSE +0 -0
  32. package/spec/fixtures/app/chromiumcontent.dll +0 -0
  33. package/spec/fixtures/app/d3dcompiler_47.dll +0 -0
  34. package/spec/fixtures/app/ffmpegsumo.dll +0 -0
  35. package/spec/fixtures/app/icudtl.dat +0 -0
  36. package/spec/fixtures/app/locales/en.pak +0 -0
  37. package/spec/fixtures/app/msvcp120.dll +0 -0
  38. package/spec/fixtures/app/msvcr120.dll +0 -0
  39. package/spec/fixtures/app/myapp.exe +0 -0
  40. package/spec/fixtures/app/natives_blob.bin +0 -0
  41. package/spec/fixtures/app/resources/app/package.json +7 -0
  42. package/spec/fixtures/app/snapshot_blob.bin +0 -0
  43. package/spec/fixtures/app/swiftshader/libEGL.dll +0 -0
  44. package/spec/fixtures/app/swiftshader/libGLESv2.dll +0 -0
  45. package/spec/fixtures/app/vccorlib120.dll +0 -0
  46. package/spec/fixtures/app/vk_swiftshader.dll +0 -0
  47. package/spec/fixtures/app/vk_swiftshader_icd.json +0 -0
  48. package/spec/fixtures/app/xinput1_3.dll +0 -0
  49. package/spec/helpers/helpers.ts +12 -0
  50. package/spec/helpers/windowsSignHook.js +8 -0
  51. package/spec/installer-spec.ts +70 -0
  52. package/spec/sign-spec.ts +48 -0
  53. package/src/index.ts +271 -0
  54. package/src/options.ts +228 -0
  55. package/src/sign.ts +89 -0
  56. package/src/spawn-promise.ts +55 -0
  57. package/src/temp-utils.ts +9 -0
  58. package/template.nuspectemplate +30 -0
  59. package/tsconfig.json +21 -0
  60. package/typedoc.json +4 -0
  61. package/vendor/7z-arm64.dll +0 -0
  62. package/vendor/7z-arm64.exe +0 -0
  63. package/vendor/7z-x64.dll +0 -0
  64. package/vendor/7z-x64.exe +0 -0
  65. package/vendor/Microsoft.Deployment.Resources.dll +0 -0
  66. package/vendor/Microsoft.Deployment.WindowsInstaller.dll +0 -0
  67. package/vendor/Setup.exe +0 -0
  68. package/vendor/Setup.pdb +0 -0
  69. package/vendor/Squirrel-Mono.exe +0 -0
  70. package/vendor/Squirrel-Mono.pdb +0 -0
  71. package/vendor/Squirrel.com +0 -0
  72. package/vendor/Squirrel.exe +0 -0
  73. package/vendor/Squirrel.pdb +0 -0
  74. package/vendor/StubExecutable.exe +0 -0
  75. package/vendor/SyncReleases.exe +0 -0
  76. package/vendor/SyncReleases.pdb +0 -0
  77. package/vendor/WixNetFxExtension.dll +0 -0
  78. package/vendor/WriteZipToSetup.exe +0 -0
  79. package/vendor/WriteZipToSetup.pdb +0 -0
  80. package/vendor/candle.exe +0 -0
  81. package/vendor/candle.exe.config +18 -0
  82. package/vendor/darice.cub +0 -0
  83. package/vendor/light.exe +0 -0
  84. package/vendor/light.exe.config +18 -0
  85. package/vendor/nuget.exe +0 -0
  86. package/vendor/rcedit.exe +0 -0
  87. package/vendor/signtool.exe +0 -0
  88. package/vendor/template.wxs +39 -0
  89. package/vendor/wconsole.dll +0 -0
  90. package/vendor/winterop.dll +0 -0
  91. package/vendor/wix.dll +0 -0
  92. 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,9 @@
1
+ import * as temp from 'temp';
2
+ import { promisify } from 'util';
3
+ temp.track();
4
+
5
+ const createTempDir = promisify(temp.mkdir);
6
+
7
+ export {
8
+ createTempDir
9
+ };
@@ -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>