electron-builder 27.0.0-alpha.7 → 27.0.0-alpha.9
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/assert-node-version.js +39 -0
- package/cli.js +5 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +19 -1
- package/dist/builder.js.map +1 -1
- package/dist/cli/cli.js +7 -0
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/create-self-signed-cert.js +1 -1
- package/dist/cli/create-self-signed-cert.js.map +1 -1
- package/dist/cli/create-update-key.d.ts +2 -0
- package/dist/cli/create-update-key.d.ts.map +1 -0
- package/dist/cli/create-update-key.js +34 -0
- package/dist/cli/create-update-key.js.map +1 -0
- package/dist/cli/migrate-schema-programmatic.d.ts.map +1 -1
- package/dist/cli/migrate-schema-programmatic.js +326 -44
- package/dist/cli/migrate-schema-programmatic.js.map +1 -1
- package/dist/cli/migrate-schema.d.ts +14 -4
- package/dist/cli/migrate-schema.d.ts.map +1 -1
- package/dist/cli/migrate-schema.js +330 -77
- package/dist/cli/migrate-schema.js.map +1 -1
- package/dist/publish.js +27 -17
- package/dist/publish.js.map +1 -1
- package/install-app-deps.js +5 -1
- package/package.json +7 -6
|
@@ -1,47 +1,34 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { AZURE_KNOWN_FIELDS, ELECTRON_DOWNLOAD_DROPPED, MAC_SIGN_FIELDS, MAC_SIGN_REMOVED_FIELDS, MAC_UNIVERSAL_FIELDS } from "app-builder-lib/internal";
|
|
2
3
|
import { log, orNullIfFileNotExist } from "builder-util";
|
|
3
4
|
import { promises as fs } from "fs";
|
|
4
5
|
import * as path from "path";
|
|
5
6
|
import { loadTypeScript, migrateProgrammaticSource } from "./migrate-schema-programmatic.js";
|
|
6
7
|
const _require = createRequire(import.meta.url);
|
|
7
8
|
// ─── Pure migration logic ─────────────────────────────────────────────────────
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"endpoint",
|
|
13
|
-
"codeSigningAccountName",
|
|
14
|
-
"certificateProfileName",
|
|
15
|
-
"publisherName",
|
|
16
|
-
"fileDigest",
|
|
17
|
-
"timestampRfc3161",
|
|
18
|
-
"timestampDigest",
|
|
19
|
-
"additionalMetadata",
|
|
20
|
-
]);
|
|
21
|
-
// macOS signing fields that moved from the platform root into the `sign` (ElectronSignOptions) bag.
|
|
22
|
-
// `signIgnore` is renamed to `sign.ignore` separately (the @electron/osx-sign canonical name).
|
|
23
|
-
export const MAC_SIGN_FIELDS = [
|
|
24
|
-
"identity",
|
|
25
|
-
"entitlements",
|
|
26
|
-
"entitlementsInherit",
|
|
27
|
-
"entitlementsLoginHelper",
|
|
28
|
-
"provisioningProfile",
|
|
29
|
-
"type",
|
|
30
|
-
"binaries",
|
|
31
|
-
"requirements",
|
|
32
|
-
"hardenedRuntime",
|
|
33
|
-
"gatekeeperAssess",
|
|
34
|
-
"strictVerify",
|
|
35
|
-
"preAutoEntitlements",
|
|
36
|
-
"timestamp",
|
|
37
|
-
"additionalArguments",
|
|
38
|
-
];
|
|
39
|
-
// Universal-build fields that moved from the platform root into the `universal` (ElectronUniversalOptions) bag.
|
|
40
|
-
export const MAC_UNIVERSAL_FIELDS = ["mergeASARs", "singleArchFiles", "x64ArchFiles"];
|
|
9
|
+
// The v26 -> v27 key mapping is owned by app-builder-lib so the runtime guard
|
|
10
|
+
// (`checkLegacyConfiguration`) and this migrator cannot drift apart. Re-exported here because these
|
|
11
|
+
// names are part of this module's existing public surface.
|
|
12
|
+
export { AZURE_KNOWN_FIELDS, ELECTRON_DOWNLOAD_DROPPED, MAC_SIGN_FIELDS, MAC_SIGN_REMOVED_FIELDS, MAC_UNIVERSAL_FIELDS } from "app-builder-lib/internal";
|
|
41
13
|
// Advisory surfaced (informational only — never rewrites the config) when a project builds an nsis-web target. As of v27,
|
|
42
14
|
// AppUpdater.disableWebInstaller defaults to true, so the auto-updater no longer downloads web-installer packages unless the app opts in at runtime.
|
|
43
15
|
export const NSIS_WEB_ADVISORY = "nsis-web target detected. In v27, autoUpdater.disableWebInstaller defaults to true, so NSIS web-installer packages are not downloaded by default. " +
|
|
44
16
|
"If your app relies on nsis-web installers for auto-updates, set autoUpdater.disableWebInstaller = false in your main process (this is an electron-updater runtime setting, not a build-config key).";
|
|
17
|
+
// Advisory surfaced when a mac/mas/masDev config does not name its own entitlements file. v27 stopped granting
|
|
18
|
+
// allow-unsigned-executable-memory and disable-library-validation in the bundled default, which only fails at runtime.
|
|
19
|
+
export const MAC_ENTITLEMENTS_ADVISORY = "In v27 the bundled default macOS entitlements grant only com.apple.security.cs.allow-jit (allow-unsigned-executable-memory and disable-library-validation are no longer granted). " +
|
|
20
|
+
"If you rely on the default (no mac.sign.entitlements and no build/entitlements.mac.plist) and your app loads native modules, frameworks, or plugins signed by another Team ID (or unsigned), " +
|
|
21
|
+
"add those entitlements back in build/entitlements.mac.plist. See https://www.electron.build/docs/migration/v27-breaking-changes#macos-default-entitlements-tightened";
|
|
22
|
+
/**
|
|
23
|
+
* macOS signing/universal fields that v26 accepted as `null` (meaning "unset") but whose v27 type has no `null`
|
|
24
|
+
* branch, so a moved `null` would fail validation. They are dropped instead of moved. `identity: null` is not in
|
|
25
|
+
* this list — it means "skip signing" and `sign.identity` still accepts it.
|
|
26
|
+
*/
|
|
27
|
+
export const MAC_NULL_MEANS_UNSET_FIELDS = new Set(["type", "provisioningProfile", "binaries", "signIgnore", "singleArchFiles", "x64ArchFiles"]);
|
|
28
|
+
/** Platform keys that accepted a platform-level `asarUnpack` in v26. */
|
|
29
|
+
export const ASAR_PLATFORM_KEYS = ["mac", "mas", "masDev", "win", "linux"];
|
|
30
|
+
/** v26 `snap` options that the v27 `snapcraft.core24` shape does not accept (core24 uses the snapcraft CLI directly). */
|
|
31
|
+
export const SNAP_CORE24_UNSUPPORTED = ["allowNativeWayland", "useTemplateApp"];
|
|
45
32
|
/** True when `target` (a string, a `{ target }` object, or an array of either) selects the nsis-web target. */
|
|
46
33
|
function hasNsisWebTarget(target) {
|
|
47
34
|
if (target == null) {
|
|
@@ -62,6 +49,26 @@ function hasNsisWebTarget(target) {
|
|
|
62
49
|
function detectNsisWebTarget(config) {
|
|
63
50
|
return hasNsisWebTarget(config.win?.target) || hasNsisWebTarget(config.target);
|
|
64
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* True when a mac/mas/masDev section signs with electron-builder's defaults without naming its own entitlements
|
|
54
|
+
* file. A custom signer (string/function `sign`) or `sign.identity: null` (skip signing) bypasses the defaults.
|
|
55
|
+
*/
|
|
56
|
+
function detectDefaultMacEntitlements(config) {
|
|
57
|
+
return ["mac", "mas", "masDev"].some(p => {
|
|
58
|
+
const platform = config[p];
|
|
59
|
+
if (!isPlainObject(platform)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const sign = platform.sign;
|
|
63
|
+
if (typeof sign === "string" || typeof sign === "function") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return !(isPlainObject(sign) && (sign.entitlements != null || sign.identity === null));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function isPlainObject(value) {
|
|
70
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
71
|
+
}
|
|
65
72
|
/**
|
|
66
73
|
* Applies all v26→v27 config transformations to a parsed config object.
|
|
67
74
|
* Pure function — no I/O, safe to call in tests.
|
|
@@ -130,11 +137,20 @@ export function migrateConfig(raw) {
|
|
|
130
137
|
}
|
|
131
138
|
}
|
|
132
139
|
if ("nativeRebuilder" in c) {
|
|
133
|
-
|
|
140
|
+
if (c.nativeRebuilder === "legacy") {
|
|
141
|
+
// v27 dropped the app-builder binary rebuilder; rebuildMode only selects @electron/rebuild's mode.
|
|
142
|
+
warnings.push('nativeRebuilder: "legacy" (the app-builder binary rebuilder) was removed in v27 — native modules are always rebuilt with @electron/rebuild. ' +
|
|
143
|
+
'The value was dropped, so the default nativeModules.rebuildMode ("sequential") applies.');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
sub.rebuildMode = c.nativeRebuilder;
|
|
147
|
+
}
|
|
134
148
|
delete c.nativeRebuilder;
|
|
135
149
|
changes.push({ key: "nativeRebuilder", description: "renamed nativeRebuilder → nativeModules.rebuildMode" });
|
|
136
150
|
}
|
|
137
|
-
c.nativeModules
|
|
151
|
+
if (Object.keys(sub).length > 0 || c.nativeModules != null) {
|
|
152
|
+
c.nativeModules = sub;
|
|
153
|
+
}
|
|
138
154
|
}
|
|
139
155
|
// ── 5. Legacy asar keys → asarUnpack ─────────────────────────────────────
|
|
140
156
|
// Hyphenated root-level variants
|
|
@@ -166,12 +182,12 @@ export function migrateConfig(raw) {
|
|
|
166
182
|
}
|
|
167
183
|
// ── 7. GithubOptions.vPrefixedTagName → tagNamePrefix ─────────────────────
|
|
168
184
|
if (c.publish != null) {
|
|
169
|
-
migratePublishEntries(c, "publish", changes);
|
|
185
|
+
migratePublishEntries(c, "publish", changes, warnings);
|
|
170
186
|
}
|
|
171
|
-
// Also handle publish nested inside mac
|
|
172
|
-
for (const
|
|
173
|
-
if (
|
|
174
|
-
migratePublishEntries(
|
|
187
|
+
// Also handle publish nested inside any platform/target section (mac, win, nsis, dmg, snap, …)
|
|
188
|
+
for (const value of Object.values(c)) {
|
|
189
|
+
if (isPlainObject(value) && value.publish != null) {
|
|
190
|
+
migratePublishEntries(value, "publish", changes, warnings);
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
193
|
// ── 8. snap → snapcraft restructuring ─────────────────────────────────────
|
|
@@ -197,10 +213,40 @@ export function migrateConfig(raw) {
|
|
|
197
213
|
delete sq.noMsi;
|
|
198
214
|
changes.push({ key: "squirrelWindows.noMsi", description: "replaced squirrelWindows.noMsi → squirrelWindows.msi (inverted boolean)" });
|
|
199
215
|
}
|
|
216
|
+
// ── 10b. squirrelWindows.customSquirrelVendorDir removed (not auto-migrated) ──
|
|
217
|
+
// The v27 replacement (a toolsets.squirrel ToolsetCustom bundle) needs a different directory layout, so the
|
|
218
|
+
// key is left in place: the build then fails with a targeted message instead of silently switching the
|
|
219
|
+
// vendored Squirrel binaries back to the default bundle.
|
|
220
|
+
if (isPlainObject(c.squirrelWindows) && "customSquirrelVendorDir" in c.squirrelWindows) {
|
|
221
|
+
warnings.push("squirrelWindows.customSquirrelVendorDir was removed in v27 and cannot be migrated automatically. " +
|
|
222
|
+
'Supply the custom bundle via toolsets.squirrel: { url: "file:///abs/path" } — the bundle must contain an electron-winstaller/vendor/ subtree ' +
|
|
223
|
+
"(customSquirrelVendorDir pointed at the vendor files directly) — or remove the key to use the default Squirrel bundle.");
|
|
224
|
+
}
|
|
200
225
|
// ── 11. asar consolidation ───────────────────────────────────────────────
|
|
201
226
|
// Move root-level asarUnpack / disableSanityCheckAsar / disableAsarIntegrity
|
|
202
|
-
// into the asar object.
|
|
203
|
-
|
|
227
|
+
// into the asar object. In v26 these applied to every platform independently of `asar`; in v27 they live
|
|
228
|
+
// inside it, and a platform-level `asar` replaces the root one wholesale — so capture them first for step 11b.
|
|
229
|
+
const rootAsarValue = c.asar;
|
|
230
|
+
const rootFlatAsar = {};
|
|
231
|
+
if ("asarUnpack" in c) {
|
|
232
|
+
rootFlatAsar.unpack = c.asarUnpack;
|
|
233
|
+
}
|
|
234
|
+
if ("disableSanityCheckAsar" in c) {
|
|
235
|
+
rootFlatAsar.disableSanityCheck = c.disableSanityCheckAsar;
|
|
236
|
+
}
|
|
237
|
+
if ("disableAsarIntegrity" in c) {
|
|
238
|
+
rootFlatAsar.disableIntegrity = c.disableAsarIntegrity;
|
|
239
|
+
}
|
|
240
|
+
if (c.asar === false) {
|
|
241
|
+
// No effect on the root build; step 11b still carries them into any platform that re-enables asar.
|
|
242
|
+
for (const key of ["asarUnpack", "disableSanityCheckAsar", "disableAsarIntegrity"]) {
|
|
243
|
+
if (key in c) {
|
|
244
|
+
delete c[key];
|
|
245
|
+
changes.push({ key, description: `removed ${key} (no effect with asar: false)` });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
204
250
|
const asarSub = typeof c.asar === "object" && c.asar != null ? { ...c.asar } : {};
|
|
205
251
|
if ("asarUnpack" in c) {
|
|
206
252
|
asarSub.unpack = mergeAsarUnpack(asarSub.unpack, c.asarUnpack);
|
|
@@ -220,45 +266,61 @@ export function migrateConfig(raw) {
|
|
|
220
266
|
if (c.asar === true) {
|
|
221
267
|
if (Object.keys(asarSub).length > 0) {
|
|
222
268
|
c.asar = asarSub;
|
|
269
|
+
changes.push({ key: "asar", description: `replaced asar: true with an asar object carrying ${Object.keys(asarSub).join(", ")}` });
|
|
223
270
|
}
|
|
224
271
|
else {
|
|
225
272
|
delete c.asar;
|
|
273
|
+
changes.push({ key: "asar", description: "removed redundant asar: true (asar is enabled by default; the explicit `true` is not needed)" });
|
|
226
274
|
}
|
|
227
|
-
changes.push({ key: "asar", description: "replaced asar: true with asar object (true is no longer a valid value)" });
|
|
228
275
|
}
|
|
229
276
|
else if (Object.keys(asarSub).length > 0) {
|
|
230
277
|
c.asar = asarSub;
|
|
231
278
|
}
|
|
232
279
|
}
|
|
280
|
+
// ── 11b. platform-level asar ──────────────────────────────────────────────
|
|
281
|
+
for (const platform of ASAR_PLATFORM_KEYS) {
|
|
282
|
+
if (isPlainObject(c[platform])) {
|
|
283
|
+
migratePlatformAsar(c, platform, rootAsarValue, rootFlatAsar, changes);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
233
286
|
// ── 12. win.sign unification ───────────────────────────────────────────────
|
|
234
287
|
// win.signtoolOptions → win.sign: { type: "signtool", … }
|
|
235
288
|
// win.azureSignOptions → win.sign: { type: "azure", … } (extra keys → additionalMetadata)
|
|
236
289
|
// win.signAndEditExecutable / win.signExecutable removed
|
|
237
290
|
if (c.win != null && typeof c.win === "object") {
|
|
238
291
|
const win = c.win;
|
|
239
|
-
// Remove defunct flags — signAndEditExecutable/signExecutable no longer exist in v27.
|
|
292
|
+
// Remove defunct flags — signAndEditExecutable/signExecutable no longer exist in v27. Either one set to
|
|
293
|
+
// false skipped ALL signing in v26, so both map to win.sign: false (signAndEditExecutable also skipped
|
|
294
|
+
// resource editing, which always runs in v27 — that part has no equivalent).
|
|
295
|
+
const signingDisabled = win.signExecutable === false || win.signAndEditExecutable === false;
|
|
240
296
|
if ("signAndEditExecutable" in win) {
|
|
241
297
|
const val = win.signAndEditExecutable;
|
|
242
298
|
delete win.signAndEditExecutable;
|
|
243
299
|
if (val === false) {
|
|
244
300
|
warnings.push("win.signAndEditExecutable: false was used to skip both resource editing and signing. " +
|
|
245
|
-
"In v27, resource editing always runs
|
|
301
|
+
"In v27, resource editing always runs; signing is still skipped via win.sign: false. " +
|
|
246
302
|
"There is no v27 equivalent that also skips resource editing — apply resources manually if needed.");
|
|
247
303
|
}
|
|
248
|
-
|
|
249
|
-
changes.push({ key: "win.signAndEditExecutable", description: "removed win.signAndEditExecutable (resource editing always runs in v27; was the default)" });
|
|
250
|
-
}
|
|
304
|
+
changes.push({ key: "win.signAndEditExecutable", description: "removed win.signAndEditExecutable (resource editing always runs in v27)" });
|
|
251
305
|
}
|
|
252
306
|
if ("signExecutable" in win) {
|
|
253
|
-
const val = win.signExecutable;
|
|
254
307
|
delete win.signExecutable;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
308
|
+
changes.push({ key: "win.signExecutable", description: "removed win.signExecutable (signing is enabled by default when credentials are available)" });
|
|
309
|
+
}
|
|
310
|
+
if (signingDisabled && win.sign == null) {
|
|
311
|
+
win.sign = false;
|
|
312
|
+
changes.push({ key: "win.sign", description: "set win.sign: false (v26 signExecutable/signAndEditExecutable: false disabled signing; resource editing still runs)" });
|
|
313
|
+
}
|
|
314
|
+
if (win.sign === false && (win.azureSignOptions != null || win.signtoolOptions != null)) {
|
|
315
|
+
// Signing was switched off, so the legacy options never applied. Only their key names are reported —
|
|
316
|
+
// the values can hold certificate passwords.
|
|
317
|
+
const dropped = ["signtoolOptions", "azureSignOptions"].filter(k => win[k] != null);
|
|
318
|
+
for (const k of dropped) {
|
|
319
|
+
delete win[k];
|
|
320
|
+
changes.push({ key: `win.${k}`, description: `removed win.${k} (signing is disabled with win.sign: false)` });
|
|
261
321
|
}
|
|
322
|
+
warnings.push(`win.sign is false (signing disabled), so [${dropped.map(k => `win.${k}`).join(", ")}] had no effect and ${dropped.length === 1 ? "was" : "were"} removed. ` +
|
|
323
|
+
'To sign in v27, replace win.sign: false with win.sign: { type: "signtool" | "azure", … } carrying those options.');
|
|
262
324
|
}
|
|
263
325
|
const hasAzure = win.azureSignOptions != null;
|
|
264
326
|
const hasSigntool = win.signtoolOptions != null;
|
|
@@ -316,12 +378,23 @@ export function migrateConfig(raw) {
|
|
|
316
378
|
}
|
|
317
379
|
// ── 14. electronDownload → electronGet ────────────────────────────────────
|
|
318
380
|
if ("electronDownload" in c) {
|
|
319
|
-
migrateElectronDownload(c, changes, warnings);
|
|
381
|
+
migrateElectronDownload(c, "electronDownload", changes, warnings);
|
|
320
382
|
}
|
|
321
|
-
|
|
383
|
+
else if (isPlainObject(c.electronGet) && Object.keys(c.electronGet).some(isLegacyElectronDownloadKey)) {
|
|
384
|
+
// A hand-renamed `electronGet` that still carries the v26 shape.
|
|
385
|
+
migrateElectronDownload(c, "electronGet", changes, warnings);
|
|
386
|
+
}
|
|
387
|
+
// ── 15. toolsets ──────────────────────────────────────────────────────────
|
|
388
|
+
if (isPlainObject(c.toolsets)) {
|
|
389
|
+
migrateToolsets(c.toolsets, changes);
|
|
390
|
+
}
|
|
391
|
+
// Advisories (not config changes): runtime defaults that changed in v27 and only surface after the build.
|
|
322
392
|
if (detectNsisWebTarget(c)) {
|
|
323
393
|
advisories.push(NSIS_WEB_ADVISORY);
|
|
324
394
|
}
|
|
395
|
+
if (detectDefaultMacEntitlements(c)) {
|
|
396
|
+
advisories.push(MAC_ENTITLEMENTS_ADVISORY);
|
|
397
|
+
}
|
|
325
398
|
return { migrated: c, changes, warnings, advisories, modified: changes.length > 0 || warnings.length > 0 };
|
|
326
399
|
}
|
|
327
400
|
/**
|
|
@@ -335,6 +408,14 @@ function migrateMacSigning(platform, name, changes, warnings) {
|
|
|
335
408
|
const hasSignIgnore = "signIgnore" in platform;
|
|
336
409
|
const existingSign = platform.sign;
|
|
337
410
|
const signIsCustom = typeof existingSign === "string" || typeof existingSign === "function";
|
|
411
|
+
// Removed outright, not moved: ElectronSignOptions omits these and the schema rejects them, so
|
|
412
|
+
// relocating them under `sign` would emit a config that fails validation on the next build.
|
|
413
|
+
for (const field of MAC_SIGN_REMOVED_FIELDS) {
|
|
414
|
+
if (field in platform) {
|
|
415
|
+
delete platform[field];
|
|
416
|
+
changes.push({ key: `${name}.${field}`, description: `removed ${name}.${field} (@electron/osx-sign 2.x dropped the spctl --assess step; there is no ${name}.sign.${field})` });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
338
419
|
if (present.length === 0 && !hasSignIgnore) {
|
|
339
420
|
// Only thing to fix is a semantically-flipped bare null
|
|
340
421
|
if (existingSign === null) {
|
|
@@ -351,16 +432,35 @@ function migrateMacSigning(platform, name, changes, warnings) {
|
|
|
351
432
|
// existingSign is null/undefined or already an object → build the merged options bag
|
|
352
433
|
const signObj = existingSign != null && typeof existingSign === "object" ? { ...existingSign } : {};
|
|
353
434
|
for (const f of present) {
|
|
435
|
+
if (dropNullField(platform, f, name, changes)) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
354
438
|
signObj[f] = platform[f];
|
|
355
439
|
delete platform[f];
|
|
356
440
|
changes.push({ key: `${name}.${f}`, description: `moved ${name}.${f} → ${name}.sign.${f}` });
|
|
357
441
|
}
|
|
358
|
-
if (hasSignIgnore) {
|
|
442
|
+
if (hasSignIgnore && !dropNullField(platform, "signIgnore", name, changes)) {
|
|
359
443
|
signObj.ignore = platform.signIgnore;
|
|
360
444
|
delete platform.signIgnore;
|
|
361
445
|
changes.push({ key: `${name}.signIgnore`, description: `renamed ${name}.signIgnore → ${name}.sign.ignore` });
|
|
362
446
|
}
|
|
363
|
-
|
|
447
|
+
if (Object.keys(signObj).length > 0 || existingSign != null) {
|
|
448
|
+
platform.sign = signObj;
|
|
449
|
+
}
|
|
450
|
+
else if (existingSign === null) {
|
|
451
|
+
// Every moved field was a dropped null — a bare `sign: null` left behind would now mean "skip signing".
|
|
452
|
+
delete platform.sign;
|
|
453
|
+
changes.push({ key: `${name}.sign`, description: `removed ${name}.sign: null (v26 "no custom signer" = v27 default; sign: null now means skip signing)` });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/** Drops a v26 `null` ("unset") for a field whose v27 type has no null branch. Returns true when dropped. */
|
|
457
|
+
function dropNullField(platform, field, name, changes) {
|
|
458
|
+
if (platform[field] !== null || !MAC_NULL_MEANS_UNSET_FIELDS.has(field)) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
delete platform[field];
|
|
462
|
+
changes.push({ key: `${name}.${field}`, description: `removed ${name}.${field}: null (null meant "unset" in v26; the v27 option does not accept null)` });
|
|
463
|
+
return true;
|
|
364
464
|
}
|
|
365
465
|
/** Moves universal-build fields from a platform config into the `universal` bag. */
|
|
366
466
|
function migrateMacUniversal(platform, name, changes) {
|
|
@@ -370,30 +470,125 @@ function migrateMacUniversal(platform, name, changes) {
|
|
|
370
470
|
}
|
|
371
471
|
const universalObj = platform.universal != null && typeof platform.universal === "object" ? { ...platform.universal } : {};
|
|
372
472
|
for (const f of present) {
|
|
473
|
+
if (dropNullField(platform, f, name, changes)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
373
476
|
universalObj[f] = platform[f];
|
|
374
477
|
delete platform[f];
|
|
375
478
|
changes.push({ key: `${name}.${f}`, description: `moved ${name}.${f} → ${name}.universal.${f}` });
|
|
376
479
|
}
|
|
377
|
-
platform.universal
|
|
480
|
+
if (Object.keys(universalObj).length > 0 || platform.universal != null) {
|
|
481
|
+
platform.universal = universalObj;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Moves a platform-level `asarUnpack` into that platform's `asar` object.
|
|
486
|
+
*
|
|
487
|
+
* v26 concatenated root and platform `asarUnpack` and always applied the root `disableSanityCheckAsar` /
|
|
488
|
+
* `disableAsarIntegrity`, whatever the platform's `asar` was. v27 keeps all three inside `asar`, and a
|
|
489
|
+
* platform-level `asar` replaces the root one entirely — so a platform that overrides `asar` (or gains one
|
|
490
|
+
* here) gets the root values folded in to keep the v26 result.
|
|
491
|
+
*/
|
|
492
|
+
function migratePlatformAsar(c, name, rootAsarValue, rootFlatAsar, changes) {
|
|
493
|
+
const platform = c[name];
|
|
494
|
+
const hasUnpack = "asarUnpack" in platform;
|
|
495
|
+
const own = platform.asar;
|
|
496
|
+
if (own == null) {
|
|
497
|
+
// Inherits the root asar — only needs an override of its own to carry a platform asarUnpack.
|
|
498
|
+
if (!hasUnpack) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (rootAsarValue === false) {
|
|
502
|
+
delete platform.asarUnpack;
|
|
503
|
+
changes.push({ key: `${name}.asarUnpack`, description: `removed ${name}.asarUnpack (no effect with asar: false)` });
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const next = isPlainObject(c.asar) ? { ...c.asar } : {};
|
|
507
|
+
next.unpack = mergeAsarUnpack(next.unpack, platform.asarUnpack);
|
|
508
|
+
platform.asar = next;
|
|
509
|
+
delete platform.asarUnpack;
|
|
510
|
+
changes.push({
|
|
511
|
+
key: `${name}.asarUnpack`,
|
|
512
|
+
description: `moved ${name}.asarUnpack → ${name}.asar.unpack (merged with the root asar options, since a platform-level asar replaces the root one)`,
|
|
513
|
+
});
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (own === false) {
|
|
517
|
+
if (hasUnpack) {
|
|
518
|
+
delete platform.asarUnpack;
|
|
519
|
+
changes.push({ key: `${name}.asarUnpack`, description: `removed ${name}.asarUnpack (no effect with ${name}.asar: false)` });
|
|
520
|
+
}
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// own is true or an AsarOptions object: fold the v26 root-level options (and the platform asarUnpack) into it.
|
|
524
|
+
const next = isPlainObject(own) ? { ...own } : {};
|
|
525
|
+
const folded = [];
|
|
526
|
+
if (rootFlatAsar.unpack != null || hasUnpack) {
|
|
527
|
+
// v26 order: root patterns first, then the platform's own.
|
|
528
|
+
let unpack = undefined;
|
|
529
|
+
for (const it of [rootFlatAsar.unpack, next.unpack, hasUnpack ? platform.asarUnpack : undefined]) {
|
|
530
|
+
if (it != null) {
|
|
531
|
+
unpack = mergeAsarUnpack(unpack, it);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (unpack !== undefined) {
|
|
535
|
+
next.unpack = unpack;
|
|
536
|
+
folded.push("unpack");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
for (const key of ["disableSanityCheck", "disableIntegrity"]) {
|
|
540
|
+
if (key in rootFlatAsar && !(key in next)) {
|
|
541
|
+
next[key] = rootFlatAsar[key];
|
|
542
|
+
folded.push(key);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (hasUnpack) {
|
|
546
|
+
delete platform.asarUnpack;
|
|
547
|
+
}
|
|
548
|
+
if (folded.length === 0) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
platform.asar = next;
|
|
552
|
+
changes.push({
|
|
553
|
+
key: hasUnpack ? `${name}.asarUnpack` : `${name}.asar`,
|
|
554
|
+
description: `folded ${folded.map(k => `asar.${k}`).join(", ")} into ${name}.asar (a platform-level asar replaces the root one in v27, so the v26 root-level values are copied)`,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/** Removes `null` toolset values (rejected by the v27 schema) and remaps pins to bundles that are no longer published. */
|
|
558
|
+
function migrateToolsets(toolsets, changes) {
|
|
559
|
+
for (const [key, value] of Object.entries(toolsets)) {
|
|
560
|
+
if (value === null) {
|
|
561
|
+
delete toolsets[key];
|
|
562
|
+
changes.push({
|
|
563
|
+
key: `toolsets.${key}`,
|
|
564
|
+
description: `removed toolsets.${key}: null (v27 rejects null; unset resolves to "latest", the newest bundle — pin "0.0.0" to keep the legacy bundle v26 used)`,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (toolsets.appimage === "1.0.2") {
|
|
569
|
+
toolsets.appimage = "1.0.3";
|
|
570
|
+
changes.push({
|
|
571
|
+
key: "toolsets.appimage",
|
|
572
|
+
description: 'replaced toolsets.appimage: "1.0.2" with "1.0.3" (1.0.2 is no longer offered; 1.0.3 is the same runtime with the #9598 fix)',
|
|
573
|
+
});
|
|
574
|
+
}
|
|
378
575
|
}
|
|
379
|
-
// electronDownload fields with no equivalent in the v27 ElectronGetOptions (@electron/get v5) shape.
|
|
380
|
-
export const ELECTRON_DOWNLOAD_DROPPED = ["cache", "customDir", "customFilename", "strictSSL", "platform", "arch", "version"];
|
|
381
576
|
/**
|
|
382
577
|
* Renames `electronDownload` → `electronGet` and reshapes it to ElectronGetOptions.
|
|
383
578
|
* `mirror` → `mirrorOptions.mirror`, `isVerifyChecksum: false` → `unsafelyDisableChecksums: true`.
|
|
384
579
|
* Fields with no v5 equivalent are dropped with a warning.
|
|
385
580
|
*/
|
|
386
|
-
function migrateElectronDownload(c, changes, warnings) {
|
|
387
|
-
const old = c
|
|
388
|
-
delete c
|
|
581
|
+
function migrateElectronDownload(c, sourceKey, changes, warnings) {
|
|
582
|
+
const old = c[sourceKey];
|
|
583
|
+
delete c[sourceKey];
|
|
389
584
|
if (old == null || typeof old !== "object") {
|
|
390
585
|
changes.push({ key: "electronDownload", description: "renamed electronDownload → electronGet" });
|
|
391
|
-
if (old != null) {
|
|
586
|
+
if (old != null && c.electronGet == null) {
|
|
392
587
|
c.electronGet = old;
|
|
393
588
|
}
|
|
394
589
|
return;
|
|
395
590
|
}
|
|
396
|
-
const next = {
|
|
591
|
+
const next = {};
|
|
397
592
|
if ("mirror" in old && old.mirror != null) {
|
|
398
593
|
next.mirrorOptions = { ...(next.mirrorOptions ?? {}), mirror: old.mirror };
|
|
399
594
|
}
|
|
@@ -410,8 +605,39 @@ function migrateElectronDownload(c, changes, warnings) {
|
|
|
410
605
|
if (dropped.length > 0) {
|
|
411
606
|
warnings.push(`electronGet (formerly electronDownload) dropped [${dropped.join(", ")}] — these have no equivalent in @electron/get v5. Set a mirror via electronGet.mirrorOptions if needed.`);
|
|
412
607
|
}
|
|
608
|
+
// An electronGet that already exists is the v27 intent: fold the legacy values in underneath it, never over it.
|
|
609
|
+
const existing = sourceKey === "electronDownload" && isPlainObject(c.electronGet) ? c.electronGet : null;
|
|
610
|
+
if (existing != null) {
|
|
611
|
+
const conflicts = [];
|
|
612
|
+
let mergedMirrorOptions;
|
|
613
|
+
for (const [k, v] of Object.entries(next)) {
|
|
614
|
+
if (k === "mirrorOptions" && isPlainObject(v) && isPlainObject(existing.mirrorOptions)) {
|
|
615
|
+
const own = existing.mirrorOptions;
|
|
616
|
+
conflicts.push(...Object.keys(v)
|
|
617
|
+
.filter(m => m in own && JSON.stringify(own[m]) !== JSON.stringify(v[m]))
|
|
618
|
+
.map(m => `mirrorOptions.${m}`));
|
|
619
|
+
mergedMirrorOptions = { ...v, ...own };
|
|
620
|
+
}
|
|
621
|
+
else if (k in existing && JSON.stringify(existing[k]) !== JSON.stringify(v)) {
|
|
622
|
+
conflicts.push(k);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
Object.assign(next, existing, mergedMirrorOptions != null ? { mirrorOptions: mergedMirrorOptions } : {});
|
|
626
|
+
if (conflicts.length > 0) {
|
|
627
|
+
warnings.push(`Both electronDownload and electronGet set [${conflicts.join(", ")}]; kept the existing electronGet values and dropped the electronDownload ones. Verify the merged electronGet.`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
413
630
|
c.electronGet = next;
|
|
414
|
-
changes.push({
|
|
631
|
+
changes.push({
|
|
632
|
+
key: sourceKey,
|
|
633
|
+
description: sourceKey === "electronDownload"
|
|
634
|
+
? "renamed electronDownload → electronGet (mirror → mirrorOptions.mirror; isVerifyChecksum → unsafelyDisableChecksums)"
|
|
635
|
+
: "reshaped electronGet to the v27 options (mirror → mirrorOptions.mirror; isVerifyChecksum → unsafelyDisableChecksums)",
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
/** Keys of the v26 `electronDownload` shape that are not valid v27 `electronGet` keys. */
|
|
639
|
+
export function isLegacyElectronDownloadKey(key) {
|
|
640
|
+
return key === "mirror" || key === "isVerifyChecksum" || ELECTRON_DOWNLOAD_DROPPED.includes(key);
|
|
415
641
|
}
|
|
416
642
|
function mergeAsarUnpack(existing, incoming) {
|
|
417
643
|
if (existing == null) {
|
|
@@ -420,15 +646,26 @@ function mergeAsarUnpack(existing, incoming) {
|
|
|
420
646
|
const arr = (Array.isArray(existing) ? existing : [existing]).concat(Array.isArray(incoming) ? incoming : [incoming]);
|
|
421
647
|
return arr.length === 1 ? arr[0] : arr;
|
|
422
648
|
}
|
|
423
|
-
function migratePublishEntries(parent, key, changes) {
|
|
649
|
+
function migratePublishEntries(parent, key, changes, warnings) {
|
|
424
650
|
const entries = Array.isArray(parent[key]) ? parent[key] : [parent[key]];
|
|
425
651
|
let changed = false;
|
|
426
652
|
for (const entry of entries) {
|
|
427
|
-
if (entry
|
|
428
|
-
|
|
653
|
+
if (entry == null || entry.provider !== "github") {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if ("vPrefixedTagName" in entry) {
|
|
657
|
+
// v26 githubTagPrefix(): a non-empty tagNamePrefix won; otherwise vPrefixedTagName (default true) chose "v" or "".
|
|
658
|
+
entry.tagNamePrefix = entry.tagNamePrefix ? entry.tagNamePrefix : entry.vPrefixedTagName === false ? "" : "v";
|
|
429
659
|
delete entry.vPrefixedTagName;
|
|
430
660
|
changed = true;
|
|
431
661
|
}
|
|
662
|
+
else if (entry.tagNamePrefix === "") {
|
|
663
|
+
// v26 ignored an empty tagNamePrefix and still tagged "v1.2.3"; v27 honors it and would tag "1.2.3".
|
|
664
|
+
entry.tagNamePrefix = "v";
|
|
665
|
+
changed = true;
|
|
666
|
+
warnings.push('A GitHub publish entry set tagNamePrefix: "". v26 ignored an empty prefix and tagged releases "v<version>"; v27 honors it. ' +
|
|
667
|
+
'It was rewritten to tagNamePrefix: "v" to keep your existing tag names — set it back to "" if you want unprefixed tags from now on.');
|
|
668
|
+
}
|
|
432
669
|
// GitLab keeps vPrefixedTagName in v27 — it remains in the type, scheme, and runtime
|
|
433
670
|
// (gitlabPublisher honors it) and has no tagNamePrefix equivalent, so leave it untouched.
|
|
434
671
|
}
|
|
@@ -471,6 +708,15 @@ function migrateSnap(c, changes, warnings) {
|
|
|
471
708
|
base = "core20";
|
|
472
709
|
assumed = true;
|
|
473
710
|
}
|
|
711
|
+
if (base === "core24") {
|
|
712
|
+
const unsupported = SNAP_CORE24_UNSUPPORTED.filter(k => k in rest);
|
|
713
|
+
for (const k of unsupported) {
|
|
714
|
+
delete rest[k];
|
|
715
|
+
}
|
|
716
|
+
if (unsupported.length > 0) {
|
|
717
|
+
warnings.push(`snap config set [${unsupported.join(", ")}], which the v27 snapcraft.core24 options do not support (core24 builds with the snapcraft CLI directly); they were dropped.`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
474
720
|
snapcraft.base = base;
|
|
475
721
|
snapcraft[base] = { ...(snapcraft[base] ?? {}), ...rest };
|
|
476
722
|
c.snapcraft = snapcraft;
|
|
@@ -675,7 +921,11 @@ async function migrateProgrammaticConfigFile(found, location, dryRun) {
|
|
|
675
921
|
return;
|
|
676
922
|
}
|
|
677
923
|
if (result.status === "no-op") {
|
|
678
|
-
|
|
924
|
+
// A no-op can still carry warnings for keys that cannot be rewritten (e.g. customSquirrelVendorDir).
|
|
925
|
+
for (const warning of result.warnings) {
|
|
926
|
+
log.warn(null, warning);
|
|
927
|
+
}
|
|
928
|
+
if (result.advisories.length === 0 && result.warnings.length === 0) {
|
|
679
929
|
log.info(null, "config is already up to date — no changes needed");
|
|
680
930
|
}
|
|
681
931
|
printAdvisories();
|
|
@@ -701,23 +951,26 @@ function printManualSteps() {
|
|
|
701
951
|
"• Remove electronCompile",
|
|
702
952
|
"• Remove framework, nodeVersion, launchUiVersion",
|
|
703
953
|
"• Rename npmSkipBuildFromSource → buildDependenciesFromSource",
|
|
704
|
-
|
|
705
|
-
"• Rename asar-unpack / asar-unpack-dir / asar.
|
|
954
|
+
'• Move buildDependenciesFromSource, nodeGypRebuild, npmRebuild, nativeRebuilder into nativeModules (rename nativeRebuilder → rebuildMode; drop a "legacy" value)',
|
|
955
|
+
"• Rename asar-unpack / asar-unpack-dir / asar.unpackDir → asarUnpack; then move asarUnpack → asar.unpack",
|
|
956
|
+
"• Move mac/mas/masDev/win/linux asarUnpack → <platform>.asar.unpack (a platform-level asar replaces the root one, so copy the root asar options into it)",
|
|
706
957
|
"• Move disableSanityCheckAsar → asar.disableSanityCheck",
|
|
707
958
|
"• Move disableAsarIntegrity → asar.disableIntegrity",
|
|
708
959
|
"• Replace asar: true with an asar object (e.g. asar: {} or omit entirely - asar is enabled by default)",
|
|
709
960
|
"• Remove appImage.systemIntegration",
|
|
710
961
|
"• Remove linux.syncDesktopName (the installed .desktop filename is always synced from desktopName in v27)",
|
|
711
962
|
"• Rename snap → snapcraft; nest options under a base-named sub-key (default base: core20)",
|
|
712
|
-
"• Replace vPrefixedTagName with tagNamePrefix on GitHub publish entries ('v' is the default prefix - just like before - but can now be customized with tagNamePrefix)",
|
|
963
|
+
"• Replace vPrefixedTagName with tagNamePrefix on GitHub publish entries ('v' is the default prefix - just like before - but can now be customized with tagNamePrefix); an empty tagNamePrefix is now honored",
|
|
713
964
|
"• Move win.signtoolOptions → win.sign: { type: 'signtool', ...fields }",
|
|
714
965
|
"• Move win.azureSignOptions → win.sign: { type: 'azure', ...fields }; move untyped extra keys into win.sign.additionalMetadata",
|
|
715
966
|
"• Remove win.signAndEditExecutable (resource editing always runs in v27); replace win.signExecutable: false with win.sign: false",
|
|
716
967
|
"• Move helper-bundle-id → mac.helperBundleId",
|
|
717
968
|
"• Replace squirrelWindows.noMsi with squirrelWindows.msi (inverted)",
|
|
718
|
-
"•
|
|
969
|
+
"• Replace squirrelWindows.customSquirrelVendorDir with a toolsets.squirrel custom bundle (it must contain an electron-winstaller/vendor/ subtree)",
|
|
970
|
+
"• Move mac/mas/masDev signing fields (identity, entitlements, entitlementsInherit, entitlementsLoginHelper, hardenedRuntime, type, requirements, timestamp, binaries, strictVerify, preAutoEntitlements, provisioningProfile, additionalArguments) into the `sign` object; rename signIgnore → sign.ignore; remove gatekeeperAssess",
|
|
719
971
|
"• Move mac/mas/masDev mergeASARs / singleArchFiles / x64ArchFiles into the `universal` object",
|
|
720
|
-
"• Rename electronDownload → electronGet (mirror → mirrorOptions.mirror; isVerifyChecksum:false → unsafelyDisableChecksums:true)",
|
|
972
|
+
"• Rename electronDownload → electronGet (mirror → mirrorOptions.mirror; isVerifyChecksum:false → unsafelyDisableChecksums:true; drop cache/customDir/customFilename/strictSSL/platform/arch/version/force)",
|
|
973
|
+
'• Remove toolsets.* entries set to null (unset resolves to "latest"); replace toolsets.appimage "1.0.2" with "1.0.3"',
|
|
721
974
|
"• Move root-level package.json directories → build.directories",
|
|
722
975
|
"",
|
|
723
976
|
"• For programmatic configs (JS/TS/MJS/CJS), apply the above changes manually to your config object",
|