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,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { AZURE_KNOWN_FIELDS, ELECTRON_DOWNLOAD_DROPPED, MAC_SIGN_FIELDS, MAC_UNIVERSAL_FIELDS, NSIS_WEB_ADVISORY, SNAP_BASES } from "./migrate-schema.js";
|
|
3
|
+
import { ASAR_PLATFORM_KEYS, AZURE_KNOWN_FIELDS, ELECTRON_DOWNLOAD_DROPPED, isLegacyElectronDownloadKey, MAC_ENTITLEMENTS_ADVISORY, MAC_NULL_MEANS_UNSET_FIELDS, MAC_SIGN_FIELDS, MAC_SIGN_REMOVED_FIELDS, MAC_UNIVERSAL_FIELDS, NSIS_WEB_ADVISORY, SNAP_BASES, SNAP_CORE24_UNSUPPORTED, } from "./migrate-schema.js";
|
|
4
4
|
const _require = createRequire(import.meta.url);
|
|
5
5
|
// `typescript` is intentionally NOT a dependency of electron-builder — it is loaded lazily (same
|
|
6
6
|
// pattern as js-yaml/json5/toml) and the feature degrades to manual steps when it is unavailable.
|
|
@@ -289,13 +289,23 @@ class ConfigCodemod {
|
|
|
289
289
|
}
|
|
290
290
|
this.ruleSyncDesktopName(root);
|
|
291
291
|
this.ruleNativeModules(root);
|
|
292
|
+
// Platform asar reads the root keys before ruleAsar edits them (edits only apply at the end, so order is for clarity).
|
|
293
|
+
for (const platform of ASAR_PLATFORM_KEYS) {
|
|
294
|
+
const p = this.getObjectProp(root, platform);
|
|
295
|
+
if (p != null) {
|
|
296
|
+
this.rulePlatformAsar(root, p, platform);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
292
299
|
this.ruleAsar(root);
|
|
293
300
|
this.ruleAppImageSystemIntegration(root);
|
|
294
301
|
this.rulePublish(root, "publish");
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
302
|
+
// publish nested in any platform/target section (mac, win, nsis, dmg, …). `snap` is skipped: ruleSnap
|
|
303
|
+
// re-emits its properties verbatim, which would overlap an edit made inside it.
|
|
304
|
+
for (const prop of root.properties) {
|
|
305
|
+
const name = this.propName(prop);
|
|
306
|
+
const obj = name != null && name !== "snap" ? this.getObjectProp(root, name) : null;
|
|
307
|
+
if (obj != null) {
|
|
308
|
+
this.rulePublish(obj, "publish", name + ".");
|
|
299
309
|
}
|
|
300
310
|
}
|
|
301
311
|
this.ruleSnap(root);
|
|
@@ -310,7 +320,9 @@ class ConfigCodemod {
|
|
|
310
320
|
}
|
|
311
321
|
}
|
|
312
322
|
this.ruleElectronDownload(root);
|
|
323
|
+
this.ruleToolsets(root);
|
|
313
324
|
this.ruleNsisWebAdvisory(root);
|
|
325
|
+
this.ruleMacEntitlementsAdvisory(root);
|
|
314
326
|
}
|
|
315
327
|
// ── Rules ───────────────────────────────────────────────────────────────
|
|
316
328
|
// Advisory only — adds no edit, so an nsis-web-only config stays a "no-op" and is never rewritten.
|
|
@@ -326,6 +338,58 @@ class ConfigCodemod {
|
|
|
326
338
|
}
|
|
327
339
|
}
|
|
328
340
|
}
|
|
341
|
+
// Advisory only. Mirrors migrateConfig: a mac/mas/masDev object that names no entitlements file (at the v26
|
|
342
|
+
// location or under `sign`) relies on the bundled default, which v27 tightened.
|
|
343
|
+
ruleMacEntitlementsAdvisory(root) {
|
|
344
|
+
const ts = this.ts;
|
|
345
|
+
const isNullLiteral = (prop) => prop != null && ts.isPropertyAssignment(prop) && this.unwrap(prop.initializer).kind === ts.SyntaxKind.NullKeyword;
|
|
346
|
+
const namesEntitlements = (obj) => {
|
|
347
|
+
const prop = obj == null ? null : this.getProp(obj, "entitlements");
|
|
348
|
+
return prop != null && !isNullLiteral(prop);
|
|
349
|
+
};
|
|
350
|
+
// Not signing with electron-builder's defaults at all: a custom signer, or identity: null (skip signing).
|
|
351
|
+
const bypassesDefaults = (p, sign) => {
|
|
352
|
+
const signProp = this.getProp(p, "sign");
|
|
353
|
+
const custom = signProp != null && ts.isPropertyAssignment(signProp) && sign == null && !isNullLiteral(signProp);
|
|
354
|
+
return custom || isNullLiteral(this.getProp(p, "identity")) || (sign != null && isNullLiteral(this.getProp(sign, "identity")));
|
|
355
|
+
};
|
|
356
|
+
for (const platform of ["mac", "mas", "masDev"]) {
|
|
357
|
+
const p = this.getObjectProp(root, platform);
|
|
358
|
+
const sign = p == null ? null : this.getObjectProp(p, "sign");
|
|
359
|
+
if (p != null && !namesEntitlements(p) && !namesEntitlements(sign) && !bypassesDefaults(p, sign)) {
|
|
360
|
+
this.advisories.push(MAC_ENTITLEMENTS_ADVISORY);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
ruleToolsets(root) {
|
|
366
|
+
const ts = this.ts;
|
|
367
|
+
const toolsets = this.getObjectProp(root, "toolsets");
|
|
368
|
+
if (toolsets == null) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
for (const prop of toolsets.properties) {
|
|
372
|
+
const name = this.propName(prop);
|
|
373
|
+
if (name == null || !ts.isPropertyAssignment(prop)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const value = this.unwrap(prop.initializer);
|
|
377
|
+
if (value.kind === ts.SyntaxKind.NullKeyword) {
|
|
378
|
+
this.removeProp(prop);
|
|
379
|
+
this.changes.push({
|
|
380
|
+
key: `toolsets.${name}`,
|
|
381
|
+
description: `removed toolsets.${name}: null (v27 rejects null; unset resolves to "latest", the newest bundle — pin "0.0.0" to keep the legacy bundle v26 used)`,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
else if (name === "appimage" && ts.isStringLiteral(value) && value.text === "1.0.2") {
|
|
385
|
+
this.replaceValue(prop.initializer, '"1.0.3"');
|
|
386
|
+
this.changes.push({
|
|
387
|
+
key: "toolsets.appimage",
|
|
388
|
+
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)',
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
329
393
|
hasNsisWebInTarget(value) {
|
|
330
394
|
const ts = this.ts;
|
|
331
395
|
if (value == null) {
|
|
@@ -389,7 +453,15 @@ class ConfigCodemod {
|
|
|
389
453
|
}
|
|
390
454
|
const nativeRebuilder = this.getProp(root, "nativeRebuilder");
|
|
391
455
|
if (nativeRebuilder != null) {
|
|
392
|
-
|
|
456
|
+
if (this.stringLiteralValue(nativeRebuilder) === "legacy") {
|
|
457
|
+
// v27 dropped the app-builder binary rebuilder; rebuildMode only selects @electron/rebuild's mode.
|
|
458
|
+
this.removeProp(nativeRebuilder);
|
|
459
|
+
this.warnings.push('nativeRebuilder: "legacy" (the app-builder binary rebuilder) was removed in v27 — native modules are always rebuilt with @electron/rebuild. ' +
|
|
460
|
+
'The value was dropped, so the default nativeModules.rebuildMode ("sequential") applies.');
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
sources.push({ prop: nativeRebuilder, renameTo: "rebuildMode" });
|
|
464
|
+
}
|
|
393
465
|
this.changes.push({ key: "nativeRebuilder", description: "renamed nativeRebuilder → nativeModules.rebuildMode" });
|
|
394
466
|
}
|
|
395
467
|
const npmSkip = this.getProp(root, "npmSkipBuildFromSource");
|
|
@@ -417,8 +489,17 @@ class ConfigCodemod {
|
|
|
417
489
|
const asarIsTrue = asarVal != null && asarVal.kind === ts.SyntaxKind.TrueKeyword;
|
|
418
490
|
const asarIsObject = asarVal != null && ts.isObjectLiteralExpression(asarVal);
|
|
419
491
|
if (asarIsFalse) {
|
|
420
|
-
|
|
421
|
-
|
|
492
|
+
const flatKeys = ["asar-unpack", "asar-unpack-dir", "asarUnpack", "disableSanityCheckAsar", "disableAsarIntegrity"].filter(k => this.getProp(root, k) != null);
|
|
493
|
+
if (flatKeys.length === 0) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (this.platformReEnablesAsar(root)) {
|
|
497
|
+
// Those root values still applied to the re-enabling platform in v26; rulePlatformAsar already warned.
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
for (const key of flatKeys) {
|
|
501
|
+
this.removeProp(this.getProp(root, key));
|
|
502
|
+
this.changes.push({ key, description: `removed ${key} (no effect with asar: false)` });
|
|
422
503
|
}
|
|
423
504
|
return;
|
|
424
505
|
}
|
|
@@ -479,16 +560,108 @@ class ConfigCodemod {
|
|
|
479
560
|
const entries = buildEntries(braceIndent + this.indentUnit);
|
|
480
561
|
if (entries.length === 0) {
|
|
481
562
|
this.removeProp(asarProp);
|
|
563
|
+
this.changes.push({ key: "asar", description: "removed redundant asar: true (asar is enabled by default; the explicit `true` is not needed)" });
|
|
482
564
|
}
|
|
483
565
|
else {
|
|
484
566
|
this.replaceValue(asarProp.initializer, this.objectLiteralTextAt(entries, braceIndent));
|
|
567
|
+
const keys = entries.map(e => e.slice(0, e.indexOf(":")));
|
|
568
|
+
this.changes.push({ key: "asar", description: `replaced asar: true with an asar object carrying ${keys.join(", ")}` });
|
|
485
569
|
}
|
|
486
|
-
this.changes.push({ key: "asar", description: "replaced asar: true with asar object (true is no longer a valid value)" });
|
|
487
570
|
return;
|
|
488
571
|
}
|
|
489
572
|
// No asar prop yet — create one on root.
|
|
490
573
|
this.createChild(root, "asar", buildEntries(this.propIndentFor(root) + this.indentUnit));
|
|
491
574
|
}
|
|
575
|
+
/** True when some platform section sets its own `asar` to anything but `false` / `null`. */
|
|
576
|
+
platformReEnablesAsar(root) {
|
|
577
|
+
const ts = this.ts;
|
|
578
|
+
return ASAR_PLATFORM_KEYS.some(platform => {
|
|
579
|
+
const p = this.getObjectProp(root, platform);
|
|
580
|
+
const prop = p == null ? null : this.getProp(p, "asar");
|
|
581
|
+
if (prop == null || !ts.isPropertyAssignment(prop)) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
const kind = this.unwrap(prop.initializer).kind;
|
|
585
|
+
return kind !== ts.SyntaxKind.FalseKeyword && kind !== ts.SyntaxKind.NullKeyword;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Platform-level `asarUnpack` → `<platform>.asar.unpack`. Only the cases where a plain move is exactly the v26
|
|
590
|
+
* result are rewritten: a platform-level `asar` replaces the root one in v27, so whenever root-level asar
|
|
591
|
+
* options exist they would have to be copied into the platform object — that is left to the user, with a warning.
|
|
592
|
+
*/
|
|
593
|
+
rulePlatformAsar(root, platform, name) {
|
|
594
|
+
const ts = this.ts;
|
|
595
|
+
const unpackProp = this.getProp(platform, "asarUnpack");
|
|
596
|
+
const asarProp = this.getProp(platform, "asar");
|
|
597
|
+
const asarVal = asarProp != null && ts.isPropertyAssignment(asarProp) ? this.unwrap(asarProp.initializer) : null;
|
|
598
|
+
const rootAsarProp = this.getProp(root, "asar");
|
|
599
|
+
const rootAsarVal = rootAsarProp != null && ts.isPropertyAssignment(rootAsarProp) ? this.unwrap(rootAsarProp.initializer) : null;
|
|
600
|
+
const rootHasFlat = ["asar-unpack", "asar-unpack-dir", "asarUnpack", "disableSanityCheckAsar", "disableAsarIntegrity"].some(k => this.getProp(root, k) != null) ||
|
|
601
|
+
(rootAsarVal != null && ts.isObjectLiteralExpression(rootAsarVal) && this.getProp(rootAsarVal, "unpackDir") != null);
|
|
602
|
+
const ownIsNull = asarVal == null || asarVal.kind === ts.SyntaxKind.NullKeyword;
|
|
603
|
+
const manual = (why) => this.warnings.push(`${name}.asar: ${why} In v27 a platform-level asar replaces the root one, so copy the root asar options into ${name}.asar` +
|
|
604
|
+
(unpackProp != null ? ` and move ${name}.asarUnpack there manually.` : " manually."));
|
|
605
|
+
// Shorthand / non-literal values cannot be reasoned about statically.
|
|
606
|
+
if ((asarProp != null && !ts.isPropertyAssignment(asarProp)) || (rootAsarProp != null && !ts.isPropertyAssignment(rootAsarProp))) {
|
|
607
|
+
if (unpackProp != null) {
|
|
608
|
+
manual("the platform or root asar value is not a literal.");
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (ownIsNull) {
|
|
613
|
+
if (unpackProp == null) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (rootAsarVal != null && rootAsarVal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
617
|
+
this.removeProp(unpackProp);
|
|
618
|
+
this.changes.push({ key: `${name}.asarUnpack`, description: `removed ${name}.asarUnpack (no effect with asar: false)` });
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const rootAsarIsTrivial = rootAsarVal == null ||
|
|
622
|
+
rootAsarVal.kind === ts.SyntaxKind.TrueKeyword ||
|
|
623
|
+
rootAsarVal.kind === ts.SyntaxKind.NullKeyword ||
|
|
624
|
+
(ts.isObjectLiteralExpression(rootAsarVal) && rootAsarVal.properties.length === 0);
|
|
625
|
+
if (rootHasFlat || !rootAsarIsTrivial) {
|
|
626
|
+
manual(`${name}.asarUnpack is set alongside root-level asar options.`);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (asarProp != null) {
|
|
630
|
+
this.removeProp(asarProp);
|
|
631
|
+
}
|
|
632
|
+
this.moveInto(platform, "asar", [{ prop: unpackProp, renameTo: "unpack" }], [], /* treatNullAsAbsent */ true);
|
|
633
|
+
this.changes.push({ key: `${name}.asarUnpack`, description: `moved ${name}.asarUnpack → ${name}.asar.unpack` });
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (asarVal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
637
|
+
if (unpackProp != null) {
|
|
638
|
+
this.removeProp(unpackProp);
|
|
639
|
+
this.changes.push({ key: `${name}.asarUnpack`, description: `removed ${name}.asarUnpack (no effect with ${name}.asar: false)` });
|
|
640
|
+
}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
// `asar: true` or an object literal
|
|
644
|
+
if (rootHasFlat) {
|
|
645
|
+
manual("root-level asarUnpack / disableSanityCheckAsar / disableAsarIntegrity applied to this platform in v26.");
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (unpackProp == null) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (asarVal.kind === ts.SyntaxKind.TrueKeyword) {
|
|
652
|
+
const braceIndent = this.lineIndentAt(this.start(asarProp.initializer));
|
|
653
|
+
this.replaceValue(asarProp.initializer, this.objectLiteralTextAt([this.entryText(unpackProp, braceIndent + this.indentUnit, "unpack")], braceIndent));
|
|
654
|
+
this.removeProp(unpackProp);
|
|
655
|
+
}
|
|
656
|
+
else if (ts.isObjectLiteralExpression(asarVal) && this.getProp(asarVal, "unpack") == null) {
|
|
657
|
+
this.moveInto(platform, "asar", [{ prop: unpackProp, renameTo: "unpack" }], []);
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
manual(`${name}.asar is not a plain object literal, or already sets unpack.`);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
this.changes.push({ key: `${name}.asarUnpack`, description: `moved ${name}.asarUnpack → ${name}.asar.unpack` });
|
|
664
|
+
}
|
|
492
665
|
ruleAppImageSystemIntegration(root) {
|
|
493
666
|
const appImage = this.getObjectProp(root, "appImage");
|
|
494
667
|
if (appImage == null) {
|
|
@@ -522,16 +695,34 @@ class ConfigCodemod {
|
|
|
522
695
|
continue;
|
|
523
696
|
}
|
|
524
697
|
const provider = this.stringLiteralValue(this.getProp(obj, "provider"));
|
|
525
|
-
|
|
526
|
-
if (
|
|
698
|
+
// GitLab keeps vPrefixedTagName in v27 (no tagNamePrefix equivalent) — leave it untouched.
|
|
699
|
+
if (provider !== "github") {
|
|
527
700
|
continue;
|
|
528
701
|
}
|
|
529
|
-
|
|
702
|
+
const vPrefixed = this.getProp(obj, "vPrefixedTagName");
|
|
703
|
+
const tagNamePrefix = this.getProp(obj, "tagNamePrefix");
|
|
704
|
+
const prefixText = this.stringLiteralValue(tagNamePrefix);
|
|
705
|
+
if (vPrefixed != null && ts.isPropertyAssignment(vPrefixed)) {
|
|
706
|
+
// v26 githubTagPrefix(): a non-empty tagNamePrefix won; otherwise vPrefixedTagName (default true) chose "v" or "".
|
|
530
707
|
const isFalse = vPrefixed.initializer.kind === ts.SyntaxKind.FalseKeyword;
|
|
531
|
-
|
|
708
|
+
if (tagNamePrefix == null) {
|
|
709
|
+
this.replaceRange(this.start(vPrefixed), vPrefixed.end, `tagNamePrefix: ${isFalse ? '""' : '"v"'}`);
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
if (prefixText === "") {
|
|
713
|
+
this.replaceValue(tagNamePrefix.initializer, isFalse ? '""' : '"v"');
|
|
714
|
+
}
|
|
715
|
+
this.removeProp(vPrefixed);
|
|
716
|
+
}
|
|
532
717
|
changed = true;
|
|
533
718
|
}
|
|
534
|
-
|
|
719
|
+
else if (prefixText === "") {
|
|
720
|
+
// v26 ignored an empty tagNamePrefix and still tagged "v1.2.3"; v27 honors it and would tag "1.2.3".
|
|
721
|
+
this.replaceValue(tagNamePrefix.initializer, '"v"');
|
|
722
|
+
changed = true;
|
|
723
|
+
this.warnings.push('A GitHub publish entry set tagNamePrefix: "". v26 ignored an empty prefix and tagged releases "v<version>"; v27 honors it. ' +
|
|
724
|
+
'It was rewritten to tagNamePrefix: "v" to keep your existing tag names — set it back to "" if you want unprefixed tags from now on.');
|
|
725
|
+
}
|
|
535
726
|
}
|
|
536
727
|
if (changed) {
|
|
537
728
|
this.changes.push({
|
|
@@ -552,6 +743,11 @@ class ConfigCodemod {
|
|
|
552
743
|
this.changes.push({ key: "snap", description: "removed empty snap config (use snapcraft in v27)" });
|
|
553
744
|
return;
|
|
554
745
|
}
|
|
746
|
+
// snap's properties are re-emitted verbatim below, so rulePublish skipped it — flag what it would have rewritten.
|
|
747
|
+
const snapPublish = this.getProp(snap, "publish");
|
|
748
|
+
if (snapPublish != null && /\bvPrefixedTagName\b|\btagNamePrefix\s*:\s*(""|'')/.test(this.text.slice(this.start(snapPublish), snapPublish.end))) {
|
|
749
|
+
this.warnings.push("snap.publish sets vPrefixedTagName or an empty tagNamePrefix; after the move, apply the GitHub tagNamePrefix change under snapcraft.<base>.publish manually.");
|
|
750
|
+
}
|
|
555
751
|
const baseProp = this.getProp(snap, "base");
|
|
556
752
|
let base = this.stringLiteralValue(baseProp);
|
|
557
753
|
const restProps = snap.properties.filter((p) => p !== baseProp);
|
|
@@ -574,8 +770,16 @@ class ConfigCodemod {
|
|
|
574
770
|
base = "core20";
|
|
575
771
|
assumed = true;
|
|
576
772
|
}
|
|
773
|
+
let nestedProps = restProps;
|
|
774
|
+
if (base === "core24") {
|
|
775
|
+
const unsupported = SNAP_CORE24_UNSUPPORTED.filter(k => this.getProp(snap, k) != null);
|
|
776
|
+
nestedProps = restProps.filter((p) => !unsupported.includes(this.propName(p)));
|
|
777
|
+
if (unsupported.length > 0) {
|
|
778
|
+
this.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.`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
577
781
|
const baseChildIndent = this.propIndentFor(root) + this.indentUnit + this.indentUnit;
|
|
578
|
-
const nestedEntries =
|
|
782
|
+
const nestedEntries = nestedProps.map((p) => this.entryText(p, baseChildIndent));
|
|
579
783
|
const snapcraftChildIndent = this.propIndentFor(root) + this.indentUnit;
|
|
580
784
|
const nestedObject = nestedEntries.length > 0 ? this.objectLiteralTextAt(nestedEntries, snapcraftChildIndent) : "{}";
|
|
581
785
|
const entries = [`base: "${base}"`, `${base}: ${nestedObject}`];
|
|
@@ -608,6 +812,12 @@ class ConfigCodemod {
|
|
|
608
812
|
if (sq == null) {
|
|
609
813
|
return;
|
|
610
814
|
}
|
|
815
|
+
// Not auto-migrated (different bundle layout) — left in place so the build fails with a targeted message.
|
|
816
|
+
if (this.getProp(sq, "customSquirrelVendorDir") != null) {
|
|
817
|
+
this.warnings.push("squirrelWindows.customSquirrelVendorDir was removed in v27 and cannot be migrated automatically. " +
|
|
818
|
+
'Supply the custom bundle via toolsets.squirrel: { url: "file:///abs/path" } — the bundle must contain an electron-winstaller/vendor/ subtree ' +
|
|
819
|
+
"(customSquirrelVendorDir pointed at the vendor files directly) — or remove the key to use the default Squirrel bundle.");
|
|
820
|
+
}
|
|
611
821
|
const noMsi = this.getProp(sq, "noMsi");
|
|
612
822
|
if (noMsi == null || !ts.isPropertyAssignment(noMsi)) {
|
|
613
823
|
return;
|
|
@@ -626,29 +836,48 @@ class ConfigCodemod {
|
|
|
626
836
|
if (win == null) {
|
|
627
837
|
return;
|
|
628
838
|
}
|
|
629
|
-
|
|
839
|
+
const signNode = this.getProp(win, "sign");
|
|
840
|
+
const existingSignIsNull = signNode != null && ts.isPropertyAssignment(signNode) && this.unwrap(signNode.initializer).kind === ts.SyntaxKind.NullKeyword;
|
|
841
|
+
// `sign: null` is "not configured" in both versions — it neither blocks nor survives a migration below.
|
|
842
|
+
let signSet = signNode != null && !existingSignIsNull;
|
|
843
|
+
// Either flag set to false skipped ALL signing in v26, so both map to win.sign: false (signAndEditExecutable
|
|
844
|
+
// also skipped resource editing, which always runs in v27 — that part has no equivalent).
|
|
845
|
+
const isFalseProp = (prop) => prop != null && ts.isPropertyAssignment(prop) && prop.initializer.kind === ts.SyntaxKind.FalseKeyword;
|
|
630
846
|
const signAndEdit = this.getProp(win, "signAndEditExecutable");
|
|
847
|
+
const signExe = this.getProp(win, "signExecutable");
|
|
848
|
+
const signingDisabled = isFalseProp(signAndEdit) || isFalseProp(signExe);
|
|
631
849
|
if (signAndEdit != null && ts.isPropertyAssignment(signAndEdit)) {
|
|
632
|
-
const isFalse = signAndEdit.initializer.kind === ts.SyntaxKind.FalseKeyword;
|
|
633
850
|
this.removeProp(signAndEdit);
|
|
634
|
-
if (
|
|
635
|
-
this.warnings.push("win.signAndEditExecutable: false was used to skip both resource editing and signing. In v27, resource editing always runs
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
this.changes.push({ key: "win.signAndEditExecutable", description: "removed win.signAndEditExecutable (resource editing always runs in v27; was the default)" });
|
|
851
|
+
if (isFalseProp(signAndEdit)) {
|
|
852
|
+
this.warnings.push("win.signAndEditExecutable: false was used to skip both resource editing and signing. In v27, resource editing always runs; signing is still skipped via win.sign: false. There is no v27 equivalent that also skips resource editing — apply resources manually if needed.");
|
|
639
853
|
}
|
|
854
|
+
this.changes.push({ key: "win.signAndEditExecutable", description: "removed win.signAndEditExecutable (resource editing always runs in v27)" });
|
|
640
855
|
}
|
|
641
|
-
const signExe = this.getProp(win, "signExecutable");
|
|
642
856
|
if (signExe != null && ts.isPropertyAssignment(signExe)) {
|
|
643
|
-
const isFalse = signExe.initializer.kind === ts.SyntaxKind.FalseKeyword;
|
|
644
857
|
this.removeProp(signExe);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
858
|
+
this.changes.push({ key: "win.signExecutable", description: "removed win.signExecutable (signing is enabled by default when credentials are available)" });
|
|
859
|
+
}
|
|
860
|
+
let signIsFalse = isFalseProp(signNode);
|
|
861
|
+
if (signingDisabled && !signSet) {
|
|
862
|
+
if (existingSignIsNull) {
|
|
863
|
+
this.removeProp(signNode);
|
|
864
|
+
}
|
|
865
|
+
this.createChild(win, "sign", [], "false");
|
|
866
|
+
signSet = true;
|
|
867
|
+
signIsFalse = true;
|
|
868
|
+
this.changes.push({ key: "win.sign", description: "set win.sign: false (v26 signExecutable/signAndEditExecutable: false disabled signing; resource editing still runs)" });
|
|
869
|
+
}
|
|
870
|
+
if (signIsFalse) {
|
|
871
|
+
// Signing is switched off, so the legacy options never applied. Only key names are reported — values can hold passwords.
|
|
872
|
+
const dropped = ["signtoolOptions", "azureSignOptions"].filter(k => this.getProp(win, k) != null);
|
|
873
|
+
if (dropped.length > 0) {
|
|
874
|
+
for (const k of dropped) {
|
|
875
|
+
this.removeProp(this.getProp(win, k));
|
|
876
|
+
this.changes.push({ key: `win.${k}`, description: `removed win.${k} (signing is disabled with win.sign: false)` });
|
|
877
|
+
}
|
|
878
|
+
this.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. ` +
|
|
879
|
+
'To sign in v27, replace win.sign: false with win.sign: { type: "signtool" | "azure", … } carrying those options.');
|
|
880
|
+
return;
|
|
652
881
|
}
|
|
653
882
|
}
|
|
654
883
|
const azure = this.getObjectProp(win, "azureSignOptions");
|
|
@@ -658,12 +887,13 @@ class ConfigCodemod {
|
|
|
658
887
|
if (!hasAzure && !hasSigntool) {
|
|
659
888
|
return;
|
|
660
889
|
}
|
|
661
|
-
|
|
662
|
-
const signAlreadySet = signSet || (originalSign != null && ts.isPropertyAssignment(originalSign) && originalSign.initializer.kind !== ts.SyntaxKind.NullKeyword);
|
|
663
|
-
if (signAlreadySet) {
|
|
890
|
+
if (signSet) {
|
|
664
891
|
this.warnings.push(`win.sign is already set alongside ${hasAzure ? "win.azureSignOptions" : "win.signtoolOptions"}. Remove the legacy key manually after verifying win.sign is correct.`);
|
|
665
892
|
return;
|
|
666
893
|
}
|
|
894
|
+
if (existingSignIsNull) {
|
|
895
|
+
this.removeProp(signNode);
|
|
896
|
+
}
|
|
667
897
|
if (hasAzure && hasSigntool) {
|
|
668
898
|
this.warnings.push("Both win.azureSignOptions and win.signtoolOptions are set. win.signtoolOptions will be dropped and win.azureSignOptions will be migrated to win.sign: { type: 'azure', … } (Azure took priority in v26). Verify the migrated win.sign block is correct for your project.");
|
|
669
899
|
this.removeProp(this.getProp(win, "signtoolOptions"));
|
|
@@ -740,6 +970,17 @@ class ConfigCodemod {
|
|
|
740
970
|
}
|
|
741
971
|
ruleMacSigning(platform, name) {
|
|
742
972
|
const ts = this.ts;
|
|
973
|
+
// Removed outright, not moved: ElectronSignOptions omits these and the schema rejects them.
|
|
974
|
+
for (const field of MAC_SIGN_REMOVED_FIELDS) {
|
|
975
|
+
const prop = this.getProp(platform, field);
|
|
976
|
+
if (prop != null) {
|
|
977
|
+
this.removeProp(prop);
|
|
978
|
+
this.changes.push({
|
|
979
|
+
key: `${name}.${field}`,
|
|
980
|
+
description: `removed ${name}.${field} (@electron/osx-sign 2.x dropped the spctl --assess step; there is no ${name}.sign.${field})`,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
743
984
|
const present = MAC_SIGN_FIELDS.filter(f => this.getProp(platform, f) != null);
|
|
744
985
|
const signIgnore = this.getProp(platform, "signIgnore");
|
|
745
986
|
const hasSignIgnore = signIgnore != null;
|
|
@@ -762,28 +1003,53 @@ class ConfigCodemod {
|
|
|
762
1003
|
const sources = [];
|
|
763
1004
|
for (const f of present) {
|
|
764
1005
|
const prop = this.getProp(platform, f);
|
|
1006
|
+
if (this.dropNullField(prop, f, name)) {
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
765
1009
|
sources.push({ prop });
|
|
766
1010
|
this.changes.push({ key: `${name}.${f}`, description: `moved ${name}.${f} → ${name}.sign.${f}` });
|
|
767
1011
|
}
|
|
768
|
-
if (hasSignIgnore) {
|
|
1012
|
+
if (hasSignIgnore && !this.dropNullField(signIgnore, "signIgnore", name)) {
|
|
769
1013
|
sources.push({ prop: signIgnore, renameTo: "ignore" });
|
|
770
1014
|
this.changes.push({ key: `${name}.signIgnore`, description: `renamed ${name}.signIgnore → ${name}.sign.ignore` });
|
|
771
1015
|
}
|
|
772
1016
|
// A bare `sign: null` should be dropped before we build the options object.
|
|
773
1017
|
if (signIsNull) {
|
|
774
1018
|
this.removeProp(existingSignProp);
|
|
1019
|
+
if (sources.length === 0) {
|
|
1020
|
+
this.changes.push({ key: `${name}.sign`, description: `removed ${name}.sign: null (v26 "no custom signer" = v27 default; sign: null now means skip signing)` });
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (sources.length > 0) {
|
|
1024
|
+
this.moveInto(platform, "sign", sources, [], /* treatNullAsAbsent */ true);
|
|
775
1025
|
}
|
|
776
|
-
|
|
1026
|
+
}
|
|
1027
|
+
/** Drops a v26 `null` ("unset") for a field whose v27 type has no null branch. Returns true when dropped. */
|
|
1028
|
+
dropNullField(prop, field, name) {
|
|
1029
|
+
const ts = this.ts;
|
|
1030
|
+
if (!MAC_NULL_MEANS_UNSET_FIELDS.has(field) || !ts.isPropertyAssignment(prop) || this.unwrap(prop.initializer).kind !== ts.SyntaxKind.NullKeyword) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
this.removeProp(prop);
|
|
1034
|
+
this.changes.push({ key: `${name}.${field}`, description: `removed ${name}.${field}: null (null meant "unset" in v26; the v27 option does not accept null)` });
|
|
1035
|
+
return true;
|
|
777
1036
|
}
|
|
778
1037
|
ruleMacUniversal(platform, name) {
|
|
779
1038
|
const present = MAC_UNIVERSAL_FIELDS.filter(f => this.getProp(platform, f) != null);
|
|
780
1039
|
if (present.length === 0) {
|
|
781
1040
|
return;
|
|
782
1041
|
}
|
|
783
|
-
const sources =
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1042
|
+
const sources = [];
|
|
1043
|
+
for (const f of present) {
|
|
1044
|
+
const prop = this.getProp(platform, f);
|
|
1045
|
+
if (!this.dropNullField(prop, f, name)) {
|
|
1046
|
+
this.changes.push({ key: `${name}.${f}`, description: `moved ${name}.${f} → ${name}.universal.${f}` });
|
|
1047
|
+
sources.push({ prop });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (sources.length === 0) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
787
1053
|
const ok = this.moveInto(platform, "universal", sources, []);
|
|
788
1054
|
if (!ok) {
|
|
789
1055
|
this.warnings.push(`${name}.universal already exists but is not an object literal; move ${present.join(", ")} into it manually.`);
|
|
@@ -791,8 +1057,22 @@ class ConfigCodemod {
|
|
|
791
1057
|
}
|
|
792
1058
|
ruleElectronDownload(root) {
|
|
793
1059
|
const ts = this.ts;
|
|
794
|
-
|
|
795
|
-
|
|
1060
|
+
let sourceKey = "electronDownload";
|
|
1061
|
+
let prop = this.getProp(root, "electronDownload");
|
|
1062
|
+
if (prop == null) {
|
|
1063
|
+
// A hand-renamed `electronGet` that still carries the v26 shape.
|
|
1064
|
+
const electronGet = this.getObjectProp(root, "electronGet");
|
|
1065
|
+
if (electronGet == null || !electronGet.properties.some((p) => isLegacyElectronDownloadKey(this.propName(p) ?? ""))) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
sourceKey = "electronGet";
|
|
1069
|
+
prop = this.getProp(root, "electronGet");
|
|
1070
|
+
}
|
|
1071
|
+
else if (this.getProp(root, "electronGet") != null) {
|
|
1072
|
+
this.warnings.push("Both electronDownload and electronGet are set; merge electronDownload into electronGet manually (mirror → mirrorOptions.mirror, isVerifyChecksum: false → unsafelyDisableChecksums: true).");
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
796
1076
|
return;
|
|
797
1077
|
}
|
|
798
1078
|
const old = this.unwrap(prop.initializer);
|
|
@@ -852,8 +1132,10 @@ class ConfigCodemod {
|
|
|
852
1132
|
this.removeProp(prop);
|
|
853
1133
|
this.createChild(root, "electronGet", entries);
|
|
854
1134
|
this.changes.push({
|
|
855
|
-
key:
|
|
856
|
-
description:
|
|
1135
|
+
key: sourceKey,
|
|
1136
|
+
description: sourceKey === "electronDownload"
|
|
1137
|
+
? "renamed electronDownload → electronGet (mirror → mirrorOptions.mirror; isVerifyChecksum → unsafelyDisableChecksums)"
|
|
1138
|
+
: "reshaped electronGet to the v27 options (mirror → mirrorOptions.mirror; isVerifyChecksum → unsafelyDisableChecksums)",
|
|
857
1139
|
});
|
|
858
1140
|
}
|
|
859
1141
|
// ── Edit primitives ───────────────────────────────────────────────────────
|