Initial commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dinlo
2026-05-31 18:44:04 +08:00
commit 436a9631fc
8616 changed files with 1389957 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
import { FileTransformer } from "builder-util";
import { AsarIntegrity } from "./asar/integrity";
import { AfterPackContext, ElectronPlatformName, Platform, PlatformPackager } from "./index";
export interface Framework {
readonly name: string;
readonly version: string;
readonly distMacOsAppName: string;
readonly macOsDefaultTargets: Array<string>;
readonly defaultAppIdPrefix: string;
readonly isNpmRebuildRequired: boolean;
readonly isCopyElevateHelper: boolean;
getDefaultIcon?(platform: Platform): string | null;
getMainFile?(platform: Platform): string | null;
getExcludedDependencies?(platform: Platform): Array<string> | null;
prepareApplicationStageDirectory(options: PrepareApplicationStageDirectoryOptions): Promise<any>;
beforeCopyExtraFiles?(options: BeforeCopyExtraFilesOptions): Promise<any>;
afterPack?(context: AfterPackContext): Promise<any>;
createTransformer?(): FileTransformer | null;
}
export interface BeforeCopyExtraFilesOptions {
packager: PlatformPackager<any>;
appOutDir: string;
asarIntegrity: AsarIntegrity | null;
platformName: string;
}
export interface PrepareApplicationStageDirectoryOptions {
readonly packager: PlatformPackager<any>;
/**
* Platform doesn't process application output directory in any way. Unpack implementation must create or empty dir if need.
*/
readonly appOutDir: string;
readonly platformName: ElectronPlatformName;
readonly arch: string;
readonly version: string;
}
export declare function isElectronBased(framework: Framework): boolean;
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isElectronBased = isElectronBased;
function isElectronBased(framework) {
return framework.name === "electron";
}
//# sourceMappingURL=Framework.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Framework.js","sourceRoot":"","sources":["../src/Framework.ts"],"names":[],"mappings":";;AAmDA,0CAEC;AAFD,SAAgB,eAAe,CAAC,SAAoB;IAClD,OAAO,SAAS,CAAC,IAAI,KAAK,UAAU,CAAA;AACtC,CAAC","sourcesContent":["import { FileTransformer } from \"builder-util\"\nimport { AsarIntegrity } from \"./asar/integrity\"\nimport { AfterPackContext, ElectronPlatformName, Platform, PlatformPackager } from \"./index\"\n\nexport interface Framework {\n readonly name: string\n readonly version: string\n readonly distMacOsAppName: string\n readonly macOsDefaultTargets: Array<string>\n readonly defaultAppIdPrefix: string\n\n readonly isNpmRebuildRequired: boolean\n\n readonly isCopyElevateHelper: boolean\n\n getDefaultIcon?(platform: Platform): string | null\n\n getMainFile?(platform: Platform): string | null\n\n getExcludedDependencies?(platform: Platform): Array<string> | null\n\n prepareApplicationStageDirectory(options: PrepareApplicationStageDirectoryOptions): Promise<any>\n\n beforeCopyExtraFiles?(options: BeforeCopyExtraFilesOptions): Promise<any>\n\n afterPack?(context: AfterPackContext): Promise<any>\n\n createTransformer?(): FileTransformer | null\n}\n\nexport interface BeforeCopyExtraFilesOptions {\n packager: PlatformPackager<any>\n appOutDir: string\n\n asarIntegrity: AsarIntegrity | null\n\n // ElectronPlatformName\n platformName: string\n}\n\nexport interface PrepareApplicationStageDirectoryOptions {\n readonly packager: PlatformPackager<any>\n /**\n * Platform doesn't process application output directory in any way. Unpack implementation must create or empty dir if need.\n */\n readonly appOutDir: string\n readonly platformName: ElectronPlatformName\n readonly arch: string\n readonly version: string\n}\n\nexport function isElectronBased(framework: Framework): boolean {\n return framework.name === \"electron\"\n}\n"]}
+10
View File
@@ -0,0 +1,10 @@
import { FileTransformer } from "builder-util";
import { Platform } from "./core";
import { LibUiFramework } from "./frameworks/LibUiFramework";
export declare class ProtonFramework extends LibUiFramework {
readonly name = "proton";
readonly defaultAppIdPrefix = "com.proton-native.";
constructor(version: string, distMacOsAppName: string, isUseLaunchUi: boolean);
getDefaultIcon(platform: Platform): string;
createTransformer(): FileTransformer | null;
}
+94
View File
@@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtonFramework = void 0;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const core_1 = require("./core");
const fileTransformer_1 = require("./fileTransformer");
const LibUiFramework_1 = require("./frameworks/LibUiFramework");
const pathManager_1 = require("./util/pathManager");
class ProtonFramework extends LibUiFramework_1.LibUiFramework {
constructor(version, distMacOsAppName, isUseLaunchUi) {
super(version, distMacOsAppName, isUseLaunchUi);
this.name = "proton";
// noinspection JSUnusedGlobalSymbols
this.defaultAppIdPrefix = "com.proton-native.";
}
getDefaultIcon(platform) {
if (platform === core_1.Platform.WINDOWS) {
return (0, pathManager_1.getTemplatePath)("icons/proton-native/proton-native.ico");
}
else if (platform === core_1.Platform.LINUX) {
return (0, pathManager_1.getTemplatePath)("icons/proton-native/linux");
}
else {
return (0, pathManager_1.getTemplatePath)("icons/proton-native/proton-native.icns");
}
}
createTransformer() {
let babel;
const babelOptions = { ast: false, sourceMaps: "inline" };
if (process.env.TEST_SET_BABEL_PRESET === "true") {
babel = require("@babel/core");
babel = testOnlyBabel(babel, babelOptions, this.version);
}
else {
try {
babel = require("babel-core");
}
catch (_e) {
// babel isn't installed
builder_util_1.log.debug(null, "don't transpile source code using Babel");
return null;
}
}
builder_util_1.log.info({
options: (0, builder_util_runtime_1.safeStringifyJson)(babelOptions, new Set(["presets"])),
}, "transpile source code using Babel");
return (file) => {
if (!(file.endsWith(".js") || file.endsWith(".jsx")) || file.includes(fileTransformer_1.NODE_MODULES_PATTERN)) {
return null;
}
return new Promise((resolve, reject) => {
return babel.transformFile(file, babelOptions, (error, result) => {
if (error == null) {
resolve(result.code);
}
else {
reject(error);
}
});
});
};
}
}
exports.ProtonFramework = ProtonFramework;
function testOnlyBabel(babel, babelOptions, nodeVersion) {
// out test dir can be located outside of electron-builder node_modules and babel cannot resolve string names of preset
babelOptions.presets = [[require("@babel/preset-env").default, { targets: { node: nodeVersion } }], require("@babel/preset-react")];
babelOptions.plugins = [
// stage 0
require("@babel/plugin-proposal-function-bind").default,
// stage 1
require("@babel/plugin-proposal-export-default-from").default,
require("@babel/plugin-proposal-logical-assignment-operators").default,
[require("@babel/plugin-proposal-optional-chaining").default, { loose: false }],
[require("@babel/plugin-proposal-pipeline-operator").default, { proposal: "minimal" }],
[require("@babel/plugin-proposal-nullish-coalescing-operator").default, { loose: false }],
require("@babel/plugin-proposal-do-expressions").default,
// stage 2
[require("@babel/plugin-proposal-decorators").default, { legacy: true }],
require("@babel/plugin-proposal-function-sent").default,
require("@babel/plugin-proposal-export-namespace-from").default,
require("@babel/plugin-proposal-numeric-separator").default,
require("@babel/plugin-proposal-throw-expressions").default,
// stage 3
require("@babel/plugin-syntax-dynamic-import").default,
require("@babel/plugin-syntax-import-meta").default,
[require("@babel/plugin-proposal-class-properties").default, { loose: false }],
require("@babel/plugin-proposal-json-strings").default,
];
babelOptions.babelrc = false;
return babel;
}
//# sourceMappingURL=ProtonFramework.js.map
File diff suppressed because one or more lines are too long
+31
View File
@@ -0,0 +1,31 @@
import { Nullish } from "builder-util-runtime";
import { PlatformSpecificBuildOptions } from "./options/PlatformSpecificBuildOptions";
import { Packager } from "./packager";
export declare function smarten(s: string): string;
export declare class AppInfo {
private readonly info;
private readonly platformSpecificOptions;
readonly description: string;
readonly version: string;
readonly type: string | undefined;
readonly shortVersion: string | undefined;
readonly shortVersionWindows: string | undefined;
readonly buildNumber: string | undefined;
readonly buildVersion: string;
readonly productName: string;
readonly sanitizedProductName: string;
readonly productFilename: string;
constructor(info: Packager, buildVersion: string | Nullish, platformSpecificOptions?: PlatformSpecificBuildOptions | null, normalizeNfd?: boolean);
get channel(): string | null;
getVersionInWeirdWindowsForm(isSetBuildNumber?: boolean): string;
private get notNullDevMetadata();
get companyName(): string | null;
get id(): string;
get macBundleIdentifier(): string;
get name(): string;
get linuxPackageName(): string;
get sanitizedName(): string;
get updaterCacheDirName(): string;
get copyright(): string;
computePackageUrl(): Promise<string | null>;
}
+152
View File
@@ -0,0 +1,152 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppInfo = void 0;
exports.smarten = smarten;
exports.filterCFBundleIdentifier = filterCFBundleIdentifier;
const builder_util_1 = require("builder-util");
const filename_1 = require("builder-util/out/filename");
const semver_1 = require("semver");
const macroExpander_1 = require("./util/macroExpander");
// fpm bug - rpm build --description is not escaped, well... decided to replace quite to smart quote
// http://leancrew.com/all-this/2010/11/smart-quotes-in-javascript/
function smarten(s) {
// opening singles
s = s.replace(/(^|[-\u2014\s(["])'/g, "$1\u2018");
// closing singles & apostrophes
s = s.replace(/'/g, "\u2019");
// opening doubles
s = s.replace(/(^|[-\u2014/[(\u2018\s])"/g, "$1\u201c");
// closing doubles
s = s.replace(/"/g, "\u201d");
return s;
}
class AppInfo {
constructor(info, buildVersion, platformSpecificOptions = null, normalizeNfd = false) {
var _a;
this.info = info;
this.platformSpecificOptions = platformSpecificOptions;
this.description = smarten(this.info.metadata.description || "");
this.version = info.metadata.version;
this.type = info.metadata.type;
if (buildVersion == null) {
buildVersion = info.config.buildVersion;
}
const buildNumberEnvs = process.env.BUILD_NUMBER ||
process.env.TRAVIS_BUILD_NUMBER ||
process.env.APPVEYOR_BUILD_NUMBER ||
process.env.CIRCLE_BUILD_NUM ||
process.env.BUILD_BUILDNUMBER ||
process.env.CI_PIPELINE_IID;
this.buildNumber = info.config.buildNumber || buildNumberEnvs;
if (buildVersion == null) {
buildVersion = this.version;
if (!(0, builder_util_1.isEmptyOrSpaces)(this.buildNumber)) {
buildVersion += `.${this.buildNumber}`;
}
}
this.buildVersion = buildVersion;
if (info.metadata.shortVersion) {
this.shortVersion = info.metadata.shortVersion;
}
if (info.metadata.shortVersionWindows) {
this.shortVersionWindows = info.metadata.shortVersionWindows;
}
this.productName = info.config.productName || info.metadata.productName || info.metadata.name;
this.sanitizedProductName = (0, filename_1.sanitizeFileName)(this.productName, normalizeNfd);
const executableName = (_a = platformSpecificOptions === null || platformSpecificOptions === void 0 ? void 0 : platformSpecificOptions.executableName) !== null && _a !== void 0 ? _a : info.config.executableName;
this.productFilename = executableName != null ? (0, filename_1.sanitizeFileName)(executableName, normalizeNfd) : this.sanitizedProductName;
}
get channel() {
const prereleaseInfo = (0, semver_1.prerelease)(this.version);
if (prereleaseInfo != null && prereleaseInfo.length > 0) {
return prereleaseInfo[0];
}
return null;
}
getVersionInWeirdWindowsForm(isSetBuildNumber = true) {
const [major, maybe_minor, maybe_patch] = this.version.split(".").map(versionPart => parseInt(versionPart));
// The major component must be present. Here it can be either NaN or undefined, which
// both returns true from isNaN.
if (isNaN(major)) {
throw new Error(`Invalid major number in: ${this.version}`);
}
// Allow missing version parts. Minor and patch can be left out and default to zero
const minor = maybe_minor !== null && maybe_minor !== void 0 ? maybe_minor : 0;
const patch = maybe_patch !== null && maybe_patch !== void 0 ? maybe_patch : 0;
// ... but reject non-integer version parts. '1.a' is not going to fly
if (isNaN(minor) || isNaN(patch)) {
throw new Error(`Invalid minor or patch number in: ${this.version}`);
}
// https://github.com/electron-userland/electron-builder/issues/2635#issuecomment-371792272
let buildNumber = isSetBuildNumber ? this.buildNumber : null;
if (buildNumber == null || !/^\d+$/.test(buildNumber)) {
buildNumber = "0";
}
return `${major}.${minor}.${patch}.${buildNumber}`;
}
get notNullDevMetadata() {
return this.info.devMetadata || {};
}
get companyName() {
const author = this.info.metadata.author || this.notNullDevMetadata.author;
return author == null ? null : author.name;
}
get id() {
let appId = null;
for (const options of [this.platformSpecificOptions, this.info.config]) {
if (options != null && appId == null) {
appId = options.appId;
}
}
const generateDefaultAppId = () => {
const info = this.info;
return `${info.framework.defaultAppIdPrefix}${info.metadata.name.toLowerCase()}`;
};
if (appId != null && (appId === "your.id" || (0, builder_util_1.isEmptyOrSpaces)(appId))) {
const incorrectAppId = appId;
appId = generateDefaultAppId();
builder_util_1.log.warn(`do not use "${incorrectAppId}" as appId, "${appId}" will be used instead`);
}
return appId == null ? generateDefaultAppId() : appId;
}
get macBundleIdentifier() {
return filterCFBundleIdentifier(this.id);
}
get name() {
return this.info.metadata.name;
}
get linuxPackageName() {
const name = this.name;
// https://github.com/electron-userland/electron-builder/issues/2963
return name.startsWith("@") ? this.sanitizedProductName : name;
}
get sanitizedName() {
return (0, filename_1.sanitizeFileName)(this.name);
}
get updaterCacheDirName() {
return this.sanitizedName.toLowerCase() + "-updater";
}
get copyright() {
const copyright = this.info.config.copyright;
if (copyright != null) {
return (0, macroExpander_1.expandMacro)(copyright, null, this);
}
return `Copyright © ${new Date().getFullYear()} ${this.companyName || this.productName}`;
}
async computePackageUrl() {
const url = this.info.metadata.homepage || this.notNullDevMetadata.homepage;
if (url != null) {
return url;
}
const info = await this.info.repositoryInfo;
return info == null || info.type !== "github" ? null : `https://${info.domain}/${info.user}/${info.project}`;
}
}
exports.AppInfo = AppInfo;
/** @internal */
function filterCFBundleIdentifier(identifier) {
// Remove special characters and allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.)
// Apple documentation: https://developer.apple.com/library/mac/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070
return identifier.replace(/ /g, "-").replace(/[^a-zA-Z0-9.-]/g, "");
}
//# sourceMappingURL=appInfo.js.map
File diff suppressed because one or more lines are too long
+38
View File
@@ -0,0 +1,38 @@
import { Stats } from "fs-extra";
export interface ReadAsarHeader {
readonly header: string;
readonly size: number;
}
export interface NodeIntegrity {
algorithm: "SHA256";
hash: string;
blockSize: number;
blocks: Array<string>;
}
export declare class Node {
files?: Record<string, Node>;
unpacked?: boolean;
size?: number;
offset?: string;
executable?: boolean;
link?: string;
integrity?: NodeIntegrity;
}
export declare class AsarFilesystem {
readonly src: string;
readonly header: Node;
readonly headerSize: number;
private offset;
constructor(src: string, header?: Node, headerSize?: number);
searchNodeFromDirectory(p: string, isCreate: boolean): Node | null;
getOrCreateNode(p: string): Node;
addFileNode(file: string, dirNode: Node, size: number, unpacked: boolean, stat: Stats, integrity?: NodeIntegrity): Node;
getNode(p: string): Node | null;
getFile(p: string, followLinks?: boolean): Node;
readJson(file: string): Promise<any>;
readFile(file: string): Promise<Buffer>;
private newNode;
}
export declare function readAsarHeader(archive: string): Promise<ReadAsarHeader>;
export declare function readAsar(archive: string): Promise<AsarFilesystem>;
export declare function readAsarJson(archive: string, file: string): Promise<any>;
+163
View File
@@ -0,0 +1,163 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsarFilesystem = exports.Node = void 0;
exports.readAsarHeader = readAsarHeader;
exports.readAsar = readAsar;
exports.readAsarJson = readAsarJson;
const chromium_pickle_js_1 = require("chromium-pickle-js");
const fs_extra_1 = require("fs-extra");
const path = require("path");
class Node {
}
exports.Node = Node;
class AsarFilesystem {
constructor(src, header = new Node(), headerSize = -1) {
this.src = src;
this.header = header;
this.headerSize = headerSize;
this.offset = 0;
if (this.header.files == null) {
this.header.files = this.newNode();
}
}
searchNodeFromDirectory(p, isCreate) {
let node = this.header;
for (const dir of p.split(path.sep)) {
if (dir !== ".") {
if (node == null) {
throw new Error(`Cannot find node for path: ${p} (node is null at ${dir})`);
}
let child = node.files[dir];
if (child == null) {
if (!isCreate) {
return null;
}
child = new Node();
child.files = this.newNode();
node.files[dir] = child;
}
node = child;
}
}
return node;
}
getOrCreateNode(p) {
if (p == null || p.length === 0) {
return this.header;
}
const name = path.basename(p);
const dirNode = this.searchNodeFromDirectory(path.dirname(p), true);
if (dirNode == null) {
throw new Error(`Cannot find node for path: ${p} (node is null at ${path.dirname(p)})`);
}
if (dirNode.files == null) {
dirNode.files = this.newNode();
}
let result = dirNode.files[name];
if (result == null) {
result = new Node();
dirNode.files[name] = result;
}
return result;
}
addFileNode(file, dirNode, size, unpacked, stat, integrity) {
if (size > 4294967295) {
throw new Error(`${file}: file size cannot be larger than 4.2GB`);
}
const node = new Node();
node.size = size;
if (integrity) {
node.integrity = integrity;
}
if (unpacked) {
node.unpacked = true;
}
else {
// electron expects string
node.offset = this.offset.toString();
if (process.platform !== "win32" && stat.mode & 0o100) {
node.executable = true;
}
this.offset += node.size;
}
let children = dirNode.files;
if (children == null) {
children = this.newNode();
dirNode.files = children;
}
children[path.basename(file)] = node;
return node;
}
getNode(p) {
const node = this.searchNodeFromDirectory(path.dirname(p), false);
if (node == null) {
throw new Error(`Cannot find node for path: ${p} (node is null at ${path.dirname(p)})`);
}
return node.files[path.basename(p)];
}
getFile(p, followLinks = true) {
const info = this.getNode(p);
// if followLinks is false we don't resolve symlinks
return followLinks && info.link != null ? this.getFile(info.link) : info;
}
async readJson(file) {
return JSON.parse((await this.readFile(file)).toString());
}
readFile(file) {
return readFileFromAsar(this, file, this.getFile(file));
}
newNode() {
return Object.create(null);
}
}
exports.AsarFilesystem = AsarFilesystem;
async function readAsarHeader(archive) {
const fd = await (0, fs_extra_1.open)(archive, "r");
let size;
let headerBuf;
try {
const sizeBuf = Buffer.allocUnsafe(8);
if ((await (0, fs_extra_1.read)(fd, sizeBuf, 0, 8, null)).bytesRead !== 8) {
throw new Error("Unable to read header size");
}
const sizePickle = (0, chromium_pickle_js_1.createFromBuffer)(sizeBuf);
size = sizePickle.createIterator().readUInt32();
headerBuf = Buffer.allocUnsafe(size);
if ((await (0, fs_extra_1.read)(fd, headerBuf, 0, size, null)).bytesRead !== size) {
throw new Error("Unable to read header");
}
}
finally {
await (0, fs_extra_1.close)(fd);
}
const headerPickle = (0, chromium_pickle_js_1.createFromBuffer)(headerBuf);
return { header: headerPickle.createIterator().readString(), size };
}
async function readAsar(archive) {
const { header, size } = await readAsarHeader(archive);
return new AsarFilesystem(archive, JSON.parse(header), size);
}
async function readAsarJson(archive, file) {
const fs = await readAsar(archive);
return await fs.readJson(file);
}
async function readFileFromAsar(filesystem, filename, info) {
const size = info.size;
const buffer = Buffer.allocUnsafe(size);
if (size <= 0) {
return buffer;
}
if (info.unpacked) {
return await (0, fs_extra_1.readFile)(path.join(`${filesystem.src}.unpacked`, filename));
}
const fd = await (0, fs_extra_1.open)(filesystem.src, "r");
try {
const offset = 8 + filesystem.headerSize + parseInt(info.offset, 10);
await (0, fs_extra_1.read)(fd, buffer, 0, size, offset);
}
finally {
await (0, fs_extra_1.close)(fd);
}
return buffer;
}
//# sourceMappingURL=asar.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
import { FilesystemEntry } from "@electron/asar/lib/filesystem";
export declare function checkFileInArchive(asarFile: string, relativeFile: string, messagePrefix: string): FilesystemEntry;
+24
View File
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkFileInArchive = checkFileInArchive;
const asar = require("@electron/asar");
function checkFileInArchive(asarFile, relativeFile, messagePrefix) {
function error(text) {
return new Error(`${messagePrefix} "${relativeFile}" in the "${asarFile}" ${text}`);
}
let stat;
try {
stat = asar.statFile(asarFile, relativeFile, false);
}
catch (e) {
if (e.message.includes("Cannot read properties of undefined (reading 'link')")) {
throw error("does not exist. Seems like a wrong configuration.");
}
throw error(`is corrupted: ${e}`);
}
if (stat.size === 0) {
throw error(`is corrupted: size 0`);
}
return stat;
}
//# sourceMappingURL=asarFileChecker.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"asarFileChecker.js","sourceRoot":"","sources":["../../src/asar/asarFileChecker.ts"],"names":[],"mappings":";;AAGA,gDAiBC;AApBD,uCAAsC;AAGtC,SAAgB,kBAAkB,CAAC,QAAgB,EAAE,YAAoB,EAAE,aAAqB;IAC9F,SAAS,KAAK,CAAC,IAAY;QACzB,OAAO,IAAI,KAAK,CAAC,GAAG,aAAa,KAAK,YAAY,aAAa,QAAQ,KAAK,IAAI,EAAE,CAAC,CAAA;IACrF,CAAC;IACD,IAAI,IAAqB,CAAA;IACzB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,CAAA;IACrD,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,sDAAsD,CAAC,EAAE,CAAC;YAC/E,MAAM,KAAK,CAAC,mDAAmD,CAAC,CAAA;QAClE,CAAC;QACD,MAAM,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAA;IACnC,CAAC;IACD,IAAK,IAA4B,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import * as asar from \"@electron/asar\"\nimport { FilesystemEntry, FilesystemFileEntry } from \"@electron/asar/lib/filesystem\"\n\nexport function checkFileInArchive(asarFile: string, relativeFile: string, messagePrefix: string) {\n function error(text: string) {\n return new Error(`${messagePrefix} \"${relativeFile}\" in the \"${asarFile}\" ${text}`)\n }\n let stat: FilesystemEntry\n try {\n stat = asar.statFile(asarFile, relativeFile, false)\n } catch (e: any) {\n if (e.message.includes(\"Cannot read properties of undefined (reading 'link')\")) {\n throw error(\"does not exist. Seems like a wrong configuration.\")\n }\n throw error(`is corrupted: ${e}`)\n }\n if ((stat as FilesystemFileEntry).size === 0) {\n throw error(`is corrupted: size 0`)\n }\n return stat\n}\n"]}
+1
View File
@@ -0,0 +1 @@
export {};
+284
View File
@@ -0,0 +1,284 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsarPackager = void 0;
const asar_1 = require("@electron/asar");
const builder_util_1 = require("builder-util");
const fs_1 = require("builder-util/out/fs");
const fs = require("fs-extra");
const fs_extra_1 = require("fs-extra");
const path = require("path");
const appFileCopier_1 = require("../util/appFileCopier");
const unpackDetector_1 = require("./unpackDetector");
const stream_1 = require("stream");
const os = require("os");
const resolvePath = async (file) => (file && (await (0, fs_1.exists)(file)) ? fs.realpath(file).catch(() => path.resolve(file)) : undefined);
const resolvePaths = async (filepaths) => {
return Promise.all(filepaths.map(resolvePath)).then(paths => paths.filter((it) => it != null));
};
const DENYLIST = resolvePaths([
"/usr",
"/lib",
"/bin",
"/sbin",
"/etc",
"/tmp",
"/var", // block whole /var by default. If $HOME is under /var, it's explicitly in ALLOWLIST - https://github.com/electron-userland/electron-builder/issues/9025#issuecomment-3575380041
// macOS system directories
"/System",
"/Library",
"/private",
// Windows system directories
process.env.SystemRoot,
process.env.WINDIR,
]);
const ALLOWLIST = resolvePaths([
os.tmpdir(), // always allow temp dir
os.homedir(), // always allow home dir
]);
/** @internal */
class AsarPackager {
constructor(packager, config) {
this.packager = packager;
this.config = config;
this.outFile = path.join(config.resourcePath, `app.asar`);
}
async pack(fileSets) {
const orderedFileSets = [
// Write dependencies first to minimize offset changes to asar header
...fileSets.slice(1),
// Finish with the app files that change most often
fileSets[0],
].map(set => this.orderFileSet(set));
const streams = await this.processFileSets(orderedFileSets);
await this.executeElectronAsar(streams);
}
async executeElectronAsar(streams) {
// override logger temporarily to clean up console (electron/asar does some internal logging that blogs up the default electron-builder logs)
const consoleLogger = console.log;
console.log = (...args) => {
if (args[0] === "Ordering file has 100% coverage.") {
return; // no need to log, this means our ordering logic is working correctly
}
builder_util_1.log.info({ args }, "logging @electron/asar");
};
await (0, asar_1.createPackageFromStreams)(this.outFile, streams);
console.log = consoleLogger;
}
async processFileSets(fileSets) {
var _a;
const unpackedPaths = new Set();
if (this.config.options.smartUnpack !== false) {
for (const fileSet of fileSets) {
(0, unpackDetector_1.detectUnpackedDirs)(fileSet, unpackedPaths);
}
}
const resultsMap = new Map();
const streamOrdering = [];
const normalizedUnpackedPaths = Array.from(unpackedPaths).map(p => path.normalize(p));
// Check whether a file or directory should be unpacked, using pre-normalized unpacked paths and early returns
const isUnpacked = (dir, file, stat) => {
var _a, _b;
const normalizedDir = path.normalize(dir);
// Check file pattern first (most specific)
if (!(0, builder_util_1.isEmptyOrSpaces)(file) && stat && ((_b = (_a = this.config).unpackPattern) === null || _b === void 0 ? void 0 : _b.call(_a, file, stat))) {
return true;
}
// Check if path is within any unpacked directory
for (const unpackedPath of normalizedUnpackedPaths) {
if (normalizedDir === unpackedPath || normalizedDir.startsWith(unpackedPath + path.sep)) {
return true;
}
}
return false;
};
// First pass: process all files in order, ensuring parent directories exist
for (const fileSet of fileSets) {
// Don't use Promise.all, we need to retain order of execution/iteration through the already-ordered fileset
for (const [index, file] of fileSet.files.entries()) {
const transformedData = (_a = fileSet.transformedFiles) === null || _a === void 0 ? void 0 : _a.get(index);
const stat = fileSet.metadata.get(file);
const destination = path.relative(this.config.defaultDestination, (0, appFileCopier_1.getDestinationPath)(file, fileSet));
// Ensure parent directories exist before processing file
this.ensureParentDirectories(destination, resultsMap, streamOrdering);
const result = await this.processFileOrSymlink({
file,
destination,
fileSet,
transformedData,
stat,
isUnpacked,
});
if (result && !resultsMap.has(result.path)) {
resultsMap.set(result.path, result);
streamOrdering.push(result.path);
}
}
}
// Second pass: propagate unpacked flag to parent directories
for (const entry of resultsMap.values()) {
if (entry.unpacked) {
this.markParentDirectoriesAsUnpacked(entry.path, resultsMap, isUnpacked);
}
}
// Build final results array maintaining processing order
return streamOrdering.reduce((streams, path) => {
const stream = resultsMap.has(path) ? resultsMap.get(path) : null;
if (stream != null) {
streams.push(stream);
}
return streams;
}, []);
}
ensureParentDirectories(destination, resultsMap, streamOrdering) {
const parents = [];
let current = path.dirname(path.normalize(destination));
// Collect all parent directories from deepest to root
while (current !== ".") {
parents.unshift(current);
current = path.dirname(current);
}
// Add parent directories in order (root to deepest)
for (const parentPath of parents) {
if (!resultsMap.has(parentPath)) {
const dir = {
type: "directory",
path: parentPath,
unpacked: false, // Updated in second pass if needed
};
resultsMap.set(parentPath, dir);
streamOrdering.push(parentPath);
}
}
}
markParentDirectoriesAsUnpacked(destination, resultsMap, isUnpacked) {
let current = path.dirname(path.normalize(destination));
while (current !== ".") {
const entry = resultsMap.get(current);
if (entry && isUnpacked(current)) {
entry.unpacked = true;
}
current = path.dirname(current);
}
}
async processFileOrSymlink(options) {
const { isUnpacked, transformedData, file, destination, stat } = options;
const unpacked = isUnpacked(destination, file, stat);
// Handle directories
if (!stat.isFile() && !stat.isSymbolicLink()) {
return { path: destination, unpacked, type: "directory" };
}
// Handle transformed data (pre-processed content)
if (transformedData != null) {
const size = Buffer.byteLength(transformedData);
return {
path: destination,
streamGenerator: () => new stream_1.Readable({
read() {
this.push(transformedData);
this.push(null);
},
}),
unpacked,
type: "file",
stat: { mode: stat.mode, size },
};
}
// verify that the file is not a direct link or symlinked to access/copy a system file
await this.protectSystemAndUnsafePaths(file, await this.packager.info.getWorkspaceRoot());
const baseConfig = {
path: destination,
streamGenerator: () => fs.createReadStream(file),
unpacked,
stat,
};
// Handle regular files
if (!stat.isSymbolicLink()) {
return { ...baseConfig, type: "file" };
}
// Handle symlinks - make relative to source location
let link = await (0, fs_extra_1.readlink)(file);
if (path.isAbsolute(link)) {
link = path.relative(path.dirname(file), link);
}
return {
...baseConfig,
type: "link",
symlink: link,
};
}
orderFileSet(fileSet) {
const sortedFileEntries = Array.from(fileSet.files.entries());
sortedFileEntries.sort(([, a], [, b]) => {
if (a === b) {
return 0;
}
// Place addons last because their signature changes per build
const isAAddon = a.endsWith(".node");
const isBAddon = b.endsWith(".node");
if (isAAddon && !isBAddon) {
return 1;
}
if (isBAddon && !isAAddon) {
return -1;
}
// Otherwise order by name
return a < b ? -1 : 1;
});
let transformedFiles;
if (fileSet.transformedFiles) {
transformedFiles = new Map();
const indexMap = new Map();
for (const [newIndex, [oldIndex]] of sortedFileEntries.entries()) {
indexMap.set(oldIndex, newIndex);
}
for (const [oldIndex, value] of fileSet.transformedFiles) {
const newIndex = indexMap.get(oldIndex);
if (newIndex === undefined) {
throw new Error(`Internal error: ${fileSet.files[oldIndex]} was lost while ordering asar`);
}
transformedFiles.set(newIndex, value);
}
}
return {
src: fileSet.src,
destination: fileSet.destination,
metadata: fileSet.metadata,
files: sortedFileEntries.map(([, file]) => file),
transformedFiles,
};
}
async checkAgainstRoots(target, allowRoots) {
const resolved = await resolvePath(target);
if (resolved == null || (0, builder_util_1.isEmptyOrSpaces)(resolved)) {
return false;
}
for (const root of allowRoots) {
if (resolved === root || resolved.startsWith(root + path.sep)) {
return true;
}
}
return false;
}
async protectSystemAndUnsafePaths(file, workspaceRoot) {
const resolved = await resolvePath(file);
const logFields = { source: file, realPath: resolved };
const workspace = await resolvePath(workspaceRoot);
// If in workspace, always safe
if (workspace && (resolved === null || resolved === void 0 ? void 0 : resolved.startsWith(workspace))) {
return;
}
// Check allowlist (priority)
if (await this.checkAgainstRoots(file, await ALLOWLIST)) {
return;
}
// Check denylist
if (await this.checkAgainstRoots(file, await DENYLIST)) {
builder_util_1.log.error(logFields, `denied access to system or unsafe path`);
throw new Error(`Cannot copy file [${file}] symlinked to file [${resolved}] outside the package to a system or unsafe path`);
}
// Default: outside explicit paths but not explicitly denied
builder_util_1.log.debug(logFields, `path is outside of explicit safe paths, defaulting to safe`);
}
}
exports.AsarPackager = AsarPackager;
//# sourceMappingURL=asarUtil.js.map
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
import { FileMatcher } from "../fileMatcher";
export interface AsarIntegrityOptions {
readonly resourcesPath: string;
readonly resourcesRelativePath: string;
readonly resourcesDestinationPath: string;
readonly extraResourceMatchers: Array<FileMatcher> | null;
}
export interface HeaderHash {
algorithm: "SHA256";
hash: string;
}
export interface AsarIntegrity {
[key: string]: HeaderHash;
}
export declare function computeData({ resourcesPath, resourcesRelativePath, resourcesDestinationPath, extraResourceMatchers }: AsarIntegrityOptions): Promise<AsarIntegrity>;
+62
View File
@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computeData = computeData;
const builder_util_1 = require("builder-util");
const crypto_1 = require("crypto");
const promises_1 = require("fs/promises");
const path = require("path");
const asar_1 = require("./asar");
async function computeData({ resourcesPath, resourcesRelativePath, resourcesDestinationPath, extraResourceMatchers }) {
const isAsar = (filepath) => filepath.endsWith(".asar");
const resources = await (0, promises_1.readdir)(resourcesPath);
const resourceAsars = resources.filter(isAsar).reduce((prev, filename) => ({
...prev,
[path.join(resourcesRelativePath, filename)]: path.join(resourcesPath, filename),
}), {});
const extraResources = await Promise.all((extraResourceMatchers !== null && extraResourceMatchers !== void 0 ? extraResourceMatchers : []).map(async (matcher) => {
const { from, to } = matcher;
const stat = await (0, builder_util_1.statOrNull)(from);
if (stat == null) {
builder_util_1.log.warn({ from }, `file source doesn't exist`);
return [];
}
if (stat.isFile()) {
return [{ from, to }];
}
if (matcher.isEmpty() || matcher.containsOnlyIgnore()) {
matcher.prependPattern("**/*");
}
const matcherFilter = matcher.createFilter();
const extraResourceMatches = await (0, builder_util_1.walk)(matcher.from, (file, stats) => matcherFilter(file, stats) || stats.isDirectory());
return extraResourceMatches.map(from => ({ from, to: matcher.to }));
}));
const extraResourceAsars = extraResources
.flat(1)
.filter(match => isAsar(match.from))
.reduce((prev, { to, from }) => {
const prefix = path.relative(resourcesDestinationPath, to);
return {
...prev,
[path.join(resourcesRelativePath, prefix, path.basename(from))]: from,
};
}, {});
// sort to produce constant result
const allAsars = [...Object.entries(resourceAsars), ...Object.entries(extraResourceAsars)].sort(([name1], [name2]) => name1.localeCompare(name2));
const hashes = await Promise.all(allAsars.map(async ([, from]) => hashHeader(from)));
const asarIntegrity = {};
for (let i = 0; i < allAsars.length; i++) {
const [asar] = allAsars[i];
asarIntegrity[asar] = hashes[i];
}
return asarIntegrity;
}
async function hashHeader(file) {
const hash = (0, crypto_1.createHash)("sha256");
const { header } = await (0, asar_1.readAsarHeader)(file);
hash.update(header);
return {
algorithm: "SHA256",
hash: hash.digest("hex"),
};
}
//# sourceMappingURL=integrity.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export declare function isLibOrExe(file: string): boolean;
+43
View File
@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLibOrExe = isLibOrExe;
exports.detectUnpackedDirs = detectUnpackedDirs;
const builder_util_1 = require("builder-util");
const isbinaryfile_1 = require("isbinaryfile");
const path = require("path");
function isLibOrExe(file) {
// https://github.com/electron-userland/electron-builder/issues/3038
return file.endsWith(".dll") || file.endsWith(".exe") || file.endsWith(".dylib") || file.endsWith(".so") || file.endsWith(".node");
}
/** @internal */
function detectUnpackedDirs(fileSet, autoUnpackDirs) {
const metadata = fileSet.metadata;
for (let i = 0, n = fileSet.files.length; i < n; i++) {
const file = fileSet.files[i];
const stat = metadata.get(file);
if (!stat.moduleRootPath || autoUnpackDirs.has(stat.moduleRootPath)) {
continue;
}
if (!stat.isFile()) {
continue;
}
// https://github.com/electron-userland/electron-builder/issues/2679
let shouldUnpack = false;
// ffprobe-static and ffmpeg-static are known packages to always unpack
const moduleName = stat.moduleName;
const fileBaseName = path.basename(file);
const hasExtension = path.extname(fileBaseName);
if (moduleName === "ffprobe-static" || moduleName === "ffmpeg-static" || isLibOrExe(file)) {
shouldUnpack = true;
}
else if (!hasExtension) {
shouldUnpack = !!(0, isbinaryfile_1.isBinaryFileSync)(file);
}
if (!shouldUnpack) {
continue;
}
builder_util_1.log.debug({ file: stat.moduleFullFilePath, reason: "contains executable code" }, "not packed into asar archive");
autoUnpackDirs.add(stat.moduleRootPath);
}
}
//# sourceMappingURL=unpackDetector.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"unpackDetector.js","sourceRoot":"","sources":["../../src/asar/unpackDetector.ts"],"names":[],"mappings":";;AAKA,gCAGC;AAGD,gDAiCC;AA5CD,+CAA+C;AAC/C,+CAA+C;AAC/C,6BAA4B;AAG5B,SAAgB,UAAU,CAAC,IAAY;IACrC,oEAAoE;IACpE,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;AACpI,CAAC;AAED,gBAAgB;AAChB,SAAgB,kBAAkB,CAAC,OAAwB,EAAE,cAA2B;IACtF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC7B,MAAM,IAAI,GAAgB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAE,CAAA;QAC7C,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;YACpE,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACnB,SAAQ;QACV,CAAC;QAED,oEAAoE;QACpE,IAAI,YAAY,GAAG,KAAK,CAAA;QACxB,uEAAuE;QACvE,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAClC,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACxC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QAC/C,IAAI,UAAU,KAAK,gBAAgB,IAAI,UAAU,KAAK,eAAe,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1F,YAAY,GAAG,IAAI,CAAA;QACrB,CAAC;aAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YACzB,YAAY,GAAG,CAAC,CAAC,IAAA,+BAAgB,EAAC,IAAI,CAAC,CAAA;QACzC,CAAC;QAED,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,SAAQ;QACV,CAAC;QAED,kBAAG,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,EAAE,0BAA0B,EAAE,EAAE,8BAA8B,CAAC,CAAA;QAChH,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IACzC,CAAC;AACH,CAAC","sourcesContent":["import { FilterStats, log } from \"builder-util\"\nimport { isBinaryFileSync } from \"isbinaryfile\"\nimport * as path from \"path\"\nimport { ResolvedFileSet } from \"../util/appFileCopier\"\n\nexport function isLibOrExe(file: string): boolean {\n // https://github.com/electron-userland/electron-builder/issues/3038\n return file.endsWith(\".dll\") || file.endsWith(\".exe\") || file.endsWith(\".dylib\") || file.endsWith(\".so\") || file.endsWith(\".node\")\n}\n\n/** @internal */\nexport function detectUnpackedDirs(fileSet: ResolvedFileSet, autoUnpackDirs: Set<string>) {\n const metadata = fileSet.metadata\n\n for (let i = 0, n = fileSet.files.length; i < n; i++) {\n const file = fileSet.files[i]\n const stat: FilterStats = metadata.get(file)!\n if (!stat.moduleRootPath || autoUnpackDirs.has(stat.moduleRootPath)) {\n continue\n }\n\n if (!stat.isFile()) {\n continue\n }\n\n // https://github.com/electron-userland/electron-builder/issues/2679\n let shouldUnpack = false\n // ffprobe-static and ffmpeg-static are known packages to always unpack\n const moduleName = stat.moduleName\n const fileBaseName = path.basename(file)\n const hasExtension = path.extname(fileBaseName)\n if (moduleName === \"ffprobe-static\" || moduleName === \"ffmpeg-static\" || isLibOrExe(file)) {\n shouldUnpack = true\n } else if (!hasExtension) {\n shouldUnpack = !!isBinaryFileSync(file)\n }\n\n if (!shouldUnpack) {\n continue\n }\n\n log.debug({ file: stat.moduleFullFilePath, reason: \"contains executable code\" }, \"not packed into asar archive\")\n autoUnpackDirs.add(stat.moduleRootPath)\n }\n}\n"]}
+21
View File
@@ -0,0 +1,21 @@
/**
* Deterministic <length>-character URL-safe hash (a–z0–9)
*/
export declare function hashUrlSafe(input: string, length?: number): string;
/**
* Get cache directory for electron-builder
*/
export declare function getCacheDirectory(isAvoidSystemOnWindows?: boolean): string;
/**
* Downloads an artifact from GitHub releases (convenience wrapper)
*/
export declare function downloadArtifact(options: {
releaseName: string;
filenameWithExt: string;
checksums: Record<string, string>;
githubOrgRepo?: string;
}): Promise<string>;
export declare function download(url: string, output: string, checksum?: string | null): Promise<void>;
export declare function getBinFromCustomLoc(name: string, version: string, binariesLocUrl: string, checksum: string): Promise<string>;
export declare function getBinFromUrl(releaseName: string, filenameWithExt: string, checksum: string, githubOrgRepo?: string): Promise<string>;
export declare function getBin(cacheKey: string, url?: string | null, checksum?: string | null): Promise<string>;
+218
View File
@@ -0,0 +1,218 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hashUrlSafe = hashUrlSafe;
exports.getCacheDirectory = getCacheDirectory;
exports.downloadArtifact = downloadArtifact;
exports.download = download;
exports.getBinFromCustomLoc = getBinFromCustomLoc;
exports.getBinFromUrl = getBinFromUrl;
exports.getBin = getBin;
const get = require("@electron/get");
const get_1 = require("@electron/get");
const builder_util_1 = require("builder-util");
const filename_1 = require("builder-util/out/filename");
const multiProgress_1 = require("electron-publish/out/multiProgress");
const fs = require("fs/promises");
const os = require("os");
const path = require("path");
const lockfile = require("proper-lockfile");
const tar = require("tar");
/**
* Deterministic <length>-character URL-safe hash (a–z0–9)
*/
function hashUrlSafe(input, length = 6) {
let hash = 5381;
for (let i = 0; i < input.length; i++) {
hash = ((hash << 5) + hash) ^ input.charCodeAt(i); // hash * 33 ^ c
}
// Force unsigned 32-bit
hash >>>= 0;
// Base-36 (0–9a–z)
const out = hash.toString(36);
// Ensure exactly `length` chars
if (out.length >= length) {
return out.slice(0, length);
}
return out.padStart(length, "0");
}
/**
* Get cache directory for electron-builder
*/
function getCacheDirectory(isAvoidSystemOnWindows = false) {
var _a, _b, _c, _d, _e;
const env = (_a = process.env.ELECTRON_BUILDER_CACHE) === null || _a === void 0 ? void 0 : _a.trim();
if (env) {
return env;
}
const appName = "electron-builder";
const platform = os.platform();
const homeDir = os.homedir();
if (platform === "darwin") {
return path.join(homeDir, "Library", "Caches", appName);
}
if (platform === "win32") {
const localAppData = (_b = process.env.LOCALAPPDATA) === null || _b === void 0 ? void 0 : _b.trim();
const username = (_d = (_c = process.env.USERNAME) === null || _c === void 0 ? void 0 : _c.trim()) === null || _d === void 0 ? void 0 : _d.toLowerCase();
const isSystemUser = isAvoidSystemOnWindows && (((_e = localAppData === null || localAppData === void 0 ? void 0 : localAppData.toLowerCase()) === null || _e === void 0 ? void 0 : _e.includes("\\windows\\system32\\")) || username === "system");
if (!localAppData || isSystemUser) {
return path.join(os.tmpdir(), `${appName}-cache`);
}
return path.join(localAppData, appName, "Cache");
}
// linux
const xdgCache = process.env.XDG_CACHE_HOME;
if (xdgCache) {
return path.join(xdgCache, appName);
}
return path.join(homeDir, ".cache", appName);
}
/**
* Downloads an artifact from GitHub releases (convenience wrapper)
*/
async function downloadArtifact(options) {
const { releaseName, filenameWithExt, checksums, githubOrgRepo = "electron-userland/electron-builder-binaries" } = options;
const file = await _downloadArtifact(`https://github.com/${githubOrgRepo}/releases/download/`, releaseName, filenameWithExt, checksums);
return file;
}
/**
* Downloads, validates, and extracts a .tar.gz from a release URL
*/
async function _downloadArtifact(baseUrl, releaseName, filenameWithExt, checksums) {
var _a;
const suffix = hashUrlSafe(`${baseUrl}-${releaseName}-${filenameWithExt}`, 5);
const folderName = `${filenameWithExt.replace(/\.(tar\.gz|tgz)$/, "")}-${suffix}`;
const extractDir = path.join(getCacheDirectory(), releaseName, folderName);
const extractionCompleteMarker = `${extractDir}.complete`;
// Ensure download directory exists before trying to lock
await fs.mkdir(extractDir, { recursive: true });
// Acquire the lock
let release;
try {
release = await lockfile.lock(extractDir, {
retries: {
retries: 5,
minTimeout: 1000,
maxTimeout: 5000,
},
stale: 60000,
});
const varName = "ELECTRON_DOWNLOAD_CACHE_MODE";
const cacheOverride = (_a = process.env[varName]) === null || _a === void 0 ? void 0 : _a.trim();
let cacheMode = get_1.ElectronDownloadCacheMode.ReadWrite;
if (cacheOverride && Number(cacheOverride) in get_1.ElectronDownloadCacheMode) {
cacheMode = Number(cacheOverride);
builder_util_1.log.debug({ mode: cacheMode }, `cache mode overridden via env var ${varName}`);
}
if (await (0, builder_util_1.exists)(extractionCompleteMarker)) {
builder_util_1.log.debug({ file: filenameWithExt, path: extractDir }, "using cached artifact - skipping download/extract");
return extractDir;
}
// These are just stubs. Actual url construction/file naming are in `mirrorOptions` below.
const details = {
// Needs to be higher than 1.3.2 to avoid @electron/get validation shortcut
// https://github.com/electron/get/blob/05c466d4fc60fa0c83064df28dce245eb83d63c9/src/index.ts#L60
version: "9.9.9",
artifactName: filenameWithExt, // also is the output filename
};
const progress = process.stdout.isTTY ? new multiProgress_1.MultiProgress() : null;
const progressBar = progress === null || progress === void 0 ? void 0 : progress.createBar(`${" ".repeat(builder_util_1.PADDING + 2)}[:bar] :percent | ${filenameWithExt}`, { total: 100 });
const downloadOptions = {
getProgressCallback: info => {
progressBar === null || progressBar === void 0 ? void 0 : progressBar.update(info.percent != null ? Math.floor(info.percent * 100) : 0);
return Promise.resolve();
},
};
const options = {
cacheRoot: path.resolve(getCacheDirectory(), "downloads"),
cacheMode,
downloadOptions,
checksums,
mirrorOptions: {
// `${opts.mirror}${opts.customDir}/${opts.customFilename}`
mirror: baseUrl,
customDir: releaseName,
customFilename: filenameWithExt,
},
};
builder_util_1.log.info({ release: releaseName, file: filenameWithExt }, "downloading");
progressBar === null || progressBar === void 0 ? void 0 : progressBar.render();
const downloadedFile = await get.downloadArtifact({
...details,
...options,
isGeneric: true,
});
await tar.extract({
file: downloadedFile,
cwd: extractDir,
strip: 1, // Strip the top-level directory from the archive
});
// Write the extraction complete marker file to indicate successful extraction and prevent future re-extraction
await fs.writeFile(extractionCompleteMarker, "");
builder_util_1.log.debug({ file: filenameWithExt, path: extractDir }, "downloaded");
progressBar === null || progressBar === void 0 ? void 0 : progressBar.update(100);
progressBar === null || progressBar === void 0 ? void 0 : progressBar.terminate();
return extractDir;
}
finally {
// Release the lock
if (release) {
await release();
}
}
}
const versionToPromise = new Map();
function download(url, output, checksum) {
const args = ["download", "--url", url, "--output", output];
if (checksum != null) {
args.push("--sha512", checksum);
}
return (0, builder_util_1.executeAppBuilder)(args);
}
function getBinFromCustomLoc(name, version, binariesLocUrl, checksum) {
const dirName = `${name}-${version}`;
return getBin(dirName, binariesLocUrl, checksum);
}
function getBinFromUrl(releaseName, filenameWithExt, checksum, githubOrgRepo = "electron-userland/electron-builder-binaries") {
let url;
if (process.env.ELECTRON_BUILDER_BINARIES_DOWNLOAD_OVERRIDE_URL) {
url = process.env.ELECTRON_BUILDER_BINARIES_DOWNLOAD_OVERRIDE_URL + "/" + filenameWithExt;
}
else {
const baseUrl = process.env.NPM_CONFIG_ELECTRON_BUILDER_BINARIES_MIRROR ||
process.env.npm_config_electron_builder_binaries_mirror ||
process.env.npm_package_config_electron_builder_binaries_mirror ||
process.env.ELECTRON_BUILDER_BINARIES_MIRROR ||
`https://github.com/${githubOrgRepo}/releases/download/`;
const middleUrl = process.env.NPM_CONFIG_ELECTRON_BUILDER_BINARIES_CUSTOM_DIR ||
process.env.npm_config_electron_builder_binaries_custom_dir ||
process.env.npm_package_config_electron_builder_binaries_custom_dir ||
process.env.ELECTRON_BUILDER_BINARIES_CUSTOM_DIR ||
releaseName;
url = `${baseUrl}${middleUrl}/${filenameWithExt}`;
}
const cacheKey = `${releaseName}-${path.basename(filenameWithExt, path.extname(filenameWithExt))}`;
return getBin(cacheKey, url, checksum);
}
function getBin(cacheKey, url, checksum) {
var _a;
// Old cache is ignored if cache environment variable changes
const cacheName = (0, filename_1.sanitizeFileName)(`${(_a = process.env.ELECTRON_BUILDER_CACHE) !== null && _a !== void 0 ? _a : ""}${cacheKey}`);
let promise = versionToPromise.get(cacheName); // if rejected, we will try to download again
if (promise != null) {
return promise;
}
promise = doGetBin(cacheKey, url, checksum);
versionToPromise.set(cacheName, promise);
return promise;
}
function doGetBin(name, url, checksum) {
const args = ["download-artifact", "--name", name];
if (url != null) {
args.push("--url", url);
}
if (checksum != null) {
args.push("--sha512", checksum);
}
return (0, builder_util_1.executeAppBuilder)(args);
}
//# sourceMappingURL=binDownload.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
import { TmpDir } from "temp-file";
/** @private */
export declare function importCertificate(cscLink: string, tmpDir: TmpDir, currentDir: string): Promise<string>;
+49
View File
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.importCertificate = importCertificate;
const builder_util_1 = require("builder-util");
const fs_extra_1 = require("fs-extra");
const os_1 = require("os");
const path = require("path");
const binDownload_1 = require("../binDownload");
/** @private */
async function importCertificate(cscLink, tmpDir, currentDir) {
var _a, _b;
cscLink = cscLink.trim();
let file = null;
if ((cscLink.length > 3 && cscLink[1] === ":") || cscLink.startsWith("/") || cscLink.startsWith(".")) {
file = cscLink;
}
else if (cscLink.startsWith("file://")) {
file = cscLink.substring("file://".length);
}
else if (cscLink.startsWith("~/")) {
file = path.join((0, os_1.homedir)(), cscLink.substring("~/".length));
}
else if (cscLink.startsWith("https://")) {
const tempFile = await tmpDir.getTempFile({ suffix: ".p12" });
await (0, binDownload_1.download)(cscLink, tempFile);
return tempFile;
}
else {
const mimeType = (_a = /data:.*;base64,/.exec(cscLink)) === null || _a === void 0 ? void 0 : _a[0];
if (mimeType || cscLink.length > 2048 || cscLink.endsWith("=")) {
const tempFile = await tmpDir.getTempFile({ suffix: ".p12" });
await (0, fs_extra_1.outputFile)(tempFile, Buffer.from(cscLink.substring((_b = mimeType === null || mimeType === void 0 ? void 0 : mimeType.length) !== null && _b !== void 0 ? _b : 0), "base64"));
return tempFile;
}
file = cscLink;
}
file = path.resolve(currentDir, file);
const stat = await (0, builder_util_1.statOrNull)(file);
if (stat == null) {
throw new builder_util_1.InvalidConfigurationError(`${file} doesn't exist`);
}
else if (!stat.isFile()) {
throw new builder_util_1.InvalidConfigurationError(`${file} not a file`);
}
else {
return file;
}
}
//# sourceMappingURL=codesign.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"codesign.js","sourceRoot":"","sources":["../../src/codeSign/codesign.ts"],"names":[],"mappings":";;AAQA,8CAiCC;AAzCD,+CAAoE;AACpE,uCAAqC;AACrC,2BAA4B;AAC5B,6BAA4B;AAE5B,gDAAyC;AAEzC,eAAe;AACR,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc,EAAE,UAAkB;;IACzF,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;IAExB,IAAI,IAAI,GAAkB,IAAI,CAAA;IAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,IAAI,GAAG,OAAO,CAAA;IAChB,CAAC;SAAM,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,CAAC;SAAM,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAA,YAAO,GAAE,EAAE,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7D,CAAC;SAAM,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7D,MAAM,IAAA,sBAAQ,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QACjC,OAAO,QAAQ,CAAA;IACjB,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,MAAA,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,0CAAG,CAAC,CAAC,CAAA;QACrD,IAAI,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7D,MAAM,IAAA,qBAAU,EAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM,mCAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;YAC3F,OAAO,QAAQ,CAAA;QACjB,CAAC;QACD,IAAI,GAAG,OAAO,CAAA;IAChB,CAAC;IAED,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IACrC,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAU,EAAC,IAAI,CAAC,CAAA;IACnC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,wCAAyB,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAA;IAC9D,CAAC;SAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1B,MAAM,IAAI,wCAAyB,CAAC,GAAG,IAAI,aAAa,CAAC,CAAA;IAC3D,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC","sourcesContent":["import { InvalidConfigurationError, statOrNull } from \"builder-util\"\nimport { outputFile } from \"fs-extra\"\nimport { homedir } from \"os\"\nimport * as path from \"path\"\nimport { TmpDir } from \"temp-file\"\nimport { download } from \"../binDownload\"\n\n/** @private */\nexport async function importCertificate(cscLink: string, tmpDir: TmpDir, currentDir: string): Promise<string> {\n cscLink = cscLink.trim()\n\n let file: string | null = null\n if ((cscLink.length > 3 && cscLink[1] === \":\") || cscLink.startsWith(\"/\") || cscLink.startsWith(\".\")) {\n file = cscLink\n } else if (cscLink.startsWith(\"file://\")) {\n file = cscLink.substring(\"file://\".length)\n } else if (cscLink.startsWith(\"~/\")) {\n file = path.join(homedir(), cscLink.substring(\"~/\".length))\n } else if (cscLink.startsWith(\"https://\")) {\n const tempFile = await tmpDir.getTempFile({ suffix: \".p12\" })\n await download(cscLink, tempFile)\n return tempFile\n } else {\n const mimeType = /data:.*;base64,/.exec(cscLink)?.[0]\n if (mimeType || cscLink.length > 2048 || cscLink.endsWith(\"=\")) {\n const tempFile = await tmpDir.getTempFile({ suffix: \".p12\" })\n await outputFile(tempFile, Buffer.from(cscLink.substring(mimeType?.length ?? 0), \"base64\"))\n return tempFile\n }\n file = cscLink\n }\n\n file = path.resolve(currentDir, file)\n const stat = await statOrNull(file)\n if (stat == null) {\n throw new InvalidConfigurationError(`${file} doesn't exist`)\n } else if (!stat.isFile()) {\n throw new InvalidConfigurationError(`${file} not a file`)\n } else {\n return file\n }\n}\n"]}
+28
View File
@@ -0,0 +1,28 @@
import { SignOptions } from "@electron/osx-sign/dist/cjs/types";
import { TmpDir } from "builder-util";
import { Nullish } from "builder-util-runtime";
export declare const appleCertificatePrefixes: string[];
export type CertType = "Developer ID Application" | "Developer ID Installer" | "3rd Party Mac Developer Application" | "3rd Party Mac Developer Installer" | "Mac Developer" | "Apple Development" | "Apple Distribution";
export interface CodeSigningInfo {
keychainFile?: string | null;
}
export declare function isSignAllowed(isPrintWarn?: boolean): boolean;
export declare function reportError(isMas: boolean, certificateTypes: CertType[], qualifier: string | Nullish, keychainFile: string | Nullish, isForceCodeSigning: boolean): Promise<void>;
export interface CreateKeychainOptions {
tmpDir: TmpDir;
cscLink: string;
cscKeyPassword: string;
cscILink?: string | null;
cscIKeyPassword?: string | null;
currentDir: string;
}
export declare function removeKeychain(keychainFile: string, printWarn?: boolean): Promise<any>;
export declare function createKeychain({ tmpDir, cscLink, cscKeyPassword, cscILink, cscIKeyPassword, currentDir }: CreateKeychainOptions): Promise<CodeSigningInfo>;
export declare function sign(opts: SignOptions): Promise<void>;
export declare let findIdentityRawResult: Promise<Array<string>> | null;
export declare class Identity {
readonly name: string;
readonly hash?: string;
constructor(name: string, hash?: string);
}
export declare function findIdentity(certType: CertType, qualifier?: string | null, keychain?: string | null): Promise<Identity | null>;
+283
View File
@@ -0,0 +1,283 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findIdentityRawResult = exports.appleCertificatePrefixes = void 0;
exports.isSignAllowed = isSignAllowed;
exports.reportError = reportError;
exports.removeKeychain = removeKeychain;
exports.createKeychain = createKeychain;
exports.sign = sign;
exports.findIdentity = findIdentity;
const osx_sign_1 = require("@electron/osx-sign");
const util_identities_1 = require("@electron/osx-sign/dist/cjs/util-identities");
const builder_util_1 = require("builder-util");
const crypto_1 = require("crypto");
const promises_1 = require("fs/promises");
const lazy_val_1 = require("lazy-val");
const os_1 = require("os");
const path = require("path");
const temp_file_1 = require("temp-file");
const flags_1 = require("../util/flags");
const codesign_1 = require("./codesign");
exports.appleCertificatePrefixes = ["Developer ID Application:", "Developer ID Installer:", "3rd Party Mac Developer Application:", "3rd Party Mac Developer Installer:"];
function isSignAllowed(isPrintWarn = true) {
if (process.platform !== "darwin") {
if (isPrintWarn) {
builder_util_1.log.warn({ reason: "supported only on macOS" }, "skipped macOS application code signing");
}
return false;
}
const buildForPrWarning = "There are serious security concerns with CSC_FOR_PULL_REQUEST=true (see the CircleCI documentation (https://circleci.com/docs/1.0/fork-pr-builds/) for details)" +
"\nIf you have SSH keys, sensitive env vars or AWS credentials stored in your project settings and untrusted forks can make pull requests against your repo, then this option isn't for you.";
if ((0, builder_util_1.isPullRequest)()) {
if ((0, builder_util_1.isEnvTrue)(process.env.CSC_FOR_PULL_REQUEST)) {
if (isPrintWarn) {
builder_util_1.log.warn(buildForPrWarning);
}
}
else {
if (isPrintWarn) {
// https://github.com/electron-userland/electron-builder/issues/1524
builder_util_1.log.warn("Current build is a part of pull request, code signing will be skipped." + "\nSet env CSC_FOR_PULL_REQUEST to true to force code signing." + `\n${buildForPrWarning}`);
}
return false;
}
}
return true;
}
async function reportError(isMas, certificateTypes, qualifier, keychainFile, isForceCodeSigning) {
const logFields = {};
if (qualifier == null) {
logFields.reason = "";
if ((0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
logFields.reason += `cannot find valid "${certificateTypes.join(", ")}" identity${isMas ? "" : ` or custom non-Apple code signing certificate, it could cause some undefined behaviour, e.g. macOS localized description not visible`}`;
}
logFields.reason += ", see https://electron.build/code-signing";
if (!(0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
logFields.CSC_IDENTITY_AUTO_DISCOVERY = false;
}
}
else {
logFields.reason = "Identity name is specified, but no valid identity with this name in the keychain";
logFields.identity = qualifier;
}
const args = ["find-identity"];
if (keychainFile != null) {
args.push(keychainFile);
}
if (qualifier != null || (0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
logFields.allIdentities = (await (0, builder_util_1.exec)("/usr/bin/security", args))
.trim()
.split("\n")
.filter(it => !(it.includes("Policy: X.509 Basic") || it.includes("Matching identities")))
.join("\n");
}
const skipMessage = "skipped macOS application code signing";
if (isMas || isForceCodeSigning) {
throw new Error(builder_util_1.Logger.createMessage(skipMessage, logFields, "error", it => it));
}
else {
builder_util_1.log.warn(logFields, skipMessage);
}
}
// "Note that filename will not be searched to resolve the signing identity's certificate chain unless it is also on the user's keychain search list."
// but "security list-keychains" doesn't support add - we should 1) get current list 2) set new list - it is very bad http://stackoverflow.com/questions/10538942/add-a-keychain-to-search-list
// "overly complicated and introduces a race condition."
// https://github.com/electron-userland/electron-builder/issues/398
const bundledCertKeychainAdded = new lazy_val_1.Lazy(async () => {
// copy to temp and then atomic rename to final path
const cacheDir = getCacheDirectory();
const tmpKeychainPath = path.join(cacheDir, (0, temp_file_1.getTempName)("electron-builder-root-certs"));
const keychainPath = path.join(cacheDir, "electron-builder-root-certs.keychain");
const results = await Promise.all([
listUserKeychains(),
(0, builder_util_1.copyFile)(path.join(__dirname, "..", "..", "certs", "root_certs.keychain"), tmpKeychainPath).then(() => (0, promises_1.rename)(tmpKeychainPath, keychainPath)),
]);
const list = results[0];
if (!list.includes(keychainPath)) {
await (0, builder_util_1.exec)("/usr/bin/security", ["list-keychains", "-d", "user", "-s", keychainPath].concat(list));
}
});
function getCacheDirectory() {
const env = process.env.ELECTRON_BUILDER_CACHE;
return (0, builder_util_1.isEmptyOrSpaces)(env) ? path.join((0, os_1.homedir)(), "Library", "Caches", "electron-builder") : path.resolve(env);
}
function listUserKeychains() {
return (0, builder_util_1.exec)("/usr/bin/security", ["list-keychains", "-d", "user"]).then(it => it
.split("\n")
.map(it => {
const r = it.trim();
return r.substring(1, r.length - 1);
})
.filter(it => it.length > 0));
}
function removeKeychain(keychainFile, printWarn = true) {
return (0, builder_util_1.exec)("/usr/bin/security", ["delete-keychain", keychainFile]).catch((e) => {
if (printWarn) {
builder_util_1.log.warn({ file: keychainFile, error: e.stack || e }, "cannot delete keychain");
}
return (0, builder_util_1.unlinkIfExists)(keychainFile);
});
}
async function createKeychain({ tmpDir, cscLink, cscKeyPassword, cscILink, cscIKeyPassword, currentDir }) {
// travis has correct AppleWWDRCA cert
if (process.env.TRAVIS !== "true") {
await bundledCertKeychainAdded.value;
}
// https://github.com/electron-userland/electron-builder/issues/3685
// use constant file
const keychainFile = path.join(process.env.APP_BUILDER_TMP_DIR || (0, os_1.tmpdir)(), `${(0, crypto_1.createHash)("sha256").update(currentDir).update("app-builder").digest("hex")}.keychain`);
// noinspection JSUnusedLocalSymbols
await removeKeychain(keychainFile, false).catch(_ => {
/* ignore*/
});
const certLinks = [cscLink];
if (cscILink != null) {
certLinks.push(cscILink);
}
const certPaths = new Array(certLinks.length);
const keychainPassword = (0, crypto_1.randomBytes)(32).toString("base64");
const securityCommands = [
["create-keychain", "-p", keychainPassword, keychainFile],
["unlock-keychain", "-p", keychainPassword, keychainFile],
["set-keychain-settings", keychainFile],
];
// https://stackoverflow.com/questions/42484678/codesign-keychain-gets-ignored
// https://github.com/electron-userland/electron-builder/issues/1457
const list = await listUserKeychains();
if (!list.includes(keychainFile)) {
securityCommands.push(["list-keychains", "-d", "user", "-s", keychainFile].concat(list));
}
await Promise.all([
// we do not clear downloaded files - will be removed on tmpDir cleanup automatically. not a security issue since in any case data is available as env variables and protected by password.
...certLinks.map((link, i) => (0, codesign_1.importCertificate)(link, tmpDir, currentDir).then(it => (certPaths[i] = it))),
// queue each security command
securityCommands.reduce((promise, cmd) => promise.then(() => (0, builder_util_1.exec)("/usr/bin/security", cmd)), new Promise(resolve => resolve(null))),
]);
const cscPasswords = [cscKeyPassword];
if (cscIKeyPassword != null) {
cscPasswords.push(cscIKeyPassword);
}
return await importCerts(keychainFile, certPaths, cscPasswords);
}
async function importCerts(keychainFile, paths, keyPasswords) {
var _a;
for (let i = 0; i < paths.length; i++) {
const password = (_a = keyPasswords[i]) !== null && _a !== void 0 ? _a : "";
await (0, builder_util_1.exec)("/usr/bin/security", ["import", paths[i], "-k", keychainFile, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", password]);
// https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
// https://github.com/electron-userland/electron-packager/issues/701#issuecomment-322315996
await (0, builder_util_1.exec)("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", password, keychainFile]);
}
return {
keychainFile,
};
}
async function sign(opts) {
return (0, builder_util_1.retry)(() => (0, osx_sign_1.signAsync)(opts), {
retries: 3,
interval: 5000,
backoff: 5000,
});
}
exports.findIdentityRawResult = null;
async function getValidIdentities(keychain) {
function addKeychain(args) {
if (keychain != null) {
args.push(keychain);
}
return args;
}
let result = exports.findIdentityRawResult;
if (result == null || keychain != null) {
// https://github.com/electron-userland/electron-builder/issues/481
// https://github.com/electron-userland/electron-builder/issues/535
result = Promise.all([
(0, builder_util_1.exec)("/usr/bin/security", addKeychain(["find-identity", "-v"])).then(it => it
.trim()
.split("\n")
.filter(it => {
for (const prefix of exports.appleCertificatePrefixes) {
if (it.includes(prefix)) {
return true;
}
}
return false;
})),
(0, builder_util_1.exec)("/usr/bin/security", addKeychain(["find-identity", "-v", "-p", "codesigning"])).then(it => it.trim().split("\n")),
]).then(it => {
const array = it[0]
.concat(it[1])
.filter(it => !it.includes("(Missing required extension)") && !it.includes("valid identities found") && !it.includes("iPhone ") && !it.includes("com.apple.idms.appleid.prd."))
// remove 1)
.map(it => it.substring(it.indexOf(")") + 1).trim());
return Array.from(new Set(array));
});
if (keychain == null) {
exports.findIdentityRawResult = result;
}
}
return result;
}
async function _findIdentity(type, qualifier, keychain) {
// https://github.com/electron-userland/electron-builder/issues/484
//noinspection SpellCheckingInspection
const lines = await getValidIdentities(keychain);
const namePrefix = `${type}:`;
for (const line of lines) {
if (qualifier != null && !line.includes(qualifier)) {
continue;
}
if (line.includes(namePrefix)) {
return parseIdentity(line);
}
}
if (type === "Developer ID Application") {
// find non-Apple certificate
// https://github.com/electron-userland/electron-builder/issues/458
l: for (const line of lines) {
if (qualifier != null && !line.includes(qualifier)) {
continue;
}
if (line.includes("Mac Developer:")) {
continue;
}
for (const prefix of exports.appleCertificatePrefixes) {
if (line.includes(prefix)) {
continue l;
}
}
return parseIdentity(line);
}
}
return null;
}
function parseIdentity(line) {
const firstQuoteIndex = line.indexOf('"');
const name = line.substring(firstQuoteIndex + 1, line.lastIndexOf('"'));
const hash = line.substring(0, firstQuoteIndex - 1);
return new util_identities_1.Identity(name, hash);
}
function findIdentity(certType, qualifier, keychain) {
let identity = qualifier || process.env.CSC_NAME;
if ((0, builder_util_1.isEmptyOrSpaces)(identity)) {
if ((0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
return _findIdentity(certType, null, keychain);
}
else {
return Promise.resolve(null);
}
}
else {
identity = identity.trim();
for (const prefix of exports.appleCertificatePrefixes) {
checkPrefix(identity, prefix);
}
return _findIdentity(certType, identity, keychain);
}
}
function checkPrefix(name, prefix) {
if (name.startsWith(prefix)) {
throw new builder_util_1.InvalidConfigurationError(`Please remove prefix "${prefix}" from the specified name — appropriate certificate will be chosen automatically`);
}
}
//# sourceMappingURL=macCodeSign.js.map
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
import { MemoLazy, Nullish } from "builder-util-runtime";
import { Lazy } from "lazy-val";
import { Target } from "../core";
import { WindowsConfiguration } from "../options/winOptions";
import { WindowsSignOptions } from "./windowsCodeSign";
import { CertificateFromStoreInfo, FileCodeSigningInfo } from "./windowsSignToolManager";
export interface SignManager {
readonly computedPublisherName: Lazy<Array<string> | null>;
readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>;
computePublisherName(target: Target, publisherName: string | Nullish): Promise<string>;
initialize(): Promise<void>;
signFile(options: WindowsSignOptions): Promise<boolean>;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=signManager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"signManager.js","sourceRoot":"","sources":["../../src/codeSign/signManager.ts"],"names":[],"mappings":"","sourcesContent":["import { MemoLazy, Nullish } from \"builder-util-runtime\"\nimport { Lazy } from \"lazy-val\"\nimport { Target } from \"../core\"\nimport { WindowsConfiguration } from \"../options/winOptions\"\nimport { WindowsSignOptions } from \"./windowsCodeSign\"\nimport { CertificateFromStoreInfo, FileCodeSigningInfo } from \"./windowsSignToolManager\"\n\nexport interface SignManager {\n readonly computedPublisherName: Lazy<Array<string> | null>\n readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>\n computePublisherName(target: Target, publisherName: string | Nullish): Promise<string>\n initialize(): Promise<void>\n signFile(options: WindowsSignOptions): Promise<boolean>\n}\n"]}
+7
View File
@@ -0,0 +1,7 @@
import { WindowsConfiguration } from "../options/winOptions";
import { WinPackager } from "../winPackager";
export interface WindowsSignOptions {
readonly path: string;
readonly options: WindowsConfiguration;
}
export declare function signWindows(options: WindowsSignOptions, packager: WinPackager): Promise<boolean>;
+38
View File
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.signWindows = signWindows;
const builder_util_1 = require("builder-util");
async function signWindows(options, packager) {
if (options.options.azureSignOptions) {
if (options.options.signtoolOptions) {
builder_util_1.log.warn(null, "ignoring signtool options, using Azure Trusted Signing; please only configure one");
}
builder_util_1.log.info({ path: builder_util_1.log.filePath(options.path) }, "signing with Azure Trusted Signing");
}
else {
builder_util_1.log.info({ path: builder_util_1.log.filePath(options.path) }, "signing with signtool.exe");
}
const packageManager = await packager.signingManager.value;
return signWithRetry(async () => packageManager.signFile(options));
}
function signWithRetry(signer) {
return (0, builder_util_1.retry)(signer, {
retries: 3,
interval: 1000,
backoff: 1000,
shouldRetry: (e) => {
const message = e.message;
if (
// https://github.com/electron-userland/electron-builder/issues/1414
(message === null || message === void 0 ? void 0 : message.includes("Couldn't resolve host name")) ||
(
// https://github.com/electron-userland/electron-builder/issues/8615
message === null || message === void 0 ? void 0 : message.includes("being used by another process."))) {
builder_util_1.log.warn({ error: message }, "attempt to sign failed, another attempt will be made");
return true;
}
return false;
},
});
}
//# sourceMappingURL=windowsCodeSign.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"windowsCodeSign.js","sourceRoot":"","sources":["../../src/codeSign/windowsCodeSign.ts"],"names":[],"mappings":";;AASA,kCAWC;AApBD,+CAAyC;AASlC,KAAK,UAAU,WAAW,CAAC,OAA2B,EAAE,QAAqB;IAClF,IAAI,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YACpC,kBAAG,CAAC,IAAI,CAAC,IAAI,EAAE,mFAAmF,CAAC,CAAA;QACrG,CAAC;QACD,kBAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,oCAAoC,CAAC,CAAA;IACtF,CAAC;SAAM,CAAC;QACN,kBAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,2BAA2B,CAAC,CAAA;IAC7E,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAA;IAC1D,OAAO,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;AACpE,CAAC;AAED,SAAS,aAAa,CAAC,MAA8B;IACnD,OAAO,IAAA,oBAAK,EAAC,MAAM,EAAE;QACnB,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,CAAC,CAAM,EAAE,EAAE;YACtB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAA;YACzB;YACE,oEAAoE;YACpE,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,4BAA4B,CAAC;;gBAC/C,oEAAoE;gBACpE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,gCAAgC,CAAC,CAAA,EACnD,CAAC;gBACD,kBAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,sDAAsD,CAAC,CAAA;gBACpF,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { log, retry } from \"builder-util\"\nimport { WindowsConfiguration } from \"../options/winOptions\"\nimport { WinPackager } from \"../winPackager\"\n\nexport interface WindowsSignOptions {\n readonly path: string\n readonly options: WindowsConfiguration\n}\n\nexport async function signWindows(options: WindowsSignOptions, packager: WinPackager): Promise<boolean> {\n if (options.options.azureSignOptions) {\n if (options.options.signtoolOptions) {\n log.warn(null, \"ignoring signtool options, using Azure Trusted Signing; please only configure one\")\n }\n log.info({ path: log.filePath(options.path) }, \"signing with Azure Trusted Signing\")\n } else {\n log.info({ path: log.filePath(options.path) }, \"signing with signtool.exe\")\n }\n const packageManager = await packager.signingManager.value\n return signWithRetry(async () => packageManager.signFile(options))\n}\n\nfunction signWithRetry(signer: () => Promise<boolean>): Promise<boolean> {\n return retry(signer, {\n retries: 3,\n interval: 1000,\n backoff: 1000,\n shouldRetry: (e: any) => {\n const message = e.message\n if (\n // https://github.com/electron-userland/electron-builder/issues/1414\n message?.includes(\"Couldn't resolve host name\") ||\n // https://github.com/electron-userland/electron-builder/issues/8615\n message?.includes(\"being used by another process.\")\n ) {\n log.warn({ error: message }, \"attempt to sign failed, another attempt will be made\")\n return true\n }\n return false\n },\n })\n}\n"]}
+21
View File
@@ -0,0 +1,21 @@
import { MemoLazy } from "builder-util-runtime";
import { Lazy } from "lazy-val";
import { WindowsConfiguration } from "../options/winOptions";
import { WinPackager } from "../winPackager";
import { SignManager } from "./signManager";
import { WindowsSignOptions } from "./windowsCodeSign";
import { CertificateFromStoreInfo, FileCodeSigningInfo } from "./windowsSignToolManager";
export declare class WindowsSignAzureManager implements SignManager {
private readonly packager;
private readonly platformSpecificBuildOptions;
readonly computedPublisherName: Lazy<string[] | null>;
constructor(packager: WinPackager);
initialize(): Promise<void>;
verifyRequiredEnvVars(): void;
verifyPrincipleSecretEnv(): boolean;
verifyPrincipleCertificateEnv(): boolean;
verifyUsernamePasswordEnv(): boolean;
computePublisherName(): Promise<string>;
readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>;
signFile(options: WindowsSignOptions): Promise<boolean>;
}
+116
View File
@@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WindowsSignAzureManager = void 0;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const lazy_val_1 = require("lazy-val");
class WindowsSignAzureManager {
constructor(packager) {
this.packager = packager;
this.computedPublisherName = new lazy_val_1.Lazy(() => {
var _a;
const publisherName = (_a = this.platformSpecificBuildOptions.azureSignOptions) === null || _a === void 0 ? void 0 : _a.publisherName;
if (publisherName === null) {
return Promise.resolve(null);
}
else if (publisherName != null) {
return Promise.resolve((0, builder_util_1.asArray)(publisherName));
}
// TODO: Is there another way to automatically pull Publisher Name from AzureTrusted service?
// For now return null.
return Promise.resolve(null);
});
this.cscInfo = new builder_util_runtime_1.MemoLazy(() => this.packager.platformSpecificBuildOptions, _selected => Promise.resolve(null));
this.platformSpecificBuildOptions = packager.platformSpecificBuildOptions;
}
async initialize() {
const vm = await this.packager.vm.value;
const ps = await vm.powershellCommand.value;
builder_util_1.log.info(null, "installing required module (TrustedSigning) with scope CurrentUser");
try {
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", "Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser"]);
}
catch (error) {
// Might not be needed, seems GH runners already have NuGet set up.
// Logging to debug just in case users run into this. If NuGet isn't present, Install-Module -Name TrustedSigning will fail, so we'll get the logs at that point
builder_util_1.log.debug({ message: error.message || error.stack }, "unable to install PackageProvider Nuget. Might be a false alarm though as some systems already have it installed");
}
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", "Install-Module -Name TrustedSigning -MinimumVersion 0.5.0 -Force -Repository PSGallery -Scope CurrentUser"]);
// Preemptively check env vars once during initialization
// Options: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition
builder_util_1.log.info(null, "verifying env vars for authenticating to Microsoft Entra ID");
this.verifyRequiredEnvVars();
if (!(this.verifyPrincipleSecretEnv() || this.verifyPrincipleCertificateEnv() || this.verifyUsernamePasswordEnv())) {
throw new builder_util_1.InvalidConfigurationError(`Unable to find valid azure env configuration for signing. Missing field(s) can be debugged via "DEBUG=electron-builder". Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition`);
}
}
verifyRequiredEnvVars() {
;
["AZURE_TENANT_ID", "AZURE_CLIENT_ID"].forEach(field => {
if (!process.env[field]) {
throw new builder_util_1.InvalidConfigurationError(`Unable to find valid azure env field ${field} for signing. Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition`);
}
});
}
verifyPrincipleSecretEnv() {
if (!process.env.AZURE_CLIENT_SECRET) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_SECRET" }, "no secret found for authenticating to Microsoft Entra ID");
return false;
}
return true;
}
verifyPrincipleCertificateEnv() {
if (!process.env.AZURE_CLIENT_CERTIFICATE_PATH) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PATH" }, "no path found for signing certificate for authenticating to Microsoft Entra ID");
return false;
}
if (!process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PASSWORD" }, "(optional) certificate password not found, assuming no password");
}
if (!process.env.AZURE_CLIENT_SEND_CERTIFICATE_CHAIN) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_SEND_CERTIFICATE_CHAIN" }, "(optional) certificate chain not found");
}
return true;
}
verifyUsernamePasswordEnv() {
if (!process.env.AZURE_USERNAME) {
builder_util_1.log.debug({ envVar: "AZURE_USERNAME" }, "no username found for authenticating to Microsoft Entra ID");
if (!process.env.AZURE_PASSWORD) {
builder_util_1.log.debug({ envVar: "AZURE_PASSWORD" }, "no password found for authenticating to Microsoft Entra ID");
}
return false;
}
return true;
}
computePublisherName() {
return Promise.resolve(this.packager.platformSpecificBuildOptions.azureSignOptions.publisherName);
}
// prerequisite: requires `initializeProviderModules` to already have been executed
async signFile(options) {
const vm = await this.packager.vm.value;
const ps = await vm.powershellCommand.value;
const { publisherName: _publisher, // extract from `extraSigningArgs`
endpoint, certificateProfileName, codeSigningAccountName, fileDigest, timestampRfc3161, timestampDigest, ...extraSigningArgs } = options.options.azureSignOptions;
const params = {
...extraSigningArgs,
Endpoint: endpoint,
CertificateProfileName: certificateProfileName,
CodeSigningAccountName: codeSigningAccountName,
TimestampRfc3161: timestampRfc3161 || "http://timestamp.acs.microsoft.com",
TimestampDigest: timestampDigest || "SHA256",
FileDigest: fileDigest || "SHA256",
Files: options.path,
};
const paramsString = Object.entries(params)
.filter(([_, value]) => value != null)
.reduce((res, [field, value]) => {
const escapedValue = String(value).replace(/'/g, "''");
return [...res, `-${field}`, `'${escapedValue}'`];
}, [])
.join(" ");
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", `Invoke-TrustedSigning ${paramsString}`]);
return true;
}
}
exports.WindowsSignAzureManager = WindowsSignAzureManager;
//# sourceMappingURL=windowsSignAzureManager.js.map
File diff suppressed because one or more lines are too long
+56
View File
@@ -0,0 +1,56 @@
import { MemoLazy } from "builder-util-runtime";
import { Lazy } from "lazy-val";
import { Target } from "../core";
import { WindowsConfiguration } from "../options/winOptions";
import { VmManager } from "../vm/vm";
import { WinPackager } from "../winPackager";
import { SignManager } from "./signManager";
import { WindowsSignOptions } from "./windowsCodeSign";
export type CustomWindowsSign = (configuration: CustomWindowsSignTaskConfiguration, packager?: WinPackager) => Promise<any>;
export interface WindowsSignToolOptions extends WindowsSignOptions {
readonly name: string;
readonly site: string | null;
}
export interface FileCodeSigningInfo {
readonly file: string;
readonly password: string | null;
}
export interface WindowsSignTaskConfiguration extends WindowsSignToolOptions {
readonly cscInfo: FileCodeSigningInfo | CertificateFromStoreInfo | null;
resultOutputPath?: string;
hash: string;
isNest: boolean;
}
export interface CustomWindowsSignTaskConfiguration extends WindowsSignTaskConfiguration {
computeSignToolArgs(isWin: boolean): Array<string>;
}
export interface CertificateInfo {
readonly commonName: string;
readonly bloodyMicrosoftSubjectDn: string;
}
export interface CertificateFromStoreInfo {
thumbprint: string;
subject: string;
store: string;
isLocalMachineStore: boolean;
}
export declare class WindowsSignToolManager implements SignManager {
private readonly packager;
private readonly platformSpecificBuildOptions;
constructor(packager: WinPackager);
readonly computedPublisherName: Lazy<string[] | null>;
readonly lazyCertInfo: MemoLazy<MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>, CertificateInfo | null>;
readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>;
initialize(): Promise<void>;
computePublisherName(target: Target, publisherName: string): Promise<string>;
signFile(options: WindowsSignOptions): Promise<boolean>;
getCertInfo(file: string, password: string): Promise<CertificateInfo>;
computeSignToolArgs(options: WindowsSignTaskConfiguration, isWin: boolean, vm?: VmManager): Array<string>;
private computeWindowsSignArgs;
private computeOsslsigncodeArgs;
private addCertificateArgs;
private addCommonSigningArgs;
getOutputPath(inputPath: string, hash: string): string;
getCertificateFromStoreInfo(options: WindowsConfiguration, vm: VmManager): Promise<CertificateFromStoreInfo>;
doSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager): Promise<void>;
}
+375
View File
@@ -0,0 +1,375 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WindowsSignToolManager = void 0;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const fs_extra_1 = require("fs-extra");
const lazy_val_1 = require("lazy-val");
const path = require("path");
const AppxTarget_1 = require("../targets/AppxTarget");
const windows_1 = require("../toolsets/windows");
const appBuilder_1 = require("../util/appBuilder");
const resolve_1 = require("../util/resolve");
const vm_1 = require("../vm/vm");
const codesign_1 = require("./codesign");
class WindowsSignToolManager {
constructor(packager) {
this.packager = packager;
this.computedPublisherName = new lazy_val_1.Lazy(async () => {
var _a;
const publisherName = (_a = this.platformSpecificBuildOptions.signtoolOptions) === null || _a === void 0 ? void 0 : _a.publisherName;
if (publisherName === null) {
return null;
}
else if (publisherName != null) {
return (0, builder_util_1.asArray)(publisherName);
}
const certInfo = await this.lazyCertInfo.value;
return certInfo == null ? null : [certInfo.commonName];
});
this.lazyCertInfo = new builder_util_runtime_1.MemoLazy(() => this.cscInfo, async (csc) => {
const cscInfo = await csc.value;
if (cscInfo == null) {
return null;
}
if ("subject" in cscInfo) {
const bloodyMicrosoftSubjectDn = cscInfo.subject;
return {
commonName: (0, builder_util_runtime_1.parseDn)(bloodyMicrosoftSubjectDn).get("CN"),
bloodyMicrosoftSubjectDn,
};
}
const cscFile = cscInfo.file;
if (cscFile == null) {
return null;
}
return await this.getCertInfo(cscFile, cscInfo.password || "");
});
this.cscInfo = new builder_util_runtime_1.MemoLazy(() => this.platformSpecificBuildOptions, platformSpecificBuildOptions => {
var _a, _b, _c;
const subjectName = (_a = platformSpecificBuildOptions.signtoolOptions) === null || _a === void 0 ? void 0 : _a.certificateSubjectName;
const shaType = (_b = platformSpecificBuildOptions.signtoolOptions) === null || _b === void 0 ? void 0 : _b.certificateSha1;
if (subjectName != null || shaType != null) {
return this.packager.vm.value
.then(vm => this.getCertificateFromStoreInfo(platformSpecificBuildOptions, vm))
.catch((e) => {
var _a;
// https://github.com/electron-userland/electron-builder/pull/2397
if (((_a = platformSpecificBuildOptions.signtoolOptions) === null || _a === void 0 ? void 0 : _a.sign) == null) {
throw e;
}
else {
builder_util_1.log.debug({ error: e }, "getCertificateFromStoreInfo error");
return null;
}
});
}
const certificateFile = (_c = platformSpecificBuildOptions.signtoolOptions) === null || _c === void 0 ? void 0 : _c.certificateFile;
if (certificateFile != null) {
const certificatePassword = this.packager.getCscPassword();
return Promise.resolve({
file: certificateFile,
password: certificatePassword == null ? null : certificatePassword.trim(),
});
}
const cscLink = this.packager.getCscLink("WIN_CSC_LINK");
if (cscLink == null || cscLink === "") {
return Promise.resolve(null);
}
return ((0, codesign_1.importCertificate)(cscLink, this.packager.info.tempDirManager, this.packager.projectDir)
// before then
.catch((e) => {
if (e instanceof builder_util_1.InvalidConfigurationError) {
throw new builder_util_1.InvalidConfigurationError(`Env WIN_CSC_LINK is not correct, cannot resolve: ${e.message}`);
}
else {
throw e;
}
})
.then(path => {
return {
file: path,
password: this.packager.getCscPassword(),
};
}));
});
this.platformSpecificBuildOptions = packager.platformSpecificBuildOptions;
}
initialize() {
return Promise.resolve();
}
// https://github.com/electron-userland/electron-builder/issues/2108#issuecomment-333200711
async computePublisherName(target, publisherName) {
if (target instanceof AppxTarget_1.default && (await this.cscInfo.value) == null) {
builder_util_1.log.info({ reason: "Windows Store only build" }, "AppX is not signed");
return publisherName || "CN=ms";
}
const certInfo = await this.lazyCertInfo.value;
const publisher = publisherName || (certInfo == null ? null : certInfo.bloodyMicrosoftSubjectDn);
if (publisher == null) {
throw new Error("Internal error: cannot compute subject using certificate info");
}
return publisher;
}
async signFile(options) {
var _a, _b;
let hashes = (_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.signingHashAlgorithms;
// msi does not support dual-signing
if (options.path.endsWith(".msi")) {
hashes = [hashes != null && !hashes.includes("sha1") ? "sha256" : "sha1"];
}
else if (options.path.endsWith(".appx")) {
hashes = ["sha256"];
}
else if (hashes == null) {
hashes = ["sha1", "sha256"];
}
else {
hashes = Array.isArray(hashes) ? hashes : [hashes];
}
const name = this.packager.appInfo.productName;
const site = await this.packager.appInfo.computePackageUrl();
const customSign = await (0, resolve_1.resolveFunction)(this.packager.appInfo.type, (_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.sign, "sign");
const cscInfo = await this.cscInfo.value;
if (cscInfo) {
let logInfo = {
file: builder_util_1.log.filePath(options.path),
};
if ("file" in cscInfo) {
logInfo = {
...logInfo,
certificateFile: cscInfo.file,
};
}
else {
logInfo = {
...logInfo,
subject: cscInfo.subject,
thumbprint: cscInfo.thumbprint,
store: cscInfo.store,
user: cscInfo.isLocalMachineStore ? "local machine" : "current user",
};
}
builder_util_1.log.info(logInfo, "signing");
}
else if (!customSign) {
builder_util_1.log.debug({ signHook: !!customSign, cscInfo }, "no signing info identified, signing is skipped");
return false;
}
const executor = customSign || ((config, packager) => this.doSign(config, packager));
let isNest = false;
for (const hash of hashes) {
const taskConfiguration = { ...options, name, site, cscInfo, hash, isNest };
await Promise.resolve(executor({
...taskConfiguration,
computeSignToolArgs: isWin => this.computeSignToolArgs(taskConfiguration, isWin),
}, this.packager));
isNest = true;
if (taskConfiguration.resultOutputPath != null) {
await (0, fs_extra_1.rename)(taskConfiguration.resultOutputPath, options.path);
}
}
return true;
}
async getCertInfo(file, password) {
let result = null;
const errorMessagePrefix = "Cannot extract publisher name from code signing certificate. As workaround, set win.publisherName. Error: ";
try {
result = await (0, appBuilder_1.executeAppBuilderAsJson)(["certificate-info", "--input", file, "--password", password]);
}
catch (e) {
throw new Error(`${errorMessagePrefix}${e.stack || e}`);
}
if (result.error != null) {
// noinspection ExceptionCaughtLocallyJS
throw new builder_util_1.InvalidConfigurationError(`${errorMessagePrefix}${result.error}`);
}
return result;
}
// on windows be aware of http://stackoverflow.com/a/32640183/1910191
computeSignToolArgs(options, isWin, vm = new vm_1.VmManager()) {
return isWin ? this.computeWindowsSignArgs(options, vm) : this.computeOsslsigncodeArgs(options, vm);
}
computeWindowsSignArgs(options, vm) {
var _a, _b, _c, _d;
const inputFile = vm.toVmFile(options.path);
const args = ["sign"];
// Timestamping
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const isRfc3161 = options.isNest || options.hash === "sha256";
args.push(isRfc3161 ? "/tr" : "/t");
const timestampUrl = isRfc3161
? ((_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.rfc3161TimeStampServer) || "http://timestamp.digicert.com"
: ((_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.timeStampServer) || "http://timestamp.digicert.com";
args.push(timestampUrl);
}
// Certificate
this.addCertificateArgs(args, options, vm, true);
// Hash algorithm
const isLegacyToolset = ((_c = this.packager.config.toolsets) === null || _c === void 0 ? void 0 : _c.winCodeSign) === "0.0.0" || ((_d = this.packager.config.toolsets) === null || _d === void 0 ? void 0 : _d.winCodeSign) == null;
if (isLegacyToolset) {
// Legacy || v0.0.0: Only add /fd for non-SHA1 (original behavior)
if (options.hash !== "sha1") {
args.push("/fd", options.hash);
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
args.push("/td", "sha256");
}
}
}
else {
// Modern: Always add /fd (required by new Windows Kits)
args.push("/fd", options.hash.toLowerCase());
// Only add /td for RFC3161 timestamps (incompatible with /t)
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true" && (options.isNest || options.hash === "sha256")) {
args.push("/td", "sha256");
}
}
// Optional parameters
this.addCommonSigningArgs(args, options, vm, true);
// Windows-specific
args.push("/debug");
args.push(inputFile); // Must be last
return args;
}
computeOsslsigncodeArgs(options, vm) {
var _a;
const inputFile = vm.toVmFile(options.path);
const outputPath = this.getOutputPath(inputFile, options.hash);
options.resultOutputPath = outputPath;
const args = ["sign", "-in", inputFile, "-out", outputPath];
// Timestamping
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const timestampUrl = ((_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.timeStampServer) || "http://timestamp.digicert.com";
args.push("-t", timestampUrl);
}
// Certificate
this.addCertificateArgs(args, options, vm, false);
// Hash algorithm
args.push("-h", options.hash.toLowerCase());
// Optional parameters
this.addCommonSigningArgs(args, options, vm, false);
// Proxy support
const httpsProxy = process.env.HTTPS_PROXY;
if (httpsProxy === null || httpsProxy === void 0 ? void 0 : httpsProxy.length) {
args.push("-p", httpsProxy);
}
return args;
}
addCertificateArgs(args, options, vm, isWin) {
const certificateFile = options.cscInfo.file;
if (certificateFile == null) {
// Certificate from store (Windows only)
if (!isWin) {
throw new Error("certificateSha1/certificateSubjectName supported only on Windows");
}
const cscInfo = options.cscInfo;
args.push("/sha1", cscInfo.thumbprint);
args.push("/s", cscInfo.store);
if (cscInfo.isLocalMachineStore) {
args.push("/sm");
}
}
else {
// Certificate file
const certExtension = path.extname(certificateFile);
if (certExtension === ".p12" || certExtension === ".pfx") {
args.push(isWin ? "/f" : "-pkcs12", vm.toVmFile(certificateFile));
}
else {
throw new Error(`Please specify pkcs12 (.p12/.pfx) file, ${certificateFile} is not correct`);
}
}
}
addCommonSigningArgs(args, options, vm, isWin) {
var _a, _b;
if (options.name) {
args.push(isWin ? "/d" : "-n", options.name);
}
if (options.site) {
args.push(isWin ? "/du" : "-i", options.site);
}
if (options.isNest) {
args.push(isWin ? "/as" : "-nest");
}
const password = (_a = options.cscInfo) === null || _a === void 0 ? void 0 : _a.password;
if (password) {
args.push(isWin ? "/p" : "-pass", password);
}
const additionalCert = (_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.additionalCertificateFile;
if (additionalCert) {
args.push(isWin ? "/ac" : "-ac", vm.toVmFile(additionalCert));
}
}
getOutputPath(inputPath, hash) {
const extension = path.extname(inputPath);
return path.join(path.dirname(inputPath), `${path.basename(inputPath, extension)}-signed-${hash}${extension}`);
}
async getCertificateFromStoreInfo(options, vm) {
var _a, _b, _c;
const certificateSubjectName = (_a = options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.certificateSubjectName;
const certificateSha1 = (_c = (_b = options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.certificateSha1) === null || _c === void 0 ? void 0 : _c.toUpperCase();
const ps = await vm.powershellCommand.value;
const rawResult = await vm.exec(ps, [
"-NoProfile",
"-NonInteractive",
"-Command",
"Get-ChildItem -Recurse Cert: -CodeSigningCert | Select-Object -Property Subject,PSParentPath,Thumbprint | ConvertTo-Json -Compress",
]);
const certList = rawResult.length === 0 ? [] : (0, builder_util_1.asArray)(JSON.parse(rawResult));
for (const certInfo of certList) {
if ((certificateSubjectName != null && !certInfo.Subject.includes(certificateSubjectName)) ||
(certificateSha1 != null && certInfo.Thumbprint.toUpperCase() !== certificateSha1)) {
continue;
}
const parentPath = certInfo.PSParentPath;
const store = parentPath.substring(parentPath.lastIndexOf("\\") + 1);
builder_util_1.log.debug({ store, PSParentPath: parentPath }, "auto-detect certificate store");
// https://github.com/electron-userland/electron-builder/issues/1717
const isLocalMachineStore = parentPath.includes("Certificate::LocalMachine");
builder_util_1.log.debug(null, "auto-detect using of LocalMachine store");
return {
thumbprint: certInfo.Thumbprint,
subject: certInfo.Subject,
store,
isLocalMachineStore,
};
}
throw new Error(`Cannot find certificate ${certificateSubjectName || certificateSha1}, all certs: ${rawResult}`);
}
async doSign(configuration, packager) {
var _a;
// https://github.com/electron-userland/electron-builder/pull/1944
const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT, 10) || 10 * 60 * 1000;
// decide runtime argument by cases
let args;
let vm;
const useVmIfNotOnWin = configuration.path.endsWith(".appx") || !("file" in configuration.cscInfo); /* certificateSubjectName and other such options */
const isWin = process.platform === "win32" || useVmIfNotOnWin;
const toolInfo = await (0, windows_1.getSignToolPath)((_a = this.packager.config.toolsets) === null || _a === void 0 ? void 0 : _a.winCodeSign, isWin);
const tool = toolInfo.path;
if (useVmIfNotOnWin) {
vm = await packager.vm.value;
args = this.computeSignToolArgs(configuration, isWin, vm);
}
else {
vm = new vm_1.VmManager();
args = configuration.computeSignToolArgs(isWin);
}
await (0, builder_util_1.retry)(() => vm.exec(tool, args, { timeout, env: { ...process.env, ...(toolInfo.env || {}) } }), {
retries: 2,
interval: 15000,
backoff: 10000,
shouldRetry: (e) => {
if (e.message.includes("The file is being used by another process") ||
e.message.includes("The specified timestamp server either could not be reached") ||
e.message.includes("No certificates were found that met all the given criteria.")) {
builder_util_1.log.warn(`Attempt to code sign failed, another attempt will be made in 15 seconds: ${e.message}`);
return true;
}
return false;
},
});
}
}
exports.WindowsSignToolManager = WindowsSignToolManager;
//# sourceMappingURL=windowsSignToolManager.js.map
File diff suppressed because one or more lines are too long
+449
View File
@@ -0,0 +1,449 @@
import { Arch } from "builder-util";
import { BeforeBuildContext, Target } from "./core";
import { ElectronBrandingOptions, ElectronDownloadOptions } from "./electron/ElectronFramework";
import { PrepareApplicationStageDirectoryOptions } from "./Framework";
import { AppXOptions } from "./options/AppXOptions";
import { AppImageOptions, DebOptions, FlatpakOptions, LinuxConfiguration, LinuxTargetSpecificOptions } from "./options/linuxOptions";
import { DmgOptions, MacConfiguration, MasConfiguration } from "./options/macOptions";
import { MsiOptions } from "./options/MsiOptions";
import { MsiWrappedOptions } from "./options/MsiWrappedOptions";
import { PkgOptions } from "./options/pkgOptions";
import { PlatformSpecificBuildOptions } from "./options/PlatformSpecificBuildOptions";
import { SnapOptions } from "./options/SnapOptions";
import { SquirrelWindowsOptions } from "./options/SquirrelWindowsOptions";
import { WindowsConfiguration } from "./options/winOptions";
import { BuildResult } from "./packager";
import { ArtifactBuildStarted, ArtifactCreated } from "./packagerApi";
import { PlatformPackager } from "./platformPackager";
import { NsisOptions, NsisWebOptions, PortableOptions } from "./targets/nsis/nsisOptions";
/**
* Configuration Options
*/
export interface CommonConfiguration {
/**
* The application id. Used as [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070) for MacOS and as
* [Application User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx) for Windows (NSIS target only, Squirrel.Windows not supported). It is strongly recommended that an explicit ID is set.
* @default com.electron.${name}
*/
readonly appId?: string | null;
/**
* As [name](#metadata), but allows you to specify a product name for your executable which contains spaces and other special characters not allowed in the [name property](https://docs.npmjs.com/files/package.json#name).
* If not specified inside of the `build` configuration, `productName` property defined at the top level of `package.json` is used. If not specified at the top level of `package.json`, [name property](https://docs.npmjs.com/files/package.json#name) is used.
*/
readonly productName?: string | null;
/**
* The human-readable copyright line for the app.
* @default Copyright © year ${author}
*/
readonly copyright?: string | null;
/**
* Directories for build resources
*/
readonly directories?: MetadataDirectories | null;
/**
* Configuration of toolsets utilized by electron-builder
*/
readonly toolsets?: ToolsetConfig | null;
/**
* Options related to how build macOS targets.
*/
readonly mac?: MacConfiguration | null;
/**
* MAS (Mac Application Store) options.
*/
readonly mas?: MasConfiguration | null;
/**
* MAS (Mac Application Store) development options (`mas-dev` target).
*/
readonly masDev?: MasConfiguration | null;
/**
* macOS DMG options.
*/
readonly dmg?: DmgOptions | null;
/**
* macOS PKG options.
*/
readonly pkg?: PkgOptions | null;
/**
* Options related to how build Windows targets.
*/
readonly win?: WindowsConfiguration | null;
readonly nsis?: NsisOptions | null;
readonly nsisWeb?: NsisWebOptions | null;
readonly portable?: PortableOptions | null;
readonly appx?: AppXOptions | null;
/** @private */
readonly msi?: MsiOptions | null;
/** @private */
readonly msiWrapped?: MsiWrappedOptions | null;
readonly squirrelWindows?: SquirrelWindowsOptions | null;
/**
* Options related to how build Linux targets.
*/
readonly linux?: LinuxConfiguration | null;
/**
* Debian package options.
*/
readonly deb?: DebOptions | null;
/**
* Snap options.
*/
readonly snap?: SnapOptions | null;
/**
* AppImage options.
*/
readonly appImage?: AppImageOptions | null;
/**
* Flatpak options.
*/
readonly flatpak?: FlatpakOptions | null;
readonly pacman?: LinuxTargetSpecificOptions | null;
readonly rpm?: LinuxTargetSpecificOptions | null;
readonly freebsd?: LinuxTargetSpecificOptions | null;
readonly p5p?: LinuxTargetSpecificOptions | null;
readonly apk?: LinuxTargetSpecificOptions | null;
/**
* Whether to build the application native dependencies from source.
* @default false
*/
buildDependenciesFromSource?: boolean;
/**
* Whether to execute `node-gyp rebuild` before starting to package the app.
*
* Don't [use](https://github.com/electron-userland/electron-builder/issues/683#issuecomment-241214075) [npm](http://electron.atom.io/docs/tutorial/using-native-node-modules/#using-npm) (neither `.npmrc`) for configuring electron headers. Use `electron-builder node-gyp-rebuild` instead.
* @default false
*/
readonly nodeGypRebuild?: boolean;
/**
* Additional command line arguments to use when installing app native deps.
*/
readonly npmArgs?: Array<string> | string | null;
/**
* Whether to [rebuild](https://docs.npmjs.com/cli/rebuild) native dependencies before starting to package the app.
* @default true
*/
readonly npmRebuild?: boolean;
/**
* Use `legacy` app-builder binary for installing native dependencies, or `@electron/rebuild` in `sequential` or `parallel` compilation modes.
* @default sequential
*/
readonly nativeRebuilder?: "legacy" | "sequential" | "parallel" | null;
/**
* The build number. Maps to the `--iteration` flag for builds using FPM on Linux.
* If not defined, then it will fallback to `BUILD_NUMBER` or `TRAVIS_BUILD_NUMBER` or `APPVEYOR_BUILD_NUMBER` or `CIRCLE_BUILD_NUM` or `BUILD_BUILDNUMBER` or `CI_PIPELINE_IID` env.
*/
readonly buildNumber?: string | null;
/**
* The build version. Maps to the `CFBundleVersion` on macOS, and `FileVersion` metadata property on Windows. Defaults to the `version`.
* If `buildVersion` is not defined and `buildNumber` (or one of the `buildNumber` envs) is defined, it will be used as a build version (`version.buildNumber`).
*/
readonly buildVersion?: string | null;
/**
* Whether to download the alternate FFmpeg library from Electron's release assets and replace the default FFmpeg library prior to signing
*/
readonly downloadAlternateFFmpeg?: boolean;
/**
* Inject properties to `package.json`.
*/
readonly extraMetadata?: any;
/**
* Whether to fail if the application is not signed (to prevent unsigned app if code signing configuration is not correct).
* @default false
*/
readonly forceCodeSigning?: boolean;
/**
* Whether to include PDB files.
* @default false
*/
readonly includePdb?: boolean;
/**
* Whether to remove `scripts` field from `package.json` files.
*
* @default true
*/
readonly removePackageScripts?: boolean;
/**
* Whether to remove `keywords` field from `package.json` files.
*
* @default true
*/
readonly removePackageKeywords?: boolean;
/**
* Options to pass to `@electron/fuses`
* Ref: https://github.com/electron/fuses
*/
readonly electronFuses?: FuseOptionsV1 | null;
/**
* [Experimental] Configuration for concurrent builds.
*/
readonly concurrency?: Concurrency | null;
}
export interface Configuration extends CommonConfiguration, PlatformSpecificBuildOptions, Hooks {
/**
* Whether to use [electron-compile](http://github.com/electron/electron-compile) to compile app. Defaults to `true` if `electron-compile` in the dependencies. And `false` if in the `devDependencies` or doesn't specified.
*/
readonly electronCompile?: boolean;
/**
* The [electron-download](https://github.com/electron-userland/electron-download#usage) options.
*/
readonly electronDownload?: ElectronDownloadOptions;
/**
* The branding used by Electron's distributables. This is needed if a fork has modified Electron's BRANDING.json file.
*/
readonly electronBranding?: ElectronBrandingOptions;
/**
* The version of electron you are packaging for. Defaults to version of `electron`, `electron-prebuilt` or `electron-prebuilt-compile` dependency.
*/
electronVersion?: string | null;
/**
* The name of a built-in configuration preset (currently, only `react-cra` is supported) or any number of paths to config files (relative to project dir).
*
* The latter allows to mixin a config from multiple other configs, as if you `Object.assign` them, but properly combine `files` glob patterns.
*
* If `react-scripts` in the app dependencies, `react-cra` will be set automatically. Set to `null` to disable automatic detection.
*/
extends?: Array<string> | string | null;
/**
* *libui-based frameworks only* The version of NodeJS you are packaging for.
* You can set it to `current` to set the Node.js version that you use to run.
*/
readonly nodeVersion?: string | null;
/**
* *libui-based frameworks only* The version of LaunchUI you are packaging for. Applicable for Windows only. Defaults to version suitable for used framework version.
*/
readonly launchUiVersion?: boolean | string | null;
/**
* The framework name. One of `electron`, `proton`, `libui`. Defaults to `electron`.
*/
readonly framework?: string | null;
/**
* Whether to disable sanity check asar package (useful for custom electron forks that implement their own encrypted integrity validation)
* @default false
*/
readonly disableSanityCheckAsar?: boolean;
}
export type Hook<T, V> = (contextOrPath: T) => Promise<V> | V;
export interface PackContext {
readonly outDir: string;
readonly appOutDir: string;
readonly packager: PlatformPackager<any>;
readonly electronPlatformName: string;
readonly arch: Arch;
readonly targets: Array<Target>;
}
export type AfterPackContext = PackContext;
export type BeforePackContext = PackContext;
export type AfterExtractContext = PackContext;
/**
* Configuration of toolsets utilized by electron-builder
*/
export interface ToolsetConfig {
/**
* `win-codesign` version to use for signing Windows artifacts.
* Located at https://github.com/electron-userland/electron-builder-binaries/releases?q=win-codesign&expanded=true
*
* Stable:
* v0.0.0 (winCodeSign)
*
* Beta:
* Windows Kits 10.0.26100.0
* v1.0.0, v1.1.0
*
* @default "0.0.0"
*/
readonly winCodeSign?: "0.0.0" | "1.0.0" | "1.1.0" | null;
/**
* `appimage` bundle version to use for Appimage packaging and runtime.
* Located at https://github.com/electron-userland/electron-builder-binaries/releases?q=appimage&expanded=true
* 0.0.0 - legacy toolset (appimage)
*
* Beta:
* 1.0.2 - Runtime 20251108
*
* @default "0.0.0"
*/
readonly appimage?: "0.0.0" | "1.0.2" | null;
}
export interface Hooks {
/**
The function (or path to file or module id) to be run before pack.
```typescript
(context: BeforePackContext): Promise<any> | any
```
!!! example "As function"
```js
beforePack: async (context) => {
// your code
}
```
Because in a configuration file you cannot use JavaScript, can be specified as a path to file or module id. Function must be exported as default export.
```json
"build": {
"beforePack": "./myBeforePackHook.js"
}
```
File `myBeforePackHook.js` in the project root directory:
!!! example "myBeforePackHook.js"
```js
exports.default = async function(context) {
// your custom code
}
```
*/
readonly beforePack?: Hook<BeforePackContext, void> | string | null;
/**
* The function (or path to file or module id) to be [run after the prebuilt Electron binary has been extracted to the output directory](#afterextract)
* Same setup as {@link beforePack}
*/
readonly afterExtract?: Hook<AfterExtractContext, void> | string | null;
/**
* The function (or path to file or module id) to be [run after pack](#afterpack) (but before pack into distributable format and sign).
* Same setup as {@link beforePack}
*/
readonly afterPack?: Hook<AfterPackContext, void> | string | null;
/**
* The function (or path to file or module id) to be [run after pack and sign](#aftersign) (but before pack into distributable format).
* Same setup as {@link beforePack}
*/
readonly afterSign?: Hook<AfterPackContext, void> | string | null;
/**
* The function (or path to file or module id) to be run on artifact build start.
* Same setup as {@link beforePack}
*/
readonly artifactBuildStarted?: Hook<ArtifactBuildStarted, void> | string | null;
/**
* The function (or path to file or module id) to be run on artifact build completed.
* Same setup as {@link beforePack}
*/
readonly artifactBuildCompleted?: Hook<ArtifactCreated, void> | string | null;
/**
* The function (or path to file or module id) to be run after all artifacts are built.
```typescript
(buildResult: BuildResult): Promise<Array<string>> | Array<string>
```
Configuration in the same way as `afterPack` (see above).
!!! example "myAfterAllArtifactBuild.js"
```js
exports.default = function () {
// you can return additional files to publish
return ["/path/to/additional/result/file"]
}
```
*/
readonly afterAllArtifactBuild?: Hook<BuildResult, Array<string>> | string | null;
/**
* The function (or path to file or module id) to be run after MSI project created on disk - not packed into .msi package yet.
*/
readonly msiProjectCreated?: Hook<string, void> | string | null;
/**
* The function (or path to file or module id) to be run after Appx manifest created on disk - not packed into .appx package yet.
*/
readonly appxManifestCreated?: Hook<string, void> | string | null;
/**
* The function (or path to file or module id) to be [run on each node module](#onnodemodulefile) file. Returning `true`/`false` will determine whether to force include or to use the default copier logic
*/
readonly onNodeModuleFile?: Hook<string, void | boolean> | string | null;
/**
* The function (or path to file or module id) to be run before dependencies are installed or rebuilt. Works when `npmRebuild` is set to `true`. Resolving to `false` will skip dependencies install or rebuild.
*
* If provided and `node_modules` are missing, it will not invoke production dependencies check.
*/
readonly beforeBuild?: Hook<BeforeBuildContext, boolean | void> | string | null;
/**
* The function (or path to file or module id) to be run when staging the electron artifact environment.
* Returns the path to custom Electron build (e.g. `~/electron/out/R`) or folder of electron zips.
*
* Zip files must follow the pattern `electron-v${version}-${platformName}-${arch}.zip`, otherwise it will be assumed to be an unpacked Electron app directory
*/
readonly electronDist?: Hook<PrepareApplicationStageDirectoryOptions, string> | string | null;
}
export interface MetadataDirectories {
/**
* The path to build resources.
*
* Please note — build resources are not packed into the app. If you need to use some files, e.g. as tray icon, please include required files explicitly: `"files": ["**\/*", "build/icon.*"]`
* @default build
*/
readonly buildResources?: string | null;
/**
* The output directory. [File macros](./file-patterns.md#file-macros) are supported.
* @default dist
*/
readonly output?: string | null;
/**
* The application directory (containing the application package.json), defaults to `app`, `www` or working directory.
*/
readonly app?: string | null;
}
/**
* All options come from [@electron/fuses](https://github.com/electron/fuses)
* Ref: https://raw.githubusercontent.com/electron/electron/refs/heads/main/docs/tutorial/fuses.md
*/
export interface FuseOptionsV1 {
/**
*The runAsNode fuse toggles whether the `ELECTRON_RUN_AS_NODE` environment variable is respected or not. Please note that if this fuse is disabled then `process.fork` in the main process will not function as expected as it depends on this environment variable to function. Instead, we recommend that you use [Utility Processes](https://github.com/electron/electron/blob/main/docs/api/utility-process.md), which work for many use cases where you need a standalone Node.js process (like a Sqlite server process or similar scenarios).
*/
runAsNode?: boolean;
/**
* The cookieEncryption fuse toggles whether the cookie store on disk is encrypted using OS level cryptography keys. By default the sqlite database that Chromium uses to store cookies stores the values in plaintext. If you wish to ensure your apps cookies are encrypted in the same way Chrome does then you should enable this fuse. Please note it is a one-way transition, if you enable this fuse existing unencrypted cookies will be encrypted-on-write but if you then disable the fuse again your cookie store will effectively be corrupt and useless. Most apps can safely enable this fuse.
*/
enableCookieEncryption?: boolean;
/**
* The nodeOptions fuse toggles whether the [`NODE_OPTIONS`](https://nodejs.org/api/cli.html#node_optionsoptions) and [`NODE_EXTRA_CA_CERTS`](https://github.com/nodejs/node/blob/main/doc/api/cli.md#node_extra_ca_certsfile) environment variables are respected. The `NODE_OPTIONS` environment variable can be used to pass all kinds of custom options to the Node.js runtime and isn't typically used by apps in production. Most apps can safely disable this fuse.
*/
enableNodeOptionsEnvironmentVariable?: boolean;
/**
* The nodeCliInspect fuse toggles whether the `--inspect`, `--inspect-brk`, etc. flags are respected or not. When disabled it also ensures that `SIGUSR1` signal does not initialize the main process inspector. Most apps can safely disable this fuse.
*/
enableNodeCliInspectArguments?: boolean;
/**
* The embeddedAsarIntegrityValidation fuse toggles an experimental feature on macOS that validates the content of the `app.asar` file when it is loaded. This feature is designed to have a minimal performance impact but may marginally slow down file reads from inside the `app.asar` archive.
* Currently, ASAR integrity checking is supported on:
*
* - macOS as of electron>=16.0.0
* - Windows as of electron>=30.0.0
*
* For more information on how to use asar integrity validation please read the [Asar Integrity](https://github.com/electron/electron/blob/main/docs/tutorial/asar-integrity.md) documentation.
*/
enableEmbeddedAsarIntegrityValidation?: boolean;
/**
* The onlyLoadAppFromAsar fuse changes the search system that Electron uses to locate your app code. By default Electron will search in the following order `app.asar` -> `app` -> `default_app.asar`. When this fuse is enabled the search order becomes a single entry `app.asar` thus ensuring that when combined with the `embeddedAsarIntegrityValidation` fuse it is impossible to load non-validated code.
*/
onlyLoadAppFromAsar?: boolean;
/**
* The loadBrowserProcessSpecificV8Snapshot fuse changes which V8 snapshot file is used for the browser process. By default Electron's processes will all use the same V8 snapshot file. When this fuse is enabled the browser process uses the file called `browser_v8_context_snapshot.bin` for its V8 snapshot. The other processes will use the V8 snapshot file that they normally do.
*/
loadBrowserProcessSpecificV8Snapshot?: boolean;
/**
* The grantFileProtocolExtraPrivileges fuse changes whether pages loaded from the `file://` protocol are given privileges beyond what they would receive in a traditional web browser. This behavior was core to Electron apps in original versions of Electron but is no longer required as apps should be [serving local files from custom protocols](https://github.com/electron/electron/blob/main/docs/tutorial/security.md#18-avoid-usage-of-the-file-protocol-and-prefer-usage-of-custom-protocols) now instead. If you aren't serving pages from `file://` you should disable this fuse.
* The extra privileges granted to the `file://` protocol by this fuse are incompletely documented below:
*
* - `file://` protocol pages can use `fetch` to load other assets over `file://`
* - `file://` protocol pages can use service workers
* - `file://` protocol pages have universal access granted to child frames also running on `file://` protocols regardless of sandbox settings
*/
grantFileProtocolExtraPrivileges?: boolean;
/**
* Resets the app signature, specifically used for macOS.
* Note: This should be unneeded since electron-builder signs the app directly after flipping the fuses.
* Ref: https://github.com/electron/fuses?tab=readme-ov-file#apple-silicon
*/
resetAdHocDarwinSignature?: boolean;
}
export interface Concurrency {
/**
* The maximum number of concurrent jobs to run.
* @default 1
*/
jobs: number;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=configuration.js.map
File diff suppressed because one or more lines are too long
+60
View File
@@ -0,0 +1,60 @@
import { Arch, ArchType, AsyncTaskManager } from "builder-util";
import { AllPublishOptions, Nullish } from "builder-util-runtime";
export type Publish = AllPublishOptions | Array<AllPublishOptions> | null;
export type TargetConfigType = Array<string | TargetConfiguration> | string | TargetConfiguration | null;
export interface TargetConfiguration {
/**
* The target name. e.g. `snap`.
*/
readonly target: string;
/**
* The arch or list of archs.
*/
readonly arch?: Array<ArchType> | ArchType;
}
export declare class Platform {
name: string;
buildConfigurationKey: string;
nodeName: NodeJS.Platform;
static MAC: Platform;
static LINUX: Platform;
static WINDOWS: Platform;
constructor(name: string, buildConfigurationKey: string, nodeName: NodeJS.Platform);
toString(): string;
createTarget(type?: string | Array<string> | null, ...archs: Array<Arch>): Map<Platform, Map<Arch, Array<string>>>;
static current(): Platform;
static fromString(name: string): Platform;
}
export declare abstract class Target {
readonly name: string;
readonly isAsyncSupported: boolean;
abstract readonly outDir: string;
abstract readonly options: TargetSpecificOptions | Nullish;
readonly buildQueueManager: AsyncTaskManager;
protected constructor(name: string, isAsyncSupported?: boolean);
checkOptions(): Promise<any>;
abstract build(appOutDir: string, arch: Arch): Promise<any>;
finishBuild(): Promise<any>;
}
export interface TargetSpecificOptions {
/**
The [artifact file name template](./configuration.md#artifact-file-name-template).
*/
readonly artifactName?: string | null;
publish?: Publish;
}
export declare const DEFAULT_TARGET = "default";
export declare const DIR_TARGET = "dir";
export type CompressionLevel = "store" | "normal" | "maximum";
export interface BeforeBuildContext {
readonly appDir: string;
readonly electronVersion: string;
readonly platform: Platform;
readonly arch: string;
}
export interface SourceRepositoryInfo {
type?: string;
domain?: string;
user: string;
project: string;
}
+66
View File
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DIR_TARGET = exports.DEFAULT_TARGET = exports.Target = exports.Platform = void 0;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
class Platform {
constructor(name, buildConfigurationKey, nodeName) {
this.name = name;
this.buildConfigurationKey = buildConfigurationKey;
this.nodeName = nodeName;
}
toString() {
return this.name;
}
createTarget(type, ...archs) {
if (type == null && (archs == null || archs.length === 0)) {
return new Map([[this, new Map()]]);
}
const archToType = new Map();
for (const arch of archs == null || archs.length === 0 ? [(0, builder_util_1.archFromString)(process.arch)] : archs) {
archToType.set(arch, type == null ? [] : Array.isArray(type) ? type : [type]);
}
return new Map([[this, archToType]]);
}
static current() {
return Platform.fromString(process.platform);
}
static fromString(name) {
name = name.toLowerCase();
switch (name) {
case Platform.MAC.nodeName:
case Platform.MAC.name:
return Platform.MAC;
case Platform.WINDOWS.nodeName:
case Platform.WINDOWS.name:
case Platform.WINDOWS.buildConfigurationKey:
return Platform.WINDOWS;
case Platform.LINUX.nodeName:
return Platform.LINUX;
default:
throw new Error(`Unknown platform: ${name}`);
}
}
}
exports.Platform = Platform;
Platform.MAC = new Platform("mac", "mac", "darwin");
Platform.LINUX = new Platform("linux", "linux", "linux");
Platform.WINDOWS = new Platform("windows", "win", "win32");
class Target {
constructor(name, isAsyncSupported = true) {
this.name = name;
this.isAsyncSupported = isAsyncSupported;
// use only for tasks that cannot be executed in parallel (such as signing on windows and hdiutil on macOS due to file locking)
this.buildQueueManager = new builder_util_1.AsyncTaskManager(new builder_util_runtime_1.CancellationToken());
}
async checkOptions() {
// ignore
}
async finishBuild() {
await this.buildQueueManager.awaitTasks();
}
}
exports.Target = Target;
exports.DEFAULT_TARGET = "default";
exports.DIR_TARGET = "dir";
//# sourceMappingURL=core.js.map
+1
View File
File diff suppressed because one or more lines are too long
+33
View File
@@ -0,0 +1,33 @@
import { Configuration } from "../configuration";
import { Framework } from "../Framework";
import { Packager } from "../index";
export type ElectronPlatformName = "darwin" | "linux" | "win32" | "mas";
/**
* Electron distributables branding options.
* @see [Electron BRANDING.json](https://github.com/electron/electron/blob/master/shell/app/BRANDING.json).
*/
export interface ElectronBrandingOptions {
projectName?: string;
productName?: string;
}
export declare function createBrandingOpts(opts: Configuration): Required<ElectronBrandingOptions>;
export interface ElectronDownloadOptions {
version?: string;
/**
* The [cache location](https://github.com/electron-userland/electron-download#cache-location).
*/
cache?: string | null;
/**
* The mirror.
*/
mirror?: string | null;
/** @private */
customDir?: string | null;
/** @private */
customFilename?: string | null;
strictSSL?: boolean;
isVerifyChecksum?: boolean;
platform?: ElectronPlatformName;
arch?: string;
}
export declare function createElectronFrameworkSupport(configuration: Configuration, packager: Packager): Promise<Framework>;
+221
View File
@@ -0,0 +1,221 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBrandingOpts = createBrandingOpts;
exports.createElectronFrameworkSupport = createElectronFrameworkSupport;
const builder_util_1 = require("builder-util");
const fs_extra_1 = require("fs-extra");
const path = require("path");
const tiny_async_pool_1 = require("tiny-async-pool");
const index_1 = require("../index");
const pathManager_1 = require("../util/pathManager");
const resolve_1 = require("../util/resolve");
const electronMac_1 = require("./electronMac");
const electronVersion_1 = require("./electronVersion");
const electronWin_1 = require("./electronWin");
const injectFFMPEG_1 = require("./injectFFMPEG");
function createBrandingOpts(opts) {
var _a, _b;
return {
projectName: ((_a = opts.electronBranding) === null || _a === void 0 ? void 0 : _a.projectName) || "electron",
productName: ((_b = opts.electronBranding) === null || _b === void 0 ? void 0 : _b.productName) || "Electron",
};
}
function createDownloadOpts(opts, platform, arch, electronVersion) {
return {
platform,
arch,
version: electronVersion,
...opts.electronDownload,
};
}
async function beforeCopyExtraFiles(options) {
const { appOutDir, packager } = options;
const electronBranding = createBrandingOpts(packager.config);
if (packager.platform === index_1.Platform.LINUX) {
const linuxPackager = packager;
const executable = path.join(appOutDir, linuxPackager.executableName);
await (0, fs_extra_1.rename)(path.join(appOutDir, electronBranding.projectName), executable);
}
else if (packager.platform === index_1.Platform.WINDOWS) {
const executable = path.join(appOutDir, `${packager.appInfo.productFilename}.exe`);
await (0, fs_extra_1.rename)(path.join(appOutDir, `${electronBranding.projectName}.exe`), executable);
if (options.asarIntegrity) {
await (0, electronWin_1.addWinAsarIntegrity)(executable, options.asarIntegrity);
}
}
else {
await (0, electronMac_1.createMacApp)(packager, appOutDir, options.asarIntegrity, options.platformName === "mas");
}
await removeUnusedLanguagesIfNeeded(options);
}
async function removeUnusedLanguagesIfNeeded(options) {
const { packager: { config, platformSpecificBuildOptions }, } = options;
const wantedLanguages = (0, builder_util_1.asArray)(platformSpecificBuildOptions.electronLanguages || config.electronLanguages);
if (!wantedLanguages.length) {
return;
}
const { dirs, langFileExt } = getLocalesConfig(options);
// noinspection SpellCheckingInspection
const deletedFiles = async (dir) => {
await (0, tiny_async_pool_1.default)(builder_util_1.MAX_FILE_REQUESTS, await (0, fs_extra_1.readdir)(dir), async (file) => {
if (path.extname(file) !== langFileExt) {
return;
}
const language = path.basename(file, langFileExt);
if (!wantedLanguages.includes(language)) {
return (0, fs_extra_1.rm)(path.join(dir, file), { recursive: true, force: true });
}
return;
});
};
await Promise.all(dirs.map(deletedFiles));
function getLocalesConfig(options) {
const { appOutDir, packager } = options;
if (packager.platform === index_1.Platform.MAC) {
return { dirs: [packager.getResourcesDir(appOutDir), packager.getMacOsElectronFrameworkResourcesDir(appOutDir)], langFileExt: ".lproj" };
}
return { dirs: [path.join(packager.getResourcesDir(appOutDir), "..", "locales")], langFileExt: ".pak" };
}
}
class ElectronFramework {
constructor(name, version, distMacOsAppName) {
this.name = name;
this.version = version;
this.distMacOsAppName = distMacOsAppName;
// noinspection JSUnusedGlobalSymbols
this.macOsDefaultTargets = ["zip", "dmg"];
// noinspection JSUnusedGlobalSymbols
this.defaultAppIdPrefix = "com.electron.";
// noinspection JSUnusedGlobalSymbols
this.isCopyElevateHelper = true;
// noinspection JSUnusedGlobalSymbols
this.isNpmRebuildRequired = true;
}
getDefaultIcon(platform) {
if (platform === index_1.Platform.LINUX) {
return path.join((0, pathManager_1.getTemplatePath)("icons"), "electron-linux");
}
else {
// default icon is embedded into app skeleton
return null;
}
}
async prepareApplicationStageDirectory(options) {
const downloadOptions = createDownloadOpts(options.packager.config, options.platformName, options.arch, this.version);
const shouldCleanup = await unpack(options, downloadOptions, this.distMacOsAppName);
await cleanupAfterUnpack(options, this.distMacOsAppName, shouldCleanup);
if (options.packager.config.downloadAlternateFFmpeg) {
await (0, injectFFMPEG_1.default)(options, this.version);
}
}
beforeCopyExtraFiles(options) {
return beforeCopyExtraFiles(options);
}
}
async function createElectronFrameworkSupport(configuration, packager) {
let version = configuration.electronVersion;
if (version == null) {
// for prepacked app asar no dev deps in the app.asar
if (packager.isPrepackedAppAsar) {
version = await (0, electronVersion_1.getElectronVersionFromInstalled)(packager.projectDir);
if (version == null) {
throw new Error(`Cannot compute electron version for prepacked asar`);
}
}
else {
version = await (0, electronVersion_1.computeElectronVersion)(packager.projectDir);
}
configuration.electronVersion = version;
}
const branding = createBrandingOpts(configuration);
return new ElectronFramework(branding.projectName, version, `${branding.productName}.app`);
}
/**
* Unpacks a custom or default Electron distribution into the app output directory.
*/
async function unpack(prepareOptions, downloadOptions, distMacOsAppName) {
const downloadUsingAdjustedConfig = (options) => {
return (0, builder_util_1.executeAppBuilder)(["unpack-electron", "--configuration", JSON.stringify([options]), "--output", appOutDir, "--distMacOsAppName", distMacOsAppName]);
};
const copyUnpackedElectronDistribution = async (folderPath) => {
builder_util_1.log.info({ electronDist: builder_util_1.log.filePath(folderPath) }, "using custom unpacked Electron distribution");
const source = packager.getElectronSrcDir(folderPath);
const destination = packager.getElectronDestinationDir(appOutDir);
builder_util_1.log.info({ source, destination }, "copying unpacked Electron");
await (0, fs_extra_1.emptyDir)(appOutDir);
await (0, builder_util_1.copyDir)(source, destination, {
isUseHardLink: builder_util_1.DO_NOT_USE_HARD_LINKS,
});
return false;
};
const selectElectron = async (filepath) => {
const resolvedDist = path.isAbsolute(filepath) ? filepath : path.resolve(packager.projectDir, filepath);
const electronDistStats = await (0, builder_util_1.statOrNull)(resolvedDist);
if (!electronDistStats) {
throw new Error(`The specified electronDist does not exist: ${resolvedDist}. Please provide a valid path to the Electron zip file, cache directory, or electron build directory.`);
}
if (resolvedDist.endsWith(".zip")) {
builder_util_1.log.info({ zipFile: resolvedDist }, "using custom electronDist zip file");
await downloadUsingAdjustedConfig({
...downloadOptions,
cache: path.dirname(resolvedDist), // set custom directory to the zip file's directory
customFilename: path.basename(resolvedDist), // set custom filename to the zip file's name
});
return false; // do not clean up after unpacking, it's a custom bundle and we should respect its configuration/contents as required
}
if (electronDistStats.isDirectory()) {
// backward compatibility: if electronDist is a directory, check for the default zip file inside it
const files = await (0, fs_extra_1.readdir)(resolvedDist);
if (files.includes(defaultZipName)) {
builder_util_1.log.info({ electronDist: builder_util_1.log.filePath(resolvedDist) }, "using custom electronDist directory");
await downloadUsingAdjustedConfig({
...downloadOptions,
cache: resolvedDist,
customFilename: defaultZipName,
});
return false;
}
// if we reach here, it means the provided electronDist is neither a zip file nor a directory with the default zip file
// e.g. we treat it as a custom already-unpacked Electron distribution
return await copyUnpackedElectronDistribution(resolvedDist);
}
throw new Error(`The specified electronDist is neither a zip file nor a directory: ${resolvedDist}. Please provide a valid path to the Electron zip file or cache directory.`);
};
const { packager, appOutDir, platformName } = prepareOptions;
const { version, arch } = downloadOptions;
const defaultZipName = `electron-v${version}-${platformName}-${arch}.zip`;
const electronDist = packager.config.electronDist;
if (typeof electronDist === "string" && !(0, builder_util_1.isEmptyOrSpaces)(electronDist)) {
return selectElectron(electronDist);
}
let resolvedDist = null;
try {
const electronDistHook = await (0, resolve_1.resolveFunction)(packager.appInfo.type, electronDist, "electronDist");
resolvedDist = typeof electronDistHook === "function" ? await Promise.resolve(electronDistHook(prepareOptions)) : electronDistHook;
}
catch (error) {
builder_util_1.log.warn({ error }, "Failed to resolve electronDist, using default unpack logic");
}
if (resolvedDist == null) {
// if no custom electronDist is provided, use the default unpack logic
builder_util_1.log.debug(null, "no custom electronDist provided, unpacking default Electron distribution");
await downloadUsingAdjustedConfig(downloadOptions);
return true; // indicates that we should clean up after unpacking
}
return selectElectron(resolvedDist);
}
function cleanupAfterUnpack(prepareOptions, distMacOsAppName, isFullCleanup) {
const out = prepareOptions.appOutDir;
const isMac = prepareOptions.packager.platform === index_1.Platform.MAC;
const resourcesPath = isMac ? path.join(out, distMacOsAppName, "Contents", "Resources") : path.join(out, "resources");
return Promise.all([
isFullCleanup ? (0, builder_util_1.unlinkIfExists)(path.join(resourcesPath, "default_app.asar")) : Promise.resolve(),
isFullCleanup ? (0, builder_util_1.unlinkIfExists)(path.join(out, "version")) : Promise.resolve(),
isMac
? Promise.resolve()
: (0, fs_extra_1.rename)(path.join(out, "LICENSE"), path.join(out, "LICENSE.electron.txt")).catch(() => {
/* ignore */
}),
]);
}
//# sourceMappingURL=ElectronFramework.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export {};
+262
View File
@@ -0,0 +1,262 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMacApp = createMacApp;
const builder_util_1 = require("builder-util");
const promises_1 = require("fs/promises");
const path = require("path");
const fs = require("fs");
const appInfo_1 = require("../appInfo");
const platformPackager_1 = require("../platformPackager");
const plist_1 = require("../util/plist");
const ElectronFramework_1 = require("./ElectronFramework");
function doRename(basePath, oldName, newName) {
return (0, promises_1.rename)(path.join(basePath, oldName), path.join(basePath, newName));
}
function moveHelpers(helperSuffixes, frameworksPath, appName, prefix) {
return Promise.all(helperSuffixes.map(suffix => {
const executableBasePath = path.join(frameworksPath, `${prefix}${suffix}.app`, "Contents", "MacOS");
return doRename(executableBasePath, `${prefix}${suffix}`, appName + suffix).then(() => doRename(frameworksPath, `${prefix}${suffix}.app`, `${appName}${suffix}.app`));
}));
}
function getAvailableHelperSuffixes(helperEHPlist, helperNPPlist, helperRendererPlist, helperPluginPlist, helperGPUPlist) {
const result = [" Helper"];
if (helperEHPlist != null) {
result.push(" Helper EH");
}
if (helperNPPlist != null) {
result.push(" Helper NP");
}
if (helperRendererPlist != null) {
result.push(" Helper (Renderer)");
}
if (helperPluginPlist != null) {
result.push(" Helper (Plugin)");
}
if (helperGPUPlist != null) {
result.push(" Helper (GPU)");
}
return result;
}
/** @internal */
async function createMacApp(packager, appOutDir, asarIntegrity, isMas) {
var _a, _b;
const appInfo = packager.appInfo;
// Electon uses the application name (CFBundleName) to resolve helper apps
// https://github.com/electron/electron/blob/main/shell/app/electron_main_delegate_mac.mm
// https://github.com/electron-userland/electron-builder/issues/6962
const appFilename = appInfo.sanitizedProductName;
const electronBranding = (0, ElectronFramework_1.createBrandingOpts)(packager.config);
const contentsPath = path.join(appOutDir, packager.info.framework.distMacOsAppName, "Contents");
const frameworksPath = path.join(contentsPath, "Frameworks");
const loginItemPath = path.join(contentsPath, "Library", "LoginItems");
const appPlistFilename = path.join(contentsPath, "Info.plist");
const helperPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper.app`, "Contents", "Info.plist");
const helperEHPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper EH.app`, "Contents", "Info.plist");
const helperNPPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper NP.app`, "Contents", "Info.plist");
const helperRendererPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper (Renderer).app`, "Contents", "Info.plist");
const helperPluginPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper (Plugin).app`, "Contents", "Info.plist");
const helperGPUPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper (GPU).app`, "Contents", "Info.plist");
const helperLoginPlistFilename = path.join(loginItemPath, `${electronBranding.productName} Login Helper.app`, "Contents", "Info.plist");
const safeParsePlistFile = async (filePath) => {
if (!fs.existsSync(filePath)) {
return null;
}
return await (0, plist_1.parsePlistFile)(filePath);
};
const appPlist = (await safeParsePlistFile(appPlistFilename));
if (appPlist == null) {
throw new Error("corrupted Electron dist");
}
// Replace the multiple parsePlistFile calls with:
const helperPlist = await safeParsePlistFile(helperPlistFilename);
const helperEHPlist = await safeParsePlistFile(helperEHPlistFilename);
const helperNPPlist = await safeParsePlistFile(helperNPPlistFilename);
const helperRendererPlist = await safeParsePlistFile(helperRendererPlistFilename);
const helperPluginPlist = await safeParsePlistFile(helperPluginPlistFilename);
const helperGPUPlist = await safeParsePlistFile(helperGPUPlistFilename);
const helperLoginPlist = await safeParsePlistFile(helperLoginPlistFilename);
const buildMetadata = packager.config;
/**
* Configure bundleIdentifier for the generic Electron Helper process
*
* This was the only Helper in Electron 5 and before. Allow users to configure
* the bundleIdentifier for continuity.
*/
const oldHelperBundleId = buildMetadata["helper-bundle-id"];
if (oldHelperBundleId != null) {
builder_util_1.log.warn("build.helper-bundle-id is deprecated, please set as build.mac.helperBundleId");
}
const defaultAppId = packager.platformSpecificBuildOptions.appId;
const cfBundleIdentifier = (0, appInfo_1.filterCFBundleIdentifier)((isMas ? (_a = packager.config.mas) === null || _a === void 0 ? void 0 : _a.appId : defaultAppId) || defaultAppId || appInfo.macBundleIdentifier);
const defaultHelperId = packager.platformSpecificBuildOptions.helperBundleId;
const helperBundleIdentifier = (0, appInfo_1.filterCFBundleIdentifier)((isMas ? (_b = packager.config.mas) === null || _b === void 0 ? void 0 : _b.helperBundleId : defaultHelperId) || defaultHelperId || oldHelperBundleId || `${cfBundleIdentifier}.helper`);
appPlist.CFBundleIdentifier = cfBundleIdentifier;
await packager.applyCommonInfo(appPlist, contentsPath);
// required for electron-updater proxy
if (!isMas) {
configureLocalhostAts(appPlist);
}
if (helperPlist != null) {
helperPlist.CFBundleExecutable = `${appFilename} Helper`;
helperPlist.CFBundleDisplayName = `${appInfo.productName} Helper`;
helperPlist.CFBundleIdentifier = helperBundleIdentifier;
helperPlist.CFBundleVersion = appPlist.CFBundleVersion;
}
/**
* Configure bundleIdentifier for Electron 5+ Helper processes
*
* In Electron 6, parts of the generic Electron Helper process were split into
* individual helper processes. Allow users to configure the bundleIdentifiers
* for continuity, specifically because macOS keychain access relies on
* bundleIdentifiers not changing (i.e. across versions of Electron).
*/
function configureHelper(helper, postfix, userProvidedBundleIdentifier) {
helper.CFBundleExecutable = `${appFilename} Helper ${postfix}`;
helper.CFBundleDisplayName = `${appInfo.productName} Helper ${postfix}`;
helper.CFBundleIdentifier = userProvidedBundleIdentifier
? (0, appInfo_1.filterCFBundleIdentifier)(userProvidedBundleIdentifier)
: (0, appInfo_1.filterCFBundleIdentifier)(`${helperBundleIdentifier}.${postfix}`);
helper.CFBundleVersion = appPlist.CFBundleVersion;
}
if (helperRendererPlist != null) {
configureHelper(helperRendererPlist, "(Renderer)", packager.platformSpecificBuildOptions.helperRendererBundleId);
}
if (helperPluginPlist != null) {
configureHelper(helperPluginPlist, "(Plugin)", packager.platformSpecificBuildOptions.helperPluginBundleId);
}
if (helperGPUPlist != null) {
configureHelper(helperGPUPlist, "(GPU)", packager.platformSpecificBuildOptions.helperGPUBundleId);
}
if (helperEHPlist != null) {
configureHelper(helperEHPlist, "EH", packager.platformSpecificBuildOptions.helperEHBundleId);
}
if (helperNPPlist != null) {
configureHelper(helperNPPlist, "NP", packager.platformSpecificBuildOptions.helperNPBundleId);
}
if (helperLoginPlist != null) {
helperLoginPlist.CFBundleExecutable = `${appFilename} Login Helper`;
helperLoginPlist.CFBundleDisplayName = `${appInfo.productName} Login Helper`;
// noinspection SpellCheckingInspection
helperLoginPlist.CFBundleIdentifier = `${cfBundleIdentifier}.loginhelper`;
helperLoginPlist.CFBundleVersion = appPlist.CFBundleVersion;
}
const protocols = (0, builder_util_1.asArray)(buildMetadata.protocols).concat((0, builder_util_1.asArray)(packager.platformSpecificBuildOptions.protocols));
if (protocols.length > 0) {
appPlist.CFBundleURLTypes = protocols.map(protocol => {
const schemes = (0, builder_util_1.asArray)(protocol.schemes);
if (schemes.length === 0) {
throw new builder_util_1.InvalidConfigurationError(`Protocol "${protocol.name}": must be at least one scheme specified`);
}
return {
CFBundleURLName: protocol.name,
CFBundleTypeRole: protocol.role || "Editor",
CFBundleURLSchemes: schemes.slice(),
};
});
}
const fileAssociations = packager.fileAssociations;
if (fileAssociations.length > 0) {
const documentTypes = await Promise.all(fileAssociations.map(async (fileAssociation) => {
const extensions = (0, builder_util_1.asArray)(fileAssociation.ext).map(platformPackager_1.normalizeExt);
const customIcon = await packager.getResource((0, builder_util_1.getPlatformIconFileName)(fileAssociation.icon, true), `${extensions[0]}.icns`);
let iconFile = appPlist.CFBundleIconFile;
if (customIcon != null) {
iconFile = path.basename(customIcon);
await (0, builder_util_1.copyOrLinkFile)(customIcon, path.join(path.join(contentsPath, "Resources"), iconFile));
}
const result = {
CFBundleTypeExtensions: extensions,
CFBundleTypeName: fileAssociation.name || extensions[0],
CFBundleTypeRole: fileAssociation.role || "Editor",
LSHandlerRank: fileAssociation.rank || "Default",
CFBundleTypeIconFile: iconFile,
};
if (fileAssociation.isPackage) {
result.LSTypeIsPackage = true;
}
return result;
}));
// `CFBundleDocumentTypes` may be defined in `mac.extendInfo`, so we need to merge it in that case
appPlist.CFBundleDocumentTypes = [...(appPlist.CFBundleDocumentTypes || []), ...documentTypes];
}
const toPlistObject = (asarIntegrity) => {
const result = {};
for (const [filePath, headerHash] of Object.entries(asarIntegrity)) {
result[filePath] = {
algorithm: headerHash.algorithm,
hash: headerHash.hash,
};
}
return result;
};
if (asarIntegrity != null) {
appPlist.ElectronAsarIntegrity = toPlistObject(asarIntegrity);
}
if (helperEHPlist != null) {
await (0, plist_1.savePlistFile)(helperEHPlistFilename, helperEHPlist);
}
if (helperNPPlist != null) {
await (0, plist_1.savePlistFile)(helperNPPlistFilename, helperNPPlist);
}
if (helperRendererPlist != null) {
await (0, plist_1.savePlistFile)(helperRendererPlistFilename, helperRendererPlist);
}
if (helperPluginPlist != null) {
await (0, plist_1.savePlistFile)(helperPluginPlistFilename, helperPluginPlist);
}
if (helperGPUPlist != null) {
await (0, plist_1.savePlistFile)(helperGPUPlistFilename, helperGPUPlist);
}
if (helperLoginPlist != null) {
await (0, plist_1.savePlistFile)(helperLoginPlistFilename, helperLoginPlist);
}
await (0, plist_1.savePlistFile)(appPlistFilename, appPlist);
if (helperPlist != null) {
await (0, plist_1.savePlistFile)(helperPlistFilename, helperPlist);
}
await Promise.all([
doRename(path.join(contentsPath, "MacOS"), electronBranding.productName, appPlist.CFBundleExecutable),
(0, builder_util_1.unlinkIfExists)(path.join(appOutDir, "LICENSE")),
(0, builder_util_1.unlinkIfExists)(path.join(appOutDir, "LICENSES.chromium.html")),
]);
await moveHelpers(getAvailableHelperSuffixes(helperEHPlist, helperNPPlist, helperRendererPlist, helperPluginPlist, helperGPUPlist), frameworksPath, appFilename, electronBranding.productName);
if (helperLoginPlist != null) {
const prefix = electronBranding.productName;
const suffix = " Login Helper";
const executableBasePath = path.join(loginItemPath, `${prefix}${suffix}.app`, "Contents", "MacOS");
await doRename(executableBasePath, `${prefix}${suffix}`, appFilename + suffix).then(() => doRename(loginItemPath, `${prefix}${suffix}.app`, `${appFilename}${suffix}.app`));
}
const appPath = path.join(appOutDir, `${appInfo.productFilename}.app`);
await (0, promises_1.rename)(path.dirname(contentsPath), appPath);
// https://github.com/electron-userland/electron-builder/issues/840
const now = Date.now() / 1000;
await (0, promises_1.utimes)(appPath, now, now);
}
function configureLocalhostAts(appPlist) {
// https://bencoding.com/2015/07/20/app-transport-security-and-localhost/
let ats = appPlist.NSAppTransportSecurity;
if (ats == null) {
ats = {};
appPlist.NSAppTransportSecurity = ats;
}
ats.NSAllowsLocalNetworking = true;
// https://github.com/electron-userland/electron-builder/issues/3377#issuecomment-446035814
ats.NSAllowsArbitraryLoads = true;
let exceptionDomains = ats.NSExceptionDomains;
if (exceptionDomains == null) {
exceptionDomains = {};
ats.NSExceptionDomains = exceptionDomains;
}
if (exceptionDomains.localhost == null) {
const allowHttp = {
NSTemporaryExceptionAllowsInsecureHTTPSLoads: false,
NSIncludesSubdomains: false,
NSTemporaryExceptionAllowsInsecureHTTPLoads: true,
NSTemporaryExceptionMinimumTLSVersion: "1.0",
NSTemporaryExceptionRequiresForwardSecrecy: false,
};
exceptionDomains.localhost = allowHttp;
exceptionDomains["127.0.0.1"] = allowHttp;
}
}
//# sourceMappingURL=electronMac.js.map
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
import { Lazy } from "lazy-val";
import { Configuration } from "../configuration";
export type MetadataValue = Lazy<Record<string, any> | null>;
export declare function getElectronVersion(projectDir: string, config?: Configuration): Promise<string>;
export declare function getElectronVersionFromInstalled(projectDir: string): Promise<string | null>;
export declare function getElectronPackage(projectDir: string): Promise<any>;
+120
View File
@@ -0,0 +1,120 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getElectronVersion = getElectronVersion;
exports.getElectronVersionFromInstalled = getElectronVersionFromInstalled;
exports.getElectronPackage = getElectronPackage;
exports.computeElectronVersion = computeElectronVersion;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const fs_extra_1 = require("fs-extra");
const path = require("path");
const semver = require("semver");
const config_1 = require("../util/config/config");
const load_1 = require("../util/config/load");
const search_module_1 = require("./search-module");
const electronPackages = ["electron", "electron-prebuilt", "electron-prebuilt-compile", "electron-nightly"];
async function getElectronVersion(projectDir, config) {
if (config == null) {
config = await (0, config_1.getConfig)(projectDir, null, null);
}
if (config.electronVersion != null) {
return config.electronVersion;
}
return computeElectronVersion(projectDir);
}
async function getElectronVersionFromInstalled(projectDir) {
for (const name of electronPackages) {
try {
return (await (0, fs_extra_1.readJson)(path.join(projectDir, "node_modules", name, "package.json"))).version;
}
catch (e) {
if (e.code !== "ENOENT") {
builder_util_1.log.warn({ name, error: e }, `cannot read electron version package.json`);
}
}
}
return null;
}
async function getElectronPackage(projectDir) {
for (const name of electronPackages) {
try {
return await (0, fs_extra_1.readJson)(path.join(projectDir, "node_modules", name, "package.json"));
}
catch (e) {
if (e.code !== "ENOENT") {
builder_util_1.log.warn({ name, error: e }, `cannot find electron in package.json`);
}
}
}
return null;
}
/** @internal */
async function computeElectronVersion(projectDir) {
const result = await getElectronVersionFromInstalled(projectDir);
if (result != null) {
return result;
}
const potentialRootDirs = [projectDir, await (0, search_module_1.getProjectRootPath)(projectDir)];
let dependency = null;
for (const dir of potentialRootDirs) {
const metadata = await (0, load_1.orNullIfFileNotExist)((0, fs_extra_1.readJson)(path.join(dir, "package.json")));
dependency = metadata ? findFromPackageMetadata(metadata) : null;
if (dependency) {
break;
}
}
if ((dependency === null || dependency === void 0 ? void 0 : dependency.name) === "electron-nightly") {
builder_util_1.log.info("You are using a nightly version of electron, be warned that those builds are highly unstable.");
const feedXml = await builder_util_1.httpExecutor.request({
hostname: "github.com",
path: `/electron/nightlies/releases.atom`,
headers: {
accept: "application/xml, application/atom+xml, text/xml, */*",
},
});
const feed = (0, builder_util_runtime_1.parseXml)(feedXml);
const latestRelease = feed.element("entry", false, `No published versions on GitHub`);
const v = /\/tag\/v?([^/]+)$/.exec(latestRelease.element("link").attribute("href"))[1];
return v.startsWith("v") ? v.substring(1) : v;
}
else if ((dependency === null || dependency === void 0 ? void 0 : dependency.version) === "latest") {
builder_util_1.log.warn('Electron version is set to "latest", but it is recommended to set it to some more restricted version range.');
try {
const releaseInfo = JSON.parse((await builder_util_1.httpExecutor.request({
hostname: "github.com",
path: `/electron/${dependency.name === "electron-nightly" ? "nightlies" : "electron"}/releases/latest`,
headers: {
accept: "application/json",
},
})));
const version = releaseInfo.tag_name.startsWith("v") ? releaseInfo.tag_name.substring(1) : releaseInfo.tag_name;
builder_util_1.log.info({ version }, `resolve ${dependency.name}@${dependency.version}`);
return version;
}
catch (e) {
builder_util_1.log.warn(e);
}
throw new builder_util_1.InvalidConfigurationError(`Cannot find electron dependency to get electron version in the '${path.join(projectDir, "package.json")}'`);
}
const version = dependency === null || dependency === void 0 ? void 0 : dependency.version;
if (version == null || !/^\d/.test(version)) {
const versionMessage = version == null ? "" : ` and version ("${version}") is not fixed in project`;
throw new builder_util_1.InvalidConfigurationError(`Cannot compute electron version from installed node modules - none of the possible electron modules are installed${versionMessage}.\nSee https://github.com/electron-userland/electron-builder/issues/3984#issuecomment-504968246`);
}
return semver.coerce(version).format();
}
function findFromPackageMetadata(packageData) {
for (const name of electronPackages) {
const devDependencies = packageData.devDependencies;
let dep = devDependencies == null ? null : devDependencies[name];
if (dep == null) {
const dependencies = packageData.dependencies;
dep = dependencies == null ? null : dependencies[name];
}
if (dep != null) {
return { name, version: dep };
}
}
return null;
}
//# sourceMappingURL=electronVersion.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export {};
+38
View File
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addWinAsarIntegrity = addWinAsarIntegrity;
const builder_util_1 = require("builder-util");
const promises_1 = require("fs/promises");
const path = require("path");
const resedit_1 = require("resedit");
/** @internal */
async function addWinAsarIntegrity(executablePath, asarIntegrity) {
const buffer = await (0, promises_1.readFile)(executablePath);
const executable = resedit_1.NtExecutable.from(buffer);
const resource = resedit_1.NtExecutableResource.from(executable);
const versionInfo = resedit_1.Resource.VersionInfo.fromEntries(resource.entries);
if (versionInfo.length !== 1) {
throw new Error(`Failed to parse version info in ${executablePath}`);
}
const languages = versionInfo[0].getAllLanguagesForStringValues();
if (languages.length !== 1) {
throw new Error(`Failed to locate languages in ${executablePath}`);
}
// See: https://github.com/electron/packager/blob/00d20b99cf4aa4621103dbbd09ff7de7d2f7f539/src/resedit.ts#L124
const integrityList = Array.from(Object.entries(asarIntegrity)).map(([file, { algorithm: alg, hash: value }]) => ({
file: path.win32.normalize(file),
alg,
value,
}));
resource.entries.push({
type: "INTEGRITY",
id: "ELECTRONASAR",
bin: Buffer.from(JSON.stringify(integrityList)),
lang: languages[0].lang,
codepage: languages[0].codepage,
});
resource.outputResource(executable);
await (0, promises_1.writeFile)(executablePath, Buffer.from(executable.generate()));
builder_util_1.log.info({ executablePath: builder_util_1.log.filePath(executablePath) }, "updating asar integrity executable resource");
}
//# sourceMappingURL=electronWin.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"electronWin.js","sourceRoot":"","sources":["../../src/electron/electronWin.ts"],"names":[],"mappings":";;AAOA,kDAkCC;AAzCD,+CAAkC;AAClC,0CAAiD;AACjD,6BAA4B;AAC5B,qCAAsE;AAGtE,gBAAgB;AACT,KAAK,UAAU,mBAAmB,CAAC,cAAsB,EAAE,aAA4B;IAC5F,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAQ,EAAC,cAAc,CAAC,CAAA;IAC7C,MAAM,UAAU,GAAG,sBAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,8BAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAEtD,MAAM,WAAW,GAAG,kBAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IACtE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,mCAAmC,cAAc,EAAE,CAAC,CAAA;IACtE,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,8BAA8B,EAAE,CAAA;IACjE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,iCAAiC,cAAc,EAAE,CAAC,CAAA;IACpE,CAAC;IAED,8GAA8G;IAC9G,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAChH,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC;QAChC,GAAG;QACH,KAAK;KACN,CAAC,CAAC,CAAA;IAEH,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;QACpB,IAAI,EAAE,WAAW;QACjB,EAAE,EAAE,cAAc;QAClB,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC/C,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;QACvB,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ;KAChC,CAAC,CAAA;IAEF,QAAQ,CAAC,cAAc,CAAC,UAAU,CAAC,CAAA;IAEnC,MAAM,IAAA,oBAAS,EAAC,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IACnE,kBAAG,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,kBAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,EAAE,6CAA6C,CAAC,CAAA;AAC3G,CAAC","sourcesContent":["import { log } from \"builder-util\"\nimport { readFile, writeFile } from \"fs/promises\"\nimport * as path from \"path\"\nimport { NtExecutable, NtExecutableResource, Resource } from \"resedit\"\nimport { AsarIntegrity } from \"../asar/integrity\"\n\n/** @internal */\nexport async function addWinAsarIntegrity(executablePath: string, asarIntegrity: AsarIntegrity) {\n const buffer = await readFile(executablePath)\n const executable = NtExecutable.from(buffer)\n const resource = NtExecutableResource.from(executable)\n\n const versionInfo = Resource.VersionInfo.fromEntries(resource.entries)\n if (versionInfo.length !== 1) {\n throw new Error(`Failed to parse version info in ${executablePath}`)\n }\n\n const languages = versionInfo[0].getAllLanguagesForStringValues()\n if (languages.length !== 1) {\n throw new Error(`Failed to locate languages in ${executablePath}`)\n }\n\n // See: https://github.com/electron/packager/blob/00d20b99cf4aa4621103dbbd09ff7de7d2f7f539/src/resedit.ts#L124\n const integrityList = Array.from(Object.entries(asarIntegrity)).map(([file, { algorithm: alg, hash: value }]) => ({\n file: path.win32.normalize(file),\n alg,\n value,\n }))\n\n resource.entries.push({\n type: \"INTEGRITY\",\n id: \"ELECTRONASAR\",\n bin: Buffer.from(JSON.stringify(integrityList)),\n lang: languages[0].lang,\n codepage: languages[0].codepage,\n })\n\n resource.outputResource(executable)\n\n await writeFile(executablePath, Buffer.from(executable.generate()))\n log.info({ executablePath: log.filePath(executablePath) }, \"updating asar integrity executable resource\")\n}\n"]}
+2
View File
@@ -0,0 +1,2 @@
import { PrepareApplicationStageDirectoryOptions } from "../Framework";
export default function injectFFMPEG(options: PrepareApplicationStageDirectoryOptions, electrionVersion: string): Promise<string>;
+43
View File
@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = injectFFMPEG;
const fs = require("fs");
const path = require("path");
const builder_util_1 = require("builder-util");
const binDownload_1 = require("../binDownload");
// NOTE: Adapted from https://github.com/MarshallOfSound/electron-packager-plugin-non-proprietary-codecs-ffmpeg to resolve dependency vulnerabilities
const downloadFFMPEG = async (electronVersion, platform, arch) => {
const ffmpegFileName = `ffmpeg-v${electronVersion}-${platform}-${arch}.zip`;
const url = `https://github.com/electron/electron/releases/download/v${electronVersion}/${ffmpegFileName}`;
builder_util_1.log.info({ file: ffmpegFileName }, "downloading non-proprietary FFMPEG");
return (0, binDownload_1.getBin)(ffmpegFileName, url);
};
const copyFFMPEG = (targetPath, platform) => (sourcePath) => {
let fileName = "ffmpeg.dll";
if (["darwin", "mas"].includes(platform)) {
fileName = "libffmpeg.dylib";
}
else if (platform === "linux") {
fileName = "libffmpeg.so";
}
const libPath = path.resolve(sourcePath, fileName);
const libTargetPath = path.resolve(targetPath, fileName);
builder_util_1.log.info({ lib: builder_util_1.log.filePath(libPath), target: builder_util_1.log.filePath(libTargetPath) }, "copying non-proprietary FFMPEG");
// If the source doesn't exist we have a problem
if (!fs.existsSync(libPath)) {
throw new Error(`Failed to find FFMPEG library file at path: ${libPath}`);
}
// If we are copying to the source we can stop immediately
if (libPath !== libTargetPath) {
fs.copyFileSync(libPath, libTargetPath);
}
return libTargetPath;
};
function injectFFMPEG(options, electrionVersion) {
let libPath = options.appOutDir;
if (options.platformName === "darwin") {
libPath = path.resolve(options.appOutDir, "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries");
}
return downloadFFMPEG(electrionVersion, options.platformName, options.arch).then(copyFFMPEG(libPath, options.platformName));
}
//# sourceMappingURL=injectFFMPEG.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"injectFFMPEG.js","sourceRoot":"","sources":["../../src/electron/injectFFMPEG.ts"],"names":[],"mappings":";;AAyCA,+BAOC;AAhDD,yBAAwB;AACxB,6BAA4B;AAG5B,+CAAkC;AAClC,gDAAuC;AAGvC,qJAAqJ;AACrJ,MAAM,cAAc,GAAG,KAAK,EAAE,eAAuB,EAAE,QAA8B,EAAE,IAAY,EAAE,EAAE;IACrG,MAAM,cAAc,GAAG,WAAW,eAAe,IAAI,QAAQ,IAAI,IAAI,MAAM,CAAA;IAC3E,MAAM,GAAG,GAAG,2DAA2D,eAAe,IAAI,cAAc,EAAE,CAAA;IAE1G,kBAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,oCAAoC,CAAC,CAAA;IACxE,OAAO,IAAA,oBAAM,EAAC,cAAc,EAAE,GAAG,CAAC,CAAA;AACpC,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,UAAkB,EAAE,QAA8B,EAAE,EAAE,CAAC,CAAC,UAAkB,EAAE,EAAE;IAChG,IAAI,QAAQ,GAAG,YAAY,CAAA;IAC3B,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,QAAQ,GAAG,iBAAiB,CAAA;IAC9B,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,QAAQ,GAAG,cAAc,CAAA;IAC3B,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IAClD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACxD,kBAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,kBAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,kBAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,EAAE,gCAAgC,CAAC,CAAA;IAE/G,gDAAgD;IAChD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,+CAA+C,OAAO,EAAE,CAAC,CAAA;IAC3E,CAAC;IAED,0DAA0D;IAC1D,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;QAC9B,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IACzC,CAAC;IACD,OAAO,aAAa,CAAA;AACtB,CAAC,CAAA;AAED,SAAwB,YAAY,CAAC,OAAgD,EAAE,gBAAwB;IAC7G,IAAI,OAAO,GAAG,OAAO,CAAC,SAAS,CAAA;IAC/B,IAAI,OAAO,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,oFAAoF,CAAC,CAAA;IACjI,CAAC;IAED,OAAO,cAAc,CAAC,gBAAgB,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAA;AAC7H,CAAC","sourcesContent":["import * as fs from \"fs\"\nimport * as path from \"path\"\nimport { ElectronPlatformName } from \"./ElectronFramework\"\n\nimport { log } from \"builder-util\"\nimport { getBin } from \"../binDownload\"\nimport { PrepareApplicationStageDirectoryOptions } from \"../Framework\"\n\n// NOTE: Adapted from https://github.com/MarshallOfSound/electron-packager-plugin-non-proprietary-codecs-ffmpeg to resolve dependency vulnerabilities\nconst downloadFFMPEG = async (electronVersion: string, platform: ElectronPlatformName, arch: string) => {\n const ffmpegFileName = `ffmpeg-v${electronVersion}-${platform}-${arch}.zip`\n const url = `https://github.com/electron/electron/releases/download/v${electronVersion}/${ffmpegFileName}`\n\n log.info({ file: ffmpegFileName }, \"downloading non-proprietary FFMPEG\")\n return getBin(ffmpegFileName, url)\n}\n\nconst copyFFMPEG = (targetPath: string, platform: ElectronPlatformName) => (sourcePath: string) => {\n let fileName = \"ffmpeg.dll\"\n if ([\"darwin\", \"mas\"].includes(platform)) {\n fileName = \"libffmpeg.dylib\"\n } else if (platform === \"linux\") {\n fileName = \"libffmpeg.so\"\n }\n\n const libPath = path.resolve(sourcePath, fileName)\n const libTargetPath = path.resolve(targetPath, fileName)\n log.info({ lib: log.filePath(libPath), target: log.filePath(libTargetPath) }, \"copying non-proprietary FFMPEG\")\n\n // If the source doesn't exist we have a problem\n if (!fs.existsSync(libPath)) {\n throw new Error(`Failed to find FFMPEG library file at path: ${libPath}`)\n }\n\n // If we are copying to the source we can stop immediately\n if (libPath !== libTargetPath) {\n fs.copyFileSync(libPath, libTargetPath)\n }\n return libTargetPath\n}\n\nexport default function injectFFMPEG(options: PrepareApplicationStageDirectoryOptions, electrionVersion: string) {\n let libPath = options.appOutDir\n if (options.platformName === \"darwin\") {\n libPath = path.resolve(options.appOutDir, \"Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries\")\n }\n\n return downloadFFMPEG(electrionVersion, options.platformName, options.arch).then(copyFFMPEG(libPath, options.platformName))\n}\n"]}
+23
View File
@@ -0,0 +1,23 @@
/**
* Find all instances of a given module in node_modules subdirectories while traversing up
* ancestor directories.
*
* @param cwd the initial directory to traverse
* @param moduleName the Node module name (should work for scoped modules as well)
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
*/
export declare function searchForModule(cwd: string, moduleName: string, rootPath?: string): Promise<string[]>;
/**
* Find all instances of node_modules subdirectories while traversing up ancestor directories.
*
* @param cwd the initial directory to traverse
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
*/
export declare function searchForNodeModules(cwd: string, rootPath?: string): Promise<string[]>;
/**
* Determine the root directory of a given project, by looking for a directory with an
* NPM or yarn lockfile or pnpm lockfile.
*
* @param cwd the initial directory to traverse
*/
export declare function getProjectRootPath(cwd: string): Promise<string>;
+73
View File
@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchForModule = searchForModule;
exports.searchForNodeModules = searchForNodeModules;
exports.getProjectRootPath = getProjectRootPath;
const fs = require("fs-extra");
const path = require("node:path");
async function shouldContinueSearch(traversedPath, rootPath, stopAtPackageJSON) {
if (rootPath) {
return Promise.resolve(traversedPath !== path.dirname(rootPath));
}
else if (stopAtPackageJSON) {
return fs.existsSync(path.join(traversedPath, "package.json"));
}
else {
return true;
}
}
async function traverseAncestorDirectories(cwd, pathGenerator, rootPath, maxItems, stopAtPackageJSON) {
const paths = [];
let traversedPath = path.resolve(cwd);
while (await shouldContinueSearch(traversedPath, rootPath, stopAtPackageJSON)) {
const generatedPath = pathGenerator(traversedPath);
if (fs.existsSync(generatedPath)) {
paths.push(generatedPath);
}
const parentPath = path.dirname(traversedPath);
if (parentPath === traversedPath || (maxItems && paths.length >= maxItems)) {
break;
}
traversedPath = parentPath;
}
return paths;
}
/**
* Find all instances of a given module in node_modules subdirectories while traversing up
* ancestor directories.
*
* @param cwd the initial directory to traverse
* @param moduleName the Node module name (should work for scoped modules as well)
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
*/
async function searchForModule(cwd, moduleName, rootPath) {
const pathGenerator = traversedPath => path.join(traversedPath, "node_modules", moduleName);
return traverseAncestorDirectories(cwd, pathGenerator, rootPath, undefined, true);
}
/**
* Find all instances of node_modules subdirectories while traversing up ancestor directories.
*
* @param cwd the initial directory to traverse
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
*/
async function searchForNodeModules(cwd, rootPath) {
const pathGenerator = traversedPath => path.join(traversedPath, "node_modules");
return traverseAncestorDirectories(cwd, pathGenerator, rootPath, undefined, true);
}
/**
* Determine the root directory of a given project, by looking for a directory with an
* NPM or yarn lockfile or pnpm lockfile.
*
* @param cwd the initial directory to traverse
*/
async function getProjectRootPath(cwd) {
for (const lockFilename of ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock", "bun.lockb"]) {
const pathGenerator = traversedPath => path.join(traversedPath, lockFilename);
const lockPaths = await traverseAncestorDirectories(cwd, pathGenerator, undefined, 1);
if (lockPaths.length > 0) {
return path.dirname(lockPaths[0]);
}
}
return cwd;
}
//# sourceMappingURL=search-module.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export declare const authorEmailIsMissed = "Please specify author 'email' in the application package.json\n\nSee https://docs.npmjs.com/files/package.json#people-fields-author-contributors\n\nIt is required to set Linux .deb package maintainer. Or you can set maintainer in the custom linux options.\n(see https://www.electron.build/linux).\n";
+11
View File
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.authorEmailIsMissed = void 0;
exports.authorEmailIsMissed = `Please specify author 'email' in the application package.json
See https://docs.npmjs.com/files/package.json#people-fields-author-contributors
It is required to set Linux .deb package maintainer. Or you can set maintainer in the custom linux options.
(see https://www.electron.build/linux).
`;
//# sourceMappingURL=errorMessages.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"errorMessages.js","sourceRoot":"","sources":["../src/errorMessages.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG;;;;;;CAMlC,CAAA","sourcesContent":["export const authorEmailIsMissed = `Please specify author 'email' in the application package.json\n\nSee https://docs.npmjs.com/files/package.json#people-fields-author-contributors\n\nIt is required to set Linux .deb package maintainer. Or you can set maintainer in the custom linux options.\n(see https://www.electron.build/linux).\n`\n"]}
+28
View File
@@ -0,0 +1,28 @@
import { Filter } from "builder-util";
import { Minimatch } from "minimatch";
import { PlatformSpecificBuildOptions } from "./index";
export declare const excludedNames: string;
export declare const excludedExts: string;
export declare class FileMatcher {
readonly macroExpander: (pattern: string) => string;
readonly from: string;
readonly to: string;
readonly patterns: Array<string>;
excludePatterns: Array<Minimatch> | null;
readonly isSpecifiedAsEmptyArray: boolean;
constructor(from: string, to: string, macroExpander: (pattern: string) => string, patterns?: Array<string> | string | null);
normalizePattern(pattern: string): string;
addPattern(pattern: string): void;
prependPattern(pattern: string): void;
isEmpty(): boolean;
containsOnlyIgnore(): boolean;
computeParsedPatterns(result: Array<Minimatch>, fromDir?: string): void;
createFilter(): Filter;
toString(): string;
}
export interface GetFileMatchersOptions {
readonly macroExpander: (pattern: string) => string;
readonly customBuildOptions: PlatformSpecificBuildOptions;
readonly globalOutDir: string;
readonly defaultSrc: string;
}
+293
View File
@@ -0,0 +1,293 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileMatcher = exports.excludedExts = exports.excludedNames = void 0;
exports.getMainFileMatchers = getMainFileMatchers;
exports.getNodeModuleFileMatcher = getNodeModuleFileMatcher;
exports.getFileMatchers = getFileMatchers;
exports.copyFiles = copyFiles;
const builder_util_1 = require("builder-util");
const promises_1 = require("fs/promises");
const minimatch_1 = require("minimatch");
const path = require("path");
const filter_1 = require("./util/filter");
// https://github.com/electron-userland/electron-builder/issues/733
const minimatchOptions = { dot: true };
// noinspection SpellCheckingInspection
exports.excludedNames = ".git,.hg,.svn,CVS,RCS,SCCS," +
"__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore," +
".idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci," +
".yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,pnpm-lock.yaml,bun.lock,bun.lockb," +
"appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github,electron-builder.env";
exports.excludedExts = "iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts," +
// https://github.com/electron-userland/electron-builder/issues/7512
"mk,a,o,obj,forge-meta";
function ensureNoEndSlash(file) {
if (path.sep !== "/") {
file = file.replace(/\//g, path.sep);
}
if (path.sep !== "\\") {
file = file.replace(/\\/g, path.sep);
}
if (file.endsWith(path.sep)) {
return file.substring(0, file.length - 1);
}
else {
return file;
}
}
class FileMatcher {
constructor(from, to, macroExpander, patterns) {
this.macroExpander = macroExpander;
this.excludePatterns = null;
this.from = ensureNoEndSlash(macroExpander(from));
this.to = ensureNoEndSlash(macroExpander(to));
this.patterns = (0, builder_util_1.asArray)(patterns).map(it => this.normalizePattern(it));
this.isSpecifiedAsEmptyArray = Array.isArray(patterns) && patterns.length === 0;
}
normalizePattern(pattern) {
if (pattern.startsWith("./")) {
pattern = pattern.substring("./".length);
}
return path.posix.normalize(this.macroExpander(pattern.replace(/\\/g, "/")));
}
addPattern(pattern) {
this.patterns.push(this.normalizePattern(pattern));
}
prependPattern(pattern) {
this.patterns.unshift(this.normalizePattern(pattern));
}
isEmpty() {
return this.patterns.length === 0;
}
containsOnlyIgnore() {
return !this.isEmpty() && this.patterns.find(it => !it.startsWith("!")) == null;
}
computeParsedPatterns(result, fromDir) {
const relativeFrom = fromDir == null ? null : path.relative(fromDir, this.from);
if (this.patterns.length === 0 && relativeFrom != null) {
// file mappings, from here is a file
result.push(new minimatch_1.Minimatch(relativeFrom, minimatchOptions));
return;
}
for (let pattern of this.patterns) {
if (relativeFrom != null) {
pattern = path.join(relativeFrom, pattern);
}
const parsedPattern = new minimatch_1.Minimatch(pattern, minimatchOptions);
result.push(parsedPattern);
// do not add if contains dot (possibly file if has extension)
if (!pattern.includes(".") && !(0, filter_1.hasMagic)(parsedPattern)) {
// https://github.com/electron-userland/electron-builder/issues/545
// add **/*
result.push(new minimatch_1.Minimatch(`${pattern}/**/*`, minimatchOptions));
}
}
}
createFilter() {
const parsedPatterns = [];
this.computeParsedPatterns(parsedPatterns);
return (0, filter_1.createFilter)(this.from, parsedPatterns, this.excludePatterns);
}
toString() {
return `from: ${this.from}, to: ${this.to}, patterns: ${this.patterns.join(", ")}`;
}
}
exports.FileMatcher = FileMatcher;
/** @internal */
function getMainFileMatchers(appDir, destination, macroExpander, platformSpecificBuildOptions, platformPackager, outDir, isElectronCompile) {
const packager = platformPackager.info;
const buildResourceDir = path.resolve(packager.projectDir, packager.buildResourcesDir);
let matchers = packager.isPrepackedAppAsar
? null
: getFileMatchers(packager.config, "files", destination, {
macroExpander,
customBuildOptions: platformSpecificBuildOptions,
globalOutDir: outDir,
defaultSrc: appDir,
});
if (matchers == null) {
matchers = [new FileMatcher(appDir, destination, macroExpander)];
}
const matcher = matchers[0];
// add default patterns, but only if from equals to app dir
if (matcher.from !== appDir) {
return matchers;
}
// https://github.com/electron-userland/electron-builder/issues/1741#issuecomment-311111418 so, do not use inclusive patterns
const patterns = matcher.patterns;
const customFirstPatterns = [];
// electron-webpack - we need to copy only package.json and node_modules from root dir (and these files are added by default), so, explicit empty array is specified
if (!matcher.isSpecifiedAsEmptyArray && (matcher.isEmpty() || matcher.containsOnlyIgnore())) {
customFirstPatterns.push("**/*");
}
else if (!patterns.includes("package.json")) {
patterns.push("package.json");
}
let insertExculdeNodeModulesIndex = -1;
for (let i = 0; i < patterns.length; i++) {
if (!patterns[i].startsWith("!") && (patterns[i].includes("/node_modules") || patterns[i].includes("node_modules/"))) {
insertExculdeNodeModulesIndex = i;
break;
}
}
if (insertExculdeNodeModulesIndex !== -1) {
patterns.splice(insertExculdeNodeModulesIndex, 0, ...["!**/node_modules/**"]);
}
else {
customFirstPatterns.push("!**/node_modules/**");
}
// https://github.com/electron-userland/electron-builder/issues/1482
const relativeBuildResourceDir = path.relative(matcher.from, buildResourceDir);
if (relativeBuildResourceDir.length !== 0 && !relativeBuildResourceDir.startsWith(".")) {
customFirstPatterns.push(`!${relativeBuildResourceDir}{,/**/*}`);
}
const relativeOutDir = matcher.normalizePattern(path.relative(packager.projectDir, outDir));
if (!relativeOutDir.startsWith(".")) {
customFirstPatterns.push(`!${relativeOutDir}{,/**/*}`);
}
// add our default exclusions after last user possibly defined "all"/permissive pattern
let insertIndex = 0;
for (let i = patterns.length - 1; i >= 0; i--) {
if (patterns[i].startsWith("**/")) {
insertIndex = i + 1;
break;
}
}
patterns.splice(insertIndex, 0, ...customFirstPatterns);
patterns.push(`!**/*.{${exports.excludedExts}${packager.config.includePdb === true ? "" : ",pdb"}}`);
patterns.push("!**/._*");
patterns.push("!**/electron-builder.{yaml,yml,json,json5,toml,ts}");
patterns.push(`!**/{${exports.excludedNames}}`);
if (isElectronCompile) {
patterns.push("!.cache{,/**/*}");
}
patterns.push("!.yarn{,/**/*}");
// https://github.com/electron-userland/electron-builder/issues/1969
// exclude ony for app root, use .yarnclean to clean node_modules
patterns.push("!.editorconfig");
patterns.push("!.yarnrc.yml");
const debugLogger = packager.debugLogger;
if (debugLogger.isEnabled) {
//tslint:disable-next-line:no-invalid-template-strings
debugLogger.add(`${macroExpander("${arch}")}.firstOrDefaultFilePatterns`, patterns);
}
return matchers;
}
/** @internal */
function getNodeModuleFileMatcher(appDir, destination, macroExpander, platformSpecificBuildOptions, packager) {
// https://github.com/electron-userland/electron-builder/pull/2948#issuecomment-392241632
// grab only excludes
const matcher = new FileMatcher(appDir, destination, macroExpander);
function addPatterns(patterns) {
if (patterns == null) {
return;
}
else if (!Array.isArray(patterns)) {
if (typeof patterns === "string" && patterns.startsWith("!")) {
matcher.addPattern(patterns);
return;
}
// ignore object form
return;
}
for (const pattern of patterns) {
if (typeof pattern === "string") {
if (pattern.startsWith("!")) {
matcher.addPattern(pattern);
}
}
else {
const fileSet = pattern;
if (fileSet.from == null || fileSet.from === ".") {
for (const p of (0, builder_util_1.asArray)(fileSet.filter)) {
matcher.addPattern(p);
}
}
}
}
}
addPatterns(packager.config.files);
addPatterns(platformSpecificBuildOptions.files);
if (!matcher.isEmpty()) {
matcher.prependPattern("**/*");
}
const debugLogger = packager.debugLogger;
if (debugLogger.isEnabled) {
//tslint:disable-next-line:no-invalid-template-strings
debugLogger.add(`${macroExpander("${arch}")}.nodeModuleFilePatterns`, matcher.patterns);
}
return matcher;
}
/** @internal */
function getFileMatchers(config, name, defaultDestination, options) {
const defaultMatcher = new FileMatcher(options.defaultSrc, defaultDestination, options.macroExpander);
const fileMatchers = [];
function addPatterns(patterns) {
if (patterns == null) {
return;
}
else if (!Array.isArray(patterns)) {
if (typeof patterns === "string") {
defaultMatcher.addPattern(patterns);
return;
}
patterns = [patterns];
}
for (const pattern of patterns) {
if (typeof pattern === "string") {
// use normalize to transform ./foo to foo
defaultMatcher.addPattern(pattern);
}
else if (name === "asarUnpack") {
throw new Error(`Advanced file copying not supported for "${name}"`);
}
else {
const from = pattern.from == null ? options.defaultSrc : path.resolve(options.defaultSrc, pattern.from);
const to = pattern.to == null ? defaultDestination : path.resolve(defaultDestination, pattern.to);
fileMatchers.push(new FileMatcher(from, to, options.macroExpander, pattern.filter));
}
}
}
if (name !== "extraDistFiles") {
addPatterns(config[name]);
}
addPatterns(options.customBuildOptions[name]);
if (!defaultMatcher.isEmpty()) {
// default matcher should be first in the array
fileMatchers.unshift(defaultMatcher);
}
// we cannot exclude the whole out dir, because sometimes users want to use some file in the out dir in the patterns
const relativeOutDir = defaultMatcher.normalizePattern(path.relative(options.defaultSrc, options.globalOutDir));
if (!relativeOutDir.startsWith(".")) {
defaultMatcher.addPattern(`!${relativeOutDir}/*-unpacked{,/**/*}`);
}
return fileMatchers.length === 0 ? null : fileMatchers;
}
/** @internal */
function copyFiles(matchers, transformer, isUseHardLink) {
if (matchers == null || matchers.length === 0) {
return Promise.resolve();
}
return Promise.all(matchers.map(async (matcher) => {
const fromStat = await (0, builder_util_1.statOrNull)(matcher.from);
if (fromStat == null) {
builder_util_1.log.warn({ from: matcher.from }, `file source doesn't exist`);
return;
}
if (fromStat.isFile()) {
const toStat = await (0, builder_util_1.statOrNull)(matcher.to);
// https://github.com/electron-userland/electron-builder/issues/1245
if (toStat != null && toStat.isDirectory()) {
return await (0, builder_util_1.copyOrLinkFile)(matcher.from, path.join(matcher.to, path.basename(matcher.from)), fromStat, isUseHardLink);
}
await (0, promises_1.mkdir)(path.dirname(matcher.to), { recursive: true });
return await (0, builder_util_1.copyOrLinkFile)(matcher.from, matcher.to, fromStat);
}
if (matcher.isEmpty() || matcher.containsOnlyIgnore()) {
matcher.prependPattern("**/*");
}
builder_util_1.log.debug({ matcher }, "copying files using pattern");
return await (0, builder_util_1.copyDir)(matcher.from, matcher.to, { filter: matcher.createFilter(), transformer, isUseHardLink: isUseHardLink ? builder_util_1.USE_HARD_LINKS : null });
}));
}
//# sourceMappingURL=fileMatcher.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export {};
+106
View File
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NODE_MODULES_PATTERN = void 0;
exports.isElectronCompileUsed = isElectronCompileUsed;
exports.hasDep = hasDep;
exports.createTransformer = createTransformer;
exports.createElectronCompilerHost = createElectronCompilerHost;
const builder_util_1 = require("builder-util");
const promises_1 = require("fs/promises");
const path = require("path");
/** @internal */
exports.NODE_MODULES_PATTERN = `${path.sep}node_modules${path.sep}`;
/** @internal */
function isElectronCompileUsed(info) {
if (info.config.electronCompile != null) {
return info.config.electronCompile;
}
// if in devDependencies - it means that babel is used for precompilation or for some reason user decided to not use electron-compile for production
return hasDep("electron-compile", info);
}
/** @internal */
function hasDep(name, info) {
const deps = info.metadata.dependencies;
return deps != null && name in deps;
}
/** @internal */
function createTransformer(srcDir, configuration, extraMetadata, extraTransformer) {
const mainPackageJson = path.join(srcDir, "package.json");
const isRemovePackageScripts = configuration.removePackageScripts !== false;
const isRemovePackageKeywords = configuration.removePackageKeywords !== false;
const packageJson = path.sep + "package.json";
return file => {
if (file === mainPackageJson) {
return modifyMainPackageJson(file, extraMetadata, isRemovePackageScripts, isRemovePackageKeywords);
}
if (file.endsWith(packageJson) && file.includes(exports.NODE_MODULES_PATTERN)) {
return (0, promises_1.readFile)(file, "utf-8")
.then(it => cleanupPackageJson(JSON.parse(it), {
isMain: false,
isRemovePackageScripts,
isRemovePackageKeywords,
}))
.catch((e) => builder_util_1.log.warn(e));
}
else if (extraTransformer != null) {
return extraTransformer(file);
}
else {
return null;
}
};
}
/** @internal */
function createElectronCompilerHost(projectDir, cacheDir) {
const electronCompilePath = path.join(projectDir, "node_modules", "electron-compile", "lib");
return require(path.join(electronCompilePath, "config-parser")).createCompilerHostFromProjectRoot(projectDir, cacheDir);
}
const ignoredPackageMetadataProperties = new Set(["dist", "gitHead", "build", "jspm", "ava", "xo", "nyc", "eslintConfig", "contributors", "bundleDependencies", "tags"]);
function cleanupPackageJson(data, options) {
const deps = data.dependencies;
// https://github.com/electron-userland/electron-builder/issues/507#issuecomment-312772099
const isRemoveBabel = deps != null && typeof deps === "object" && !Object.getOwnPropertyNames(deps).some(it => it.startsWith("babel"));
try {
let changed = false;
for (const prop of Object.getOwnPropertyNames(data)) {
// removing devDependencies from package.json breaks levelup in electron, so, remove it only from main package.json
if (prop[0] === "_" ||
ignoredPackageMetadataProperties.has(prop) ||
(options.isRemovePackageScripts && prop === "scripts") ||
(options.isRemovePackageKeywords && prop === "keywords") ||
(options.isMain && prop === "devDependencies") ||
(!options.isMain && prop === "bugs") ||
(isRemoveBabel && prop === "babel")) {
delete data[prop];
changed = true;
}
}
if (changed) {
return JSON.stringify(data, null, 2);
}
}
catch (e) {
(0, builder_util_1.debug)(e);
}
return null;
}
async function modifyMainPackageJson(file, extraMetadata, isRemovePackageScripts, isRemovePackageKeywords) {
const mainPackageData = JSON.parse(await (0, promises_1.readFile)(file, "utf-8"));
if (extraMetadata != null) {
(0, builder_util_1.deepAssign)(mainPackageData, extraMetadata);
}
// https://github.com/electron-userland/electron-builder/issues/1212
const serializedDataIfChanged = cleanupPackageJson(mainPackageData, {
isMain: true,
isRemovePackageScripts,
isRemovePackageKeywords,
});
if (serializedDataIfChanged != null) {
return serializedDataIfChanged;
}
else if (extraMetadata != null) {
return JSON.stringify(mainPackageData, null, 2);
}
return null;
}
//# sourceMappingURL=fileTransformer.js.map
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
import { PackagerOptions } from "./packagerApi";
export interface ForgeOptions {
readonly dir: string;
}
export declare function buildForge(forgeOptions: ForgeOptions, options: PackagerOptions): Promise<string[]>;
+19
View File
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildForge = buildForge;
const path = require("path");
const index_1 = require("./index");
function buildForge(forgeOptions, options) {
const appDir = forgeOptions.dir;
return (0, index_1.build)({
prepackaged: appDir,
config: {
directories: {
// https://github.com/electron-userland/electron-forge/blob/master/src/makers/generic/zip.js
output: path.resolve(appDir, "..", "make"),
},
},
...options,
});
}
//# sourceMappingURL=forge-maker.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"forge-maker.js","sourceRoot":"","sources":["../src/forge-maker.ts"],"names":[],"mappings":";;AAQA,gCAYC;AApBD,6BAA4B;AAC5B,mCAA+B;AAO/B,SAAgB,UAAU,CAAC,YAA0B,EAAE,OAAwB;IAC7E,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAA;IAC/B,OAAO,IAAA,aAAK,EAAC;QACX,WAAW,EAAE,MAAM;QACnB,MAAM,EAAE;YACN,WAAW,EAAE;gBACX,4FAA4F;gBAC5F,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;aAC3C;SACF;QACD,GAAG,OAAO;KACX,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import * as path from \"path\"\nimport { build } from \"./index\"\nimport { PackagerOptions } from \"./packagerApi\"\n\nexport interface ForgeOptions {\n readonly dir: string\n}\n\nexport function buildForge(forgeOptions: ForgeOptions, options: PackagerOptions) {\n const appDir = forgeOptions.dir\n return build({\n prepackaged: appDir,\n config: {\n directories: {\n // https://github.com/electron-userland/electron-forge/blob/master/src/makers/generic/zip.js\n output: path.resolve(appDir, \"..\", \"make\"),\n },\n },\n ...options,\n })\n}\n"]}
+21
View File
@@ -0,0 +1,21 @@
import { AfterPackContext } from "../configuration";
import { Platform } from "../core";
import { Framework, PrepareApplicationStageDirectoryOptions } from "../Framework";
export declare class LibUiFramework implements Framework {
readonly version: string;
readonly distMacOsAppName: string;
protected readonly isUseLaunchUi: boolean;
readonly name: string;
readonly macOsDefaultTargets: string[];
readonly defaultAppIdPrefix: string;
readonly isCopyElevateHelper = false;
readonly isNpmRebuildRequired = false;
constructor(version: string, distMacOsAppName: string, isUseLaunchUi: boolean);
prepareApplicationStageDirectory(options: PrepareApplicationStageDirectoryOptions): Promise<void>;
private prepareMacosApplicationStageDirectory;
private prepareLinuxApplicationStageDirectory;
afterPack(context: AfterPackContext): Promise<void>;
getMainFile(platform: Platform): string | null;
private isUseLaunchUiForPlatform;
getExcludedDependencies(platform: Platform): Array<string> | null;
}
+106
View File
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LibUiFramework = void 0;
const builder_util_1 = require("builder-util");
const fs_extra_1 = require("fs-extra");
const promises_1 = require("fs/promises");
const path = require("path");
const core_1 = require("../core");
const plist_1 = require("../util/plist");
class LibUiFramework {
constructor(version, distMacOsAppName, isUseLaunchUi) {
this.version = version;
this.distMacOsAppName = distMacOsAppName;
this.isUseLaunchUi = isUseLaunchUi;
this.name = "libui";
// noinspection JSUnusedGlobalSymbols
this.macOsDefaultTargets = ["dmg"];
this.defaultAppIdPrefix = "com.libui.";
// noinspection JSUnusedGlobalSymbols
this.isCopyElevateHelper = false;
// noinspection JSUnusedGlobalSymbols
this.isNpmRebuildRequired = false;
}
async prepareApplicationStageDirectory(options) {
await (0, fs_extra_1.emptyDir)(options.appOutDir);
const packager = options.packager;
const platform = packager.platform;
if (this.isUseLaunchUiForPlatform(platform)) {
const appOutDir = options.appOutDir;
await (0, builder_util_1.executeAppBuilder)([
"proton-native",
"--node-version",
this.version,
"--use-launch-ui",
"--platform",
platform.nodeName,
"--arch",
options.arch,
"--stage",
appOutDir,
"--executable",
`${packager.appInfo.productFilename}${platform === core_1.Platform.WINDOWS ? ".exe" : ""}`,
]);
return;
}
if (platform === core_1.Platform.MAC) {
await this.prepareMacosApplicationStageDirectory(packager, options);
}
else if (platform === core_1.Platform.LINUX) {
await this.prepareLinuxApplicationStageDirectory(options);
}
}
async prepareMacosApplicationStageDirectory(packager, options) {
const appContentsDir = path.join(options.appOutDir, this.distMacOsAppName, "Contents");
await (0, promises_1.mkdir)(path.join(appContentsDir, "Resources"), { recursive: true });
await (0, promises_1.mkdir)(path.join(appContentsDir, "MacOS"), { recursive: true });
await (0, builder_util_1.executeAppBuilder)(["proton-native", "--node-version", this.version, "--platform", "darwin", "--stage", path.join(appContentsDir, "MacOS")]);
const appPlist = {
// https://github.com/albe-rosado/create-proton-app/issues/13
NSHighResolutionCapable: true,
};
await packager.applyCommonInfo(appPlist, appContentsDir);
await (0, plist_1.savePlistFile)(path.join(appContentsDir, "Info.plist"), appPlist);
await writeExecutableMain(path.join(appContentsDir, "MacOS", appPlist.CFBundleExecutable), `#!/bin/sh
DIR=$(dirname "$0")
"$DIR/node" "$DIR/../Resources/app/${options.packager.info.metadata.main || "index.js"}"
`);
}
async prepareLinuxApplicationStageDirectory(options) {
const appOutDir = options.appOutDir;
await (0, builder_util_1.executeAppBuilder)(["proton-native", "--node-version", this.version, "--platform", "linux", "--arch", options.arch, "--stage", appOutDir]);
const mainPath = path.join(appOutDir, options.packager.executableName);
await writeExecutableMain(mainPath, `#!/bin/sh
DIR=$(dirname "$0")
"$DIR/node" "$DIR/app/${options.packager.info.metadata.main || "index.js"}"
`);
}
async afterPack(context) {
const packager = context.packager;
if (!this.isUseLaunchUiForPlatform(packager.platform)) {
return;
}
// LaunchUI requires main.js, rename if need
const userMain = packager.info.metadata.main || "index.js";
if (userMain === "main.js") {
return;
}
await (0, promises_1.rename)(path.join(context.appOutDir, "app", userMain), path.join(context.appOutDir, "app", "main.js"));
}
getMainFile(platform) {
return this.isUseLaunchUiForPlatform(platform) ? "main.js" : null;
}
isUseLaunchUiForPlatform(platform) {
return platform === core_1.Platform.WINDOWS || (this.isUseLaunchUi && platform === core_1.Platform.LINUX);
}
getExcludedDependencies(platform) {
// part of launchui
return this.isUseLaunchUiForPlatform(platform) ? ["libui-node"] : null;
}
}
exports.LibUiFramework = LibUiFramework;
async function writeExecutableMain(file, content) {
await (0, promises_1.writeFile)(file, content, { mode: 0o755 });
await (0, promises_1.chmod)(file, 0o755);
}
//# sourceMappingURL=LibUiFramework.js.map
File diff suppressed because one or more lines are too long
+37
View File
@@ -0,0 +1,37 @@
import { PublishOptions } from "electron-publish";
import { Packager } from "./packager";
import { PackagerOptions } from "./packagerApi";
export { Arch, archFromString, getArchSuffix } from "builder-util";
export { AppInfo } from "./appInfo";
export { AfterExtractContext, AfterPackContext, BeforePackContext, CommonConfiguration, Configuration, FuseOptionsV1, Hook, Hooks, MetadataDirectories, PackContext, ToolsetConfig, } from "./configuration";
export { BeforeBuildContext, CompressionLevel, DEFAULT_TARGET, DIR_TARGET, Platform, SourceRepositoryInfo, Target, TargetConfigType, TargetConfiguration, TargetSpecificOptions, } from "./core";
export { ElectronBrandingOptions, ElectronDownloadOptions, ElectronPlatformName } from "./electron/ElectronFramework";
export { AppXOptions } from "./options/AppXOptions";
export { CommonWindowsInstallerConfiguration } from "./options/CommonWindowsInstallerConfiguration";
export { FileAssociation } from "./options/FileAssociation";
export { AppImageOptions, CommonLinuxOptions, DebOptions, FlatpakOptions, LinuxConfiguration, LinuxDesktopFile, LinuxTargetSpecificOptions } from "./options/linuxOptions";
export { DmgContent, DmgOptions, DmgWindow, MacConfiguration, MacOsTargetName, MasConfiguration } from "./options/macOptions";
export { AuthorMetadata, Metadata, RepositoryInfo } from "./options/metadata";
export { MsiOptions } from "./options/MsiOptions";
export { MsiWrappedOptions } from "./options/MsiWrappedOptions";
export { BackgroundAlignment, BackgroundScaling, PkgBackgroundOptions, PkgOptions } from "./options/pkgOptions";
export { AsarOptions, FileSet, FilesBuildOptions, PlatformSpecificBuildOptions, Protocol, ReleaseInfo } from "./options/PlatformSpecificBuildOptions";
export { PlugDescriptor, SlotDescriptor, SnapOptions } from "./options/SnapOptions";
export { SquirrelWindowsOptions } from "./options/SquirrelWindowsOptions";
export { WindowsAzureSigningConfiguration, WindowsConfiguration, WindowsSigntoolConfiguration } from "./options/winOptions";
export { BuildResult, Packager } from "./packager";
export { ArtifactBuildStarted, ArtifactCreated, PackagerOptions } from "./packagerApi";
export { CommonNsisOptions, CustomNsisBinary, NsisOptions, NsisWebOptions, PortableOptions } from "./targets/nsis/nsisOptions";
export { CancellationToken, ProgressInfo } from "builder-util-runtime";
export { PublishOptions, UploadTask } from "electron-publish";
export { WindowsSignOptions } from "./codeSign/windowsCodeSign";
export { CertificateFromStoreInfo, CustomWindowsSign, CustomWindowsSignTaskConfiguration, FileCodeSigningInfo, WindowsSignTaskConfiguration, } from "./codeSign/windowsSignToolManager";
export { ForgeOptions, buildForge } from "./forge-maker";
export { Framework, PrepareApplicationStageDirectoryOptions } from "./Framework";
export { LinuxPackager } from "./linuxPackager";
export { CustomMacSign, CustomMacSignOptions, MacPackager } from "./macPackager";
export { PlatformPackager } from "./platformPackager";
export { PublishManager } from "./publish/PublishManager";
export { WinPackager } from "./winPackager";
export declare function checkBuildRequestOptions(options: PackagerOptions & PublishOptions): void;
export declare function build(options: PackagerOptions & PublishOptions, packager?: Packager): Promise<Array<string>>;
+97
View File
@@ -0,0 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WinPackager = exports.PublishManager = exports.PlatformPackager = exports.MacPackager = exports.LinuxPackager = exports.buildForge = exports.CancellationToken = exports.Packager = exports.Target = exports.Platform = exports.DIR_TARGET = exports.DEFAULT_TARGET = exports.AppInfo = exports.getArchSuffix = exports.archFromString = exports.Arch = void 0;
exports.checkBuildRequestOptions = checkBuildRequestOptions;
exports.build = build;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const packager_1 = require("./packager");
const PublishManager_1 = require("./publish/PublishManager");
const resolve_1 = require("./util/resolve");
var builder_util_2 = require("builder-util");
Object.defineProperty(exports, "Arch", { enumerable: true, get: function () { return builder_util_2.Arch; } });
Object.defineProperty(exports, "archFromString", { enumerable: true, get: function () { return builder_util_2.archFromString; } });
Object.defineProperty(exports, "getArchSuffix", { enumerable: true, get: function () { return builder_util_2.getArchSuffix; } });
var appInfo_1 = require("./appInfo");
Object.defineProperty(exports, "AppInfo", { enumerable: true, get: function () { return appInfo_1.AppInfo; } });
var core_1 = require("./core");
Object.defineProperty(exports, "DEFAULT_TARGET", { enumerable: true, get: function () { return core_1.DEFAULT_TARGET; } });
Object.defineProperty(exports, "DIR_TARGET", { enumerable: true, get: function () { return core_1.DIR_TARGET; } });
Object.defineProperty(exports, "Platform", { enumerable: true, get: function () { return core_1.Platform; } });
Object.defineProperty(exports, "Target", { enumerable: true, get: function () { return core_1.Target; } });
var packager_2 = require("./packager");
Object.defineProperty(exports, "Packager", { enumerable: true, get: function () { return packager_2.Packager; } });
var builder_util_runtime_2 = require("builder-util-runtime");
Object.defineProperty(exports, "CancellationToken", { enumerable: true, get: function () { return builder_util_runtime_2.CancellationToken; } });
var forge_maker_1 = require("./forge-maker");
Object.defineProperty(exports, "buildForge", { enumerable: true, get: function () { return forge_maker_1.buildForge; } });
var linuxPackager_1 = require("./linuxPackager");
Object.defineProperty(exports, "LinuxPackager", { enumerable: true, get: function () { return linuxPackager_1.LinuxPackager; } });
var macPackager_1 = require("./macPackager");
Object.defineProperty(exports, "MacPackager", { enumerable: true, get: function () { return macPackager_1.MacPackager; } });
var platformPackager_1 = require("./platformPackager");
Object.defineProperty(exports, "PlatformPackager", { enumerable: true, get: function () { return platformPackager_1.PlatformPackager; } });
var PublishManager_2 = require("./publish/PublishManager");
Object.defineProperty(exports, "PublishManager", { enumerable: true, get: function () { return PublishManager_2.PublishManager; } });
var winPackager_1 = require("./winPackager");
Object.defineProperty(exports, "WinPackager", { enumerable: true, get: function () { return winPackager_1.WinPackager; } });
const expectedOptions = new Set(["publish", "targets", "mac", "win", "linux", "projectDir", "platformPackagerFactory", "config", "effectiveOptionComputed", "prepackaged"]);
function checkBuildRequestOptions(options) {
for (const optionName of Object.keys(options)) {
if (!expectedOptions.has(optionName) && options[optionName] !== undefined) {
throw new builder_util_1.InvalidConfigurationError(`Unknown option "${optionName}"`);
}
}
}
function build(options, packager = new packager_1.Packager(options)) {
checkBuildRequestOptions(options);
const publishManager = new PublishManager_1.PublishManager(packager, options);
const sigIntHandler = () => {
builder_util_1.log.warn("cancelled by SIGINT");
packager.cancellationToken.cancel();
publishManager.cancelTasks();
};
process.once("SIGINT", sigIntHandler);
const promise = packager.build().then(async (buildResult) => {
const afterAllArtifactBuild = await (0, resolve_1.resolveFunction)(packager.appInfo.type, buildResult.configuration.afterAllArtifactBuild, "afterAllArtifactBuild");
if (afterAllArtifactBuild != null) {
const newArtifacts = (0, builder_util_runtime_1.asArray)(await Promise.resolve(afterAllArtifactBuild(buildResult)));
if (newArtifacts.length === 0 || !publishManager.isPublish) {
return buildResult.artifactPaths;
}
const publishConfigurations = await publishManager.getGlobalPublishConfigurations();
if (publishConfigurations == null || publishConfigurations.length === 0) {
return buildResult.artifactPaths;
}
for (const newArtifact of newArtifacts) {
if (buildResult.artifactPaths.includes(newArtifact)) {
builder_util_1.log.warn({ newArtifact }, "skipping publish of artifact, already published");
continue;
}
buildResult.artifactPaths.push(newArtifact);
for (const publishConfiguration of publishConfigurations) {
await publishManager.scheduleUpload(publishConfiguration, {
file: newArtifact,
arch: null,
}, packager.appInfo);
}
}
}
return buildResult.artifactPaths;
});
return (0, builder_util_1.executeFinally)(promise, isErrorOccurred => {
let promise;
if (isErrorOccurred) {
publishManager.cancelTasks();
promise = Promise.resolve(null);
}
else {
promise = publishManager.awaitTasks();
}
return promise.then(() => {
packager.clearPackagerEventListeners();
process.removeListener("SIGINT", sigIntHandler);
});
});
}
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
import { Arch } from "builder-util";
import { Target } from "./core";
import { LinuxConfiguration } from "./options/linuxOptions";
import { Packager } from "./packager";
import { PlatformPackager } from "./platformPackager";
export declare class LinuxPackager extends PlatformPackager<LinuxConfiguration> {
readonly executableName: string;
constructor(info: Packager);
get defaultTarget(): Array<string>;
createTargets(targets: Array<string>, mapper: (name: string, factory: (outDir: string) => Target) => void): void;
}
export declare function toAppImageOrSnapArch(arch: Arch): string;
+77
View File
@@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinuxPackager = void 0;
exports.toAppImageOrSnapArch = toAppImageOrSnapArch;
const builder_util_1 = require("builder-util");
const filename_1 = require("builder-util/out/filename");
const core_1 = require("./core");
const platformPackager_1 = require("./platformPackager");
const LinuxTargetHelper_1 = require("./targets/LinuxTargetHelper");
const targetFactory_1 = require("./targets/targetFactory");
class LinuxPackager extends platformPackager_1.PlatformPackager {
constructor(info) {
var _a;
super(info, core_1.Platform.LINUX);
const executableName = (_a = this.platformSpecificBuildOptions.executableName) !== null && _a !== void 0 ? _a : info.config.executableName;
this.executableName = executableName == null ? this.appInfo.sanitizedName.toLowerCase() : (0, filename_1.sanitizeFileName)(executableName);
}
get defaultTarget() {
return ["snap", "appimage"];
}
createTargets(targets, mapper) {
let helper;
const getHelper = () => {
if (helper == null) {
helper = new LinuxTargetHelper_1.LinuxTargetHelper(this);
}
return helper;
};
for (const name of targets) {
if (name === core_1.DIR_TARGET) {
continue;
}
const targetClass = (() => {
switch (name) {
case "appimage":
return require("./targets/appimage/AppImageTarget").default;
case "snap":
return require("./targets/snap").default;
case "flatpak":
return require("./targets/FlatpakTarget").default;
case "deb":
case "rpm":
case "sh":
case "freebsd":
case "pacman":
case "apk":
case "p5p":
return require("./targets/FpmTarget").default;
default:
return null;
}
})();
mapper(name, outDir => {
if (targetClass === null) {
return (0, targetFactory_1.createCommonTarget)(name, outDir, this);
}
return new targetClass(name, this, getHelper(), outDir);
});
}
}
}
exports.LinuxPackager = LinuxPackager;
function toAppImageOrSnapArch(arch) {
switch (arch) {
case builder_util_1.Arch.x64:
return "x86_64";
case builder_util_1.Arch.ia32:
return "i386";
case builder_util_1.Arch.armv7l:
return "arm";
case builder_util_1.Arch.arm64:
return "arm_aarch64";
default:
throw new Error(`Unsupported arch ${arch}`);
}
}
//# sourceMappingURL=linuxPackager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"linuxPackager.js","sourceRoot":"","sources":["../src/linuxPackager.ts"],"names":[],"mappings":";;;AAyEA,oDAcC;AAvFD,+CAAmC;AACnC,wDAA4D;AAC5D,iCAAqD;AAGrD,yDAAqD;AAIrD,mEAA+D;AAE/D,2DAA4D;AAE5D,MAAa,aAAc,SAAQ,mCAAoC;IAGrE,YAAY,IAAc;;QACxB,KAAK,CAAC,IAAI,EAAE,eAAQ,CAAC,KAAK,CAAC,CAAA;QAE3B,MAAM,cAAc,GAAG,MAAA,IAAI,CAAC,4BAA4B,CAAC,cAAc,mCAAI,IAAI,CAAC,MAAM,CAAC,cAAc,CAAA;QACrG,IAAI,CAAC,cAAc,GAAG,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAA,2BAAgB,EAAC,cAAc,CAAC,CAAA;IAC5H,CAAC;IAED,IAAI,aAAa;QACf,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC7B,CAAC;IAED,aAAa,CAAC,OAAsB,EAAE,MAAmE;QACvG,IAAI,MAAgC,CAAA;QACpC,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;gBACnB,MAAM,GAAG,IAAI,qCAAiB,CAAC,IAAI,CAAC,CAAA;YACtC,CAAC;YACD,OAAO,MAAM,CAAA;QACf,CAAC,CAAA;QAED,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,IAAI,IAAI,KAAK,iBAAU,EAAE,CAAC;gBACxB,SAAQ;YACV,CAAC;YAED,MAAM,WAAW,GAA+F,CAAC,GAAG,EAAE;gBACpH,QAAQ,IAAI,EAAE,CAAC;oBACb,KAAK,UAAU;wBACb,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC,OAAO,CAAA;oBAC7D,KAAK,MAAM;wBACT,OAAO,OAAO,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAA;oBAC1C,KAAK,SAAS;wBACZ,OAAO,OAAO,CAAC,yBAAyB,CAAC,CAAC,OAAO,CAAA;oBACnD,KAAK,KAAK,CAAC;oBACX,KAAK,KAAK,CAAC;oBACX,KAAK,IAAI,CAAC;oBACV,KAAK,SAAS,CAAC;oBACf,KAAK,QAAQ,CAAC;oBACd,KAAK,KAAK,CAAC;oBACX,KAAK,KAAK;wBACR,OAAO,OAAO,CAAC,qBAAqB,CAAC,CAAC,OAAO,CAAA;oBAC/C;wBACE,OAAO,IAAI,CAAA;gBACf,CAAC;YACH,CAAC,CAAC,EAAE,CAAA;YAEJ,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;gBACpB,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;oBACzB,OAAO,IAAA,kCAAkB,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;gBAC/C,CAAC;gBAED,OAAO,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAA;YACzD,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;CACF;AA1DD,sCA0DC;AAED,SAAgB,oBAAoB,CAAC,IAAU;IAC7C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,mBAAI,CAAC,GAAG;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,mBAAI,CAAC,IAAI;YACZ,OAAO,MAAM,CAAA;QACf,KAAK,mBAAI,CAAC,MAAM;YACd,OAAO,KAAK,CAAA;QACd,KAAK,mBAAI,CAAC,KAAK;YACb,OAAO,aAAa,CAAA;QAEtB;YACE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC","sourcesContent":["import { Arch } from \"builder-util\"\nimport { sanitizeFileName } from \"builder-util/out/filename\"\nimport { DIR_TARGET, Platform, Target } from \"./core\"\nimport { LinuxConfiguration } from \"./options/linuxOptions\"\nimport { Packager } from \"./packager\"\nimport { PlatformPackager } from \"./platformPackager\"\nimport AppImageTarget from \"./targets/appimage/AppImageTarget\"\nimport FlatpakTarget from \"./targets/FlatpakTarget\"\nimport FpmTarget from \"./targets/FpmTarget\"\nimport { LinuxTargetHelper } from \"./targets/LinuxTargetHelper\"\nimport SnapTarget from \"./targets/snap\"\nimport { createCommonTarget } from \"./targets/targetFactory\"\n\nexport class LinuxPackager extends PlatformPackager<LinuxConfiguration> {\n readonly executableName: string\n\n constructor(info: Packager) {\n super(info, Platform.LINUX)\n\n const executableName = this.platformSpecificBuildOptions.executableName ?? info.config.executableName\n this.executableName = executableName == null ? this.appInfo.sanitizedName.toLowerCase() : sanitizeFileName(executableName)\n }\n\n get defaultTarget(): Array<string> {\n return [\"snap\", \"appimage\"]\n }\n\n createTargets(targets: Array<string>, mapper: (name: string, factory: (outDir: string) => Target) => void): void {\n let helper: LinuxTargetHelper | null\n const getHelper = () => {\n if (helper == null) {\n helper = new LinuxTargetHelper(this)\n }\n return helper\n }\n\n for (const name of targets) {\n if (name === DIR_TARGET) {\n continue\n }\n\n const targetClass: typeof AppImageTarget | typeof SnapTarget | typeof FlatpakTarget | typeof FpmTarget | null = (() => {\n switch (name) {\n case \"appimage\":\n return require(\"./targets/appimage/AppImageTarget\").default\n case \"snap\":\n return require(\"./targets/snap\").default\n case \"flatpak\":\n return require(\"./targets/FlatpakTarget\").default\n case \"deb\":\n case \"rpm\":\n case \"sh\":\n case \"freebsd\":\n case \"pacman\":\n case \"apk\":\n case \"p5p\":\n return require(\"./targets/FpmTarget\").default\n default:\n return null\n }\n })()\n\n mapper(name, outDir => {\n if (targetClass === null) {\n return createCommonTarget(name, outDir, this)\n }\n\n return new targetClass(name, this, getHelper(), outDir)\n })\n }\n }\n}\n\nexport function toAppImageOrSnapArch(arch: Arch): string {\n switch (arch) {\n case Arch.x64:\n return \"x86_64\"\n case Arch.ia32:\n return \"i386\"\n case Arch.armv7l:\n return \"arm\"\n case Arch.arm64:\n return \"arm_aarch64\"\n\n default:\n throw new Error(`Unsupported arch ${arch}`)\n }\n}\n"]}
+35
View File
@@ -0,0 +1,35 @@
import { SignOptions } from "@electron/osx-sign/dist/cjs/types";
import { Identity } from "@electron/osx-sign/dist/cjs/util-identities";
import { Arch, AsyncTaskManager } from "builder-util";
import { MemoLazy, Nullish } from "builder-util-runtime";
import { AppInfo } from "./appInfo";
import { CodeSigningInfo, CreateKeychainOptions } from "./codeSign/macCodeSign";
import { Target } from "./core";
import { AfterPackContext } from "./index";
import { MacConfiguration } from "./options/macOptions";
import { Packager } from "./packager";
import { DoPackOptions, PlatformPackager } from "./platformPackager";
export type CustomMacSignOptions = SignOptions;
export type CustomMacSign = (configuration: CustomMacSignOptions, packager: MacPackager) => Promise<void>;
export declare class MacPackager extends PlatformPackager<MacConfiguration> {
readonly codeSigningInfo: MemoLazy<CreateKeychainOptions | null, CodeSigningInfo>;
private _iconPath;
constructor(info: Packager);
get defaultTarget(): Array<string>;
expandArch(pattern: string, arch?: Arch | null): string[];
protected prepareAppInfo(appInfo: AppInfo): AppInfo;
getIconPath(): Promise<string | null>;
createTargets(targets: Array<string>, mapper: (name: string, factory: (outDir: string) => Target) => void): void;
protected doPack(config: DoPackOptions<MacConfiguration>): Promise<any>;
pack(outDir: string, arch: Arch, targets: Array<Target>, taskManager: AsyncTaskManager): Promise<void>;
private sign;
private getOptionsForFile;
protected doSign(opts: SignOptions, customSignOptions: MacConfiguration, identity: Identity | null): Promise<void>;
protected doFlat(appPath: string, outFile: string, identity: Identity, keychain: string | Nullish): Promise<any>;
getElectronSrcDir(dist: string): string;
getElectronDestinationDir(appOutDir: string): string;
applyCommonInfo(appPlist: any, contentsPath: string): Promise<void>;
protected signApp(packContext: AfterPackContext, isAsar: boolean): Promise<boolean>;
notarizeIfProvided(appPath: string): Promise<void>;
private getNotarizeOptions;
}
+564
View File
@@ -0,0 +1,564 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MacPackager = void 0;
const notarize_1 = require("@electron/notarize");
const util_identities_1 = require("@electron/osx-sign/dist/cjs/util-identities");
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const fs = require("fs/promises");
const promises_1 = require("fs/promises");
const lazy_val_1 = require("lazy-val");
const path = require("path");
const appInfo_1 = require("./appInfo");
const macCodeSign_1 = require("./codeSign/macCodeSign");
const core_1 = require("./core");
const platformPackager_1 = require("./platformPackager");
const ArchiveTarget_1 = require("./targets/ArchiveTarget");
const pkg_1 = require("./targets/pkg");
const targetFactory_1 = require("./targets/targetFactory");
const macosVersion_1 = require("./util/macosVersion");
const pathManager_1 = require("./util/pathManager");
const resolve_1 = require("./util/resolve");
const macroExpander_1 = require("./util/macroExpander");
const universal_1 = require("@electron/universal");
class MacPackager extends platformPackager_1.PlatformPackager {
constructor(info) {
super(info, core_1.Platform.MAC);
this.codeSigningInfo = new builder_util_runtime_1.MemoLazy(() => {
const cscLink = this.getCscLink();
if (cscLink == null || process.platform !== "darwin") {
return null;
}
const selected = {
tmpDir: this.info.tempDirManager,
cscLink,
cscKeyPassword: this.getCscPassword(),
cscILink: (0, platformPackager_1.chooseNotNull)(this.platformSpecificBuildOptions.cscInstallerLink, process.env.CSC_INSTALLER_LINK),
cscIKeyPassword: (0, platformPackager_1.chooseNotNull)(this.platformSpecificBuildOptions.cscInstallerKeyPassword, process.env.CSC_INSTALLER_KEY_PASSWORD),
currentDir: this.projectDir,
};
return selected;
}, async (selected) => {
if (selected) {
return (0, macCodeSign_1.createKeychain)(selected).then(result => {
const keychainFile = result.keychainFile;
if (keychainFile != null) {
this.info.disposeOnBuildFinish(() => (0, macCodeSign_1.removeKeychain)(keychainFile));
}
return result;
});
}
return Promise.resolve({ keychainFile: process.env.CSC_KEYCHAIN || null });
});
this._iconPath = new lazy_val_1.Lazy(() => this.getOrConvertIcon("icns"));
}
get defaultTarget() {
return this.info.framework.macOsDefaultTargets;
}
expandArch(pattern, arch) {
if (arch === builder_util_1.Arch.universal) {
// Universal build has `app-x64.asar.unpacked` & `app-arm64.asar.unpacked`
return [(0, macroExpander_1.expandMacro)(pattern, builder_util_1.Arch[builder_util_1.Arch.arm64], this.appInfo, {}, false), (0, macroExpander_1.expandMacro)(pattern, builder_util_1.Arch[builder_util_1.Arch.x64], this.appInfo, {}, false)];
}
// Every other build keeps the name as `app.asar.unpacked`
return [(0, macroExpander_1.expandMacro)(pattern, null, this.appInfo, {}, false)];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
prepareAppInfo(appInfo) {
// codesign requires the filename to be normalized to the NFD form
return new appInfo_1.AppInfo(this.info, this.platformSpecificBuildOptions.bundleVersion, this.platformSpecificBuildOptions, true);
}
async getIconPath() {
return this._iconPath.value;
}
createTargets(targets, mapper) {
for (const name of targets) {
switch (name) {
case core_1.DIR_TARGET:
break;
case "dmg": {
const { DmgTarget } = require("dmg-builder");
mapper(name, outDir => new DmgTarget(this, outDir));
break;
}
case "zip":
// https://github.com/electron-userland/electron-builder/issues/2313
mapper(name, outDir => new ArchiveTarget_1.ArchiveTarget(name, outDir, this, true));
break;
case "pkg":
mapper(name, outDir => new pkg_1.PkgTarget(this, outDir));
break;
default:
mapper(name, outDir => (name === "mas" || name === "mas-dev" ? new targetFactory_1.NoOpTarget(name) : (0, targetFactory_1.createCommonTarget)(name, outDir, this)));
break;
}
}
}
async doPack(config) {
var _a;
const { outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets } = config;
switch (arch) {
default: {
return super.doPack(config);
}
case builder_util_1.Arch.universal: {
const outDirName = (arch) => `${appOutDir}-${builder_util_1.Arch[arch]}-temp`;
const options = {
...config,
options: {
sign: false,
disableAsarIntegrity: true,
disableFuses: true,
},
};
const x64Arch = builder_util_1.Arch.x64;
const x64AppOutDir = outDirName(x64Arch);
await super.doPack({ ...options, appOutDir: x64AppOutDir, arch: x64Arch });
if (this.info.cancellationToken.cancelled) {
return;
}
const arm64Arch = builder_util_1.Arch.arm64;
const arm64AppOutPath = outDirName(arm64Arch);
await super.doPack({ ...options, appOutDir: arm64AppOutPath, arch: arm64Arch });
if (this.info.cancellationToken.cancelled) {
return;
}
const framework = this.info.framework;
builder_util_1.log.info({
platform: platformName,
arch: builder_util_1.Arch[arch],
[`${framework.name}`]: framework.version,
appOutDir: builder_util_1.log.filePath(appOutDir),
}, `packaging`);
const appFile = `${this.appInfo.productFilename}.app`;
// Make sure the Assets.car file is the same for both architectures
const sourceCatalogPath = path.join(x64AppOutDir, appFile, "Contents/Resources/Assets.car");
if (await (0, builder_util_1.exists)(sourceCatalogPath)) {
const targetCatalogPath = path.join(arm64AppOutPath, appFile, "Contents/Resources/Assets.car");
await fs.copyFile(sourceCatalogPath, targetCatalogPath);
}
await (0, universal_1.makeUniversalApp)({
x64AppPath: path.join(x64AppOutDir, appFile),
arm64AppPath: path.join(arm64AppOutPath, appFile),
outAppPath: path.join(appOutDir, appFile),
force: true,
mergeASARs: (_a = platformSpecificBuildOptions.mergeASARs) !== null && _a !== void 0 ? _a : true, // must be ?? to allow false
singleArchFiles: platformSpecificBuildOptions.singleArchFiles || undefined,
x64ArchFiles: platformSpecificBuildOptions.x64ArchFiles || undefined,
});
await fs.rm(x64AppOutDir, { recursive: true, force: true });
await fs.rm(arm64AppOutPath, { recursive: true, force: true });
// Give users a final opportunity to perform things on the combined universal package before signing
const packContext = {
appOutDir,
outDir,
arch,
targets,
packager: this,
electronPlatformName: platformName,
};
await this.info.emitAfterPack(packContext);
if (this.info.cancellationToken.cancelled) {
return;
}
await this.doAddElectronFuses(packContext);
await this.doSignAfterPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets);
break;
}
}
}
async pack(outDir, arch, targets, taskManager) {
const hasMas = targets.length !== 0 && targets.some(it => it.name === "mas" || it.name === "mas-dev");
const prepackaged = this.packagerOptions.prepackaged;
for (const target of targets) {
const targetName = target.name;
if (!(targetName === "mas" || targetName === "mas-dev")) {
continue;
}
const masBuildOptions = (0, builder_util_1.deepAssign)({}, this.platformSpecificBuildOptions, this.config.mas);
if (targetName === "mas-dev") {
(0, builder_util_1.deepAssign)(masBuildOptions, this.config.masDev, {
type: "development",
});
}
const targetOutDir = path.join(outDir, `${targetName}${(0, builder_util_1.getArchSuffix)(arch, this.platformSpecificBuildOptions.defaultArch)}`);
if (prepackaged == null) {
await this.doPack({ outDir, appOutDir: targetOutDir, platformName: "mas", arch, platformSpecificBuildOptions: masBuildOptions, targets: [target] });
await this.sign(path.join(targetOutDir, `${this.appInfo.productFilename}.app`), targetOutDir, masBuildOptions, arch);
}
else {
await this.sign(prepackaged, targetOutDir, masBuildOptions, arch);
}
}
if (!hasMas || targets.length > 1) {
const appPath = prepackaged == null ? path.join(this.computeAppOutDir(outDir, arch), `${this.appInfo.productFilename}.app`) : prepackaged;
if (prepackaged == null) {
await this.doPack({
outDir,
appOutDir: path.dirname(appPath),
platformName: this.platform.nodeName,
arch,
platformSpecificBuildOptions: this.platformSpecificBuildOptions,
targets,
});
}
this.packageInDistributableFormat(appPath, arch, targets, taskManager);
}
}
async sign(appPath, outDir, masOptions, arch) {
if (!(0, macCodeSign_1.isSignAllowed)()) {
return false;
}
const isMas = masOptions != null;
const options = masOptions == null ? this.platformSpecificBuildOptions : masOptions;
const qualifier = options.identity;
const fallBackToAdhoc = (arch === builder_util_1.Arch.arm64 || arch === builder_util_1.Arch.universal) && !this.forceCodeSigning;
if (qualifier === null) {
if (this.forceCodeSigning) {
throw new builder_util_1.InvalidConfigurationError("identity explicitly is set to null, but forceCodeSigning is set to true");
}
builder_util_1.log.info({ reason: "identity explicitly is set to null" }, "skipped macOS code signing");
if (fallBackToAdhoc) {
builder_util_1.log.warn("arm64 requires signing, but identity is set to null and signing is being skipped");
}
return false;
}
const keychainFile = (await this.codeSigningInfo.value).keychainFile;
const explicitType = options.type;
const type = explicitType || "distribution";
const isDevelopment = type === "development";
const certificateTypes = getCertificateTypes(isMas, isDevelopment);
let identity = null;
for (const certificateType of certificateTypes) {
identity = await (0, macCodeSign_1.findIdentity)(certificateType, qualifier, keychainFile);
if (identity != null) {
break;
}
}
if (identity == null) {
if (!isMas && !isDevelopment && explicitType !== "distribution") {
identity = await (0, macCodeSign_1.findIdentity)("Mac Developer", qualifier, keychainFile);
if (identity != null) {
builder_util_1.log.warn("Mac Developer is used to sign app — it is only for development and testing, not for production");
}
}
const noIdentity = !options.sign && identity == null;
if (qualifier === "-") {
identity = new util_identities_1.Identity("-", undefined);
}
else if (noIdentity && fallBackToAdhoc) {
builder_util_1.log.warn(null, "falling back to ad-hoc signature for macOS application code signing");
identity = new util_identities_1.Identity("-", undefined);
}
else if (noIdentity) {
await (0, macCodeSign_1.reportError)(isMas, certificateTypes, qualifier, keychainFile, this.forceCodeSigning);
return false;
}
}
if (!(0, macosVersion_1.isMacOsHighSierra)()) {
throw new builder_util_1.InvalidConfigurationError("macOS High Sierra 10.13.6 is required to sign");
}
let filter = options.signIgnore;
if (Array.isArray(filter)) {
if (filter.length == 0) {
filter = null;
}
}
else if (filter != null) {
filter = filter.length === 0 ? null : [filter];
}
const filterRe = filter == null ? null : filter.map(it => new RegExp(it));
let binaries = options.binaries || undefined;
if (binaries) {
// Accept absolute paths for external binaries, else resolve relative paths from the artifact's app Contents path.
binaries = (await Promise.all(binaries.flatMap(async (destination) => {
const expandedDestination = this.expandArch(destination, arch);
return await Promise.all(expandedDestination.map(async (d) => {
if (await (0, builder_util_1.statOrNull)(d)) {
return d;
}
return path.resolve(appPath, d);
}));
}))).flat();
builder_util_1.log.info({ binaries, arch: arch == null ? null : builder_util_1.Arch[arch] }, "signing additional user-defined binaries for arch");
}
const customSignOptions = (isMas ? masOptions : this.platformSpecificBuildOptions) || this.platformSpecificBuildOptions;
const signOptions = {
identityValidation: false,
// https://github.com/electron-userland/electron-builder/issues/1699
// kext are signed by the chipset manufacturers. You need a special certificate (only available on request) from Apple to be able to sign kext.
ignore: (file) => {
if (filterRe != null) {
for (const regExp of filterRe) {
if (regExp.test(file)) {
return true;
}
}
}
return (file.endsWith(".kext") ||
file.startsWith("/Contents/PlugIns", appPath.length) ||
file.includes("/node_modules/puppeteer/.local-chromium") ||
file.includes("/node_modules/playwright-firefox/.local-browsers") ||
file.includes("/node_modules/playwright/.local-browsers"));
/* Those are browser automating modules, browser (chromium, nightly) cannot be signed
https://github.com/electron-userland/electron-builder/issues/2010
https://github.com/electron-userland/electron-builder/issues/5383
*/
},
identity: identity ? identity.hash || identity.name : undefined,
type,
platform: isMas ? "mas" : "darwin",
version: this.config.electronVersion || undefined,
app: appPath,
keychain: keychainFile || undefined,
binaries,
// https://github.com/electron-userland/electron-builder/issues/1480
strictVerify: options.strictVerify,
preAutoEntitlements: options.preAutoEntitlements,
optionsForFile: await this.getOptionsForFile(appPath, isMas, customSignOptions),
provisioningProfile: customSignOptions.provisioningProfile || undefined,
};
await this.doSign(signOptions, customSignOptions, identity);
// https://github.com/electron-userland/electron-builder/issues/1196#issuecomment-312310209
if (masOptions != null && !isDevelopment) {
const certType = isDevelopment ? "Mac Developer" : "3rd Party Mac Developer Installer";
const masInstallerIdentity = await (0, macCodeSign_1.findIdentity)(certType, masOptions.identity, keychainFile);
if (masInstallerIdentity == null) {
throw new builder_util_1.InvalidConfigurationError(`Cannot find valid "${certType}" identity to sign MAS installer, please see https://electron.build/code-signing`);
}
// mas uploaded to AppStore, so, use "-" instead of space for name
const artifactName = this.expandArtifactNamePattern(masOptions, "pkg", arch);
const artifactPath = path.join(outDir, artifactName);
await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile);
await this.info.emitArtifactBuildCompleted({
file: artifactPath,
target: null,
arch: builder_util_1.Arch.x64,
safeArtifactName: this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch),
packager: this,
});
}
if (!isMas) {
await this.notarizeIfProvided(appPath);
}
return true;
}
async getOptionsForFile(appPath, isMas, customSignOptions) {
const resourceList = await this.resourceList;
const entitlementsSuffix = isMas ? "mas" : "mac";
const getEntitlements = (filePath) => {
// check if root app, then use main entitlements
if (filePath === appPath) {
if (customSignOptions.entitlements) {
return customSignOptions.entitlements;
}
const p = `entitlements.${entitlementsSuffix}.plist`;
if (resourceList.includes(p)) {
return path.join(this.info.buildResourcesDir, p);
}
else {
return (0, pathManager_1.getTemplatePath)("entitlements.mac.plist");
}
}
// It's a login helper...
if (filePath.includes("Library/LoginItems")) {
return customSignOptions.entitlementsLoginHelper;
}
// Only remaining option is that it's inherited entitlements
if (customSignOptions.entitlementsInherit) {
return customSignOptions.entitlementsInherit;
}
const p = `entitlements.${entitlementsSuffix}.inherit.plist`;
if (resourceList.includes(p)) {
return path.join(this.info.buildResourcesDir, p);
}
else {
return (0, pathManager_1.getTemplatePath)("entitlements.mac.plist");
}
};
const requirements = isMas || this.platformSpecificBuildOptions.requirements == null ? undefined : await this.getResource(this.platformSpecificBuildOptions.requirements);
// harden by default for mac builds. Only harden mas builds if explicitly true (backward compatibility)
const hardenedRuntime = isMas ? customSignOptions.hardenedRuntime === true : customSignOptions.hardenedRuntime !== false;
const optionsForFile = filePath => {
const entitlements = getEntitlements(filePath);
const args = {
entitlements: entitlements || undefined,
hardenedRuntime: hardenedRuntime !== null && hardenedRuntime !== void 0 ? hardenedRuntime : undefined,
timestamp: customSignOptions.timestamp || undefined,
requirements: requirements || undefined,
additionalArguments: customSignOptions.additionalArguments || [],
};
return args;
};
return optionsForFile;
}
//noinspection JSMethodCanBeStatic
async doSign(opts, customSignOptions, identity) {
const customSign = await (0, resolve_1.resolveFunction)(this.appInfo.type, customSignOptions.sign, "sign");
const { app, platform, type, provisioningProfile } = opts;
builder_util_1.log.info({
file: builder_util_1.log.filePath(app),
platform,
type,
identityName: (identity === null || identity === void 0 ? void 0 : identity.name) || "none",
identityHash: (identity === null || identity === void 0 ? void 0 : identity.hash) || "none",
provisioningProfile: provisioningProfile || "none",
}, customSign ? "executing custom sign" : "signing");
return customSign ? Promise.resolve(customSign(opts, this)) : (0, macCodeSign_1.sign)({ ...opts, identity: identity ? identity.name : undefined });
}
//noinspection JSMethodCanBeStatic
async doFlat(appPath, outFile, identity, keychain) {
// productbuild doesn't created directory for out file
await (0, promises_1.mkdir)(path.dirname(outFile), { recursive: true });
const args = (0, pkg_1.prepareProductBuildArgs)(identity, keychain);
args.push("--component", appPath, "/Applications");
args.push(outFile);
return await (0, builder_util_1.exec)("productbuild", args);
}
getElectronSrcDir(dist) {
return path.resolve(this.projectDir, dist, this.info.framework.distMacOsAppName);
}
getElectronDestinationDir(appOutDir) {
return path.join(appOutDir, this.info.framework.distMacOsAppName);
}
// todo fileAssociations
async applyCommonInfo(appPlist, contentsPath) {
const appInfo = this.appInfo;
const appFilename = appInfo.productFilename;
// https://github.com/electron-userland/electron-builder/issues/1278
appPlist.CFBundleExecutable = appFilename.endsWith(" Helper") ? appFilename.substring(0, appFilename.length - " Helper".length) : appFilename;
const resourcesPath = path.join(contentsPath, "Resources");
// Support both legacy `.icns` and modern `.icon` (Icon Composer) inputs via `mac.icon`.
// Prefer `.icon` if provided; still accept `.icns`.
const configuredIcon = this.platformSpecificBuildOptions.icon;
const isIconComposer = typeof configuredIcon === "string" && configuredIcon.toLowerCase().endsWith(".icon");
// Set the app name
appPlist.CFBundleName = appInfo.productName;
appPlist.CFBundleDisplayName = appInfo.productName;
// Bundle legacy `icns` format - this should also run when `.icon` is provided
const setIcnsFile = async (iconPath) => {
const oldIcon = appPlist.CFBundleIconFile;
if (oldIcon != null) {
await (0, builder_util_1.unlinkIfExists)(path.join(resourcesPath, oldIcon));
}
const iconFileName = "icon.icns";
appPlist.CFBundleIconFile = iconFileName;
await (0, builder_util_1.copyFile)(iconPath, path.join(resourcesPath, iconFileName));
};
const icnsFilePath = await this.getIconPath();
if (icnsFilePath != null) {
await setIcnsFile(icnsFilePath);
}
// Bundle new `icon` format
if (isIconComposer && configuredIcon) {
const iconComposerPath = await this.getResource(configuredIcon);
if (iconComposerPath) {
const { assetCatalog } = await this.generateAssetCatalogData(iconComposerPath);
// Create and setup the asset catalog
appPlist.CFBundleIconName = "Icon";
await fs.writeFile(path.join(resourcesPath, "Assets.car"), assetCatalog);
}
}
const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion;
if (minimumSystemVersion != null) {
appPlist.LSMinimumSystemVersion = minimumSystemVersion;
}
appPlist.CFBundleShortVersionString = this.platformSpecificBuildOptions.bundleShortVersion || appInfo.version;
appPlist.CFBundleVersion = appInfo.buildVersion;
(0, builder_util_1.use)(this.platformSpecificBuildOptions.category || this.config.category, it => (appPlist.LSApplicationCategoryType = it));
appPlist.NSHumanReadableCopyright = appInfo.copyright;
if (this.platformSpecificBuildOptions.darkModeSupport) {
appPlist.NSRequiresAquaSystemAppearance = false;
}
const extendInfo = this.platformSpecificBuildOptions.extendInfo;
if (extendInfo != null) {
Object.assign(appPlist, extendInfo);
}
for (const [k, v] of Object.entries(appPlist)) {
if (v === null || v === undefined) {
delete appPlist[k];
}
}
}
async signApp(packContext, isAsar) {
const readDirectoryAndSign = async (sourceDirectory, directories, shouldSign) => {
await Promise.all(directories.map(async (file) => {
if (shouldSign(file)) {
await this.sign(path.join(sourceDirectory, file), null, null, packContext.arch);
}
}));
return true;
};
const appFileName = `${this.appInfo.productFilename}.app`;
await readDirectoryAndSign(packContext.appOutDir, await (0, promises_1.readdir)(packContext.appOutDir), file => file === appFileName);
if (!isAsar) {
return true;
}
const outResourcesDir = path.join(packContext.appOutDir, "resources", "app.asar.unpacked");
await readDirectoryAndSign(outResourcesDir, await (0, builder_util_1.orIfFileNotExist)((0, promises_1.readdir)(outResourcesDir), []), file => file.endsWith(".app"));
return true;
}
async notarizeIfProvided(appPath) {
const notarizeOptions = this.platformSpecificBuildOptions.notarize;
if (notarizeOptions === false) {
builder_util_1.log.info({ reason: "`notarize` options were set explicitly `false`" }, "skipped macOS notarization");
return;
}
const options = this.getNotarizeOptions(appPath);
if (!options) {
builder_util_1.log.warn({ reason: "`notarize` options were unable to be generated" }, "skipped macOS notarization");
return;
}
await (0, notarize_1.notarize)(options);
builder_util_1.log.info(null, "notarization successful");
}
getNotarizeOptions(appPath) {
const teamId = process.env.APPLE_TEAM_ID;
const appleId = process.env.APPLE_ID;
const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD;
const tool = "notarytool";
// option 1: app specific password
if (appleId || appleIdPassword) {
if (!appleId) {
throw new builder_util_1.InvalidConfigurationError(`APPLE_ID env var needs to be set`);
}
if (!appleIdPassword) {
throw new builder_util_1.InvalidConfigurationError(`APPLE_APP_SPECIFIC_PASSWORD env var needs to be set`);
}
if (!teamId) {
throw new builder_util_1.InvalidConfigurationError(`APPLE_TEAM_ID env var needs to be set`);
}
return { tool, appPath, appleId, appleIdPassword, teamId };
}
// option 2: API key
const appleApiKey = process.env.APPLE_API_KEY;
const appleApiKeyId = process.env.APPLE_API_KEY_ID;
const appleApiIssuer = process.env.APPLE_API_ISSUER;
if (appleApiKey || appleApiKeyId || appleApiIssuer) {
if (!appleApiKey || !appleApiKeyId || !appleApiIssuer) {
throw new builder_util_1.InvalidConfigurationError(`Env vars APPLE_API_KEY, APPLE_API_KEY_ID and APPLE_API_ISSUER need to be set`);
}
return { tool, appPath, appleApiKey, appleApiKeyId, appleApiIssuer };
}
// option 3: keychain
const keychain = process.env.APPLE_KEYCHAIN;
const keychainProfile = process.env.APPLE_KEYCHAIN_PROFILE;
if (keychainProfile) {
let args = { keychainProfile };
if (keychain) {
args = { ...args, keychain };
}
return { tool, appPath, ...args };
}
// if no credentials provided, skip silently
return undefined;
}
}
exports.MacPackager = MacPackager;
function getCertificateTypes(isMas, isDevelopment) {
if (isDevelopment) {
return isMas ? ["Mac Developer", "Apple Development"] : ["Mac Developer", "Developer ID Application"];
}
return isMas ? ["Apple Distribution", "3rd Party Mac Developer Application"] : ["Developer ID Application"];
}
//# sourceMappingURL=macPackager.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
import { PM } from "./packageManager";
import { TraversedDependency } from "./types";
import { TraversalNodeModulesCollector } from "./traversalNodeModulesCollector";
export declare class BunNodeModulesCollector extends TraversalNodeModulesCollector {
readonly installOptions: {
manager: PM;
lockfile: string;
};
protected getDependenciesTree(pm: PM): Promise<TraversedDependency>;
}
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BunNodeModulesCollector = void 0;
const builder_util_1 = require("builder-util");
const packageManager_1 = require("./packageManager");
const traversalNodeModulesCollector_1 = require("./traversalNodeModulesCollector");
class BunNodeModulesCollector extends traversalNodeModulesCollector_1.TraversalNodeModulesCollector {
constructor() {
super(...arguments);
this.installOptions = { manager: packageManager_1.PM.BUN, lockfile: "bun.lock" };
}
async getDependenciesTree(pm) {
builder_util_1.log.info(null, "note: bun does not support any CLI for dependency tree extraction, utilizing file traversal collector instead");
return super.getDependenciesTree(pm);
}
}
exports.BunNodeModulesCollector = BunNodeModulesCollector;
//# sourceMappingURL=bunNodeModulesCollector.js.map
@@ -0,0 +1 @@
{"version":3,"file":"bunNodeModulesCollector.js","sourceRoot":"","sources":["../../src/node-module-collector/bunNodeModulesCollector.ts"],"names":[],"mappings":";;;AAAA,+CAAkC;AAClC,qDAAqC;AAErC,mFAA+E;AAE/E,MAAa,uBAAwB,SAAQ,6DAA6B;IAA1E;;QACkB,mBAAc,GAAG,EAAE,OAAO,EAAE,mBAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;IAM5E,CAAC;IAJW,KAAK,CAAC,mBAAmB,CAAC,EAAM;QACxC,kBAAG,CAAC,IAAI,CAAC,IAAI,EAAE,+GAA+G,CAAC,CAAA;QAC/H,OAAO,KAAK,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAA;IACtC,CAAC;CACF;AAPD,0DAOC","sourcesContent":["import { log } from \"builder-util\"\nimport { PM } from \"./packageManager\"\nimport { TraversedDependency } from \"./types\"\nimport { TraversalNodeModulesCollector } from \"./traversalNodeModulesCollector\"\n\nexport class BunNodeModulesCollector extends TraversalNodeModulesCollector {\n public readonly installOptions = { manager: PM.BUN, lockfile: \"bun.lock\" }\n\n protected async getDependenciesTree(pm: PM): Promise<TraversedDependency> {\n log.info(null, \"note: bun does not support any CLI for dependency tree extraction, utilizing file traversal collector instead\")\n return super.getDependenciesTree(pm)\n }\n}\n"]}
+123
View File
@@ -0,0 +1,123 @@
/**
BSD 2-Clause License
Copyright (c) 2016-present, Yarn Contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* High-level node_modules hoisting algorithm recipe
*
* 1. Take input dependency graph and start traversing it,
* as you visit new node in the graph - clone it if there can be multiple paths
* to access the node from the graph root to the node, e.g. essentially represent
* the graph with a tree as you go, to make hoisting possible.
* 2. You want to hoist every node possible to the top root node first,
* then to each of its children etc, so you need to keep track what is your current
* root node into which you are hoisting
* 3. Traverse the dependency graph from the current root node and for each package name
* that can be potentially hoisted to the current root node build a list of idents
* in descending hoisting preference. You will check in next steps whether most preferred ident
* for the given package name can be hoisted first, and if not, then you check the
* less preferred ident, etc, until either some ident will be hoisted
* or you run out of idents to check
* (no need to convert the graph to the tree when you build this preference map).
* 4. The children of the root node are already "hoisted", so you need to start
* from the dependencies of these children. You take some child and
* sort its dependencies so that regular dependencies without peer dependencies
* will come first and then those dependencies that peer depend on them.
* This is needed to make algorithm more efficient and hoist nodes which are easier
* to hoist first and then handle peer dependent nodes.
* 5. You take this sorted list of dependencies and check if each of them can be
* hoisted to the current root node. To answer is the node can be hoisted you check
* your constraints - require promise and peer dependency promise.
* The possible answers can be: YES - the node is hoistable to the current root,
* NO - the node is not hoistable to the current root
* and DEPENDS - the node is hoistable to the root if nodes X, Y, Z are hoistable
* to the root. The case DEPENDS happens when all the require and other
* constraints are met, except peer dependency constraints. Note, that the nodes
* that are not package idents currently at the top of preference list are considered
* to have the answer NO right away, before doing any other constraint checks.
* 6. When you have hoistable answer for each dependency of a node you then build
* a list of nodes that are NOT hoistable. These are the nodes that have answer NO
* and the nodes that DEPENDS on these nodes. All the other nodes are hoistable,
* those that have answer YES and those that have answer DEPENDS,
* because they are cyclically dependent on each another
* 7. You hoist all the hoistable nodes to the current root and continue traversing
* the tree. Note, you need to track newly added nodes to the current root,
* because after you finished tree traversal you want to come back to these new nodes
* first thing and hoist everything from each of them to the current tree root.
* 8. After you have finished traversing newly hoisted current root nodes
* it means you cannot hoist anything to the current tree root and you need to pick
* the next node as current tree root and run the algorithm again
* until you run out of candidates for current tree root.
*/
type PackageName = string;
export declare enum HoisterDependencyKind {
REGULAR = 0,
WORKSPACE = 1,
EXTERNAL_SOFT_LINK = 2
}
export type HoisterTree = {
name: PackageName;
identName: PackageName;
reference: string;
dependencies: Set<HoisterTree>;
peerNames: Set<PackageName>;
hoistPriority?: number;
dependencyKind?: HoisterDependencyKind;
};
export type HoisterResult = {
name: PackageName;
identName: PackageName;
references: Set<string>;
dependencies: Set<HoisterResult>;
};
type Locator = string;
declare enum DebugLevel {
NONE = -1,
PERF = 0,
CHECK = 1,
REASONS = 2,
INTENSIVE_CHECK = 9
}
export type HoistOptions = {
/** Runs self-checks after hoisting is finished */
check?: boolean;
/** Debug level */
debugLevel?: DebugLevel;
/** Hoist borders are defined by parent node locator and its dependency name. The dependency is considered a border, nothing can be hoisted past this dependency, but dependency can be hoisted */
hoistingLimits?: Map<Locator, Set<PackageName>>;
};
/**
* Hoists package tree.
*
* The root node of a tree must has id: '.'.
* This function does not mutate its arguments, it hoists and returns tree copy.
*
* @param tree package tree (cycles in the tree are allowed)
*
* @returns hoisted tree copy
*/
export declare const hoist: (tree: HoisterTree, opts?: HoistOptions) => HoisterResult;
export {};
+884
View File
@@ -0,0 +1,884 @@
"use strict";
// copy from https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-nm/sources/hoist.ts
/**
BSD 2-Clause License
Copyright (c) 2016-present, Yarn Contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.hoist = exports.HoisterDependencyKind = void 0;
var HoisterDependencyKind;
(function (HoisterDependencyKind) {
HoisterDependencyKind[HoisterDependencyKind["REGULAR"] = 0] = "REGULAR";
HoisterDependencyKind[HoisterDependencyKind["WORKSPACE"] = 1] = "WORKSPACE";
HoisterDependencyKind[HoisterDependencyKind["EXTERNAL_SOFT_LINK"] = 2] = "EXTERNAL_SOFT_LINK";
})(HoisterDependencyKind || (exports.HoisterDependencyKind = HoisterDependencyKind = {}));
var Hoistable;
(function (Hoistable) {
Hoistable[Hoistable["YES"] = 0] = "YES";
Hoistable[Hoistable["NO"] = 1] = "NO";
Hoistable[Hoistable["DEPENDS"] = 2] = "DEPENDS";
})(Hoistable || (Hoistable = {}));
const makeLocator = (name, reference) => `${name}@${reference}`;
const makeIdent = (name, reference) => {
const hashIdx = reference.indexOf(`#`);
// Strip virtual reference part, we don't need it for hoisting purposes
const realReference = hashIdx >= 0 ? reference.substring(hashIdx + 1) : reference;
return makeLocator(name, realReference);
};
var DebugLevel;
(function (DebugLevel) {
DebugLevel[DebugLevel["NONE"] = -1] = "NONE";
DebugLevel[DebugLevel["PERF"] = 0] = "PERF";
DebugLevel[DebugLevel["CHECK"] = 1] = "CHECK";
DebugLevel[DebugLevel["REASONS"] = 2] = "REASONS";
DebugLevel[DebugLevel["INTENSIVE_CHECK"] = 9] = "INTENSIVE_CHECK";
})(DebugLevel || (DebugLevel = {}));
/**
* Hoists package tree.
*
* The root node of a tree must has id: '.'.
* This function does not mutate its arguments, it hoists and returns tree copy.
*
* @param tree package tree (cycles in the tree are allowed)
*
* @returns hoisted tree copy
*/
const hoist = (tree, opts = {}) => {
const debugLevel = opts.debugLevel || Number(process.env.NM_DEBUG_LEVEL || DebugLevel.NONE);
const check = opts.check || debugLevel >= DebugLevel.INTENSIVE_CHECK;
const hoistingLimits = opts.hoistingLimits || new Map();
const options = { check, debugLevel, hoistingLimits, fastLookupPossible: true };
let startTime;
if (options.debugLevel >= DebugLevel.PERF)
startTime = Date.now();
const treeCopy = cloneTree(tree, options);
let anotherRoundNeeded = false;
let round = 0;
do {
const result = hoistTo(treeCopy, [treeCopy], new Set([treeCopy.locator]), new Map(), options);
anotherRoundNeeded = result.anotherRoundNeeded || result.isGraphChanged;
options.fastLookupPossible = false;
round++;
} while (anotherRoundNeeded);
if (options.debugLevel >= DebugLevel.PERF)
console.log(`hoist time: ${Date.now() - startTime}ms, rounds: ${round}`);
if (options.debugLevel >= DebugLevel.CHECK) {
const prevTreeDump = dumpDepTree(treeCopy);
const isGraphChanged = hoistTo(treeCopy, [treeCopy], new Set([treeCopy.locator]), new Map(), options).isGraphChanged;
if (isGraphChanged)
throw new Error(`The hoisting result is not terminal, prev tree:\n${prevTreeDump}, next tree:\n${dumpDepTree(treeCopy)}`);
const checkLog = selfCheck(treeCopy);
if (checkLog) {
throw new Error(`${checkLog}, after hoisting finished:\n${dumpDepTree(treeCopy)}`);
}
}
if (options.debugLevel >= DebugLevel.REASONS)
console.log(dumpDepTree(treeCopy));
return shrinkTree(treeCopy);
};
exports.hoist = hoist;
const getZeroRoundUsedDependencies = (rootNodePath) => {
const rootNode = rootNodePath[rootNodePath.length - 1];
const usedDependencies = new Map();
const seenNodes = new Set();
const addUsedDependencies = (node) => {
if (seenNodes.has(node))
return;
seenNodes.add(node);
for (const dep of node.hoistedDependencies.values())
usedDependencies.set(dep.name, dep);
for (const dep of node.dependencies.values()) {
if (!node.peerNames.has(dep.name)) {
addUsedDependencies(dep);
}
}
};
addUsedDependencies(rootNode);
return usedDependencies;
};
const getUsedDependencies = (rootNodePath) => {
const rootNode = rootNodePath[rootNodePath.length - 1];
const usedDependencies = new Map();
const seenNodes = new Set();
const hiddenDependencies = new Set();
const addUsedDependencies = (node, hiddenDependencies) => {
if (seenNodes.has(node))
return;
seenNodes.add(node);
for (const dep of node.hoistedDependencies.values()) {
if (!hiddenDependencies.has(dep.name)) {
let reachableDependency;
for (const node of rootNodePath) {
reachableDependency = node.dependencies.get(dep.name);
if (reachableDependency) {
usedDependencies.set(reachableDependency.name, reachableDependency);
}
}
}
}
const childrenHiddenDependencies = new Set();
for (const dep of node.dependencies.values())
childrenHiddenDependencies.add(dep.name);
for (const dep of node.dependencies.values()) {
if (!node.peerNames.has(dep.name)) {
addUsedDependencies(dep, childrenHiddenDependencies);
}
}
};
addUsedDependencies(rootNode, hiddenDependencies);
return usedDependencies;
};
/**
* This method clones the node and returns cloned node copy, if the node was not previously decoupled.
*
* The node is considered decoupled if there is no multiple parents to any node
* on the path from the dependency graph root up to this node. This means that there are no other
* nodes in dependency graph that somehow transitively use this node and hence node can be hoisted without
* side effects.
*
* The process of node decoupling is done by going from root node of the graph up to the node in concern
* and decoupling each node on this graph path.
*
* @param node original node
*
* @returns decoupled node
*/
const decoupleGraphNode = (parent, node) => {
if (node.decoupled)
return node;
const { name, references, ident, locator, dependencies, originalDependencies, hoistedDependencies, peerNames, reasons, isHoistBorder, hoistPriority, dependencyKind, hoistedFrom, hoistedTo, } = node;
// To perform node hoisting from parent node we must clone parent nodes up to the root node,
// because some other package in the tree might depend on the parent package where hoisting
// cannot be performed
const clone = {
name,
references: new Set(references),
ident,
locator,
dependencies: new Map(dependencies),
originalDependencies: new Map(originalDependencies),
hoistedDependencies: new Map(hoistedDependencies),
peerNames: new Set(peerNames),
reasons: new Map(reasons),
decoupled: true,
isHoistBorder,
hoistPriority,
dependencyKind,
hoistedFrom: new Map(hoistedFrom),
hoistedTo: new Map(hoistedTo),
};
const selfDep = clone.dependencies.get(name);
if (selfDep && selfDep.ident == clone.ident) // Update self-reference
{
clone.dependencies.set(name, clone);
}
parent.dependencies.set(clone.name, clone);
return clone;
};
/**
* Builds a map of most preferred packages that might be hoisted to the root node.
*
* The values in the map are idents sorted by preference from most preferred to less preferred.
* If the root node has already some version of a package, the value array will contain only
* one element, since it is not possible for other versions of a package to be hoisted.
*
* @param rootNode root node
* @param preferenceMap preference map
*/
const getHoistIdentMap = (rootNode, preferenceMap) => {
const identMap = new Map([[rootNode.name, [rootNode.ident]]]);
for (const dep of rootNode.dependencies.values()) {
if (!rootNode.peerNames.has(dep.name)) {
identMap.set(dep.name, [dep.ident]);
}
}
const keyList = Array.from(preferenceMap.keys());
keyList.sort((key1, key2) => {
const entry1 = preferenceMap.get(key1);
const entry2 = preferenceMap.get(key2);
if (entry2.hoistPriority !== entry1.hoistPriority) {
return entry2.hoistPriority - entry1.hoistPriority;
}
else {
const entry1Usages = entry1.dependents.size + entry1.peerDependents.size;
const entry2Usages = entry2.dependents.size + entry2.peerDependents.size;
return entry2Usages - entry1Usages;
}
});
for (const key of keyList) {
const name = key.substring(0, key.indexOf(`@`, 1));
const ident = key.substring(name.length + 1);
if (!rootNode.peerNames.has(name)) {
let idents = identMap.get(name);
if (!idents) {
idents = [];
identMap.set(name, idents);
}
if (idents.indexOf(ident) < 0) {
idents.push(ident);
}
}
}
return identMap;
};
/**
* Gets regular node dependencies only and sorts them in the order so that
* peer dependencies come before the dependency that rely on them.
*
* @param node graph node
* @returns sorted regular dependencies
*/
const getSortedRegularDependencies = (node) => {
const dependencies = new Set();
const addDep = (dep, seenDeps = new Set()) => {
if (seenDeps.has(dep))
return;
seenDeps.add(dep);
for (const peerName of dep.peerNames) {
if (!node.peerNames.has(peerName)) {
const peerDep = node.dependencies.get(peerName);
if (peerDep && !dependencies.has(peerDep)) {
addDep(peerDep, seenDeps);
}
}
}
dependencies.add(dep);
};
for (const dep of node.dependencies.values()) {
if (!node.peerNames.has(dep.name)) {
addDep(dep);
}
}
return dependencies;
};
/**
* Performs hoisting all the dependencies down the tree to the root node.
*
* The algorithm used here reduces dependency graph by deduplicating
* instances of the packages while keeping:
* 1. Regular dependency promise: the package should require the exact version of the dependency
* that was declared in its `package.json`
* 2. Peer dependency promise: the package and its direct parent package
* must use the same instance of the peer dependency
*
* The regular and peer dependency promises are kept while performing transform
* on tree branches of packages at a time:
* `root package` -> `parent package 1` ... `parent package n` -> `dependency`
* We check wether we can hoist `dependency` to `root package`, this boils down basically
* to checking:
* 1. Wether `root package` does not depend on other version of `dependency`
* 2. Wether all the peer dependencies of a `dependency` had already been hoisted from all `parent packages`
*
* If many versions of the `dependency` can be hoisted to the `root package` we choose the most used
* `dependency` version in the project among them.
*
* This function mutates the tree.
*
* @param tree package dependencies graph
* @param rootNode root node to hoist to
* @param rootNodePath root node path in the tree
* @param rootNodePathLocators a set of locators for nodes that lead from the top of the tree up to root node
* @param options hoisting options
*/
const hoistTo = (tree, rootNodePath, rootNodePathLocators, parentShadowedNodes, options, seenNodes = new Set()) => {
const rootNode = rootNodePath[rootNodePath.length - 1];
if (seenNodes.has(rootNode))
return { anotherRoundNeeded: false, isGraphChanged: false };
seenNodes.add(rootNode);
const preferenceMap = buildPreferenceMap(rootNode);
const hoistIdentMap = getHoistIdentMap(rootNode, preferenceMap);
const usedDependencies = tree == rootNode ? new Map() : options.fastLookupPossible ? getZeroRoundUsedDependencies(rootNodePath) : getUsedDependencies(rootNodePath);
let wasStateChanged;
let anotherRoundNeeded = false;
let isGraphChanged = false;
const hoistIdents = new Map(Array.from(hoistIdentMap.entries()).map(([k, v]) => [k, v[0]]));
const shadowedNodes = new Map();
do {
const result = hoistGraph(tree, rootNodePath, rootNodePathLocators, usedDependencies, hoistIdents, hoistIdentMap, parentShadowedNodes, shadowedNodes, options);
if (result.isGraphChanged)
isGraphChanged = true;
if (result.anotherRoundNeeded)
anotherRoundNeeded = true;
wasStateChanged = false;
for (const [name, idents] of hoistIdentMap) {
if (idents.length > 1 && !rootNode.dependencies.has(name)) {
hoistIdents.delete(name);
idents.shift();
hoistIdents.set(name, idents[0]);
wasStateChanged = true;
}
}
} while (wasStateChanged);
for (const dependency of rootNode.dependencies.values()) {
if (!rootNode.peerNames.has(dependency.name) && !rootNodePathLocators.has(dependency.locator)) {
rootNodePathLocators.add(dependency.locator);
const result = hoistTo(tree, [...rootNodePath, dependency], rootNodePathLocators, shadowedNodes, options);
if (result.isGraphChanged)
isGraphChanged = true;
if (result.anotherRoundNeeded)
anotherRoundNeeded = true;
rootNodePathLocators.delete(dependency.locator);
}
}
return { anotherRoundNeeded, isGraphChanged };
};
const hasUnhoistedDependencies = (node) => {
for (const [subName, subDependency] of node.dependencies) {
if (!node.peerNames.has(subName) && subDependency.ident !== node.ident) {
return true;
}
}
return false;
};
const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason, fastLookupPossible }) => {
let reasonRoot;
let reason = null;
let dependsOn = new Set();
if (outputReason) {
reasonRoot = `${Array.from(rootNodePathLocators)
.map(x => prettyPrintLocator(x))
.join(`→`)}`;
}
const parentNode = nodePath[nodePath.length - 1];
// We cannot hoist self-references
const isSelfReference = node.ident === parentNode.ident;
let isHoistable = !isSelfReference;
if (outputReason && !isHoistable)
reason = `- self-reference`;
if (isHoistable) {
isHoistable = node.dependencyKind !== HoisterDependencyKind.WORKSPACE;
if (outputReason && !isHoistable) {
reason = `- workspace`;
}
}
if (isHoistable && node.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK) {
isHoistable = !hasUnhoistedDependencies(node);
if (outputReason && !isHoistable) {
reason = `- external soft link with unhoisted dependencies`;
}
}
if (isHoistable) {
isHoistable = !rootNode.peerNames.has(node.name);
if (outputReason && !isHoistable) {
reason = `- cannot shadow peer: ${prettyPrintLocator(rootNode.originalDependencies.get(node.name).locator)} at ${reasonRoot}`;
}
}
if (isHoistable) {
let isNameAvailable = false;
const usedDep = usedDependencies.get(node.name);
isNameAvailable = !usedDep || usedDep.ident === node.ident;
if (outputReason && !isNameAvailable)
reason = `- filled by: ${prettyPrintLocator(usedDep.locator)} at ${reasonRoot}`;
if (isNameAvailable) {
for (let idx = nodePath.length - 1; idx >= 1; idx--) {
const parent = nodePath[idx];
const parentDep = parent.dependencies.get(node.name);
if (parentDep && parentDep.ident !== node.ident) {
isNameAvailable = false;
let shadowedNames = shadowedNodes.get(parentNode);
if (!shadowedNames) {
shadowedNames = new Set();
shadowedNodes.set(parentNode, shadowedNames);
}
shadowedNames.add(node.name);
if (outputReason) {
reason = `- filled by ${prettyPrintLocator(parentDep.locator)} at ${nodePath
.slice(0, idx)
.map(x => prettyPrintLocator(x.locator))
.join(`→`)}`;
}
break;
}
}
}
isHoistable = isNameAvailable;
}
if (isHoistable) {
const hoistedIdent = hoistIdents.get(node.name);
isHoistable = hoistedIdent === node.ident;
if (outputReason && !isHoistable) {
reason = `- filled by: ${prettyPrintLocator(hoistIdentMap.get(node.name)[0])} at ${reasonRoot}`;
}
}
if (isHoistable) {
let arePeerDepsSatisfied = true;
const checkList = new Set(node.peerNames);
for (let idx = nodePath.length - 1; idx >= 1; idx--) {
const parent = nodePath[idx];
for (const name of checkList) {
if (parent.peerNames.has(name) && parent.originalDependencies.has(name))
continue;
const parentDepNode = parent.dependencies.get(name);
if (parentDepNode && rootNode.dependencies.get(name) !== parentDepNode) {
if (idx === nodePath.length - 1) {
dependsOn.add(parentDepNode);
}
else {
dependsOn = null;
arePeerDepsSatisfied = false;
if (outputReason) {
reason = `- peer dependency ${prettyPrintLocator(parentDepNode.locator)} from parent ${prettyPrintLocator(parent.locator)} was not hoisted to ${reasonRoot}`;
}
}
}
checkList.delete(name);
}
if (!arePeerDepsSatisfied) {
break;
}
}
isHoistable = arePeerDepsSatisfied;
}
if (isHoistable && !fastLookupPossible) {
for (const origDep of node.hoistedDependencies.values()) {
const usedDep = usedDependencies.get(origDep.name) || rootNode.dependencies.get(origDep.name);
if (!usedDep || origDep.ident !== usedDep.ident) {
isHoistable = false;
if (outputReason)
reason = `- previously hoisted dependency mismatch, needed: ${prettyPrintLocator(origDep.locator)}, available: ${prettyPrintLocator(usedDep === null || usedDep === void 0 ? void 0 : usedDep.locator)}`;
break;
}
}
}
if (dependsOn !== null && dependsOn.size > 0) {
return { isHoistable: Hoistable.DEPENDS, dependsOn, reason };
}
else {
return { isHoistable: isHoistable ? Hoistable.YES : Hoistable.NO, reason };
}
};
const getAliasedLocator = (node) => `${node.name}@${node.locator}`;
/**
* Performs actual graph transformation, by hoisting packages to the root node.
*
* @param tree dependency tree
* @param rootNodePath root node path in the tree
* @param rootNodePathLocators a set of locators for nodes that lead from the top of the tree up to root node
* @param usedDependencies map of dependency nodes from parents of root node used by root node and its children via parent lookup
* @param hoistIdents idents that should be attempted to be hoisted to the root node
*/
const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies, hoistIdents, hoistIdentMap, parentShadowedNodes, shadowedNodes, options) => {
const rootNode = rootNodePath[rootNodePath.length - 1];
const seenNodes = new Set();
let anotherRoundNeeded = false;
let isGraphChanged = false;
const hoistNodeDependencies = (nodePath, locatorPath, aliasedLocatorPath, parentNode, newNodes) => {
if (seenNodes.has(parentNode))
return;
const nextLocatorPath = [...locatorPath, getAliasedLocator(parentNode)];
const nextAliasedLocatorPath = [...aliasedLocatorPath, getAliasedLocator(parentNode)];
const dependantTree = new Map();
const hoistInfos = new Map();
for (const subDependency of getSortedRegularDependencies(parentNode)) {
const hoistInfo = getNodeHoistInfo(rootNode, rootNodePathLocators, [rootNode, ...nodePath, parentNode], subDependency, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason: options.debugLevel >= DebugLevel.REASONS, fastLookupPossible: options.fastLookupPossible });
hoistInfos.set(subDependency, hoistInfo);
if (hoistInfo.isHoistable === Hoistable.DEPENDS) {
for (const node of hoistInfo.dependsOn) {
const nodeDependants = dependantTree.get(node.name) || new Set();
nodeDependants.add(subDependency.name);
dependantTree.set(node.name, nodeDependants);
}
}
}
const unhoistableNodes = new Set();
const addUnhoistableNode = (node, hoistInfo, reason) => {
if (!unhoistableNodes.has(node)) {
unhoistableNodes.add(node);
hoistInfos.set(node, { isHoistable: Hoistable.NO, reason });
for (const dependantName of dependantTree.get(node.name) || []) {
addUnhoistableNode(parentNode.dependencies.get(dependantName), hoistInfo, options.debugLevel >= DebugLevel.REASONS
? `- peer dependency ${prettyPrintLocator(node.locator)} from parent ${prettyPrintLocator(parentNode.locator)} was not hoisted`
: ``);
}
}
};
for (const [node, hoistInfo] of hoistInfos)
if (hoistInfo.isHoistable === Hoistable.NO)
addUnhoistableNode(node, hoistInfo, hoistInfo.reason);
let wereNodesHoisted = false;
for (const node of hoistInfos.keys()) {
if (!unhoistableNodes.has(node)) {
isGraphChanged = true;
const shadowedNames = parentShadowedNodes.get(parentNode);
if (shadowedNames && shadowedNames.has(node.name))
anotherRoundNeeded = true;
wereNodesHoisted = true;
parentNode.dependencies.delete(node.name);
parentNode.hoistedDependencies.set(node.name, node);
parentNode.reasons.delete(node.name);
const hoistedNode = rootNode.dependencies.get(node.name);
if (options.debugLevel >= DebugLevel.REASONS) {
const hoistedFrom = Array.from(locatorPath)
.concat([parentNode.locator])
.map(x => prettyPrintLocator(x))
.join(`→`);
let hoistedFromArray = rootNode.hoistedFrom.get(node.name);
if (!hoistedFromArray) {
hoistedFromArray = [];
rootNode.hoistedFrom.set(node.name, hoistedFromArray);
}
hoistedFromArray.push(hoistedFrom);
parentNode.hoistedTo.set(node.name, Array.from(rootNodePath)
.map(x => prettyPrintLocator(x.locator))
.join(`→`));
}
// Add hoisted node to root node, in case it is not already there
if (!hoistedNode) {
// Avoid adding other version of root node to itself
if (rootNode.ident !== node.ident) {
rootNode.dependencies.set(node.name, node);
newNodes.add(node);
}
}
else {
for (const reference of node.references) {
hoistedNode.references.add(reference);
}
}
}
}
if (parentNode.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK && wereNodesHoisted)
anotherRoundNeeded = true;
if (options.check) {
const checkLog = selfCheck(tree);
if (checkLog) {
throw new Error(`${checkLog}, after hoisting dependencies of ${[rootNode, ...nodePath, parentNode].map(x => prettyPrintLocator(x.locator)).join(`→`)}:\n${dumpDepTree(tree)}`);
}
}
const children = getSortedRegularDependencies(parentNode);
for (const node of children) {
if (unhoistableNodes.has(node)) {
const hoistInfo = hoistInfos.get(node);
const hoistableIdent = hoistIdents.get(node.name);
if ((hoistableIdent === node.ident || !parentNode.reasons.has(node.name)) && hoistInfo.isHoistable !== Hoistable.YES)
parentNode.reasons.set(node.name, hoistInfo.reason);
if (!node.isHoistBorder && nextAliasedLocatorPath.indexOf(getAliasedLocator(node)) < 0) {
seenNodes.add(parentNode);
const decoupledNode = decoupleGraphNode(parentNode, node);
hoistNodeDependencies([...nodePath, parentNode], nextLocatorPath, nextAliasedLocatorPath, decoupledNode, nextNewNodes);
seenNodes.delete(parentNode);
}
}
}
};
let newNodes;
let nextNewNodes = new Set(getSortedRegularDependencies(rootNode));
const aliasedRootNodePathLocators = Array.from(rootNodePath).map(x => getAliasedLocator(x));
do {
newNodes = nextNewNodes;
nextNewNodes = new Set();
for (const dep of newNodes) {
if (dep.locator === rootNode.locator || dep.isHoistBorder)
continue;
const decoupledDependency = decoupleGraphNode(rootNode, dep);
hoistNodeDependencies([], Array.from(rootNodePathLocators), aliasedRootNodePathLocators, decoupledDependency, nextNewNodes);
}
} while (nextNewNodes.size > 0);
return { anotherRoundNeeded, isGraphChanged };
};
const selfCheck = (tree) => {
const log = [];
const seenNodes = new Set();
const parents = new Set();
const checkNode = (node, parentDeps, parent) => {
if (seenNodes.has(node))
return;
seenNodes.add(node);
if (parents.has(node))
return;
const dependencies = new Map(parentDeps);
for (const dep of node.dependencies.values())
if (!node.peerNames.has(dep.name))
dependencies.set(dep.name, dep);
for (const origDep of node.originalDependencies.values()) {
const dep = dependencies.get(origDep.name);
const prettyPrintTreePath = () => `${Array.from(parents)
.concat([node])
.map(x => prettyPrintLocator(x.locator))
.join(`→`)}`;
if (node.peerNames.has(origDep.name)) {
const parentDep = parentDeps.get(origDep.name);
if (parentDep !== dep || !parentDep || parentDep.ident !== origDep.ident) {
log.push(`${prettyPrintTreePath()} - broken peer promise: expected ${origDep.ident} but found ${parentDep ? parentDep.ident : parentDep}`);
}
}
else {
const hoistedFrom = parent.hoistedFrom.get(node.name);
const originalHoistedTo = node.hoistedTo.get(origDep.name);
const prettyHoistedFrom = `${hoistedFrom ? ` hoisted from ${hoistedFrom.join(`, `)}` : ``}`;
const prettyOriginalHoistedTo = `${originalHoistedTo ? ` hoisted to ${originalHoistedTo}` : ``}`;
const prettyNodePath = `${prettyPrintTreePath()}${prettyHoistedFrom}`;
if (!dep) {
log.push(`${prettyNodePath} - broken require promise: no required dependency ${origDep.name}${prettyOriginalHoistedTo} found`);
}
else if (dep.ident !== origDep.ident) {
log.push(`${prettyNodePath} - broken require promise for ${origDep.name}${prettyOriginalHoistedTo}: expected ${origDep.ident}, but found: ${dep.ident}`);
}
}
}
parents.add(node);
for (const dep of node.dependencies.values()) {
if (!node.peerNames.has(dep.name)) {
checkNode(dep, dependencies, node);
}
}
parents.delete(node);
};
checkNode(tree, tree.dependencies, tree);
return log.join(`\n`);
};
/**
* Creates a clone of package tree with extra fields used for hoisting purposes.
*
* @param tree package tree clone
*/
const cloneTree = (tree, options) => {
const { identName, name, reference, peerNames } = tree;
const treeCopy = {
name,
references: new Set([reference]),
locator: makeLocator(identName, reference),
ident: makeIdent(identName, reference),
dependencies: new Map(),
originalDependencies: new Map(),
hoistedDependencies: new Map(),
peerNames: new Set(peerNames),
reasons: new Map(),
decoupled: true,
isHoistBorder: true,
hoistPriority: 0,
dependencyKind: HoisterDependencyKind.WORKSPACE,
hoistedFrom: new Map(),
hoistedTo: new Map(),
};
const seenNodes = new Map([[tree, treeCopy]]);
const addNode = (node, parentNode) => {
let workNode = seenNodes.get(node);
const isSeen = !!workNode;
if (!workNode) {
const { name, identName, reference, peerNames, hoistPriority, dependencyKind } = node;
const dependenciesNmHoistingLimits = options.hoistingLimits.get(parentNode.locator);
workNode = {
name,
references: new Set([reference]),
locator: makeLocator(identName, reference),
ident: makeIdent(identName, reference),
dependencies: new Map(),
originalDependencies: new Map(),
hoistedDependencies: new Map(),
peerNames: new Set(peerNames),
reasons: new Map(),
decoupled: true,
isHoistBorder: dependenciesNmHoistingLimits ? dependenciesNmHoistingLimits.has(name) : false,
hoistPriority: hoistPriority || 0,
dependencyKind: dependencyKind || HoisterDependencyKind.REGULAR,
hoistedFrom: new Map(),
hoistedTo: new Map(),
};
seenNodes.set(node, workNode);
}
parentNode.dependencies.set(node.name, workNode);
parentNode.originalDependencies.set(node.name, workNode);
if (!isSeen) {
for (const dep of node.dependencies) {
addNode(dep, workNode);
}
}
else {
const seenCoupledNodes = new Set();
const markNodeCoupled = (node) => {
if (seenCoupledNodes.has(node))
return;
seenCoupledNodes.add(node);
node.decoupled = false;
for (const dep of node.dependencies.values()) {
if (!node.peerNames.has(dep.name)) {
markNodeCoupled(dep);
}
}
};
markNodeCoupled(workNode);
}
};
for (const dep of tree.dependencies)
addNode(dep, treeCopy);
return treeCopy;
};
const getIdentName = (locator) => locator.substring(0, locator.indexOf(`@`, 1));
/**
* Creates a clone of hoisted package tree with extra fields removed
*
* @param tree stripped down hoisted package tree clone
*/
const shrinkTree = (tree) => {
const treeCopy = {
name: tree.name,
identName: getIdentName(tree.locator),
references: new Set(tree.references),
dependencies: new Set(),
};
const seenNodes = new Set([tree]);
const addNode = (node, parentWorkNode, parentNode) => {
const isSeen = seenNodes.has(node);
let resultNode;
if (parentWorkNode === node) {
resultNode = parentNode;
}
else {
const { name, references, locator } = node;
resultNode = {
name,
identName: getIdentName(locator),
references,
dependencies: new Set(),
};
}
parentNode.dependencies.add(resultNode);
if (!isSeen) {
seenNodes.add(node);
for (const dep of node.dependencies.values()) {
if (!node.peerNames.has(dep.name)) {
addNode(dep, node, resultNode);
}
}
seenNodes.delete(node);
}
};
for (const dep of tree.dependencies.values())
addNode(dep, tree, treeCopy);
return treeCopy;
};
/**
* Builds mapping, where key is an alias + dependent package ident and the value is the list of
* parent package idents who depend on this package.
*
* @param rootNode package tree root node
*
* @returns preference map
*/
const buildPreferenceMap = (rootNode) => {
const preferenceMap = new Map();
const seenNodes = new Set([rootNode]);
const getPreferenceKey = (node) => `${node.name}@${node.ident}`;
const getOrCreatePreferenceEntry = (node) => {
const key = getPreferenceKey(node);
let entry = preferenceMap.get(key);
if (!entry) {
entry = { dependents: new Set(), peerDependents: new Set(), hoistPriority: 0 };
preferenceMap.set(key, entry);
}
return entry;
};
const addDependent = (dependent, node) => {
const isSeen = !!seenNodes.has(node);
const entry = getOrCreatePreferenceEntry(node);
entry.dependents.add(dependent.ident);
if (!isSeen) {
seenNodes.add(node);
for (const dep of node.dependencies.values()) {
const entry = getOrCreatePreferenceEntry(dep);
entry.hoistPriority = Math.max(entry.hoistPriority, dep.hoistPriority);
if (node.peerNames.has(dep.name)) {
entry.peerDependents.add(node.ident);
}
else {
addDependent(node, dep);
}
}
}
};
for (const dep of rootNode.dependencies.values())
if (!rootNode.peerNames.has(dep.name))
addDependent(rootNode, dep);
return preferenceMap;
};
const prettyPrintLocator = (locator) => {
if (!locator)
return `none`;
const idx = locator.indexOf(`@`, 1);
let name = locator.substring(0, idx);
if (name.endsWith(`$wsroot$`))
name = `wh:${name.replace(`$wsroot$`, ``)}`;
const reference = locator.substring(idx + 1);
if (reference === `workspace:.`) {
return `.`;
}
else if (!reference) {
return `${name}`;
}
else {
let version = (reference.indexOf(`#`) > 0 ? reference.split(`#`)[1] : reference).replace(`npm:`, ``);
if (reference.startsWith(`virtual`))
name = `v:${name}`;
if (version.startsWith(`workspace`)) {
name = `w:${name}`;
version = ``;
}
return `${name}${version ? `@${version}` : ``}`;
}
};
const MAX_NODES_TO_DUMP = 50000;
/**
* Pretty-prints dependency tree in the `yarn why`-like format
*
* The function is used for troubleshooting purposes only.
*
* @param pkg node_modules tree
*
* @returns sorted node_modules tree
*/
const dumpDepTree = (tree) => {
let nodeCount = 0;
const dumpPackage = (pkg, parents, prefix = ``) => {
if (nodeCount > MAX_NODES_TO_DUMP || parents.has(pkg))
return ``;
nodeCount++;
const dependencies = Array.from(pkg.dependencies.values()).sort((n1, n2) => {
if (n1.name === n2.name) {
return 0;
}
else {
return n1.name > n2.name ? 1 : -1;
}
});
let str = ``;
parents.add(pkg);
for (let idx = 0; idx < dependencies.length; idx++) {
const dep = dependencies[idx];
if (!pkg.peerNames.has(dep.name) && dep !== pkg) {
const reason = pkg.reasons.get(dep.name);
const identName = getIdentName(dep.locator);
str += `${prefix}${idx < dependencies.length - 1 ? `├─` : `└─`}${(parents.has(dep) ? `>` : ``) + (identName !== dep.name ? `a:${dep.name}:` : ``) + prettyPrintLocator(dep.locator) + (reason ? ` ${reason}` : ``)}\n`;
str += dumpPackage(dep, parents, `${prefix}${idx < dependencies.length - 1 ? `│ ` : ` `}`);
}
}
parents.delete(pkg);
return str;
};
const treeDump = dumpPackage(tree, new Set());
return treeDump + (nodeCount > MAX_NODES_TO_DUMP ? `\nTree is too large, part of the tree has been dunped\n` : ``);
};
//# sourceMappingURL=hoist.js.map
File diff suppressed because one or more lines are too long
+18
View File
@@ -0,0 +1,18 @@
import { Nullish } from "builder-util-runtime";
import { TmpDir } from "temp-file";
import { NpmNodeModulesCollector } from "./npmNodeModulesCollector";
import { getPackageManagerCommand, PM } from "./packageManager";
import { PnpmNodeModulesCollector } from "./pnpmNodeModulesCollector";
import { BunNodeModulesCollector } from "./bunNodeModulesCollector";
import { Lazy } from "lazy-val";
import { TraversalNodeModulesCollector } from "./traversalNodeModulesCollector";
export { getPackageManagerCommand, PM };
export declare function getCollectorByPackageManager(pm: PM, rootDir: string, tempDirManager: TmpDir): NpmNodeModulesCollector | PnpmNodeModulesCollector | TraversalNodeModulesCollector | BunNodeModulesCollector;
export declare const determinePackageManagerEnv: ({ projectDir, appDir, workspaceRoot }: {
projectDir: string;
appDir: string;
workspaceRoot: string | Nullish;
}) => Lazy<{
pm: PM;
workspaceRoot: Promise<string | undefined>;
}>;

Some files were not shown because too many files have changed in this diff Show More