Delete 100 files via Git Manager GUI
This commit is contained in:
-12
@@ -1,12 +0,0 @@
|
||||
import { NativeModule } from './index.js';
|
||||
export declare class PrebuildInstall extends NativeModule {
|
||||
usesTool(): Promise<boolean>;
|
||||
locateBinary(): Promise<string | null>;
|
||||
run(prebuildInstallPath: string): Promise<void>;
|
||||
findPrebuiltModule(): Promise<boolean>;
|
||||
/**
|
||||
* Whether a prebuild-install-based native module exists.
|
||||
*/
|
||||
prebuiltModuleExists(): Promise<boolean>;
|
||||
getPrebuildInstallRuntimeArgs(): Promise<string[]>;
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
import debug from 'debug';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { locateBinary, NativeModule } from './index.js';
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
const d = debug('electron-rebuild');
|
||||
export class PrebuildInstall extends NativeModule {
|
||||
async usesTool() {
|
||||
const packageName = await this.findPackageInDependencies('prebuild-install');
|
||||
return !!packageName;
|
||||
}
|
||||
async locateBinary() {
|
||||
const packageName = await this.findPackageInDependencies('prebuild-install');
|
||||
if (!packageName)
|
||||
return null;
|
||||
return locateBinary(this.modulePath, `node_modules/${packageName}/bin.js`);
|
||||
}
|
||||
async run(prebuildInstallPath) {
|
||||
await spawn(process.execPath, [
|
||||
path.resolve(import.meta.dirname, '..', `prebuild-shim.js`),
|
||||
prebuildInstallPath,
|
||||
`--arch=${this.rebuilder.arch}`,
|
||||
`--platform=${this.rebuilder.platform}`,
|
||||
`--tag-prefix=${this.rebuilder.prebuildTagPrefix}`,
|
||||
...(await this.getPrebuildInstallRuntimeArgs()),
|
||||
], {
|
||||
cwd: this.modulePath,
|
||||
});
|
||||
}
|
||||
async findPrebuiltModule() {
|
||||
const prebuildInstallPath = await this.locateBinary();
|
||||
if (prebuildInstallPath) {
|
||||
d(`triggering prebuild download step: ${this.moduleName}`);
|
||||
try {
|
||||
await this.run(prebuildInstallPath);
|
||||
return true;
|
||||
}
|
||||
catch (err) {
|
||||
d('failed to use prebuild-install:', err);
|
||||
if (err?.message?.includes('requires Node-API but Electron')) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
d(`could not find prebuild-install relative to: ${this.modulePath}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Whether a prebuild-install-based native module exists.
|
||||
*/
|
||||
async prebuiltModuleExists() {
|
||||
return fs.existsSync(path.resolve(this.modulePath, 'prebuilds', `${this.rebuilder.platform}-${this.rebuilder.arch}`, `electron-${this.rebuilder.ABI}.node`));
|
||||
}
|
||||
async getPrebuildInstallRuntimeArgs() {
|
||||
const moduleNapiVersions = await this.getSupportedNapiVersions();
|
||||
if (moduleNapiVersions) {
|
||||
const napiVersion = this.nodeAPI.getNapiVersion(moduleNapiVersions);
|
||||
return ['--runtime=napi', `--target=${napiVersion}`];
|
||||
}
|
||||
else {
|
||||
return ['--runtime=electron', `--target=${this.rebuilder.electronVersion}`];
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=prebuild-install.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"prebuild-install.js","sourceRoot":"","sources":["../../src/module-type/prebuild-install.ts"],"names":[],"mappings":"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"}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { NativeModule } from './index.js';
|
||||
export declare function determineNativePrebuildArch(arch: string): string;
|
||||
/**
|
||||
* The extension of `prebuildify`-generated native modules, after the last `.`. This value differs
|
||||
* based on whether the target arch is ARM-based.
|
||||
*/
|
||||
export declare function determineNativePrebuildExtension(arch: string): string;
|
||||
export declare class Prebuildify extends NativeModule {
|
||||
usesTool(): Promise<boolean>;
|
||||
findPrebuiltModule(): Promise<boolean>;
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import debug from 'debug';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { getNodeArch } from '../arch.js';
|
||||
import { NativeModule } from './index.js';
|
||||
const d = debug('electron-rebuild');
|
||||
export function determineNativePrebuildArch(arch) {
|
||||
if (arch === 'armv7l') {
|
||||
return 'arm';
|
||||
}
|
||||
return arch;
|
||||
}
|
||||
/**
|
||||
* The extension of `prebuildify`-generated native modules, after the last `.`. This value differs
|
||||
* based on whether the target arch is ARM-based.
|
||||
*/
|
||||
export function determineNativePrebuildExtension(arch) {
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
return 'armv8.node';
|
||||
case 'armv7l':
|
||||
return 'armv7.node';
|
||||
}
|
||||
return 'node';
|
||||
}
|
||||
export class Prebuildify extends NativeModule {
|
||||
async usesTool() {
|
||||
const packageName = await this.findPackageInDependencies('prebuildify', 'devDependencies');
|
||||
return !!packageName;
|
||||
}
|
||||
async findPrebuiltModule() {
|
||||
d(`Checking for prebuilds for "${this.moduleName}"`);
|
||||
const prebuildsDir = path.join(this.modulePath, 'prebuilds');
|
||||
if (!fs.existsSync(prebuildsDir)) {
|
||||
d(`Could not find the prebuilds directory at "${prebuildsDir}"`);
|
||||
return false;
|
||||
}
|
||||
const nodeArch = getNodeArch(this.rebuilder.arch, process.config.variables);
|
||||
const prebuiltModuleDir = path.join(prebuildsDir, `${this.rebuilder.platform}-${determineNativePrebuildArch(nodeArch)}`);
|
||||
const nativeExt = determineNativePrebuildExtension(nodeArch);
|
||||
const electronNapiModuleFilename = path.join(prebuiltModuleDir, `electron.napi.${nativeExt}`);
|
||||
const nodejsNapiModuleFilename = path.join(prebuiltModuleDir, `node.napi.${nativeExt}`);
|
||||
const abiModuleFilename = path.join(prebuiltModuleDir, `electron.abi${this.rebuilder.ABI}.${nativeExt}`);
|
||||
if (fs.existsSync(electronNapiModuleFilename) || fs.existsSync(nodejsNapiModuleFilename)) {
|
||||
this.nodeAPI.ensureElectronSupport();
|
||||
d(`Found prebuilt Node-API module in ${prebuiltModuleDir}"`);
|
||||
}
|
||||
else if (fs.existsSync(abiModuleFilename)) {
|
||||
d(`Found prebuilt module: "${abiModuleFilename}"`);
|
||||
}
|
||||
else {
|
||||
d(`Could not locate "${electronNapiModuleFilename}", "${nodejsNapiModuleFilename}", or "${abiModuleFilename}"`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=prebuildify.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"prebuildify.js","sourceRoot":"","sources":["../../src/module-type/prebuildify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAmB,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAEpC,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gCAAgC,CAAC,IAAY;IAC3D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,YAAY,CAAC;QACtB,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC;IACxB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,OAAO,WAAY,SAAQ,YAAY;IAC3C,KAAK,CAAC,QAAQ;QACZ,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;QAC3F,OAAO,CAAC,CAAC,WAAW,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,CAAC,CAAC,+BAA+B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QAErD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAC7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,CAAC,CAAC,8CAA8C,YAAY,GAAG,CAAC,CAAC;YACjE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAA4B,CAAC,CAAC;QAC/F,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CACjC,YAAY,EACZ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,IAAI,2BAA2B,CAAC,QAAQ,CAAC,EAAE,CACtE,CAAC;QACF,MAAM,SAAS,GAAG,gCAAgC,CAAC,QAAQ,CAAC,CAAC;QAC7D,MAAM,0BAA0B,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,iBAAiB,SAAS,EAAE,CAAC,CAAC;QAC9F,MAAM,wBAAwB,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,SAAS,EAAE,CAAC,CAAC;QACxF,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CACjC,iBAAiB,EACjB,eAAe,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,SAAS,EAAE,CACjD,CAAC;QAEF,IAAI,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,wBAAwB,CAAC,EAAE,CAAC;YACzF,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC;YACrC,CAAC,CAAC,qCAAqC,iBAAiB,GAAG,CAAC,CAAC;QAC/D,CAAC;aAAM,IAAI,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC5C,CAAC,CAAC,2BAA2B,iBAAiB,GAAG,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,CAAC,CACC,qBAAqB,0BAA0B,OAAO,wBAAwB,UAAU,iBAAiB,GAAG,CAC7G,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
export type ModuleType = 'prod' | 'dev' | 'optional';
|
||||
export declare class ModuleWalker {
|
||||
buildPath: string;
|
||||
modulesToRebuild: string[];
|
||||
onlyModules: string[] | null;
|
||||
prodDeps: Set<string>;
|
||||
projectRootPath?: string;
|
||||
realModulePaths: Set<string>;
|
||||
realNodeModulesPaths: Set<string>;
|
||||
types: ModuleType[];
|
||||
constructor(buildPath: string, projectRootPath: string | undefined, types: ModuleType[], prodDeps: Set<string>, onlyModules: string[] | null);
|
||||
get nodeModulesPaths(): Promise<string[]>;
|
||||
walkModules(): Promise<void>;
|
||||
findModule(moduleName: string, fromDir: string, foundFn: (p: string) => Promise<void>): Promise<void[]>;
|
||||
markChildrenAsProdDeps(modulePath: string): Promise<void>;
|
||||
findAllModulesIn(nodeModulesPath: string, prefix?: string): Promise<void>;
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
import debug from 'debug';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { readPackageJson } from './read-package-json.js';
|
||||
import { searchForModule, searchForNodeModules } from './search-module.js';
|
||||
const d = debug('electron-rebuild');
|
||||
export class ModuleWalker {
|
||||
buildPath;
|
||||
modulesToRebuild;
|
||||
onlyModules;
|
||||
prodDeps;
|
||||
projectRootPath;
|
||||
realModulePaths;
|
||||
realNodeModulesPaths;
|
||||
types;
|
||||
constructor(buildPath, projectRootPath, types, prodDeps, onlyModules) {
|
||||
this.buildPath = buildPath;
|
||||
this.modulesToRebuild = [];
|
||||
this.projectRootPath = projectRootPath;
|
||||
this.types = types;
|
||||
this.prodDeps = prodDeps;
|
||||
this.onlyModules = onlyModules;
|
||||
this.realModulePaths = new Set();
|
||||
this.realNodeModulesPaths = new Set();
|
||||
}
|
||||
get nodeModulesPaths() {
|
||||
return searchForNodeModules(this.buildPath, this.projectRootPath);
|
||||
}
|
||||
async walkModules() {
|
||||
const rootPackageJson = await readPackageJson(this.buildPath);
|
||||
const markWaiters = [];
|
||||
const depKeys = [];
|
||||
if (this.types.includes('prod') || this.onlyModules) {
|
||||
depKeys.push(...Object.keys(rootPackageJson.dependencies || {}));
|
||||
}
|
||||
if (this.types.includes('optional') || this.onlyModules) {
|
||||
depKeys.push(...Object.keys(rootPackageJson.optionalDependencies || {}));
|
||||
}
|
||||
if (this.types.includes('dev') || this.onlyModules) {
|
||||
depKeys.push(...Object.keys(rootPackageJson.devDependencies || {}));
|
||||
}
|
||||
for (const key of depKeys) {
|
||||
this.prodDeps.add(key);
|
||||
const modulePaths = await searchForModule(this.buildPath, key, this.projectRootPath);
|
||||
for (const modulePath of modulePaths) {
|
||||
markWaiters.push(this.markChildrenAsProdDeps(modulePath));
|
||||
}
|
||||
}
|
||||
await Promise.all(markWaiters);
|
||||
d('identified prod deps:', this.prodDeps);
|
||||
}
|
||||
async findModule(moduleName, fromDir, foundFn) {
|
||||
const testPaths = await searchForModule(fromDir, moduleName, this.projectRootPath);
|
||||
const foundFns = testPaths.map((testPath) => foundFn(testPath));
|
||||
return Promise.all(foundFns);
|
||||
}
|
||||
async markChildrenAsProdDeps(modulePath) {
|
||||
if (!fs.existsSync(modulePath)) {
|
||||
return;
|
||||
}
|
||||
d('exploring', modulePath);
|
||||
let childPackageJson;
|
||||
try {
|
||||
childPackageJson = await readPackageJson(modulePath, true);
|
||||
}
|
||||
catch {
|
||||
return;
|
||||
}
|
||||
const moduleWait = [];
|
||||
const callback = this.markChildrenAsProdDeps.bind(this);
|
||||
for (const key of Object.keys(childPackageJson.dependencies || {}).concat(Object.keys(childPackageJson.optionalDependencies || {}))) {
|
||||
if (this.prodDeps.has(key)) {
|
||||
continue;
|
||||
}
|
||||
this.prodDeps.add(key);
|
||||
moduleWait.push(this.findModule(key, modulePath, callback));
|
||||
}
|
||||
await Promise.all(moduleWait);
|
||||
}
|
||||
async findAllModulesIn(nodeModulesPath, prefix = '') {
|
||||
// Some package managers use symbolic links when installing node modules
|
||||
// we need to be sure we've never tested the a package before by resolving
|
||||
// all symlinks in the path and testing against a set
|
||||
const realNodeModulesPath = await fs.promises.realpath(nodeModulesPath);
|
||||
if (this.realNodeModulesPaths.has(realNodeModulesPath)) {
|
||||
return;
|
||||
}
|
||||
this.realNodeModulesPaths.add(realNodeModulesPath);
|
||||
d('scanning:', realNodeModulesPath);
|
||||
for (const modulePath of await fs.promises.readdir(realNodeModulesPath)) {
|
||||
// Ignore the magical .bin directory
|
||||
if (modulePath === '.bin')
|
||||
continue;
|
||||
const subPath = path.resolve(nodeModulesPath, modulePath);
|
||||
// Ensure that we don't mark modules as needing to be rebuilt more than once
|
||||
// by ignoring / resolving symlinks
|
||||
let realPath;
|
||||
try {
|
||||
realPath = await fs.promises.realpath(subPath);
|
||||
}
|
||||
catch (error) {
|
||||
// pnpm leaves dangling symlinks when modules are removed
|
||||
if (error.code === 'ENOENT') {
|
||||
const stat = await fs.promises.lstat(subPath);
|
||||
if (stat.isSymbolicLink()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (this.realModulePaths.has(realPath)) {
|
||||
continue;
|
||||
}
|
||||
this.realModulePaths.add(realPath);
|
||||
const moduleName = `${prefix}${modulePath}`;
|
||||
if (this.prodDeps.has(moduleName) &&
|
||||
(!this.onlyModules || this.onlyModules.includes(moduleName))) {
|
||||
this.modulesToRebuild.push(realPath);
|
||||
}
|
||||
if (modulePath.startsWith('@')) {
|
||||
await this.findAllModulesIn(realPath, `${modulePath}/`);
|
||||
}
|
||||
if (fs.existsSync(path.resolve(nodeModulesPath, modulePath, 'node_modules'))) {
|
||||
await this.findAllModulesIn(path.resolve(realPath, 'node_modules'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=module-walker.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"module-walker.js","sourceRoot":"","sources":["../src/module-walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE3E,MAAM,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAIpC,MAAM,OAAO,YAAY;IACvB,SAAS,CAAS;IAClB,gBAAgB,CAAW;IAC3B,WAAW,CAAkB;IAC7B,QAAQ,CAAc;IACtB,eAAe,CAAU;IACzB,eAAe,CAAc;IAC7B,oBAAoB,CAAc;IAClC,KAAK,CAAe;IAEpB,YACE,SAAiB,EACjB,eAAmC,EACnC,KAAmB,EACnB,QAAqB,EACrB,WAA4B;QAE5B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,IAAI,GAAG,EAAE,CAAC;QACjC,IAAI,CAAC,oBAAoB,GAAG,IAAI,GAAG,EAAE,CAAC;IACxC,CAAC;IAED,IAAI,gBAAgB;QAClB,OAAO,oBAAoB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,WAAW;QACf,MAAM,eAAe,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAoB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,EAAE,CAAC;QAEnB,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnD,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC;QACtE,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACvB,MAAM,WAAW,GAAa,MAAM,eAAe,CACjD,IAAI,CAAC,SAAS,EACd,GAAG,EACH,IAAI,CAAC,eAAe,CACrB,CAAC;YACF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;gBACrC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAED,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAE/B,CAAC,CAAC,uBAAuB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,UAAU,CACd,UAAkB,EAClB,OAAe,EACf,OAAqC;QAErC,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACnF,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEhE,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,UAAkB;QAC7C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,CAAC,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAC3B,IAAI,gBAAgB,CAAC;QACrB,IAAI,CAAC;YACH,gBAAgB,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAsB,EAAE,CAAC;QAEzC,MAAM,QAAQ,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,CACvE,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,oBAAoB,IAAI,EAAE,CAAC,CACzD,EAAE,CAAC;YACF,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,SAAS;YACX,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAEvB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,eAAuB,EAAE,MAAM,GAAG,EAAE;QACzD,wEAAwE;QACxE,0EAA0E;QAC1E,qDAAqD;QACrD,MAAM,mBAAmB,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACvD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QAEnD,CAAC,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QAEpC,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACxE,oCAAoC;YACpC,IAAI,UAAU,KAAK,MAAM;gBAAE,SAAS;YAEpC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;YAE1D,4EAA4E;YAC5E,mCAAmC;YACnC,IAAI,QAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACjD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,yDAAyD;gBACzD,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACvD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBAC9C,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;wBAC1B,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;YAED,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvC,SAAS;YACX,CAAC;YACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAEnC,MAAM,UAAU,GAAG,GAAG,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5C,IACE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC;gBAC7B,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAC5D,CAAC;gBACD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,UAAU,GAAG,CAAC,CAAC;YAC1D,CAAC;YAED,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;gBAC7E,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
export declare class NodeAPI {
|
||||
private moduleName;
|
||||
private electronVersion;
|
||||
constructor(moduleName: string, electronVersion: string);
|
||||
ensureElectronSupport(): void;
|
||||
getVersionForElectron(): number;
|
||||
getNapiVersion(moduleNapiVersions: number[]): number;
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
import { fromElectronVersion as napiVersionFromElectronVersion } from 'node-api-version';
|
||||
export class NodeAPI {
|
||||
moduleName;
|
||||
electronVersion;
|
||||
constructor(moduleName, electronVersion) {
|
||||
this.moduleName = moduleName;
|
||||
this.electronVersion = electronVersion;
|
||||
}
|
||||
ensureElectronSupport() {
|
||||
this.getVersionForElectron();
|
||||
}
|
||||
getVersionForElectron() {
|
||||
const electronNapiVersion = napiVersionFromElectronVersion(this.electronVersion);
|
||||
if (!electronNapiVersion) {
|
||||
throw new Error(`Native module '${this.moduleName}' requires Node-API but Electron v${this.electronVersion} does not support Node-API`);
|
||||
}
|
||||
return electronNapiVersion;
|
||||
}
|
||||
getNapiVersion(moduleNapiVersions) {
|
||||
const electronNapiVersion = this.getVersionForElectron();
|
||||
// Filter out Node-API versions that are too high
|
||||
const filteredVersions = moduleNapiVersions.filter((v) => v <= electronNapiVersion);
|
||||
if (filteredVersions.length === 0) {
|
||||
throw new Error(`Native module '${this.moduleName}' supports Node-API versions ${moduleNapiVersions.join(', ')} but Electron v${this.electronVersion} only supports Node-API v${electronNapiVersion}`);
|
||||
}
|
||||
return Math.max(...filteredVersions);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=node-api.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"node-api.js","sourceRoot":"","sources":["../src/node-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,IAAI,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAEzF,MAAM,OAAO,OAAO;IACV,UAAU,CAAS;IACnB,eAAe,CAAS;IAEhC,YAAY,UAAkB,EAAE,eAAuB;QACrD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;IAED,qBAAqB;QACnB,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC/B,CAAC;IAED,qBAAqB;QACnB,MAAM,mBAAmB,GAAG,8BAA8B,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAEjF,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,CAAC,UAAU,qCAAqC,IAAI,CAAC,eAAe,4BAA4B,CACvH,CAAC;QACJ,CAAC;QAED,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,cAAc,CAAC,kBAA4B;QACzC,MAAM,mBAAmB,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAEzD,iDAAiD;QACjD,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC;QAEpF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,CAAC,UAAU,gCAAgC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,eAAe,4BAA4B,mBAAmB,EAAE,CACtL,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,CAAC;IACvC,CAAC;CACF"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export {};
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { pathToFileURL } from 'node:url';
|
||||
process.argv.splice(1, 1);
|
||||
// This tricks prebuild-install into not validating on the
|
||||
// 1.8.x and 8.x ABI collision
|
||||
Object.defineProperty(process.versions, 'modules', { value: '-1', writable: false });
|
||||
void import(pathToFileURL(process.argv[1]).toString());
|
||||
//# sourceMappingURL=prebuild-shim.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"prebuild-shim.js","sourceRoot":"","sources":["../src/prebuild-shim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE1B,0DAA0D;AAC1D,8BAA8B;AAC9B,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AAErF,KAAK,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export declare function readPackageJson(dir: string, safe?: boolean): Promise<any>;
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export async function readPackageJson(dir, safe = false) {
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(path.resolve(dir, 'package.json'), {
|
||||
encoding: 'utf-8',
|
||||
}));
|
||||
}
|
||||
catch (err) {
|
||||
if (safe) {
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=read-package-json.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"read-package-json.js","sourceRoot":"","sources":["../src/read-package-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,IAAI,GAAG,KAAK;IAC7D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CACf,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE;YACnD,QAAQ,EAAE,OAAO;SAClB,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;AACH,CAAC"}
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { BuildType, IRebuilder, RebuildMode } from './types.js';
|
||||
import { ModuleType } from './module-walker.js';
|
||||
export interface RebuildOptions {
|
||||
/**
|
||||
* The path to the `node_modules` directory to rebuild.
|
||||
*/
|
||||
buildPath: string;
|
||||
/**
|
||||
* The version of Electron to build against.
|
||||
*/
|
||||
electronVersion: string;
|
||||
/**
|
||||
* Override the target platform to something other than the host system platform.
|
||||
* Note: This only applies to downloading prebuilt binaries. **It is not possible to cross-compile native modules.**
|
||||
*
|
||||
* @defaultValue The system {@link https://nodejs.org/api/process.html#processplatform | `process.platform`} value
|
||||
*/
|
||||
platform?: NodeJS.Platform;
|
||||
/**
|
||||
* Override the target rebuild architecture to something other than the host system architecture.
|
||||
*
|
||||
* @defaultValue The system {@link https://nodejs.org/api/process.html#processarch | `process.arch`} value
|
||||
*/
|
||||
arch?: string;
|
||||
/**
|
||||
* An array of module names to rebuild in addition to detected modules
|
||||
* @default []
|
||||
*/
|
||||
extraModules?: string[];
|
||||
/**
|
||||
* An array of module names to rebuild. **Only** these modules will be rebuilt.
|
||||
*/
|
||||
onlyModules?: string[] | null;
|
||||
/**
|
||||
* Force a rebuild of modules regardless of their current build state.
|
||||
*/
|
||||
force?: boolean;
|
||||
/**
|
||||
* URL to download Electron header files from.
|
||||
* @defaultValue `https://www.electronjs.org/headers`
|
||||
*/
|
||||
headerURL?: string;
|
||||
/**
|
||||
* Array of types of dependencies to rebuild. Possible values are `prod`, `dev`, and `optional`.
|
||||
*
|
||||
* @defaultValue `['prod', 'optional']`
|
||||
*/
|
||||
types?: ModuleType[];
|
||||
/**
|
||||
* Whether to rebuild modules sequentially or in parallel.
|
||||
*
|
||||
* @defaultValue `sequential`
|
||||
*/
|
||||
mode?: RebuildMode;
|
||||
/**
|
||||
* Rebuilds a Debug build of target modules. If this is `false`, a Release build will be generated instead.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Enables hash-based caching to speed up local rebuilds.
|
||||
*
|
||||
* @experimental
|
||||
* @defaultValue false
|
||||
*/
|
||||
useCache?: boolean;
|
||||
/**
|
||||
* Whether to use the `clang` executable that Electron uses when building.
|
||||
* This will guarantee compiler compatibility.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
useElectronClang?: boolean;
|
||||
/**
|
||||
* Sets a custom cache path for the {@link useCache} option.
|
||||
* @experimental
|
||||
* @defaultValue a `.electron-rebuild-cache` folder in the `os.homedir()` directory
|
||||
*/
|
||||
cachePath?: string;
|
||||
/**
|
||||
* GitHub tag prefix passed to {@link https://www.npmjs.com/package/prebuild-install | `prebuild-install`}.
|
||||
* @defaultValue `v`
|
||||
*/
|
||||
prebuildTagPrefix?: string;
|
||||
/**
|
||||
* Path to the root of the project if using npm or yarn workspaces.
|
||||
*/
|
||||
projectRootPath?: string;
|
||||
/**
|
||||
* Override the Application Binary Interface (ABI) version for the version of Electron you are targeting.
|
||||
* Only use when targeting nightly releases.
|
||||
*
|
||||
* @see the {@link https://github.com/electron/node-abi | electron/node-abi} repository for a list of Electron and Node.js ABIs
|
||||
*/
|
||||
forceABI?: number;
|
||||
/**
|
||||
* Disables the copying of `.node` files if not needed.
|
||||
* @defaultValue false
|
||||
*/
|
||||
disablePreGypCopy?: boolean;
|
||||
/**
|
||||
* Skip prebuild download and rebuild module from source.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
buildFromSource?: boolean;
|
||||
/**
|
||||
* Array of module names to ignore during the rebuild process.
|
||||
*/
|
||||
ignoreModules?: string[];
|
||||
/**
|
||||
* Number of parallel compile jobs `node-gyp` should run, passed through as node-gyp's `--jobs` flag.
|
||||
*
|
||||
* @defaultValue node-gyp's own default when unset
|
||||
*/
|
||||
jobs?: number;
|
||||
}
|
||||
export interface RebuilderOptions extends RebuildOptions {
|
||||
lifecycle: EventEmitter;
|
||||
}
|
||||
export declare class Rebuilder implements IRebuilder {
|
||||
private ABIVersion;
|
||||
private moduleWalker;
|
||||
rebuilds: (() => Promise<void>)[];
|
||||
lifecycle: EventEmitter;
|
||||
buildPath: string;
|
||||
electronVersion: string;
|
||||
platform: NodeJS.Platform;
|
||||
arch: string;
|
||||
force: boolean;
|
||||
headerURL: string;
|
||||
mode: RebuildMode;
|
||||
debug: boolean;
|
||||
useCache: boolean;
|
||||
cachePath: string;
|
||||
prebuildTagPrefix: string;
|
||||
msvsVersion?: string;
|
||||
useElectronClang: boolean;
|
||||
disablePreGypCopy: boolean;
|
||||
buildFromSource: boolean;
|
||||
ignoreModules: string[];
|
||||
jobs?: number;
|
||||
constructor(options: RebuilderOptions);
|
||||
get ABI(): string;
|
||||
get buildType(): BuildType;
|
||||
rebuild(): Promise<void>;
|
||||
modulesToRebuild(): Promise<string[]>;
|
||||
rebuildModuleAt(modulePath: string): Promise<void>;
|
||||
}
|
||||
export type RebuildResult = Promise<void> & {
|
||||
lifecycle: EventEmitter;
|
||||
};
|
||||
export declare function rebuild(options: RebuildOptions): RebuildResult;
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
import debug from 'debug';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import fs from 'node:fs';
|
||||
import { getAbi } from 'node-abi';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { generateCacheKey, lookupModuleState } from './cache.js';
|
||||
import { BuildType } from './types.js';
|
||||
import { ModuleRebuilder } from './module-rebuilder.js';
|
||||
import { ModuleWalker } from './module-walker.js';
|
||||
const d = debug('electron-rebuild');
|
||||
const defaultMode = 'sequential';
|
||||
const defaultTypes = ['prod', 'optional'];
|
||||
export class Rebuilder {
|
||||
ABIVersion;
|
||||
moduleWalker;
|
||||
rebuilds;
|
||||
lifecycle;
|
||||
buildPath;
|
||||
electronVersion;
|
||||
platform;
|
||||
arch;
|
||||
force;
|
||||
headerURL;
|
||||
mode;
|
||||
debug;
|
||||
useCache;
|
||||
cachePath;
|
||||
prebuildTagPrefix;
|
||||
msvsVersion;
|
||||
useElectronClang;
|
||||
disablePreGypCopy;
|
||||
buildFromSource;
|
||||
ignoreModules;
|
||||
jobs;
|
||||
constructor(options) {
|
||||
this.lifecycle = options.lifecycle;
|
||||
this.buildPath = options.buildPath;
|
||||
this.electronVersion = options.electronVersion;
|
||||
this.platform = options.platform || process.platform;
|
||||
this.arch = options.arch || process.arch;
|
||||
this.force = options.force || false;
|
||||
this.headerURL = options.headerURL || 'https://www.electronjs.org/headers';
|
||||
this.mode = options.mode || defaultMode;
|
||||
this.debug = options.debug || false;
|
||||
this.useCache = options.useCache || false;
|
||||
this.useElectronClang = options.useElectronClang || false;
|
||||
this.cachePath = options.cachePath || path.resolve(os.homedir(), '.electron-rebuild-cache');
|
||||
this.prebuildTagPrefix = options.prebuildTagPrefix ?? 'v';
|
||||
this.msvsVersion = process.env.GYP_MSVS_VERSION;
|
||||
this.disablePreGypCopy = options.disablePreGypCopy || false;
|
||||
this.buildFromSource = options.buildFromSource || false;
|
||||
this.ignoreModules = options.ignoreModules || [];
|
||||
this.jobs = options.jobs;
|
||||
d('ignoreModules', this.ignoreModules);
|
||||
if (this.useCache && this.force) {
|
||||
console.warn('[WARNING]: Electron Rebuild has force enabled and cache enabled, force take precedence and the cache will not be used.');
|
||||
this.useCache = false;
|
||||
}
|
||||
// FIXME: this entire guard is unreachable according to the types
|
||||
/* oxlint-disable typescript-eslint(restrict-template-expressions) */
|
||||
if (typeof this.electronVersion === 'number') {
|
||||
if (`${this.electronVersion}`.split('.').length === 1) {
|
||||
this.electronVersion = `${this.electronVersion}.0.0`;
|
||||
}
|
||||
else {
|
||||
this.electronVersion = `${this.electronVersion}.0`;
|
||||
}
|
||||
}
|
||||
/* oxlint-enable typescript-eslint(restrict-template-expressions) */
|
||||
if (typeof this.electronVersion !== 'string') {
|
||||
throw new Error(`Expected a string version for electron version, got a "${typeof this.electronVersion}"`);
|
||||
}
|
||||
this.ABIVersion = options.forceABI?.toString();
|
||||
const onlyModules = options.onlyModules || null;
|
||||
const extraModules = new Set(options.extraModules);
|
||||
const types = options.types || defaultTypes;
|
||||
this.moduleWalker = new ModuleWalker(this.buildPath, options.projectRootPath, types, extraModules, onlyModules);
|
||||
this.rebuilds = [];
|
||||
d('rebuilding with args:', this.buildPath, this.electronVersion, this.platform, this.arch, extraModules, this.force, this.headerURL, types, this.debug);
|
||||
}
|
||||
get ABI() {
|
||||
if (this.ABIVersion === undefined) {
|
||||
this.ABIVersion = getAbi(this.electronVersion, 'electron');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.ABIVersion;
|
||||
}
|
||||
get buildType() {
|
||||
return this.debug ? BuildType.Debug : BuildType.Release;
|
||||
}
|
||||
async rebuild() {
|
||||
if (!path.isAbsolute(this.buildPath)) {
|
||||
throw new Error('Expected buildPath to be an absolute path');
|
||||
}
|
||||
this.lifecycle.emit('start');
|
||||
const candidatePaths = [...(await this.modulesToRebuild()), this.buildPath];
|
||||
const nativeModulePaths = candidatePaths.filter((p) => fs.existsSync(path.resolve(p, 'binding.gyp')));
|
||||
this.lifecycle.emit('modules-found', nativeModulePaths.map((p) => {
|
||||
const name = path.basename(p);
|
||||
const parent = path.basename(path.dirname(p));
|
||||
return parent !== 'node_modules' ? `${parent}/${name}` : name;
|
||||
}));
|
||||
for (const modulePath of nativeModulePaths) {
|
||||
this.rebuilds.push(() => this.rebuildModuleAt(modulePath));
|
||||
}
|
||||
if (this.mode !== 'sequential') {
|
||||
await Promise.all(this.rebuilds.map((fn) => fn()));
|
||||
}
|
||||
else {
|
||||
for (const rebuildFn of this.rebuilds) {
|
||||
await rebuildFn();
|
||||
}
|
||||
}
|
||||
}
|
||||
async modulesToRebuild() {
|
||||
await this.moduleWalker.walkModules();
|
||||
for (const nodeModulesPath of await this.moduleWalker.nodeModulesPaths) {
|
||||
await this.moduleWalker.findAllModulesIn(nodeModulesPath);
|
||||
}
|
||||
return this.moduleWalker.modulesToRebuild;
|
||||
}
|
||||
async rebuildModuleAt(modulePath) {
|
||||
const moduleRebuilder = new ModuleRebuilder(this, modulePath);
|
||||
let moduleName = path.basename(modulePath);
|
||||
const parentName = path.basename(path.dirname(modulePath));
|
||||
if (parentName !== 'node_modules') {
|
||||
moduleName = `${parentName}/${moduleName}`;
|
||||
}
|
||||
this.lifecycle.emit('module-found', moduleName);
|
||||
if (!this.force && (await moduleRebuilder.alreadyBuiltByRebuild())) {
|
||||
d(`skipping: ${moduleName} as it is already built`);
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
this.lifecycle.emit('module-skip', moduleName);
|
||||
return;
|
||||
}
|
||||
d('checking', moduleName, 'against', this.ignoreModules);
|
||||
if (this.ignoreModules.includes(moduleName)) {
|
||||
d(`skipping: ${moduleName} as it is in the ignoreModules array`);
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
this.lifecycle.emit('module-skip', moduleName);
|
||||
return;
|
||||
}
|
||||
if (await moduleRebuilder.prebuildInstallNativeModuleExists()) {
|
||||
d(`skipping: ${moduleName} as it was prebuilt`);
|
||||
return;
|
||||
}
|
||||
let cacheKey;
|
||||
if (this.useCache) {
|
||||
cacheKey = await generateCacheKey({
|
||||
ABI: this.ABI,
|
||||
arch: this.arch,
|
||||
platform: this.platform,
|
||||
debug: this.debug,
|
||||
electronVersion: this.electronVersion,
|
||||
headerURL: this.headerURL,
|
||||
modulePath,
|
||||
});
|
||||
const applyDiffFn = await lookupModuleState(this.cachePath, cacheKey);
|
||||
if (typeof applyDiffFn === 'function') {
|
||||
await applyDiffFn(modulePath);
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await moduleRebuilder.rebuild(cacheKey)) {
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function rebuild(options) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
d('rebuilding with args:', arguments);
|
||||
const lifecycle = new EventEmitter();
|
||||
const rebuilderOptions = { ...options, lifecycle };
|
||||
const rebuilder = new Rebuilder(rebuilderOptions);
|
||||
const ret = rebuilder.rebuild();
|
||||
ret.lifecycle = lifecycle;
|
||||
return ret;
|
||||
}
|
||||
//# sourceMappingURL=rebuild.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-23
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 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>;
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import path from '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.
|
||||
*/
|
||||
export 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.
|
||||
*/
|
||||
export 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
|
||||
*/
|
||||
export async function getProjectRootPath(cwd) {
|
||||
for (const lockFilename of ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml']) {
|
||||
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
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"search-module.js","sourceRoot":"","sources":["../src/search-module.ts"],"names":[],"mappings":"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"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export declare function downloadLinuxSysroot(electronVersion: string, targetArch: string): Promise<string>;
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import crypto from 'node:crypto';
|
||||
import debug from 'debug';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { ELECTRON_GYP_DIR } from './constants.js';
|
||||
import { fetchUrl } from './fetcher.js';
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
const d = debug('electron-rebuild');
|
||||
const sysrootArchAliases = {
|
||||
x64: 'amd64',
|
||||
ia32: 'i386',
|
||||
};
|
||||
const SYSROOT_BASE_URL = 'https://dev-cdn.electronjs.org/linux-sysroots';
|
||||
export async function downloadLinuxSysroot(electronVersion, targetArch) {
|
||||
d('fetching sysroot for Electron:', electronVersion);
|
||||
const sysrootDir = path.resolve(ELECTRON_GYP_DIR, `${electronVersion}-sysroot`);
|
||||
if (fs.existsSync(path.resolve(sysrootDir, 'lib')))
|
||||
return sysrootDir;
|
||||
await fs.promises.mkdir(sysrootDir, { recursive: true });
|
||||
const linuxArch = sysrootArchAliases[targetArch] || targetArch;
|
||||
const electronSysroots = JSON.parse(await fetchUrl(`https://raw.githubusercontent.com/electron/electron/v${electronVersion}/script/sysroots.json`, 'text'));
|
||||
const { Sha1Sum: sha, Tarball: fileName } = electronSysroots[`sid_${linuxArch}`] || electronSysroots[`bullseye_${linuxArch}`];
|
||||
const sysrootURL = `${SYSROOT_BASE_URL}/${sha}/${fileName}`;
|
||||
const sysrootBuffer = await fetchUrl(sysrootURL, 'buffer');
|
||||
const actualSha = crypto.createHash('SHA1').update(sysrootBuffer).digest('hex');
|
||||
d('expected sha:', sha);
|
||||
d('actual sha:', actualSha);
|
||||
if (sha !== actualSha)
|
||||
throw new Error(`Attempted to download the linux sysroot for ${electronVersion} but the SHA checksum did not match`);
|
||||
d('writing sysroot to disk');
|
||||
const tmpTarFile = path.resolve(ELECTRON_GYP_DIR, `${electronVersion}-${fileName}`);
|
||||
if (fs.existsSync(tmpTarFile))
|
||||
await fs.promises.rm(tmpTarFile, { recursive: true, force: true });
|
||||
await fs.promises.writeFile(tmpTarFile, sysrootBuffer);
|
||||
d('decompressing sysroot');
|
||||
await spawn('tar', ['-xf', tmpTarFile, '-C', sysrootDir], { stdio: 'ignore' });
|
||||
return sysrootDir;
|
||||
}
|
||||
//# sourceMappingURL=sysroot-fetcher.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"sysroot-fetcher.js","sourceRoot":"","sources":["../src/sysroot-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAEpD,MAAM,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAEpC,MAAM,kBAAkB,GAA2B;IACjD,GAAG,EAAE,OAAO;IACZ,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,gBAAgB,GAAG,+CAA+C,CAAC;AAEzE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,eAAuB,EACvB,UAAkB;IAElB,CAAC,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,eAAe,UAAU,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC;IACtE,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzD,MAAM,SAAS,GAAG,kBAAkB,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;IAC/D,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CACjC,MAAM,QAAQ,CACZ,wDAAwD,eAAe,uBAAuB,EAC9F,MAAM,CACP,CACF,CAAC;IAEF,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GACvC,gBAAgB,CAAC,OAAO,SAAS,EAAE,CAAC,IAAI,gBAAgB,CAAC,YAAY,SAAS,EAAE,CAAC,CAAC;IACpF,MAAM,UAAU,GAAG,GAAG,gBAAgB,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5D,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE3D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChF,CAAC,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAC5B,IAAI,GAAG,KAAK,SAAS;QACnB,MAAM,IAAI,KAAK,CACb,+CAA+C,eAAe,qCAAqC,CACpG,CAAC;IAEJ,CAAC,CAAC,yBAAyB,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,eAAe,IAAI,QAAQ,EAAE,CAAC,CAAC;IACpF,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAEvD,CAAC,CAAC,uBAAuB,CAAC,CAAC;IAC3B,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE/E,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
export declare enum BuildType {
|
||||
Debug = "Debug",
|
||||
Release = "Release"
|
||||
}
|
||||
export type RebuildMode = 'sequential' | 'parallel';
|
||||
export interface IRebuilder {
|
||||
ABI: string;
|
||||
arch: string;
|
||||
buildPath: string;
|
||||
buildType: BuildType;
|
||||
cachePath: string;
|
||||
debug: boolean;
|
||||
disablePreGypCopy: boolean;
|
||||
electronVersion: string;
|
||||
force: boolean;
|
||||
headerURL: string;
|
||||
jobs?: number;
|
||||
lifecycle: EventEmitter;
|
||||
mode: RebuildMode;
|
||||
msvsVersion?: string;
|
||||
platform: NodeJS.Platform;
|
||||
prebuildTagPrefix: string;
|
||||
buildFromSource: boolean;
|
||||
useCache: boolean;
|
||||
useElectronClang: boolean;
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export var BuildType;
|
||||
(function (BuildType) {
|
||||
BuildType["Debug"] = "Debug";
|
||||
BuildType["Release"] = "Release";
|
||||
})(BuildType || (BuildType = {}));
|
||||
//# sourceMappingURL=types.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAN,IAAY,SAGX;AAHD,WAAY,SAAS;IACnB,4BAAe,CAAA;IACf,gCAAmB,CAAA;AACrB,CAAC,EAHW,SAAS,KAAT,SAAS,QAGpB"}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
{
|
||||
"name": "@electron/rebuild",
|
||||
"version": "4.2.1",
|
||||
"description": "Electron supporting package to rebuild native node modules against the currently installed electron",
|
||||
"type": "module",
|
||||
"exports": "./lib/main.js",
|
||||
"typings": "lib/main.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:docs": "typedoc src/main.ts",
|
||||
"coverage": "yarn prewarm-headers && yarn spec --coverage",
|
||||
"watch": "tsc -w",
|
||||
"prepare": "husky && yarn build",
|
||||
"lint": "oxlint && oxfmt --check",
|
||||
"spec": "tsc && vitest run",
|
||||
"test": "yarn prewarm-headers && yarn lint && yarn spec",
|
||||
"prewarm-headers": "node-gyp install --ensure"
|
||||
},
|
||||
"bin": {
|
||||
"electron-rebuild": "lib/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/electron/rebuild.git"
|
||||
},
|
||||
"keywords": [
|
||||
"electron"
|
||||
],
|
||||
"authors": [
|
||||
"Ani Betts <anais@anaisbetts.org>",
|
||||
"Electron Community"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/electron/rebuild/issues"
|
||||
},
|
||||
"homepage": "https://github.com/electron/rebuild",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"dependencies": {
|
||||
"@malept/cross-spawn-promise": "^2.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"node-abi": "^4.2.0",
|
||||
"node-api-version": "^0.2.1",
|
||||
"node-gyp": "^12.2.0",
|
||||
"read-binary-file-arch": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/node": "~22.12.0",
|
||||
"@types/node-abi": "^3.0.0",
|
||||
"@vitest/coverage-v8": "^5.0.1",
|
||||
"electron": "^39.8.5",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^17.4.1",
|
||||
"oxfmt": "^0.42.0",
|
||||
"oxlint": "^1.57.0",
|
||||
"oxlint-tsgolint": "^0.18.1",
|
||||
"typedoc": "~0.28.0",
|
||||
"typescript": "^6.0.2",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^5.0.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"oxlint --fix",
|
||||
"oxfmt --write"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/typescript-estree/minimatch": "^9.0.7"
|
||||
},
|
||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
||||
"dependenciesMeta": {
|
||||
"electron": {
|
||||
"built": true
|
||||
}
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Contributors to the Electron project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
# @electron/universal
|
||||
|
||||
> Create universal macOS Electron applications
|
||||
|
||||
[](https://github.com/electron/universal/actions/workflows/test.yml)
|
||||
[](https://npm.im/@electron/universal)
|
||||
|
||||
## Usage
|
||||
|
||||
This package takes an x64 app and an arm64 app and glues them together into a
|
||||
[Universal macOS binary](https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary).
|
||||
|
||||
Note that parameters need to be **absolute** paths.
|
||||
|
||||
```typescript
|
||||
import { makeUniversalApp } from '@electron/universal';
|
||||
|
||||
await makeUniversalApp({
|
||||
x64AppPath: 'path/to/App_x64.app',
|
||||
arm64AppPath: 'path/to/App_arm64.app',
|
||||
outAppPath: 'path/to/App_universal.app',
|
||||
});
|
||||
```
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
The basic usage patterns will work for most apps out of the box. Additional configuration
|
||||
options are available for advanced usecases.
|
||||
|
||||
### Merging ASAR archives to reduce app size
|
||||
|
||||
**Added in [v1.2.0](https://github.com/electron/universal/commit/38ab1c3559e25382957d608e49e624dc72a4409c)**
|
||||
|
||||
If you are using ASAR archives to store your Electron app's JavaScript code, you can use the
|
||||
`mergeASARs` option to merge your x64 and arm64 ASAR files to reduce the bundle size of
|
||||
the output Universal app.
|
||||
|
||||
If some files are present in only the x64 app but not the arm64 version (or vice-versa),
|
||||
you can exclude them from the merging process by specifying a `minimatch` pattern
|
||||
in `singleArchFiles`.
|
||||
|
||||
```typescript
|
||||
import { makeUniversalApp } from '@electron/universal';
|
||||
|
||||
await makeUniversalApp({
|
||||
x64AppPath: 'path/to/App_x64.app',
|
||||
arm64AppPath: 'path/to/App_arm64.app',
|
||||
outAppPath: 'path/to/App_universal.app',
|
||||
mergeASARs: true,
|
||||
singleArchFiles: 'node_modules/some-native-module/lib/binding/Release/**', // if you have files in your asar that are unique to x64 or arm64 apps
|
||||
});
|
||||
```
|
||||
|
||||
If `@electron/universal` detects an architecture-unique file that isn't covered by the
|
||||
`singleArchFiles` rule, an error will be thrown.
|
||||
|
||||
### Skip lipo for certain binaries in your Universal app
|
||||
|
||||
**Added in [1.3.0](https://github.com/electron/universal/commit/01dfb8a9636965fe154192b07934670dd42509f3)**
|
||||
|
||||
If your Electron app contains binary resources that are already merged with the
|
||||
`lipo` tool, providing a [`minimatch`] pattern to matching files in the `x64ArchFiles`
|
||||
parameter will prevent `@electron/universal` from attempting to merge them a second time.
|
||||
|
||||
```typescript
|
||||
import { makeUniversalApp } from '@electron/universal';
|
||||
|
||||
await makeUniversalApp({
|
||||
x64AppPath: 'path/to/App_x64.app',
|
||||
arm64AppPath: 'path/to/App_arm64.app',
|
||||
outAppPath: 'path/to/App_universal.app',
|
||||
mergeASARs: true,
|
||||
x64ArchFiles: '*/electron-helper', // `electron-helper` is a binary merged using `lipo`
|
||||
});
|
||||
```
|
||||
|
||||
If `@electron/universal` detects a lipo'd file that isn't covered by the `x64ArchFiles` rule,
|
||||
an error will be thrown.
|
||||
|
||||
### Including already codesigned app bundles into your Universal app
|
||||
|
||||
**Added in [v1.4.0](https://github.com/electron/universal/commit/b02ce7697fd2a3c2c79e1f6ab6bf7052125865cc)**
|
||||
|
||||
By default, the merging process will generate an `ElectronAsarIntegrity` key for
|
||||
any `Info.plist` files in your Electron app.
|
||||
|
||||
If your Electron app bundles another `.app` that is already signed, you need to use
|
||||
the `infoPlistsToIgnore` option to avoid modifying that app's plist.
|
||||
|
||||
```typescript
|
||||
import { makeUniversalApp } from '@electron/universal';
|
||||
|
||||
await makeUniversalApp({
|
||||
x64AppPath: 'path/to/App_x64.app',
|
||||
arm64AppPath: 'path/to/App_arm64.app',
|
||||
outAppPath: 'path/to/App_universal.app',
|
||||
infoPlistsToIgnore: 'my-internal.app/Contents/Info.plist'
|
||||
});
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
#### The app is twice as big now, why?
|
||||
|
||||
A Universal app is just the x64 app and the arm64 app glued together into a single application.
|
||||
It's twice as big because it contains two apps in one.
|
||||
|
||||
Merging your ASAR bundles can yield significant app size reductions depending on how large
|
||||
your `app.asar` file is.
|
||||
|
||||
#### What about native modules?
|
||||
|
||||
Out of the box, you don't need to worry about building universal versions of your
|
||||
native modules. As long as your x64 and arm64 apps work in isolation, the Universal
|
||||
app will work as well.
|
||||
|
||||
Note that if you are using `mergeASARs`, you may need to add architecture-specific
|
||||
binary resources to the `singleArchFiles` pattern.
|
||||
See [Merging ASARs usage](#merging-asar-archives-to-reduce-app-size) for an example.
|
||||
|
||||
#### How do I build my app for Apple silicon in the first place?
|
||||
|
||||
Check out the [Electron Apple silicon blog post](https://www.electronjs.org/blog/apple-silicon).
|
||||
|
||||
[`minimatch`]: https://github.com/isaacs/minimatch?tab=readme-ov-file#features
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AsarMode {
|
||||
NO_ASAR = 0,
|
||||
HAS_ASAR = 1
|
||||
}
|
||||
export type MergeASARsOptions = {
|
||||
x64AsarPath: string;
|
||||
arm64AsarPath: string;
|
||||
outputAsarPath: string;
|
||||
singleArchFiles?: string;
|
||||
};
|
||||
export declare const detectAsarMode: (appPath: string) => Promise<AsarMode>;
|
||||
export declare const generateAsarIntegrity: (asarPath: string) => {
|
||||
algorithm: "SHA256";
|
||||
hash: string;
|
||||
};
|
||||
export declare const mergeASARs: ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }: MergeASARsOptions) => Promise<void>;
|
||||
export declare const isUniversalMachO: (fileContent: Buffer) => boolean;
|
||||
-180
@@ -1,180 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isUniversalMachO = exports.mergeASARs = exports.generateAsarIntegrity = exports.detectAsarMode = exports.AsarMode = void 0;
|
||||
const asar_1 = __importDefault(require("@electron/asar"));
|
||||
const child_process_1 = require("child_process");
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const fs_extra_1 = __importDefault(require("fs-extra"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const minimatch_1 = require("minimatch");
|
||||
const os_1 = __importDefault(require("os"));
|
||||
const debug_1 = require("./debug");
|
||||
const LIPO = 'lipo';
|
||||
var AsarMode;
|
||||
(function (AsarMode) {
|
||||
AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR";
|
||||
AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR";
|
||||
})(AsarMode || (exports.AsarMode = AsarMode = {}));
|
||||
// See: https://github.com/apple-opensource-mirror/llvmCore/blob/0c60489d96c87140db9a6a14c6e82b15f5e5d252/include/llvm/Object/MachOFormat.h#L108-L112
|
||||
const MACHO_MAGIC = new Set([
|
||||
// 32-bit Mach-O
|
||||
0xfeedface, 0xcefaedfe,
|
||||
// 64-bit Mach-O
|
||||
0xfeedfacf, 0xcffaedfe,
|
||||
]);
|
||||
const MACHO_UNIVERSAL_MAGIC = new Set([
|
||||
// universal
|
||||
0xcafebabe, 0xbebafeca,
|
||||
]);
|
||||
const detectAsarMode = async (appPath) => {
|
||||
(0, debug_1.d)('checking asar mode of', appPath);
|
||||
const asarPath = path_1.default.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
||||
if (!(await fs_extra_1.default.pathExists(asarPath))) {
|
||||
(0, debug_1.d)('determined no asar');
|
||||
return AsarMode.NO_ASAR;
|
||||
}
|
||||
(0, debug_1.d)('determined has asar');
|
||||
return AsarMode.HAS_ASAR;
|
||||
};
|
||||
exports.detectAsarMode = detectAsarMode;
|
||||
const generateAsarIntegrity = (asarPath) => {
|
||||
return {
|
||||
algorithm: 'SHA256',
|
||||
hash: crypto_1.default
|
||||
.createHash('SHA256')
|
||||
.update(asar_1.default.getRawHeader(asarPath).headerString)
|
||||
.digest('hex'),
|
||||
};
|
||||
};
|
||||
exports.generateAsarIntegrity = generateAsarIntegrity;
|
||||
function toRelativePath(file) {
|
||||
return file.replace(/^\//, '');
|
||||
}
|
||||
function isDirectory(a, file) {
|
||||
return Boolean('files' in asar_1.default.statFile(a, file));
|
||||
}
|
||||
function checkSingleArch(archive, file, allowList) {
|
||||
if (allowList === undefined || !(0, minimatch_1.minimatch)(file, allowList, { matchBase: true })) {
|
||||
throw new Error(`Detected unique file "${file}" in "${archive}" not covered by ` +
|
||||
`allowList rule: "${allowList}"`);
|
||||
}
|
||||
}
|
||||
const mergeASARs = async ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }) => {
|
||||
(0, debug_1.d)(`merging ${x64AsarPath} and ${arm64AsarPath}`);
|
||||
const x64Files = new Set(asar_1.default.listPackage(x64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
const arm64Files = new Set(asar_1.default.listPackage(arm64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
//
|
||||
// Build set of unpacked directories and files
|
||||
//
|
||||
const unpackedFiles = new Set();
|
||||
function buildUnpacked(a, fileList) {
|
||||
for (const file of fileList) {
|
||||
const stat = asar_1.default.statFile(a, file);
|
||||
if (!('unpacked' in stat) || !stat.unpacked) {
|
||||
continue;
|
||||
}
|
||||
if ('files' in stat) {
|
||||
continue;
|
||||
}
|
||||
unpackedFiles.add(file);
|
||||
}
|
||||
}
|
||||
buildUnpacked(x64AsarPath, x64Files);
|
||||
buildUnpacked(arm64AsarPath, arm64Files);
|
||||
//
|
||||
// Build list of files/directories unique to each asar
|
||||
//
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
checkSingleArch(x64AsarPath, file, singleArchFiles);
|
||||
}
|
||||
}
|
||||
const arm64Unique = [];
|
||||
for (const file of arm64Files) {
|
||||
if (!x64Files.has(file)) {
|
||||
checkSingleArch(arm64AsarPath, file, singleArchFiles);
|
||||
arm64Unique.push(file);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Find common bindings with different content
|
||||
//
|
||||
const commonBindings = [];
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
continue;
|
||||
}
|
||||
// Skip directories
|
||||
if (isDirectory(x64AsarPath, file)) {
|
||||
continue;
|
||||
}
|
||||
const x64Content = asar_1.default.extractFile(x64AsarPath, file);
|
||||
const arm64Content = asar_1.default.extractFile(arm64AsarPath, file);
|
||||
// Skip file if the same content
|
||||
if (x64Content.compare(arm64Content) === 0) {
|
||||
continue;
|
||||
}
|
||||
// Skip universal Mach-O files.
|
||||
if ((0, exports.isUniversalMachO)(x64Content)) {
|
||||
continue;
|
||||
}
|
||||
if (!MACHO_MAGIC.has(x64Content.readUInt32LE(0))) {
|
||||
throw new Error(`Can't reconcile two non-macho files ${file}`);
|
||||
}
|
||||
commonBindings.push(file);
|
||||
}
|
||||
//
|
||||
// Extract both
|
||||
//
|
||||
const x64Dir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'x64-'));
|
||||
const arm64Dir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'arm64-'));
|
||||
try {
|
||||
(0, debug_1.d)(`extracting ${x64AsarPath} to ${x64Dir}`);
|
||||
asar_1.default.extractAll(x64AsarPath, x64Dir);
|
||||
(0, debug_1.d)(`extracting ${arm64AsarPath} to ${arm64Dir}`);
|
||||
asar_1.default.extractAll(arm64AsarPath, arm64Dir);
|
||||
for (const file of arm64Unique) {
|
||||
const source = path_1.default.resolve(arm64Dir, file);
|
||||
const destination = path_1.default.resolve(x64Dir, file);
|
||||
if (isDirectory(arm64AsarPath, file)) {
|
||||
(0, debug_1.d)(`creating unique directory: ${file}`);
|
||||
await fs_extra_1.default.mkdirp(destination);
|
||||
continue;
|
||||
}
|
||||
(0, debug_1.d)(`xopying unique file: ${file}`);
|
||||
await fs_extra_1.default.mkdirp(path_1.default.dirname(destination));
|
||||
await fs_extra_1.default.copy(source, destination);
|
||||
}
|
||||
for (const binding of commonBindings) {
|
||||
const source = await fs_extra_1.default.realpath(path_1.default.resolve(arm64Dir, binding));
|
||||
const destination = await fs_extra_1.default.realpath(path_1.default.resolve(x64Dir, binding));
|
||||
(0, debug_1.d)(`merging binding: ${binding}`);
|
||||
(0, child_process_1.execFileSync)(LIPO, [source, destination, '-create', '-output', destination]);
|
||||
}
|
||||
(0, debug_1.d)(`creating archive at ${outputAsarPath}`);
|
||||
const resolvedUnpack = Array.from(unpackedFiles).map((file) => path_1.default.join(x64Dir, file));
|
||||
let unpack;
|
||||
if (resolvedUnpack.length > 1) {
|
||||
unpack = `{${resolvedUnpack.join(',')}}`;
|
||||
}
|
||||
else if (resolvedUnpack.length === 1) {
|
||||
unpack = resolvedUnpack[0];
|
||||
}
|
||||
await asar_1.default.createPackageWithOptions(x64Dir, outputAsarPath, {
|
||||
unpack,
|
||||
});
|
||||
(0, debug_1.d)('done merging');
|
||||
}
|
||||
finally {
|
||||
await Promise.all([fs_extra_1.default.remove(x64Dir), fs_extra_1.default.remove(arm64Dir)]);
|
||||
}
|
||||
};
|
||||
exports.mergeASARs = mergeASARs;
|
||||
const isUniversalMachO = (fileContent) => {
|
||||
return MACHO_UNIVERSAL_MAGIC.has(fileContent.readUInt32LE(0));
|
||||
};
|
||||
exports.isUniversalMachO = isUniversalMachO;
|
||||
//# sourceMappingURL=asar-utils.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-2
@@ -1,2 +0,0 @@
|
||||
import debug from 'debug';
|
||||
export declare const d: debug.Debugger;
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.d = void 0;
|
||||
const debug_1 = __importDefault(require("debug"));
|
||||
exports.d = (0, debug_1.default)('electron-universal');
|
||||
//# sourceMappingURL=debug.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAEb,QAAA,CAAC,GAAG,IAAA,eAAK,EAAC,oBAAoB,CAAC,CAAC"}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AppFileType {
|
||||
MACHO = 0,
|
||||
PLAIN = 1,
|
||||
INFO_PLIST = 2,
|
||||
SNAPSHOT = 3,
|
||||
APP_CODE = 4
|
||||
}
|
||||
export type AppFile = {
|
||||
relativePath: string;
|
||||
type: AppFileType;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
export declare const getAllAppFiles: (appPath: string) => Promise<AppFile[]>;
|
||||
export declare const readMachOHeader: (path: string) => Promise<Buffer>;
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
||||
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readMachOHeader = exports.getAllAppFiles = exports.AppFileType = void 0;
|
||||
const cross_spawn_promise_1 = require("@malept/cross-spawn-promise");
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const path = __importStar(require("path"));
|
||||
const node_stream_1 = require("node:stream");
|
||||
const MACHO_PREFIX = 'Mach-O ';
|
||||
var AppFileType;
|
||||
(function (AppFileType) {
|
||||
AppFileType[AppFileType["MACHO"] = 0] = "MACHO";
|
||||
AppFileType[AppFileType["PLAIN"] = 1] = "PLAIN";
|
||||
AppFileType[AppFileType["INFO_PLIST"] = 2] = "INFO_PLIST";
|
||||
AppFileType[AppFileType["SNAPSHOT"] = 3] = "SNAPSHOT";
|
||||
AppFileType[AppFileType["APP_CODE"] = 4] = "APP_CODE";
|
||||
})(AppFileType || (exports.AppFileType = AppFileType = {}));
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
const getAllAppFiles = async (appPath) => {
|
||||
const files = [];
|
||||
const visited = new Set();
|
||||
const traverse = async (p) => {
|
||||
p = await fs.realpath(p);
|
||||
if (visited.has(p))
|
||||
return;
|
||||
visited.add(p);
|
||||
const info = await fs.stat(p);
|
||||
if (info.isSymbolicLink())
|
||||
return;
|
||||
if (info.isFile()) {
|
||||
let fileType = AppFileType.PLAIN;
|
||||
var fileOutput = '';
|
||||
try {
|
||||
fileOutput = await (0, cross_spawn_promise_1.spawn)('file', ['--brief', '--no-pad', p]);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof cross_spawn_promise_1.ExitCodeError) {
|
||||
/* silently accept error codes from "file" */
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (p.endsWith('.asar')) {
|
||||
fileType = AppFileType.APP_CODE;
|
||||
}
|
||||
else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||
fileType = AppFileType.MACHO;
|
||||
}
|
||||
else if (p.endsWith('.bin')) {
|
||||
fileType = AppFileType.SNAPSHOT;
|
||||
}
|
||||
else if (path.basename(p) === 'Info.plist') {
|
||||
fileType = AppFileType.INFO_PLIST;
|
||||
}
|
||||
files.push({
|
||||
relativePath: path.relative(appPath, p),
|
||||
type: fileType,
|
||||
});
|
||||
}
|
||||
if (info.isDirectory()) {
|
||||
for (const child of await fs.readdir(p)) {
|
||||
await traverse(path.resolve(p, child));
|
||||
}
|
||||
}
|
||||
};
|
||||
await traverse(appPath);
|
||||
return files;
|
||||
};
|
||||
exports.getAllAppFiles = getAllAppFiles;
|
||||
const readMachOHeader = async (path) => {
|
||||
const chunks = [];
|
||||
// no need to read the entire file, we only need the first 4 bytes of the file to determine the header
|
||||
await node_stream_1.promises.pipeline(fs.createReadStream(path, { start: 0, end: 3 }), function (source) {
|
||||
return __asyncGenerator(this, arguments, function* () {
|
||||
var _a, e_1, _b, _c;
|
||||
try {
|
||||
for (var _d = true, source_1 = __asyncValues(source), source_1_1; source_1_1 = yield __await(source_1.next()), _a = source_1_1.done, !_a; _d = true) {
|
||||
_c = source_1_1.value;
|
||||
_d = false;
|
||||
const chunk = _c;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
});
|
||||
});
|
||||
return Buffer.concat(chunks);
|
||||
};
|
||||
exports.readMachOHeader = readMachOHeader;
|
||||
//# sourceMappingURL=file-utils.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file-utils.ts"],"names":[],"mappings":"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"}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Options to pass into the {@link makeUniversalApp} function.
|
||||
*
|
||||
* Requires absolute paths for input x64 and arm64 apps and an absolute path to the
|
||||
* output universal app.
|
||||
*/
|
||||
export type MakeUniversalOpts = {
|
||||
/**
|
||||
* Absolute file system path to the x64 version of your application (e.g. `/Foo/bar/MyApp_x64.app`).
|
||||
*/
|
||||
x64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path to the arm64 version of your application (e.g. `/Foo/bar/MyApp_arm64.app`).
|
||||
*/
|
||||
arm64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path you want the universal app to be written to (e.g. `/Foo/var/MyApp_universal.app`).
|
||||
*
|
||||
* If this file exists on disk already, it will be overwritten ONLY if {@link MakeUniversalOpts.force} is set to `true`.
|
||||
*/
|
||||
outAppPath: string;
|
||||
/**
|
||||
* Forcefully overwrite any existing files that are in the way of generating the universal application.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
force?: boolean;
|
||||
/**
|
||||
* Merge x64 and arm64 ASARs into one.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
mergeASARs?: boolean;
|
||||
/**
|
||||
* If {@link MakeUniversalOpts.mergeASARs} is enabled, this property provides a
|
||||
* {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of paths that are allowed to be present in one of the ASAR files, but not in the other.
|
||||
*
|
||||
*/
|
||||
singleArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of binaries that are expected to be the same x64 binary in both
|
||||
*
|
||||
* Use this if your application contains binaries that have already been merged into a universal file
|
||||
* using the `lipo` tool.
|
||||
*
|
||||
* @see Apple's {@link https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary | Building a universal macOS binary} documentation
|
||||
*
|
||||
*/
|
||||
x64ArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} pattern of `Info.plist`
|
||||
* paths that should not receive an injected `ElectronAsarIntegrity` value.
|
||||
*
|
||||
* Use this if your application contains another bundle that's already signed.
|
||||
*/
|
||||
infoPlistsToIgnore?: string;
|
||||
};
|
||||
export declare const makeUniversalApp: (opts: MakeUniversalOpts) => Promise<void>;
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.makeUniversalApp = void 0;
|
||||
const asar = __importStar(require("@electron/asar"));
|
||||
const cross_spawn_promise_1 = require("@malept/cross-spawn-promise");
|
||||
const dircompare = __importStar(require("dir-compare"));
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const minimatch_1 = require("minimatch");
|
||||
const os = __importStar(require("os"));
|
||||
const path = __importStar(require("path"));
|
||||
const plist = __importStar(require("plist"));
|
||||
const asar_utils_1 = require("./asar-utils");
|
||||
const file_utils_1 = require("./file-utils");
|
||||
const sha_1 = require("./sha");
|
||||
const debug_1 = require("./debug");
|
||||
const integrity_1 = require("./integrity");
|
||||
const dupedFiles = (files) => files.filter((f) => f.type !== file_utils_1.AppFileType.SNAPSHOT && f.type !== file_utils_1.AppFileType.APP_CODE);
|
||||
const makeUniversalApp = async (opts) => {
|
||||
(0, debug_1.d)('making a universal app with options', opts);
|
||||
if (process.platform !== 'darwin')
|
||||
throw new Error('@electron/universal is only supported on darwin platforms');
|
||||
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
|
||||
throw new Error('Expected opts.x64AppPath to be an absolute path but it was not');
|
||||
if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath))
|
||||
throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not');
|
||||
if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath))
|
||||
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
||||
if (await fs.pathExists(opts.outAppPath)) {
|
||||
(0, debug_1.d)('output path exists already');
|
||||
if (!opts.force) {
|
||||
throw new Error(`The out path "${opts.outAppPath}" already exists and force is not set to true`);
|
||||
}
|
||||
else {
|
||||
(0, debug_1.d)('overwriting existing application because force == true');
|
||||
await fs.remove(opts.outAppPath);
|
||||
}
|
||||
}
|
||||
const x64AsarMode = await (0, asar_utils_1.detectAsarMode)(opts.x64AppPath);
|
||||
const arm64AsarMode = await (0, asar_utils_1.detectAsarMode)(opts.arm64AppPath);
|
||||
(0, debug_1.d)('detected x64AsarMode =', x64AsarMode);
|
||||
(0, debug_1.d)('detected arm64AsarMode =', arm64AsarMode);
|
||||
if (x64AsarMode !== arm64AsarMode)
|
||||
throw new Error('Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)');
|
||||
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||
(0, debug_1.d)('building universal app in', tmpDir);
|
||||
try {
|
||||
(0, debug_1.d)('copying x64 app as starter template');
|
||||
const tmpApp = path.resolve(tmpDir, 'Tmp.app');
|
||||
await (0, cross_spawn_promise_1.spawn)('cp', ['-R', opts.x64AppPath, tmpApp]);
|
||||
const uniqueToX64 = [];
|
||||
const uniqueToArm64 = [];
|
||||
const x64Files = await (0, file_utils_1.getAllAppFiles)(await fs.realpath(tmpApp));
|
||||
const arm64Files = await (0, file_utils_1.getAllAppFiles)(await fs.realpath(opts.arm64AppPath));
|
||||
for (const file of dupedFiles(x64Files)) {
|
||||
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToX64.push(file.relativePath);
|
||||
}
|
||||
for (const file of dupedFiles(arm64Files)) {
|
||||
if (!x64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToArm64.push(file.relativePath);
|
||||
}
|
||||
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
||||
(0, debug_1.d)('some files were not in both builds, aborting');
|
||||
console.error({
|
||||
uniqueToX64,
|
||||
uniqueToArm64,
|
||||
});
|
||||
throw new Error('While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds');
|
||||
}
|
||||
for (const file of x64Files.filter((f) => f.type === file_utils_1.AppFileType.PLAIN)) {
|
||||
const x64Sha = await (0, sha_1.sha)(path.resolve(opts.x64AppPath, file.relativePath));
|
||||
const arm64Sha = await (0, sha_1.sha)(path.resolve(opts.arm64AppPath, file.relativePath));
|
||||
if (x64Sha !== arm64Sha) {
|
||||
(0, debug_1.d)('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`);
|
||||
// The MainMenu.nib files generated by Xcode13 are deterministic in effect but not deterministic in generated sequence
|
||||
if (path.basename(path.dirname(file.relativePath)) === 'MainMenu.nib') {
|
||||
// The mismatch here is OK so we just move on to the next one
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`);
|
||||
}
|
||||
}
|
||||
const knownMergedMachOFiles = new Set();
|
||||
for (const machOFile of x64Files.filter((f) => f.type === file_utils_1.AppFileType.MACHO)) {
|
||||
const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath));
|
||||
const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if ((0, asar_utils_1.isUniversalMachO)(await (0, file_utils_1.readMachOHeader)(first)) &&
|
||||
(0, asar_utils_1.isUniversalMachO)(await (0, file_utils_1.readMachOHeader)(second))) {
|
||||
(0, debug_1.d)(machOFile.relativePath, `is already universal across builds, skipping lipo`);
|
||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||
continue;
|
||||
}
|
||||
const x64Sha = await (0, sha_1.sha)(path.resolve(opts.x64AppPath, machOFile.relativePath));
|
||||
const arm64Sha = await (0, sha_1.sha)(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if (x64Sha === arm64Sha) {
|
||||
if (opts.x64ArchFiles === undefined ||
|
||||
!(0, minimatch_1.minimatch)(machOFile.relativePath, opts.x64ArchFiles, { matchBase: true })) {
|
||||
throw new Error(`Detected file "${machOFile.relativePath}" that's the same in both x64 and arm64 builds and not covered by the ` +
|
||||
`x64ArchFiles rule: "${opts.x64ArchFiles}"`);
|
||||
}
|
||||
(0, debug_1.d)('SHA for Mach-O file', machOFile.relativePath, `matches across builds ${x64Sha}===${arm64Sha}, skipping lipo`);
|
||||
continue;
|
||||
}
|
||||
(0, debug_1.d)('joining two MachO files with lipo', {
|
||||
first,
|
||||
second,
|
||||
});
|
||||
await (0, cross_spawn_promise_1.spawn)('lipo', [
|
||||
first,
|
||||
second,
|
||||
'-create',
|
||||
'-output',
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||
]);
|
||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||
}
|
||||
/**
|
||||
* If we don't have an ASAR we need to check if the two "app" folders are identical, if
|
||||
* they are then we can just leave one there and call it a day. If the app folders for x64
|
||||
* and arm64 are different though we need to rename each folder and create a new fake "app"
|
||||
* entrypoint to dynamically load the correct app folder
|
||||
*/
|
||||
if (x64AsarMode === asar_utils_1.AsarMode.NO_ASAR) {
|
||||
(0, debug_1.d)('checking if the x64 and arm64 app folders are identical');
|
||||
const comparison = await dircompare.compare(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), { compareSize: true, compareContent: true });
|
||||
const differences = comparison.diffSet.filter((difference) => difference.state !== 'equal');
|
||||
(0, debug_1.d)(`Found ${differences.length} difference(s) between the x64 and arm64 folders`);
|
||||
const nonMergedDifferences = differences.filter((difference) => !difference.name1 ||
|
||||
!knownMergedMachOFiles.has(path.join('Contents', 'Resources', 'app', difference.relativePath, difference.name1)));
|
||||
(0, debug_1.d)(`After discluding MachO files merged with lipo ${nonMergedDifferences.length} remain.`);
|
||||
if (nonMergedDifferences.length > 0) {
|
||||
(0, debug_1.d)('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'));
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'));
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
}
|
||||
else {
|
||||
(0, debug_1.d)('x64 and arm64 app folders are the same');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If we have an ASAR we just need to check if the two "app.asar" files have the same hash,
|
||||
* if they are, same as above, we can leave one there and call it a day. If they're different
|
||||
* we have to make a dynamic entrypoint. There is an assumption made here that every file in
|
||||
* app.asar.unpacked is a native node module. This assumption _may_ not be true so we should
|
||||
* look at codifying that assumption as actual logic.
|
||||
*/
|
||||
// FIXME: Codify the assumption that app.asar.unpacked only contains native modules
|
||||
if (x64AsarMode === asar_utils_1.AsarMode.HAS_ASAR && opts.mergeASARs) {
|
||||
(0, debug_1.d)('merging x64 and arm64 asars');
|
||||
const output = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await (0, asar_utils_1.mergeASARs)({
|
||||
x64AsarPath: path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||
arm64AsarPath: path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
||||
outputAsarPath: output,
|
||||
singleArchFiles: opts.singleArchFiles,
|
||||
});
|
||||
}
|
||||
else if (x64AsarMode === asar_utils_1.AsarMode.HAS_ASAR) {
|
||||
(0, debug_1.d)('checking if the x64 and arm64 asars are identical');
|
||||
const x64AsarSha = await (0, sha_1.sha)(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
const arm64AsarSha = await (0, sha_1.sha)(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'));
|
||||
if (x64AsarSha !== arm64AsarSha) {
|
||||
(0, debug_1.d)('x64 and arm64 asars are different');
|
||||
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
|
||||
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(x64Unpacked)) {
|
||||
await fs.move(x64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'));
|
||||
}
|
||||
const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), arm64AsarPath);
|
||||
const arm64Unpacked = path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(arm64Unpacked)) {
|
||||
await fs.copy(arm64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'));
|
||||
}
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = JSON.parse((await asar.extractFile(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 'package.json')).toString('utf8'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await asar.createPackage(entryAsar, asarPath);
|
||||
}
|
||||
else {
|
||||
(0, debug_1.d)('x64 and arm64 asars are the same');
|
||||
}
|
||||
}
|
||||
const generatedIntegrity = await (0, integrity_1.computeIntegrityData)(path.join(tmpApp, 'Contents'));
|
||||
const plistFiles = x64Files.filter((f) => f.type === file_utils_1.AppFileType.INFO_PLIST);
|
||||
for (const plistFile of plistFiles) {
|
||||
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
|
||||
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);
|
||||
const _a = plist.parse(await fs.readFile(x64PlistPath, 'utf8')), { ElectronAsarIntegrity: x64Integrity } = _a, x64Plist = __rest(_a, ["ElectronAsarIntegrity"]);
|
||||
const _b = plist.parse(await fs.readFile(arm64PlistPath, 'utf8')), { ElectronAsarIntegrity: arm64Integrity } = _b, arm64Plist = __rest(_b, ["ElectronAsarIntegrity"]);
|
||||
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
|
||||
throw new Error(`Expected all Info.plist files to be identical when ignoring integrity when creating a universal build but "${plistFile.relativePath}" was not`);
|
||||
}
|
||||
const injectAsarIntegrity = !opts.infoPlistsToIgnore ||
|
||||
(0, minimatch_1.minimatch)(plistFile.relativePath, opts.infoPlistsToIgnore, { matchBase: true });
|
||||
const mergedPlist = injectAsarIntegrity
|
||||
? Object.assign(Object.assign({}, x64Plist), { ElectronAsarIntegrity: generatedIntegrity }) : Object.assign({}, x64Plist);
|
||||
await fs.writeFile(path.resolve(tmpApp, plistFile.relativePath), plist.build(mergedPlist));
|
||||
}
|
||||
for (const snapshotsFile of arm64Files.filter((f) => f.type === file_utils_1.AppFileType.SNAPSHOT)) {
|
||||
(0, debug_1.d)('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath));
|
||||
}
|
||||
(0, debug_1.d)('moving final universal app to target destination');
|
||||
await fs.mkdirp(path.dirname(opts.outAppPath));
|
||||
await (0, cross_spawn_promise_1.spawn)('mv', [tmpApp, opts.outAppPath]);
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
};
|
||||
exports.makeUniversalApp = makeUniversalApp;
|
||||
//# sourceMappingURL=index.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-8
@@ -1,8 +0,0 @@
|
||||
export interface HeaderHash {
|
||||
algorithm: 'SHA256';
|
||||
hash: string;
|
||||
}
|
||||
export interface AsarIntegrity {
|
||||
[key: string]: HeaderHash;
|
||||
}
|
||||
export declare function computeIntegrityData(contentsPath: string): Promise<AsarIntegrity>;
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.computeIntegrityData = void 0;
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const file_utils_1 = require("./file-utils");
|
||||
const asar_utils_1 = require("./asar-utils");
|
||||
async function computeIntegrityData(contentsPath) {
|
||||
const root = await fs.realpath(contentsPath);
|
||||
const resourcesRelativePath = 'Resources';
|
||||
const resourcesPath = path_1.default.resolve(root, resourcesRelativePath);
|
||||
const resources = await (0, file_utils_1.getAllAppFiles)(resourcesPath);
|
||||
const resourceAsars = resources
|
||||
.filter((file) => file.type === file_utils_1.AppFileType.APP_CODE)
|
||||
.reduce((prev, file) => (Object.assign(Object.assign({}, prev), { [path_1.default.join(resourcesRelativePath, file.relativePath)]: path_1.default.join(resourcesPath, file.relativePath) })), {});
|
||||
// sort to produce constant result
|
||||
const allAsars = Object.entries(resourceAsars).sort(([name1], [name2]) => name1.localeCompare(name2));
|
||||
const hashes = await Promise.all(allAsars.map(async ([, from]) => (0, asar_utils_1.generateAsarIntegrity)(from)));
|
||||
const asarIntegrity = {};
|
||||
for (let i = 0; i < allAsars.length; i++) {
|
||||
const [asar] = allAsars[i];
|
||||
asarIntegrity[asar] = hashes[i];
|
||||
}
|
||||
return asarIntegrity;
|
||||
}
|
||||
exports.computeIntegrityData = computeIntegrityData;
|
||||
//# sourceMappingURL=integrity.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"integrity.js","sourceRoot":"","sources":["../../src/integrity.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAC/B,gDAAwB;AACxB,6CAA2D;AAE3D,6CAAqD;AAe9C,KAAK,UAAU,oBAAoB,CAAC,YAAoB;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAE7C,MAAM,qBAAqB,GAAG,WAAW,CAAC;IAC1C,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAEhE,MAAM,SAAS,GAAG,MAAM,IAAA,2BAAc,EAAC,aAAa,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,SAAS;SAC5B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,wBAAW,CAAC,QAAQ,CAAC;SACpD,MAAM,CACL,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,iCACX,IAAI,KACP,CAAC,cAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,cAAI,CAAC,IAAI,CAC9D,aAAa,EACb,IAAI,CAAC,YAAY,CAClB,IACD,EACF,EAAE,CACH,CAAC;IAEJ,kCAAkC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CACvE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAC3B,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAA,kCAAqB,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,aAAa,GAAkB,EAAE,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACxC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3B,aAAa,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;KACjC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AA/BD,oDA+BC"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export declare const sha: (filePath: string) => Promise<any>;
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sha = void 0;
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const crypto = __importStar(require("crypto"));
|
||||
const promises_1 = require("stream/promises");
|
||||
const debug_1 = require("./debug");
|
||||
const sha = async (filePath) => {
|
||||
(0, debug_1.d)('hashing', filePath);
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.setEncoding('hex');
|
||||
await (0, promises_1.pipeline)(fs.createReadStream(filePath), hash);
|
||||
return hash.read();
|
||||
};
|
||||
exports.sha = sha;
|
||||
//# sourceMappingURL=sha.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"sha.js","sourceRoot":"","sources":["../../src/sha.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAC/B,+CAAiC;AACjC,8CAA2C;AAE3C,mCAA4B;AAErB,MAAM,GAAG,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5C,IAAA,SAAC,EAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,IAAA,mBAAQ,EAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC;AANW,QAAA,GAAG,OAMd"}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AsarMode {
|
||||
NO_ASAR = 0,
|
||||
HAS_ASAR = 1
|
||||
}
|
||||
export type MergeASARsOptions = {
|
||||
x64AsarPath: string;
|
||||
arm64AsarPath: string;
|
||||
outputAsarPath: string;
|
||||
singleArchFiles?: string;
|
||||
};
|
||||
export declare const detectAsarMode: (appPath: string) => Promise<AsarMode>;
|
||||
export declare const generateAsarIntegrity: (asarPath: string) => {
|
||||
algorithm: "SHA256";
|
||||
hash: string;
|
||||
};
|
||||
export declare const mergeASARs: ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }: MergeASARsOptions) => Promise<void>;
|
||||
export declare const isUniversalMachO: (fileContent: Buffer) => boolean;
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
import asar from '@electron/asar';
|
||||
import { execFileSync } from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { minimatch } from 'minimatch';
|
||||
import os from 'os';
|
||||
import { d } from './debug';
|
||||
const LIPO = 'lipo';
|
||||
export var AsarMode;
|
||||
(function (AsarMode) {
|
||||
AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR";
|
||||
AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR";
|
||||
})(AsarMode || (AsarMode = {}));
|
||||
// See: https://github.com/apple-opensource-mirror/llvmCore/blob/0c60489d96c87140db9a6a14c6e82b15f5e5d252/include/llvm/Object/MachOFormat.h#L108-L112
|
||||
const MACHO_MAGIC = new Set([
|
||||
// 32-bit Mach-O
|
||||
0xfeedface, 0xcefaedfe,
|
||||
// 64-bit Mach-O
|
||||
0xfeedfacf, 0xcffaedfe,
|
||||
]);
|
||||
const MACHO_UNIVERSAL_MAGIC = new Set([
|
||||
// universal
|
||||
0xcafebabe, 0xbebafeca,
|
||||
]);
|
||||
export const detectAsarMode = async (appPath) => {
|
||||
d('checking asar mode of', appPath);
|
||||
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
||||
if (!(await fs.pathExists(asarPath))) {
|
||||
d('determined no asar');
|
||||
return AsarMode.NO_ASAR;
|
||||
}
|
||||
d('determined has asar');
|
||||
return AsarMode.HAS_ASAR;
|
||||
};
|
||||
export const generateAsarIntegrity = (asarPath) => {
|
||||
return {
|
||||
algorithm: 'SHA256',
|
||||
hash: crypto
|
||||
.createHash('SHA256')
|
||||
.update(asar.getRawHeader(asarPath).headerString)
|
||||
.digest('hex'),
|
||||
};
|
||||
};
|
||||
function toRelativePath(file) {
|
||||
return file.replace(/^\//, '');
|
||||
}
|
||||
function isDirectory(a, file) {
|
||||
return Boolean('files' in asar.statFile(a, file));
|
||||
}
|
||||
function checkSingleArch(archive, file, allowList) {
|
||||
if (allowList === undefined || !minimatch(file, allowList, { matchBase: true })) {
|
||||
throw new Error(`Detected unique file "${file}" in "${archive}" not covered by ` +
|
||||
`allowList rule: "${allowList}"`);
|
||||
}
|
||||
}
|
||||
export const mergeASARs = async ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }) => {
|
||||
d(`merging ${x64AsarPath} and ${arm64AsarPath}`);
|
||||
const x64Files = new Set(asar.listPackage(x64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
const arm64Files = new Set(asar.listPackage(arm64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
//
|
||||
// Build set of unpacked directories and files
|
||||
//
|
||||
const unpackedFiles = new Set();
|
||||
function buildUnpacked(a, fileList) {
|
||||
for (const file of fileList) {
|
||||
const stat = asar.statFile(a, file);
|
||||
if (!('unpacked' in stat) || !stat.unpacked) {
|
||||
continue;
|
||||
}
|
||||
if ('files' in stat) {
|
||||
continue;
|
||||
}
|
||||
unpackedFiles.add(file);
|
||||
}
|
||||
}
|
||||
buildUnpacked(x64AsarPath, x64Files);
|
||||
buildUnpacked(arm64AsarPath, arm64Files);
|
||||
//
|
||||
// Build list of files/directories unique to each asar
|
||||
//
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
checkSingleArch(x64AsarPath, file, singleArchFiles);
|
||||
}
|
||||
}
|
||||
const arm64Unique = [];
|
||||
for (const file of arm64Files) {
|
||||
if (!x64Files.has(file)) {
|
||||
checkSingleArch(arm64AsarPath, file, singleArchFiles);
|
||||
arm64Unique.push(file);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Find common bindings with different content
|
||||
//
|
||||
const commonBindings = [];
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
continue;
|
||||
}
|
||||
// Skip directories
|
||||
if (isDirectory(x64AsarPath, file)) {
|
||||
continue;
|
||||
}
|
||||
const x64Content = asar.extractFile(x64AsarPath, file);
|
||||
const arm64Content = asar.extractFile(arm64AsarPath, file);
|
||||
// Skip file if the same content
|
||||
if (x64Content.compare(arm64Content) === 0) {
|
||||
continue;
|
||||
}
|
||||
// Skip universal Mach-O files.
|
||||
if (isUniversalMachO(x64Content)) {
|
||||
continue;
|
||||
}
|
||||
if (!MACHO_MAGIC.has(x64Content.readUInt32LE(0))) {
|
||||
throw new Error(`Can't reconcile two non-macho files ${file}`);
|
||||
}
|
||||
commonBindings.push(file);
|
||||
}
|
||||
//
|
||||
// Extract both
|
||||
//
|
||||
const x64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'x64-'));
|
||||
const arm64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'arm64-'));
|
||||
try {
|
||||
d(`extracting ${x64AsarPath} to ${x64Dir}`);
|
||||
asar.extractAll(x64AsarPath, x64Dir);
|
||||
d(`extracting ${arm64AsarPath} to ${arm64Dir}`);
|
||||
asar.extractAll(arm64AsarPath, arm64Dir);
|
||||
for (const file of arm64Unique) {
|
||||
const source = path.resolve(arm64Dir, file);
|
||||
const destination = path.resolve(x64Dir, file);
|
||||
if (isDirectory(arm64AsarPath, file)) {
|
||||
d(`creating unique directory: ${file}`);
|
||||
await fs.mkdirp(destination);
|
||||
continue;
|
||||
}
|
||||
d(`xopying unique file: ${file}`);
|
||||
await fs.mkdirp(path.dirname(destination));
|
||||
await fs.copy(source, destination);
|
||||
}
|
||||
for (const binding of commonBindings) {
|
||||
const source = await fs.realpath(path.resolve(arm64Dir, binding));
|
||||
const destination = await fs.realpath(path.resolve(x64Dir, binding));
|
||||
d(`merging binding: ${binding}`);
|
||||
execFileSync(LIPO, [source, destination, '-create', '-output', destination]);
|
||||
}
|
||||
d(`creating archive at ${outputAsarPath}`);
|
||||
const resolvedUnpack = Array.from(unpackedFiles).map((file) => path.join(x64Dir, file));
|
||||
let unpack;
|
||||
if (resolvedUnpack.length > 1) {
|
||||
unpack = `{${resolvedUnpack.join(',')}}`;
|
||||
}
|
||||
else if (resolvedUnpack.length === 1) {
|
||||
unpack = resolvedUnpack[0];
|
||||
}
|
||||
await asar.createPackageWithOptions(x64Dir, outputAsarPath, {
|
||||
unpack,
|
||||
});
|
||||
d('done merging');
|
||||
}
|
||||
finally {
|
||||
await Promise.all([fs.remove(x64Dir), fs.remove(arm64Dir)]);
|
||||
}
|
||||
};
|
||||
export const isUniversalMachO = (fileContent) => {
|
||||
return MACHO_UNIVERSAL_MAGIC.has(fileContent.readUInt32LE(0));
|
||||
};
|
||||
//# sourceMappingURL=asar-utils.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-2
@@ -1,2 +0,0 @@
|
||||
import debug from 'debug';
|
||||
export declare const d: debug.Debugger;
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
import debug from 'debug';
|
||||
export const d = debug('electron-universal');
|
||||
//# sourceMappingURL=debug.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC"}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AppFileType {
|
||||
MACHO = 0,
|
||||
PLAIN = 1,
|
||||
INFO_PLIST = 2,
|
||||
SNAPSHOT = 3,
|
||||
APP_CODE = 4
|
||||
}
|
||||
export type AppFile = {
|
||||
relativePath: string;
|
||||
type: AppFileType;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
export declare const getAllAppFiles: (appPath: string) => Promise<AppFile[]>;
|
||||
export declare const readMachOHeader: (path: string) => Promise<Buffer>;
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
||||
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};
|
||||
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { promises as stream } from 'node:stream';
|
||||
const MACHO_PREFIX = 'Mach-O ';
|
||||
export var AppFileType;
|
||||
(function (AppFileType) {
|
||||
AppFileType[AppFileType["MACHO"] = 0] = "MACHO";
|
||||
AppFileType[AppFileType["PLAIN"] = 1] = "PLAIN";
|
||||
AppFileType[AppFileType["INFO_PLIST"] = 2] = "INFO_PLIST";
|
||||
AppFileType[AppFileType["SNAPSHOT"] = 3] = "SNAPSHOT";
|
||||
AppFileType[AppFileType["APP_CODE"] = 4] = "APP_CODE";
|
||||
})(AppFileType || (AppFileType = {}));
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
export const getAllAppFiles = async (appPath) => {
|
||||
const files = [];
|
||||
const visited = new Set();
|
||||
const traverse = async (p) => {
|
||||
p = await fs.realpath(p);
|
||||
if (visited.has(p))
|
||||
return;
|
||||
visited.add(p);
|
||||
const info = await fs.stat(p);
|
||||
if (info.isSymbolicLink())
|
||||
return;
|
||||
if (info.isFile()) {
|
||||
let fileType = AppFileType.PLAIN;
|
||||
var fileOutput = '';
|
||||
try {
|
||||
fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof ExitCodeError) {
|
||||
/* silently accept error codes from "file" */
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (p.endsWith('.asar')) {
|
||||
fileType = AppFileType.APP_CODE;
|
||||
}
|
||||
else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||
fileType = AppFileType.MACHO;
|
||||
}
|
||||
else if (p.endsWith('.bin')) {
|
||||
fileType = AppFileType.SNAPSHOT;
|
||||
}
|
||||
else if (path.basename(p) === 'Info.plist') {
|
||||
fileType = AppFileType.INFO_PLIST;
|
||||
}
|
||||
files.push({
|
||||
relativePath: path.relative(appPath, p),
|
||||
type: fileType,
|
||||
});
|
||||
}
|
||||
if (info.isDirectory()) {
|
||||
for (const child of await fs.readdir(p)) {
|
||||
await traverse(path.resolve(p, child));
|
||||
}
|
||||
}
|
||||
};
|
||||
await traverse(appPath);
|
||||
return files;
|
||||
};
|
||||
export const readMachOHeader = async (path) => {
|
||||
const chunks = [];
|
||||
// no need to read the entire file, we only need the first 4 bytes of the file to determine the header
|
||||
await stream.pipeline(fs.createReadStream(path, { start: 0, end: 3 }), function (source) {
|
||||
return __asyncGenerator(this, arguments, function* () {
|
||||
var _a, e_1, _b, _c;
|
||||
try {
|
||||
for (var _d = true, source_1 = __asyncValues(source), source_1_1; source_1_1 = yield __await(source_1.next()), _a = source_1_1.done, !_a; _d = true) {
|
||||
_c = source_1_1.value;
|
||||
_d = false;
|
||||
const chunk = _c;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
});
|
||||
});
|
||||
return Buffer.concat(chunks);
|
||||
};
|
||||
//# sourceMappingURL=file-utils.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file-utils.ts"],"names":[],"mappings":"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"}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Options to pass into the {@link makeUniversalApp} function.
|
||||
*
|
||||
* Requires absolute paths for input x64 and arm64 apps and an absolute path to the
|
||||
* output universal app.
|
||||
*/
|
||||
export type MakeUniversalOpts = {
|
||||
/**
|
||||
* Absolute file system path to the x64 version of your application (e.g. `/Foo/bar/MyApp_x64.app`).
|
||||
*/
|
||||
x64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path to the arm64 version of your application (e.g. `/Foo/bar/MyApp_arm64.app`).
|
||||
*/
|
||||
arm64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path you want the universal app to be written to (e.g. `/Foo/var/MyApp_universal.app`).
|
||||
*
|
||||
* If this file exists on disk already, it will be overwritten ONLY if {@link MakeUniversalOpts.force} is set to `true`.
|
||||
*/
|
||||
outAppPath: string;
|
||||
/**
|
||||
* Forcefully overwrite any existing files that are in the way of generating the universal application.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
force?: boolean;
|
||||
/**
|
||||
* Merge x64 and arm64 ASARs into one.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
mergeASARs?: boolean;
|
||||
/**
|
||||
* If {@link MakeUniversalOpts.mergeASARs} is enabled, this property provides a
|
||||
* {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of paths that are allowed to be present in one of the ASAR files, but not in the other.
|
||||
*
|
||||
*/
|
||||
singleArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of binaries that are expected to be the same x64 binary in both
|
||||
*
|
||||
* Use this if your application contains binaries that have already been merged into a universal file
|
||||
* using the `lipo` tool.
|
||||
*
|
||||
* @see Apple's {@link https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary | Building a universal macOS binary} documentation
|
||||
*
|
||||
*/
|
||||
x64ArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} pattern of `Info.plist`
|
||||
* paths that should not receive an injected `ElectronAsarIntegrity` value.
|
||||
*
|
||||
* Use this if your application contains another bundle that's already signed.
|
||||
*/
|
||||
infoPlistsToIgnore?: string;
|
||||
};
|
||||
export declare const makeUniversalApp: (opts: MakeUniversalOpts) => Promise<void>;
|
||||
-235
@@ -1,235 +0,0 @@
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
import * as asar from '@electron/asar';
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
import * as dircompare from 'dir-compare';
|
||||
import * as fs from 'fs-extra';
|
||||
import { minimatch } from 'minimatch';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as plist from 'plist';
|
||||
import { AsarMode, detectAsarMode, isUniversalMachO, mergeASARs } from './asar-utils';
|
||||
import { AppFileType, getAllAppFiles, readMachOHeader } from './file-utils';
|
||||
import { sha } from './sha';
|
||||
import { d } from './debug';
|
||||
import { computeIntegrityData } from './integrity';
|
||||
const dupedFiles = (files) => files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
|
||||
export const makeUniversalApp = async (opts) => {
|
||||
d('making a universal app with options', opts);
|
||||
if (process.platform !== 'darwin')
|
||||
throw new Error('@electron/universal is only supported on darwin platforms');
|
||||
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
|
||||
throw new Error('Expected opts.x64AppPath to be an absolute path but it was not');
|
||||
if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath))
|
||||
throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not');
|
||||
if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath))
|
||||
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
||||
if (await fs.pathExists(opts.outAppPath)) {
|
||||
d('output path exists already');
|
||||
if (!opts.force) {
|
||||
throw new Error(`The out path "${opts.outAppPath}" already exists and force is not set to true`);
|
||||
}
|
||||
else {
|
||||
d('overwriting existing application because force == true');
|
||||
await fs.remove(opts.outAppPath);
|
||||
}
|
||||
}
|
||||
const x64AsarMode = await detectAsarMode(opts.x64AppPath);
|
||||
const arm64AsarMode = await detectAsarMode(opts.arm64AppPath);
|
||||
d('detected x64AsarMode =', x64AsarMode);
|
||||
d('detected arm64AsarMode =', arm64AsarMode);
|
||||
if (x64AsarMode !== arm64AsarMode)
|
||||
throw new Error('Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)');
|
||||
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||
d('building universal app in', tmpDir);
|
||||
try {
|
||||
d('copying x64 app as starter template');
|
||||
const tmpApp = path.resolve(tmpDir, 'Tmp.app');
|
||||
await spawn('cp', ['-R', opts.x64AppPath, tmpApp]);
|
||||
const uniqueToX64 = [];
|
||||
const uniqueToArm64 = [];
|
||||
const x64Files = await getAllAppFiles(await fs.realpath(tmpApp));
|
||||
const arm64Files = await getAllAppFiles(await fs.realpath(opts.arm64AppPath));
|
||||
for (const file of dupedFiles(x64Files)) {
|
||||
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToX64.push(file.relativePath);
|
||||
}
|
||||
for (const file of dupedFiles(arm64Files)) {
|
||||
if (!x64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToArm64.push(file.relativePath);
|
||||
}
|
||||
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
||||
d('some files were not in both builds, aborting');
|
||||
console.error({
|
||||
uniqueToX64,
|
||||
uniqueToArm64,
|
||||
});
|
||||
throw new Error('While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds');
|
||||
}
|
||||
for (const file of x64Files.filter((f) => f.type === AppFileType.PLAIN)) {
|
||||
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
|
||||
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
|
||||
if (x64Sha !== arm64Sha) {
|
||||
d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`);
|
||||
// The MainMenu.nib files generated by Xcode13 are deterministic in effect but not deterministic in generated sequence
|
||||
if (path.basename(path.dirname(file.relativePath)) === 'MainMenu.nib') {
|
||||
// The mismatch here is OK so we just move on to the next one
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`);
|
||||
}
|
||||
}
|
||||
const knownMergedMachOFiles = new Set();
|
||||
for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) {
|
||||
const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath));
|
||||
const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if (isUniversalMachO(await readMachOHeader(first)) &&
|
||||
isUniversalMachO(await readMachOHeader(second))) {
|
||||
d(machOFile.relativePath, `is already universal across builds, skipping lipo`);
|
||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||
continue;
|
||||
}
|
||||
const x64Sha = await sha(path.resolve(opts.x64AppPath, machOFile.relativePath));
|
||||
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if (x64Sha === arm64Sha) {
|
||||
if (opts.x64ArchFiles === undefined ||
|
||||
!minimatch(machOFile.relativePath, opts.x64ArchFiles, { matchBase: true })) {
|
||||
throw new Error(`Detected file "${machOFile.relativePath}" that's the same in both x64 and arm64 builds and not covered by the ` +
|
||||
`x64ArchFiles rule: "${opts.x64ArchFiles}"`);
|
||||
}
|
||||
d('SHA for Mach-O file', machOFile.relativePath, `matches across builds ${x64Sha}===${arm64Sha}, skipping lipo`);
|
||||
continue;
|
||||
}
|
||||
d('joining two MachO files with lipo', {
|
||||
first,
|
||||
second,
|
||||
});
|
||||
await spawn('lipo', [
|
||||
first,
|
||||
second,
|
||||
'-create',
|
||||
'-output',
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||
]);
|
||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||
}
|
||||
/**
|
||||
* If we don't have an ASAR we need to check if the two "app" folders are identical, if
|
||||
* they are then we can just leave one there and call it a day. If the app folders for x64
|
||||
* and arm64 are different though we need to rename each folder and create a new fake "app"
|
||||
* entrypoint to dynamically load the correct app folder
|
||||
*/
|
||||
if (x64AsarMode === AsarMode.NO_ASAR) {
|
||||
d('checking if the x64 and arm64 app folders are identical');
|
||||
const comparison = await dircompare.compare(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), { compareSize: true, compareContent: true });
|
||||
const differences = comparison.diffSet.filter((difference) => difference.state !== 'equal');
|
||||
d(`Found ${differences.length} difference(s) between the x64 and arm64 folders`);
|
||||
const nonMergedDifferences = differences.filter((difference) => !difference.name1 ||
|
||||
!knownMergedMachOFiles.has(path.join('Contents', 'Resources', 'app', difference.relativePath, difference.name1)));
|
||||
d(`After discluding MachO files merged with lipo ${nonMergedDifferences.length} remain.`);
|
||||
if (nonMergedDifferences.length > 0) {
|
||||
d('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'));
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'));
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
}
|
||||
else {
|
||||
d('x64 and arm64 app folders are the same');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If we have an ASAR we just need to check if the two "app.asar" files have the same hash,
|
||||
* if they are, same as above, we can leave one there and call it a day. If they're different
|
||||
* we have to make a dynamic entrypoint. There is an assumption made here that every file in
|
||||
* app.asar.unpacked is a native node module. This assumption _may_ not be true so we should
|
||||
* look at codifying that assumption as actual logic.
|
||||
*/
|
||||
// FIXME: Codify the assumption that app.asar.unpacked only contains native modules
|
||||
if (x64AsarMode === AsarMode.HAS_ASAR && opts.mergeASARs) {
|
||||
d('merging x64 and arm64 asars');
|
||||
const output = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await mergeASARs({
|
||||
x64AsarPath: path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||
arm64AsarPath: path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
||||
outputAsarPath: output,
|
||||
singleArchFiles: opts.singleArchFiles,
|
||||
});
|
||||
}
|
||||
else if (x64AsarMode === AsarMode.HAS_ASAR) {
|
||||
d('checking if the x64 and arm64 asars are identical');
|
||||
const x64AsarSha = await sha(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
const arm64AsarSha = await sha(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'));
|
||||
if (x64AsarSha !== arm64AsarSha) {
|
||||
d('x64 and arm64 asars are different');
|
||||
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
|
||||
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(x64Unpacked)) {
|
||||
await fs.move(x64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'));
|
||||
}
|
||||
const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), arm64AsarPath);
|
||||
const arm64Unpacked = path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(arm64Unpacked)) {
|
||||
await fs.copy(arm64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'));
|
||||
}
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = JSON.parse((await asar.extractFile(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 'package.json')).toString('utf8'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await asar.createPackage(entryAsar, asarPath);
|
||||
}
|
||||
else {
|
||||
d('x64 and arm64 asars are the same');
|
||||
}
|
||||
}
|
||||
const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents'));
|
||||
const plistFiles = x64Files.filter((f) => f.type === AppFileType.INFO_PLIST);
|
||||
for (const plistFile of plistFiles) {
|
||||
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
|
||||
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);
|
||||
const _a = plist.parse(await fs.readFile(x64PlistPath, 'utf8')), { ElectronAsarIntegrity: x64Integrity } = _a, x64Plist = __rest(_a, ["ElectronAsarIntegrity"]);
|
||||
const _b = plist.parse(await fs.readFile(arm64PlistPath, 'utf8')), { ElectronAsarIntegrity: arm64Integrity } = _b, arm64Plist = __rest(_b, ["ElectronAsarIntegrity"]);
|
||||
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
|
||||
throw new Error(`Expected all Info.plist files to be identical when ignoring integrity when creating a universal build but "${plistFile.relativePath}" was not`);
|
||||
}
|
||||
const injectAsarIntegrity = !opts.infoPlistsToIgnore ||
|
||||
minimatch(plistFile.relativePath, opts.infoPlistsToIgnore, { matchBase: true });
|
||||
const mergedPlist = injectAsarIntegrity
|
||||
? Object.assign(Object.assign({}, x64Plist), { ElectronAsarIntegrity: generatedIntegrity }) : Object.assign({}, x64Plist);
|
||||
await fs.writeFile(path.resolve(tmpApp, plistFile.relativePath), plist.build(mergedPlist));
|
||||
}
|
||||
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
|
||||
d('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath));
|
||||
}
|
||||
d('moving final universal app to target destination');
|
||||
await fs.mkdirp(path.dirname(opts.outAppPath));
|
||||
await spawn('mv', [tmpApp, opts.outAppPath]);
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-8
@@ -1,8 +0,0 @@
|
||||
export interface HeaderHash {
|
||||
algorithm: 'SHA256';
|
||||
hash: string;
|
||||
}
|
||||
export interface AsarIntegrity {
|
||||
[key: string]: HeaderHash;
|
||||
}
|
||||
export declare function computeIntegrityData(contentsPath: string): Promise<AsarIntegrity>;
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { AppFileType, getAllAppFiles } from './file-utils';
|
||||
import { generateAsarIntegrity } from './asar-utils';
|
||||
export async function computeIntegrityData(contentsPath) {
|
||||
const root = await fs.realpath(contentsPath);
|
||||
const resourcesRelativePath = 'Resources';
|
||||
const resourcesPath = path.resolve(root, resourcesRelativePath);
|
||||
const resources = await getAllAppFiles(resourcesPath);
|
||||
const resourceAsars = resources
|
||||
.filter((file) => file.type === AppFileType.APP_CODE)
|
||||
.reduce((prev, file) => (Object.assign(Object.assign({}, prev), { [path.join(resourcesRelativePath, file.relativePath)]: path.join(resourcesPath, file.relativePath) })), {});
|
||||
// sort to produce constant result
|
||||
const allAsars = Object.entries(resourceAsars).sort(([name1], [name2]) => name1.localeCompare(name2));
|
||||
const hashes = await Promise.all(allAsars.map(async ([, from]) => generateAsarIntegrity(from)));
|
||||
const asarIntegrity = {};
|
||||
for (let i = 0; i < allAsars.length; i++) {
|
||||
const [asar] = allAsars[i];
|
||||
asarIntegrity[asar] = hashes[i];
|
||||
}
|
||||
return asarIntegrity;
|
||||
}
|
||||
//# sourceMappingURL=integrity.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"integrity.js","sourceRoot":"","sources":["../../src/integrity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAerD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,YAAoB;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAE7C,MAAM,qBAAqB,GAAG,WAAW,CAAC;IAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAEhE,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,SAAS;SAC5B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,QAAQ,CAAC;SACpD,MAAM,CACL,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,iCACX,IAAI,KACP,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAC9D,aAAa,EACb,IAAI,CAAC,YAAY,CAClB,IACD,EACF,EAAE,CACH,CAAC;IAEJ,kCAAkC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CACvE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAC3B,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,aAAa,GAAkB,EAAE,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACxC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3B,aAAa,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;KACjC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export declare const sha: (filePath: string) => Promise<any>;
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as crypto from 'crypto';
|
||||
import { pipeline } from 'stream/promises';
|
||||
import { d } from './debug';
|
||||
export const sha = async (filePath) => {
|
||||
d('hashing', filePath);
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.setEncoding('hex');
|
||||
await pipeline(fs.createReadStream(filePath), hash);
|
||||
return hash.read();
|
||||
};
|
||||
//# sourceMappingURL=sha.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"sha.js","sourceRoot":"","sources":["../../src/sha.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,CAAC,EAAE,MAAM,SAAS,CAAC;AAE5B,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5C,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,QAAQ,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron_1 = require("electron");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
if (process.arch === 'arm64') {
|
||||
setPaths('arm64');
|
||||
}
|
||||
else {
|
||||
setPaths('x64');
|
||||
}
|
||||
function setPaths(platform) {
|
||||
// This should return the full path, ending in something like
|
||||
// Notion.app/Contents/Resources/app.asar
|
||||
const appPath = electron_1.app.getAppPath();
|
||||
const asarFile = `app-${platform}.asar`;
|
||||
// Maybe we'll handle this in Electron one day
|
||||
if (path_1.default.basename(appPath) === 'app.asar') {
|
||||
const platformAppPath = path_1.default.join(path_1.default.dirname(appPath), asarFile);
|
||||
// This is an undocumented API. It exists.
|
||||
electron_1.app.setAppPath(platformAppPath);
|
||||
}
|
||||
process._archPath = require.resolve(`../${asarFile}`);
|
||||
}
|
||||
require(process._archPath);
|
||||
//# sourceMappingURL=has-asar.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"has-asar.js","sourceRoot":"","sources":["has-asar.ts"],"names":[],"mappings":";;;;;AAAA,uCAA+B;AAC/B,gDAAwB;AAExB,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE;IAC5B,QAAQ,CAAC,OAAO,CAAC,CAAC;CACnB;KAAM;IACL,QAAQ,CAAC,KAAK,CAAC,CAAC;CACjB;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,6DAA6D;IAC7D,yCAAyC;IACzC,MAAM,OAAO,GAAG,cAAG,CAAC,UAAU,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,QAAQ,OAAO,CAAC;IAExC,8CAA8C;IAC9C,IAAI,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE;QACzC,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAEnE,0CAA0C;QAC1C,cAAG,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;KACjC;IAED,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const electron_1 = require("electron");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
if (process.arch === 'arm64') {
|
||||
setPaths('arm64');
|
||||
}
|
||||
else {
|
||||
setPaths('x64');
|
||||
}
|
||||
function setPaths(platform) {
|
||||
// This should return the full path, ending in something like
|
||||
// Notion.app/Contents/Resources/app
|
||||
const appPath = electron_1.app.getAppPath();
|
||||
const appFolder = `app-${platform}`;
|
||||
// Maybe we'll handle this in Electron one day
|
||||
if (path_1.default.basename(appPath) === 'app') {
|
||||
const platformAppPath = path_1.default.join(path_1.default.dirname(appPath), appFolder);
|
||||
// This is an undocumented private API. It exists.
|
||||
electron_1.app.setAppPath(platformAppPath);
|
||||
}
|
||||
process._archPath = require.resolve(`../${appFolder}`);
|
||||
}
|
||||
require(process._archPath);
|
||||
//# sourceMappingURL=no-asar.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"no-asar.js","sourceRoot":"","sources":["no-asar.ts"],"names":[],"mappings":";;;;;AAAA,uCAA+B;AAC/B,gDAAwB;AAExB,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE;IAC5B,QAAQ,CAAC,OAAO,CAAC,CAAC;CACnB;KAAM;IACL,QAAQ,CAAC,KAAK,CAAC,CAAC;CACjB;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,6DAA6D;IAC7D,oCAAoC;IACpC,MAAM,OAAO,GAAG,cAAG,CAAC,UAAU,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,OAAO,QAAQ,EAAE,CAAC;IAEpC,8CAA8C;IAC9C,IAAI,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,KAAK,EAAE;QACpC,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;QAEpE,kDAAkD;QAClD,cAAG,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;KACjC;IAED,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"name": "@electron/universal",
|
||||
"version": "2.0.3",
|
||||
"description": "Utility for creating Universal macOS applications from two x64 and arm64 Electron applications",
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"electron",
|
||||
"apple silicon",
|
||||
"universal"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/electron/universal.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.4"
|
||||
},
|
||||
"files": [
|
||||
"dist/*",
|
||||
"entry-asar/*",
|
||||
"!entry-asar/**/*.ts",
|
||||
"README.md"
|
||||
],
|
||||
"author": "Samuel Attard",
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && tsc -p tsconfig.entry-asar.json",
|
||||
"build:docs": "npx typedoc",
|
||||
"lint": "prettier --check \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"",
|
||||
"prettier:write": "prettier --write \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "jest",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/get": "^3.0.0",
|
||||
"@types/cross-zip": "^4.0.1",
|
||||
"@types/debug": "^4.1.10",
|
||||
"@types/fs-extra": "^11.0.3",
|
||||
"@types/jest": "^29.5.7",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
"@types/node": "^20.8.10",
|
||||
"@types/plist": "^3.0.4",
|
||||
"cross-zip": "^4.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"prettier": "^3.0.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typedoc": "~0.25.13",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.3.1",
|
||||
"@malept/cross-spawn-promise": "^2.0.0",
|
||||
"debug": "^4.3.1",
|
||||
"dir-compare": "^4.2.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"minimatch": "^9.0.3",
|
||||
"plist": "^3.1.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"jackspeak": "2.1.1"
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
Copyright (c) Felix Rieseberg
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* 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.
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
# @electron/windows-sign [![npm][npm_img]][npm_url]
|
||||
|
||||
Codesign your app for Windows. Made for [Electron][electron] but really supports any folder with binary files. `electron-windows-sign` scans a folder for [signable files](#file-types) and codesigns them with both SHA-1 and SHA-256. It can be highly customized and used either programmatically or on the command line.
|
||||
|
||||
This tool is particularly useful if you want to code sign Electron or other Windows binaries with an EV certificate or an HSM setup (like DigiCert KeyLocker, AWS CloudHSM, Azure Key Vault HSM, Google Cloud Key Management Service HSM, and other similar services). For more details on how you'd exactly do that, please see [Use with Cloud HSM Providers](#use-with-cloud-hsm-providers) below.
|
||||
|
||||
# Requirements
|
||||
|
||||
By default, this module spawns `signtool.exe` and needs to run on Windows. If you're building an Electron app and care enough to codesign them, I would heavily recommend that you build and test your apps on the platforms you're building for.
|
||||
|
||||
# Usage
|
||||
|
||||
Most developers of Electron apps will likely not use this module directly - and instead use it indirectly
|
||||
instead. If you are one of those developers who is using a module like `@electron/forge` or `@electron/packager`, you can configure this module with global environment variables. If that describes
|
||||
you, you can skip ahead to your use case:
|
||||
|
||||
- [With a certificate file and password](#with-a-certificate-file-and-password)
|
||||
- [With a custom binary or custom parameters](#with-a-custom-signtoolexe-or-custom-parameters)
|
||||
- [With a completely custom hook](#with-a-custom-hook-function)
|
||||
|
||||
## Direct Usage
|
||||
|
||||
`@electron/windows-codesign` is built to both esm and cjs and can be used both as a module as well as directly from the command line.
|
||||
|
||||
```ts
|
||||
import { sign } from "@electron/windows-sign"
|
||||
// or const { sign } = require("@electron/windows-sign")
|
||||
|
||||
await sign(signOptions)
|
||||
```
|
||||
|
||||
```ps1
|
||||
electron-windows-sign $PATH_TO_APP_DIRECTORY [options ...]
|
||||
```
|
||||
|
||||
## With a certificate file and password
|
||||
|
||||
This is the "traditional" way to codesign Electron apps on Windows. You pass in a certificate file
|
||||
(like a .pfx) and a password, which will then be passed to a built-in version of `signtool.exe` taken
|
||||
directly from the Windows SDK.
|
||||
|
||||
```ts
|
||||
await sign({
|
||||
appDirectory: "C:\\Path\\To\\App",
|
||||
// or process.env.WINDOWS_CERTIFICATE_FILE
|
||||
certificateFile: "C:\\Cert.pfx",
|
||||
// or process.env.WINDOWS_CERTIFICATE_PASSWORD
|
||||
certificatePassword: "hunter99"
|
||||
})
|
||||
```
|
||||
|
||||
```ps1
|
||||
electron-windows-sign $PATH_TO_APP_DIRECTORY --certificate-file=$PATH_TO_CERT --certificate-password=$CERT-PASSWORD
|
||||
```
|
||||
|
||||
### Full configuration
|
||||
```ts
|
||||
// Path to a timestamp server. Defaults to http://timestamp.digicert.com
|
||||
// Can also be passed as process.env.WINDOWS_TIMESTAMP_SERVER
|
||||
timestampServer = "http://timestamp.digicert.com"
|
||||
// Description of the signed content. Will be passed to signtool.exe as /d
|
||||
// Can also be passed as process.env.WINDOWS_SIGN_DESCRIPTION
|
||||
description = "My content"
|
||||
// URL of the signed content. Will be passed to signtool.exe as /du
|
||||
// Can also be passed as process.env.WINDOWS_SIGN_WEBSITE
|
||||
website = "https://mywebsite.com"
|
||||
// If enabled, attempt to sign .js JavaScript files. Disabled by default
|
||||
signJavaScript = true
|
||||
// If unspecified, both sha1 and sha256 signatures will be appended. Will be passed to signtool.exe as the /fd option.
|
||||
hashes = ["sha256"]
|
||||
```
|
||||
|
||||
## With a custom signtool.exe or custom parameters
|
||||
|
||||
Sometimes, you need to specify specific signing parameters or use a different version
|
||||
of `signtool.exe`. In this mode, `@electron/windows-sign` will call the provided binary
|
||||
with the provided parameters for each file to sign.
|
||||
|
||||
If you only provide `signToolPath`, the default parameters will be used.
|
||||
If you only provide `signWithParams`, the default `signtool.exe` will be used.
|
||||
|
||||
All the [additional configuration](#additional-configuration) mentioned above is also
|
||||
available here, but only used if you do not provide your own parameters.
|
||||
|
||||
```ts
|
||||
await sign({
|
||||
appDirectory: "C:\\Path\\To\\App",
|
||||
// or process.env.WINDOWS_CERTIFICATE_FILE
|
||||
certificateFile: "C:\\Cert.pfx",
|
||||
// or process.env.WINDOWS_CERTIFICATE_PASSWORD
|
||||
certificatePassword: "hunter99",
|
||||
// or process.env.WINDOWS_SIGNTOOL_PATH
|
||||
signToolPath: "C:\\Path\\To\\my-custom-tool.exe",
|
||||
// or process.env.WINDOWS_SIGN_WITH_PARAMS
|
||||
signWithParams: "--my=custom --parameters"
|
||||
})
|
||||
```
|
||||
|
||||
```ps1
|
||||
electron-windows-sign $PATH_TO_APP_DIRECTORY --sign-tool-path=$PATH_TO_TOOL --sign-with-params="--my=custom --parameters"
|
||||
```
|
||||
|
||||
## With a custom hook function
|
||||
|
||||
Sometimes, you just want all modules depending on `@electron/windows-sign` to call
|
||||
your completely custom logic. You can either specify a `hookFunction` (if you're calling
|
||||
this module yourself) or a `hookModulePath`, which this module will attempt to require.
|
||||
|
||||
Using the `hookModulePath` has the benefit that you can override how any other users
|
||||
of this module (like `@electron/packager`) codesign your app.
|
||||
|
||||
```ts
|
||||
await sign({
|
||||
// A function with the following signature:
|
||||
// (fileToSign: string) => void | Promise<void>
|
||||
//
|
||||
// This function will be called sequentially for each file that
|
||||
// @electron/windows-sign wants to sign.
|
||||
hookFunction: myHookFunction
|
||||
// Path to a hook module.
|
||||
hookModulePath: "C:\\Path\\To\\my-hook-module.js",
|
||||
})
|
||||
```
|
||||
|
||||
Your hook module should either directly export a function or
|
||||
export a `default` function.
|
||||
```js
|
||||
// Good:
|
||||
module.exports = function (filePath) {
|
||||
console.log(`Path to file to sign: ${filePath}`)
|
||||
}
|
||||
|
||||
// Good:
|
||||
module.exports = async function (filePath) {
|
||||
console.log(`Path to file to sign: ${filePath}`)
|
||||
}
|
||||
|
||||
// Good:
|
||||
export default async function (filePath) {
|
||||
console.log(`Path to file to sign: ${filePath}`)
|
||||
}
|
||||
|
||||
// Bad:
|
||||
module.exports = {
|
||||
myCustomHookName: function (filePath) {
|
||||
console.log(`Path to file to sign: ${filePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Bad:
|
||||
export async function myCustomHookName(filePath) {
|
||||
console.log(`Path to file to sign: ${filePath}`)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
SYNOPSIS
|
||||
electron-windows-sign app [options ...]
|
||||
|
||||
DESCRIPTION
|
||||
app
|
||||
Path to the application to sign. It must be a directory.
|
||||
|
||||
certificate-file
|
||||
Path to the certificate file (.pfx) to use for signing. Uses
|
||||
environment variable WINDOWS_CERTIFICATE_FILE if not provided.
|
||||
|
||||
certificate-password
|
||||
Password to use for the certificate. Uses environment variable
|
||||
WINDOWS_CERTIFICATE_PASSWORD if not provided.
|
||||
|
||||
sign-tool-path
|
||||
Path to the signtool.exe binary. If not specified, the tool will attempt
|
||||
use a built-in version.
|
||||
|
||||
timestamp-server
|
||||
URL of the timestamp server to use. If not specified, the tool will
|
||||
attempt to use a built-in server (http://timestamp.digicert.com)
|
||||
|
||||
description
|
||||
Description to use for the signed files. Passed as /d to signtool.exe.
|
||||
|
||||
website
|
||||
URL of the website to use for the signed files. Passed as /du to
|
||||
signtool.exe.
|
||||
|
||||
sign-with-params
|
||||
Additional parameters to pass to signtool.exe. This can be used to
|
||||
specify additional certificates to use for cross-signing.
|
||||
|
||||
automatically-select-certificate
|
||||
Automatically select the best certificate to use for signing. On by default.
|
||||
|
||||
help
|
||||
Print this usage information.
|
||||
|
||||
debug
|
||||
Print additional debug information.
|
||||
```
|
||||
|
||||
# File Types
|
||||
This tool will aggressively attempt to sign all files that _can_
|
||||
be signed, excluding scripts.
|
||||
|
||||
- [Portable executable files][pe] (.exe, .dll, .sys, .efi, .scr, .node)
|
||||
- Microsoft installers (.msi)
|
||||
- APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle)
|
||||
- Catalog files (.cat)
|
||||
- Cabinet files (.cab)
|
||||
- Silverlight applications (.xap)
|
||||
- Scripts (.vbs, .wsf, .ps1)
|
||||
|
||||
If you do want to sign JavaScript, please enable it with the `signJavaScript`
|
||||
parameter. As far as we are aware, there are no benefits to signing
|
||||
JavaScript files, so we do not by default.
|
||||
|
||||
# Use with Cloud HSM Providers
|
||||
|
||||
Since 2023, Microsoft requires that Windows software be signed with an extended validation (EV) certificate in order to avoid a Windows Defender popup. This presents a challenge for developers who are used to building and signing in the cloud or some continuous integration setup like GitHub Actions, CircleCI, Travis CI, or AppVeyor because EV certificates require a Hardware Security Module (HSM). Or, in simpler words: If you want to code sign your Windows software so that your customers don't see a scary Windows Defender dialog, you need to code sign on a computer with a USB device plugged in.
|
||||
|
||||
The industry has an answer to this problem: Services like DigiCert KeyLocker, AWS CloudHSM, Azure Key Vault HSM, or Google Cloud Key Management Service HSM allow code signing binaries with an Extended Validation (EV) certificate from the cloud. `@electron/windows-sign` is compatible with all of these services.
|
||||
|
||||
## Custom signtool parameters
|
||||
|
||||
Most services allow signing your code with Microsoft's `signtool.exe`. Let's take DigiCert KeyLocker as an example. [DigiCert's documentation](https://docs.digicert.com/en/digicert-keylocker/signing-tools/sign-authenticode-files-with-signtool-on-windows.html) explains the steps necessary to setup your signing machine. Once done, you can sign with the following call:
|
||||
|
||||
```
|
||||
signtool.exe sign /csp "DigiCert Signing Manager KSP" /kc <keypair_alias> /f <certificate_file> /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 <file_to_be_signed>
|
||||
```
|
||||
|
||||
To sign with `@electron/windows-sign` using those instructions, you would take the parameters and add them to `signWithParams`:
|
||||
|
||||
```js
|
||||
await sign({
|
||||
signWithParams: "/csp \"DigiCert Signing Manager KSP\" /kc <keypair_alias> /f <certificate_file> /tr http://timestamp.digicert.com /td SHA256 /fd SHA256"
|
||||
})
|
||||
```
|
||||
|
||||
Both Google's and Amazon's solutions similarly allow you to sign with Microsoft's SignTool. Documentation for [Google can be found here](https://cloud.google.com/kms/docs/reference/cng-signtool), [Amazon's lives here](https://docs.aws.amazon.com/cloudhsm/latest/userguide/signtool.html).
|
||||
|
||||
## Custom signtool.exe
|
||||
|
||||
Some providers provide drop-in replacements for `signtool.exe`. If that's the case, simply pass the path to that replacement:
|
||||
|
||||
```ts
|
||||
await sign({
|
||||
// or process.env.WINDOWS_SIGNTOOL_PATH
|
||||
signToolPath: "C:\\Path\\To\\my-custom-tool.exe",
|
||||
})
|
||||
```
|
||||
|
||||
## Fully custom process
|
||||
|
||||
If your HSM provider has a more complex setup, you might have to call a custom file with custom parameters. If that's the case, use a hook function or hook module, which allows you to completely customize what to do with each file that `@electron/windows-sign` wants to sign. More documentation about this option can be found in the ["Signing with a custom hook function" section above](#with-a-custom-hook-function).
|
||||
|
||||
# License
|
||||
BSD 2-Clause "Simplified". Please see LICENSE for details.
|
||||
|
||||
[electron]: https://github.com/electron/electron
|
||||
[npm_img]: https://img.shields.io/npm/v/@electron/windows-sign.svg
|
||||
[npm_url]: https://npmjs.org/package/@electron/windows-sign
|
||||
[pe]: https://en.wikipedia.org/wiki/Portable_Executable
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
NAME
|
||||
electron-windows-sign -- code signing for Electron apps on Windows.
|
||||
|
||||
SYNOPSIS
|
||||
electron-windows-sign app [options ...]
|
||||
|
||||
DESCRIPTION
|
||||
app
|
||||
Path to the application to sign. It must be a directory.
|
||||
|
||||
certificate-file
|
||||
Path to the certificate file (.pfx) to use for signing.
|
||||
|
||||
certificate-password
|
||||
Password to use for the certificate.
|
||||
|
||||
sign-tool-path
|
||||
Path to the signtool.exe binary. If not specified, the tool will attempt
|
||||
use a built-in version.
|
||||
|
||||
timestamp-server
|
||||
URL of the timestamp server to use. If not specified, the tool will
|
||||
attempt to use a built-in server (http://timestamp.digicert.com)
|
||||
|
||||
description
|
||||
Description to use for the signed files. Passed as /d to signtool.exe.
|
||||
|
||||
website
|
||||
URL of the website to use for the signed files. Passed as /du to
|
||||
signtool.exe.
|
||||
|
||||
sign-with-params
|
||||
Additional parameters to pass to signtool.exe. This can be used to
|
||||
specify additional certificates to use for cross-signing.
|
||||
|
||||
automatically-select-certificate
|
||||
Automatically select the best certificate to use for signing. On by default.
|
||||
|
||||
help
|
||||
Print this usage information.
|
||||
|
||||
debug
|
||||
Print additional debug information.
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
string: [
|
||||
'certificate-file',
|
||||
'certificate-password',
|
||||
'sign-tool-path',
|
||||
'timestamp-server',
|
||||
'description',
|
||||
'website',
|
||||
'sign-with-params'
|
||||
],
|
||||
boolean: [
|
||||
'help',
|
||||
'debug'
|
||||
],
|
||||
default: {
|
||||
'automatically-select-certificate': true
|
||||
}
|
||||
});
|
||||
const usage = fs.readFileSync(path.join(__dirname, 'electron-windows-sign-usage.txt')).toString();
|
||||
const sign = require('../').sign;
|
||||
|
||||
args.app = args._.shift();
|
||||
|
||||
if (!args.app || args.help) {
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Remove excess arguments
|
||||
delete args._;
|
||||
delete args.help;
|
||||
|
||||
sign(args, function done(err) {
|
||||
if (err) {
|
||||
console.error('Sign failed:');
|
||||
if (err.message) console.error(err.message);
|
||||
else if (err.stack) console.error(err.stack);
|
||||
else console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Application signed:', args.app);
|
||||
process.exit(0);
|
||||
});
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { SignOptions } from './types';
|
||||
/**
|
||||
* Recursively goes through an entire directory and returns an array
|
||||
* of full paths for files ot sign.
|
||||
*
|
||||
* - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node)
|
||||
* - Microsoft installers (.msi)
|
||||
* - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle)
|
||||
* - Catalog files (.cat)
|
||||
* - Cabinet files (.cab)
|
||||
* - Silverlight applications (.xap)
|
||||
* - Scripts (.vbs, .wsf, .ps1)
|
||||
* If configured:
|
||||
* - JavaScript files (.js)
|
||||
*/
|
||||
export declare function getFilesToSign(options: SignOptions, dir?: string): Array<string>;
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getFilesToSign = void 0;
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const fs_extra_1 = __importDefault(require("fs-extra"));
|
||||
const IS_PE_REGEX = /\.(exe|dll|sys|efi|scr|node)$/i;
|
||||
const IS_MSI_REGEX = /\.msi$/i;
|
||||
const IS_PACKAGE_REGEX = /\.(appx|appxbundle|msix|msixbundle)$/i;
|
||||
const IS_CATCAB_REGEX = /\.(cat|cab)$/i;
|
||||
const IS_SILVERLIGHT_REGEX = /\.xap$/i;
|
||||
const IS_SCRIPT_REGEX = /\.(vbs|wsf|ps1)$/i;
|
||||
const IS_JS_REGEX = /\.js$/i;
|
||||
/**
|
||||
* Recursively goes through an entire directory and returns an array
|
||||
* of full paths for files ot sign.
|
||||
*
|
||||
* - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node)
|
||||
* - Microsoft installers (.msi)
|
||||
* - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle)
|
||||
* - Catalog files (.cat)
|
||||
* - Cabinet files (.cab)
|
||||
* - Silverlight applications (.xap)
|
||||
* - Scripts (.vbs, .wsf, .ps1)
|
||||
* If configured:
|
||||
* - JavaScript files (.js)
|
||||
*/
|
||||
function getFilesToSign(options, dir) {
|
||||
if (isSignOptionsForFiles(options)) {
|
||||
return options.files;
|
||||
}
|
||||
dir = dir || options.appDirectory;
|
||||
// Array of file paths to sign
|
||||
const result = [];
|
||||
// Iterate over the app directory, looking for files to sign
|
||||
const files = fs_extra_1.default.readdirSync(dir);
|
||||
const regexes = [
|
||||
IS_PE_REGEX,
|
||||
IS_MSI_REGEX,
|
||||
IS_PACKAGE_REGEX,
|
||||
IS_CATCAB_REGEX,
|
||||
IS_SILVERLIGHT_REGEX,
|
||||
IS_SCRIPT_REGEX
|
||||
];
|
||||
if (options.signJavaScript) {
|
||||
regexes.push(IS_JS_REGEX);
|
||||
}
|
||||
for (const file of files) {
|
||||
const fullPath = path_1.default.resolve(dir, file);
|
||||
if (fs_extra_1.default.statSync(fullPath).isDirectory()) {
|
||||
// If it's a directory, recurse
|
||||
result.push(...getFilesToSign(options, fullPath));
|
||||
}
|
||||
else if (regexes.some((regex) => regex.test(file))) {
|
||||
// If it's a match, add it to the list
|
||||
result.push(fullPath);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.getFilesToSign = getFilesToSign;
|
||||
function isSignOptionsForFiles(input) {
|
||||
return !!input.files;
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
import { sign } from './sign';
|
||||
import { createSeaSignTool, SeaOptions, InternalSeaOptions } from './sea';
|
||||
import { HookFunction, OptionalHookOptions, OptionalSignToolOptions, SignOptions, SignToolOptions, SignOptionsForDirectory, SignOptionsForFiles } from './types';
|
||||
export { sign, SignOptions, SignToolOptions, HookFunction, OptionalSignToolOptions, OptionalHookOptions, createSeaSignTool, SeaOptions, InternalSeaOptions, SignOptionsForDirectory, SignOptionsForFiles };
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createSeaSignTool = exports.sign = void 0;
|
||||
const sign_1 = require("./sign");
|
||||
Object.defineProperty(exports, "sign", { enumerable: true, get: function () { return sign_1.sign; } });
|
||||
const sea_1 = require("./sea");
|
||||
Object.defineProperty(exports, "createSeaSignTool", { enumerable: true, get: function () { return sea_1.createSeaSignTool; } });
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
import { SignToolOptions } from './types';
|
||||
/**
|
||||
* Options for signing with a Node.js single executable application.
|
||||
*
|
||||
* @category Single executable applications
|
||||
*/
|
||||
export interface SeaOptions {
|
||||
/**
|
||||
* Full path to the Node.js single executable application. Needs to end with `.exe`.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* A binary to use. Will use the current executable (process.execPath) by default.
|
||||
*
|
||||
* @defaultValue The Node.js {@link https://nodejs.org/api/process.html#processexecpath | `process.execPath`}
|
||||
*/
|
||||
bin?: string;
|
||||
/**
|
||||
* Options to pass to SignTool.
|
||||
*/
|
||||
windowsSign: SignToolOptions;
|
||||
}
|
||||
/**
|
||||
* This interface represents {@link SeaOptions} with all optional properties
|
||||
* inferred by `@electron/windows-sign` if not passed in by the user.
|
||||
*
|
||||
* @category Single executable applications
|
||||
*/
|
||||
export interface InternalSeaOptions extends Required<SeaOptions> {
|
||||
/**
|
||||
* Directory of the Node.js single executable application.
|
||||
*/
|
||||
dir: string;
|
||||
/**
|
||||
* File name of the Node.js single executable application.
|
||||
*/
|
||||
filename: string;
|
||||
}
|
||||
/**
|
||||
* cross-dir uses new Error() stacks
|
||||
* to figure out our directory in a way
|
||||
* that's somewhat cross-compatible.
|
||||
*
|
||||
* We can't just use __dirname because it's
|
||||
* undefined in ESM - and we can't use import.meta.url
|
||||
* because TypeScript won't allow usage unless you're
|
||||
* _only_ compiling for ESM.
|
||||
*/
|
||||
export declare const DIRNAME: string;
|
||||
/**
|
||||
* Uses Node's "Single Executable App" functionality
|
||||
* to create a Node-driven signtool.exe that calls this
|
||||
* module.
|
||||
*
|
||||
* This is useful with other tooling that _always_ calls
|
||||
* a signtool.exe to sign. Some of those tools cannot be
|
||||
* easily configured, but we _can_ override their signtool.exe.
|
||||
*
|
||||
* @category Single executable applications
|
||||
*/
|
||||
export declare function createSeaSignTool(options?: Partial<SeaOptions>): Promise<InternalSeaOptions>;
|
||||
-220
@@ -1,220 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createSeaSignTool = exports.DIRNAME = void 0;
|
||||
const cross_dirname_1 = require("cross-dirname");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const os_1 = __importDefault(require("os"));
|
||||
const fs_extra_1 = __importDefault(require("fs-extra"));
|
||||
const postject_1 = __importDefault(require("postject"));
|
||||
const spawn_1 = require("./spawn");
|
||||
const log_1 = require("./utils/log");
|
||||
/**
|
||||
* cross-dir uses new Error() stacks
|
||||
* to figure out our directory in a way
|
||||
* that's somewhat cross-compatible.
|
||||
*
|
||||
* We can't just use __dirname because it's
|
||||
* undefined in ESM - and we can't use import.meta.url
|
||||
* because TypeScript won't allow usage unless you're
|
||||
* _only_ compiling for ESM.
|
||||
*/
|
||||
exports.DIRNAME = (0, cross_dirname_1.getDirname)();
|
||||
const FILENAMES = {
|
||||
SEA_CONFIG: 'sea-config.json',
|
||||
SEA_MAIN: 'sea.js',
|
||||
SEA_BLOB: 'sea.blob',
|
||||
SEA_RECEIVER: 'receiver.mjs'
|
||||
};
|
||||
const SEA_MAIN_SCRIPT = `
|
||||
const bin = "%PATH_TO_BIN%";
|
||||
const script = "%PATH_TO_SCRIPT%";
|
||||
const options = %WINDOWS_SIGN_OPTIONS%
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
function main() {
|
||||
console.log("@electron/windows-sign sea");
|
||||
console.log({ bin, script });
|
||||
|
||||
try {
|
||||
const spawn = spawnSync(
|
||||
bin,
|
||||
[ script, JSON.stringify(options), JSON.stringify(process.argv.slice(1)) ],
|
||||
{ stdio: ['inherit', 'inherit', 'pipe'] }
|
||||
);
|
||||
|
||||
if (spawn.status !== 0) {
|
||||
throw new Error(\`Spawn failed with code: \${spawn.status}. Stderr: \${spawn.stderr}\`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Do not rethrow the error or write it to stdout/stderr. Then the process won't terminate.
|
||||
// See: https://github.com/electron/windows-sign/pull/48
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
`;
|
||||
const SEA_RECEIVER_SCRIPT = `
|
||||
import { sign } from '@electron/windows-sign';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
const logPath = path.join('electron-windows-sign.log');
|
||||
const options = JSON.parse(process.argv[2]);
|
||||
const signArgv = JSON.parse(process.argv[3]);
|
||||
const files = signArgv.slice(-1);
|
||||
|
||||
try {
|
||||
fs.appendFileSync(logPath, \`\\nCalled with: \${JSON.stringify(process.argv, null, 2)}\`);
|
||||
|
||||
sign({ ...options, files })
|
||||
.then((result) => {
|
||||
fs.appendFileSync(logPath, \`\\nSuccessfully signed with result: \${result}\`);
|
||||
})
|
||||
.catch((error) => {
|
||||
fs.appendFileSync(logPath, \`\\nError from sign: \${error}\`);
|
||||
throw new Error(error);
|
||||
});
|
||||
} catch (error) {
|
||||
fs.appendFileSync(logPath, \`\\Error invoking sign: \${error}\`);
|
||||
throw new Error(error);
|
||||
}
|
||||
`;
|
||||
/**
|
||||
* Uses Node's "Single Executable App" functionality
|
||||
* to create a Node-driven signtool.exe that calls this
|
||||
* module.
|
||||
*
|
||||
* This is useful with other tooling that _always_ calls
|
||||
* a signtool.exe to sign. Some of those tools cannot be
|
||||
* easily configured, but we _can_ override their signtool.exe.
|
||||
*
|
||||
* @category Single executable applications
|
||||
*/
|
||||
async function createSeaSignTool(options = {}) {
|
||||
checkCompatibility();
|
||||
const requiredOptions = await getOptions(options);
|
||||
await createFiles(requiredOptions);
|
||||
await createBlob(requiredOptions);
|
||||
await createBinary(requiredOptions);
|
||||
await createSeaReceiver(requiredOptions);
|
||||
await cleanup(requiredOptions);
|
||||
return requiredOptions;
|
||||
}
|
||||
exports.createSeaSignTool = createSeaSignTool;
|
||||
async function createSeaReceiver(options) {
|
||||
const receiverPath = path_1.default.join(options.dir, FILENAMES.SEA_RECEIVER);
|
||||
await fs_extra_1.default.ensureFile(receiverPath);
|
||||
await fs_extra_1.default.writeFile(receiverPath, SEA_RECEIVER_SCRIPT);
|
||||
}
|
||||
async function createFiles(options) {
|
||||
const { dir, bin } = options;
|
||||
const receiverPath = path_1.default.join(options.dir, FILENAMES.SEA_RECEIVER);
|
||||
// sea-config.json
|
||||
await fs_extra_1.default.outputJSON(path_1.default.join(dir, FILENAMES.SEA_CONFIG), {
|
||||
main: FILENAMES.SEA_MAIN,
|
||||
output: FILENAMES.SEA_BLOB,
|
||||
disableExperimentalSEAWarning: true
|
||||
}, {
|
||||
spaces: 2
|
||||
});
|
||||
// signtool.js
|
||||
const binPath = bin || process.execPath;
|
||||
const script = SEA_MAIN_SCRIPT
|
||||
.replace('%PATH_TO_BIN%', escapeMaybe(binPath))
|
||||
.replace('%PATH_TO_SCRIPT%', escapeMaybe(receiverPath))
|
||||
.replace('%WINDOWS_SIGN_OPTIONS%', JSON.stringify(options.windowsSign));
|
||||
await fs_extra_1.default.outputFile(path_1.default.join(dir, FILENAMES.SEA_MAIN), script);
|
||||
}
|
||||
async function createBlob(options) {
|
||||
const args = ['--experimental-sea-config', 'sea-config.json'];
|
||||
const bin = process.execPath;
|
||||
const cwd = options.dir;
|
||||
(0, log_1.log)(`Calling ${bin} with options:`, args);
|
||||
const { stderr, stdout } = await (0, spawn_1.spawnPromise)(bin, args, {
|
||||
cwd
|
||||
});
|
||||
(0, log_1.log)('stdout:', stdout);
|
||||
(0, log_1.log)('stderr:', stderr);
|
||||
}
|
||||
async function createBinary(options) {
|
||||
const { dir, filename } = options;
|
||||
(0, log_1.log)(`Creating ${filename} in ${dir}`);
|
||||
// Copy Node over
|
||||
const seaPath = path_1.default.join(dir, filename);
|
||||
await fs_extra_1.default.copyFile(process.execPath, seaPath);
|
||||
// Remove the Node signature
|
||||
const signtool = path_1.default.join(exports.DIRNAME, '../../vendor/signtool.exe');
|
||||
await (0, spawn_1.spawnPromise)(signtool, [
|
||||
'remove',
|
||||
'/s',
|
||||
seaPath
|
||||
]);
|
||||
// Inject the blob
|
||||
const blob = await fs_extra_1.default.readFile(path_1.default.join(dir, FILENAMES.SEA_BLOB));
|
||||
await postject_1.default.inject(seaPath, 'NODE_SEA_BLOB', blob, {
|
||||
sentinelFuse: 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2'
|
||||
});
|
||||
}
|
||||
async function cleanup(options) {
|
||||
const { dir } = options;
|
||||
const toRemove = [
|
||||
FILENAMES.SEA_BLOB,
|
||||
FILENAMES.SEA_MAIN,
|
||||
FILENAMES.SEA_CONFIG
|
||||
];
|
||||
for (const file of toRemove) {
|
||||
try {
|
||||
await fs_extra_1.default.remove(path_1.default.join(dir, file));
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Tried and failed to remove ${file}. Continuing.`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function getOptions(options) {
|
||||
const cloned = { ...options };
|
||||
if (!cloned.path) {
|
||||
cloned.path = path_1.default.join(os_1.default.homedir(), '.electron', 'windows-sign', 'sea.exe');
|
||||
await fs_extra_1.default.ensureFile(cloned.path);
|
||||
}
|
||||
if (!cloned.bin) {
|
||||
cloned.bin = process.execPath;
|
||||
}
|
||||
if (!cloned.windowsSign) {
|
||||
throw new Error('Did not find windowsSign options, which are required');
|
||||
}
|
||||
return {
|
||||
path: cloned.path,
|
||||
dir: path_1.default.dirname(cloned.path),
|
||||
filename: path_1.default.basename(cloned.path),
|
||||
bin: cloned.bin,
|
||||
windowsSign: cloned.windowsSign
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Ensures that the current Node.js version supports SEA app generation and errors if not.
|
||||
*/
|
||||
function checkCompatibility() {
|
||||
const version = process.versions.node;
|
||||
const split = version.split('.');
|
||||
const major = parseInt(split[0], 10);
|
||||
if (major >= 20) {
|
||||
return true;
|
||||
}
|
||||
throw new Error(`Your Node.js version (${process.version}) does not support Single Executable Applications. Please upgrade your version of Node.js.`);
|
||||
}
|
||||
/** Make sure that the input string has escaped backwards slashes
|
||||
* - but never double-escaped backwards slashes.
|
||||
*/
|
||||
function escapeMaybe(input) {
|
||||
const result = input.split(path_1.default.sep).join('\\\\');
|
||||
if (result.includes('\\\\\\\\')) {
|
||||
throw new Error(`Your passed input ${input} contains escaped slashes. Please do not escape them`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
import { InternalSignOptions } from './types';
|
||||
/**
|
||||
* Sign with a hook function, basically letting everyone
|
||||
* write completely custom sign logic
|
||||
*
|
||||
* @param {InternalSignOptions} options
|
||||
*/
|
||||
export declare function signWithHook(options: InternalSignOptions): Promise<void>;
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.signWithHook = void 0;
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const log_1 = require("./utils/log");
|
||||
let hookFunction;
|
||||
function getHookFunction(options) {
|
||||
if (options.hookFunction) {
|
||||
return options.hookFunction;
|
||||
}
|
||||
if (options.hookModulePath) {
|
||||
const module = require(path_1.default.resolve(options.hookModulePath));
|
||||
if (module.default) {
|
||||
return module.default;
|
||||
}
|
||||
if (typeof module === 'function') {
|
||||
return module;
|
||||
}
|
||||
}
|
||||
if (!hookFunction) {
|
||||
throw new Error('No hook function found. Signing will not be possible. Please see the documentation for how to pass a hook function to @electron/windows-sign');
|
||||
}
|
||||
return hookFunction;
|
||||
}
|
||||
/**
|
||||
* Sign with a hook function, basically letting everyone
|
||||
* write completely custom sign logic
|
||||
*
|
||||
* @param {InternalSignOptions} options
|
||||
*/
|
||||
async function signWithHook(options) {
|
||||
hookFunction = getHookFunction(options);
|
||||
for (const file of options.files) {
|
||||
try {
|
||||
await hookFunction(file);
|
||||
}
|
||||
catch (error) {
|
||||
(0, log_1.log)(`Error signing ${file}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.signWithHook = signWithHook;
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
import { InternalSignOptions } from './types';
|
||||
export declare function signWithSignTool(options: InternalSignOptions): Promise<void>;
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.signWithSignTool = void 0;
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const log_1 = require("./utils/log");
|
||||
const spawn_1 = require("./spawn");
|
||||
const cross_dirname_1 = require("cross-dirname");
|
||||
const DIRNAME = (0, cross_dirname_1.getDirname)();
|
||||
function getSigntoolArgs(options) {
|
||||
// See the following url for docs
|
||||
// https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
|
||||
const { certificateFile, certificatePassword, hash, timestampServer } = options;
|
||||
const args = ['sign'];
|
||||
// Automatically select cert
|
||||
if (options.automaticallySelectCertificate) {
|
||||
args.push('/a');
|
||||
}
|
||||
// Dual-sign
|
||||
if (options.appendSignature) {
|
||||
args.push('/as');
|
||||
}
|
||||
// Timestamp
|
||||
if (hash === "sha256" /* HASHES.sha256 */) {
|
||||
args.push('/tr', timestampServer);
|
||||
args.push('/td', hash);
|
||||
}
|
||||
else {
|
||||
args.push('/t', timestampServer);
|
||||
}
|
||||
// Certificate file
|
||||
if (certificateFile) {
|
||||
args.push('/f', path_1.default.resolve(certificateFile));
|
||||
}
|
||||
// Certificate password
|
||||
if (certificatePassword) {
|
||||
args.push('/p', certificatePassword);
|
||||
}
|
||||
// Hash
|
||||
args.push('/fd', hash);
|
||||
// Description
|
||||
if (options.description) {
|
||||
args.push('/d', options.description);
|
||||
}
|
||||
// Website
|
||||
if (options.website) {
|
||||
args.push('/du', options.website);
|
||||
}
|
||||
// Debug
|
||||
if (options.debug) {
|
||||
args.push('/debug');
|
||||
}
|
||||
if (options.signWithParams) {
|
||||
const extraArgs = [];
|
||||
if (Array.isArray(options.signWithParams)) {
|
||||
extraArgs.push(...options.signWithParams);
|
||||
}
|
||||
else {
|
||||
// Split up at spaces and doublequotes
|
||||
extraArgs.push(...options.signWithParams.match(/(?:[^\s"]+|"[^"]*")+/g));
|
||||
}
|
||||
(0, log_1.log)('Parsed signWithParams as:', extraArgs);
|
||||
args.push(...extraArgs);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
async function execute(options) {
|
||||
const { signToolPath, files } = options;
|
||||
const args = getSigntoolArgs(options);
|
||||
(0, log_1.log)('Executing signtool with args', { args, files });
|
||||
const { code, stderr, stdout } = await (0, spawn_1.spawnPromise)(signToolPath, [...args, ...files], {
|
||||
env: process.env,
|
||||
cwd: process.cwd()
|
||||
});
|
||||
if (code !== 0) {
|
||||
throw new Error(`Signtool exited with code ${code}. Stderr: ${stderr}. Stdout: ${stdout}`);
|
||||
}
|
||||
}
|
||||
async function signWithSignTool(options) {
|
||||
const certificatePassword = options.certificatePassword || process.env.WINDOWS_CERTIFICATE_PASSWORD;
|
||||
const certificateFile = options.certificateFile || process.env.WINDOWS_CERTIFICATE_FILE;
|
||||
const signWithParams = options.signWithParams || process.env.WINDOWS_SIGN_WITH_PARAMS;
|
||||
const timestampServer = options.timestampServer || process.env.WINDOWS_TIMESTAMP_SERVER || 'http://timestamp.digicert.com';
|
||||
const signToolPath = options.signToolPath || process.env.WINDOWS_SIGNTOOL_PATH || path_1.default.join(DIRNAME, '../../vendor/signtool.exe');
|
||||
const description = options.description || process.env.WINDOWS_SIGN_DESCRIPTION;
|
||||
const website = options.website || process.env.WINDOWS_SIGN_WEBSITE;
|
||||
if (!certificateFile && !(signWithParams || signToolPath)) {
|
||||
throw new Error('You must provide a certificateFile and a signToolPath or signing parameters');
|
||||
}
|
||||
if (!signToolPath && !signWithParams && !certificatePassword) {
|
||||
throw new Error('You must provide a certificatePassword or signing parameters');
|
||||
}
|
||||
const internalOptions = {
|
||||
appendSignature: false,
|
||||
...options,
|
||||
certificateFile,
|
||||
certificatePassword,
|
||||
signWithParams,
|
||||
signToolPath,
|
||||
description,
|
||||
timestampServer,
|
||||
website
|
||||
};
|
||||
const hashes = options.hashes == null || options.hashes.length === 0
|
||||
? ["sha1" /* HASHES.sha1 */, "sha256" /* HASHES.sha256 */]
|
||||
: options.hashes;
|
||||
if (hashes.includes("sha1" /* HASHES.sha1 */)) {
|
||||
await execute({ ...internalOptions, hash: "sha1" /* HASHES.sha1 */ });
|
||||
// If we signed with SHA1, we need to append the SHA256 signature:
|
||||
internalOptions.appendSignature = true;
|
||||
}
|
||||
if (hashes.includes("sha256" /* HASHES.sha256 */)) {
|
||||
await execute({ ...internalOptions, hash: "sha256" /* HASHES.sha256 */ });
|
||||
}
|
||||
}
|
||||
exports.signWithSignTool = signWithSignTool;
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { SignOptions } from './types';
|
||||
/**
|
||||
* This is the main function exported from this module. It'll
|
||||
* look at your options, determine the best way to sign a file,
|
||||
* and then return one of our internal functions to do the actual
|
||||
* signing.
|
||||
*
|
||||
* @param options
|
||||
* @returns {Promise<void>}
|
||||
*
|
||||
* @category Sign
|
||||
*/
|
||||
export declare function sign(options: SignOptions): Promise<void>;
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sign = void 0;
|
||||
const files_1 = require("./files");
|
||||
const sign_with_hook_1 = require("./sign-with-hook");
|
||||
const sign_with_signtool_1 = require("./sign-with-signtool");
|
||||
const log_1 = require("./utils/log");
|
||||
const parse_env_1 = require("./utils/parse-env");
|
||||
/**
|
||||
* This is the main function exported from this module. It'll
|
||||
* look at your options, determine the best way to sign a file,
|
||||
* and then return one of our internal functions to do the actual
|
||||
* signing.
|
||||
*
|
||||
* @param options
|
||||
* @returns {Promise<void>}
|
||||
*
|
||||
* @category Sign
|
||||
*/
|
||||
async function sign(options) {
|
||||
const signJavaScript = options.signJavaScript || (0, parse_env_1.booleanFromEnv)('WINDOWS_SIGN_JAVASCRIPT');
|
||||
const hookModulePath = options.hookModulePath || process.env.WINDOWS_SIGN_HOOK_MODULE_PATH;
|
||||
if (options.debug) {
|
||||
(0, log_1.enableDebugging)();
|
||||
}
|
||||
(0, log_1.log)('Called with options', { options });
|
||||
const files = (0, files_1.getFilesToSign)(options);
|
||||
const internalOptions = {
|
||||
...options,
|
||||
signJavaScript,
|
||||
hookModulePath,
|
||||
files
|
||||
};
|
||||
// If a hook is provides, sign with the hook
|
||||
if (internalOptions.hookFunction || internalOptions.hookModulePath) {
|
||||
(0, log_1.log)('Signing with hook');
|
||||
return (0, sign_with_hook_1.signWithHook)(internalOptions);
|
||||
}
|
||||
// If we're going with the defaults, we're signing
|
||||
// with signtool. Custom signing tools are also
|
||||
// handled here.
|
||||
(0, log_1.log)('Signing with signtool');
|
||||
return (0, sign_with_signtool_1.signWithSignTool)(internalOptions);
|
||||
}
|
||||
exports.sign = sign;
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
import { SpawnOptions } from 'child_process';
|
||||
export interface SpawnPromiseResult {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
code: number;
|
||||
}
|
||||
/**
|
||||
* Spawn a process as a promise
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Array<string>} args
|
||||
* @param {SpawnOptions} [options]
|
||||
* @returns {Promise<SpawnPromiseResult>}
|
||||
*/
|
||||
export declare function spawnPromise(name: string, args: Array<string>, options?: SpawnOptions): Promise<SpawnPromiseResult>;
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.spawnPromise = void 0;
|
||||
const log_1 = require("./utils/log");
|
||||
/**
|
||||
* Spawn a process as a promise
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Array<string>} args
|
||||
* @param {SpawnOptions} [options]
|
||||
* @returns {Promise<SpawnPromiseResult>}
|
||||
*/
|
||||
function spawnPromise(name, args, options) {
|
||||
return new Promise((resolve) => {
|
||||
const { spawn } = require('child_process');
|
||||
const fork = spawn(name, args, options);
|
||||
(0, log_1.log)(`Spawning ${name} with ${args}`);
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
fork.stdout.on('data', (data) => {
|
||||
(0, log_1.log)(`Spawn ${name} stdout: ${data}`);
|
||||
stdout += data;
|
||||
});
|
||||
fork.stderr.on('data', (data) => {
|
||||
(0, log_1.log)(`Spawn ${name} stderr: ${data}`);
|
||||
stderr += data;
|
||||
});
|
||||
fork.on('close', (code) => {
|
||||
(0, log_1.log)(`Spawn ${name}: Child process exited with code ${code}`);
|
||||
resolve({ stdout, stderr, code });
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.spawnPromise = spawnPromise;
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* SHA-1 has been deprecated on Windows since 2016. We'll still dualsign.
|
||||
* https://social.technet.microsoft.com/wiki/contents/articles/32288.windows-enforcement-of-sha1-certificates.aspx#Post-February_TwentySeventeen_Plan
|
||||
*/
|
||||
export declare const enum HASHES {
|
||||
sha1 = "sha1",
|
||||
sha256 = "sha256"
|
||||
}
|
||||
/**
|
||||
* Signing can be either by specifying a directory of files to sign.
|
||||
*
|
||||
* @category Sign
|
||||
*/
|
||||
export type SignOptions = SignOptionsForDirectory | SignOptionsForFiles;
|
||||
/**
|
||||
* Options for signing by passing a path to a directory to be codesigned.
|
||||
*
|
||||
* @category Sign
|
||||
*/
|
||||
export interface SignOptionsForDirectory extends SignToolOptions {
|
||||
/**
|
||||
* Path to the application directory. We will scan this
|
||||
* directory for any `.dll`, `.exe`, `.msi`, or `.node` files and
|
||||
* codesign them with `signtool.exe`.
|
||||
*/
|
||||
appDirectory: string;
|
||||
}
|
||||
/**
|
||||
* Options for signing by passing an array of files to be codesigned.
|
||||
*
|
||||
* @category Sign
|
||||
*/
|
||||
export interface SignOptionsForFiles extends SignToolOptions {
|
||||
/**
|
||||
* Array of paths to files to be codesigned with `signtool.exe`.
|
||||
*/
|
||||
files: Array<string>;
|
||||
}
|
||||
/**
|
||||
* @category Utility
|
||||
*/
|
||||
export interface SignToolOptions extends OptionalSignToolOptions, OptionalHookOptions {
|
||||
}
|
||||
export interface InternalSignOptions extends SignOptionsForFiles {
|
||||
}
|
||||
export interface InternalSignToolOptions extends OptionalSignToolOptions, OptionalHookOptions {
|
||||
signToolPath: string;
|
||||
timestampServer: string;
|
||||
files: Array<string>;
|
||||
hash: HASHES;
|
||||
appendSignature?: boolean;
|
||||
}
|
||||
/**
|
||||
* @category Utility
|
||||
*/
|
||||
export interface OptionalSignToolOptions {
|
||||
/**
|
||||
* Path to a `.pfx` code signing certificate.
|
||||
* Will use `process.env.WINDOWS_CERTIFICATE_FILE` if this option is not provided.
|
||||
*/
|
||||
certificateFile?: string;
|
||||
/**
|
||||
* Password to {@link certificateFile}. If you don't provide this,
|
||||
* you need to provide the {@link signWithParams} option.
|
||||
* Will use `process.env.WINDOWS_CERTIFICATE_PASSWORD` if this option is not provided.
|
||||
*/
|
||||
certificatePassword?: string;
|
||||
/**
|
||||
* Path to a timestamp server.
|
||||
* Will use `process.env.WINDOWS_TIMESTAMP_SERVER` if this option is not provided.
|
||||
*
|
||||
* @defaultValue http://timestamp.digicert.com
|
||||
*/
|
||||
timestampServer?: string;
|
||||
/**
|
||||
* Description of the signed content. Will be passed to `signtool.exe` as `/d`.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* URL for the expanded description of the signed content. Will be passed to `signtool.exe` as `/du`.
|
||||
*/
|
||||
website?: string;
|
||||
/**
|
||||
* Path to the `signtool.exe` used to sign. Will use `vendor/signtool.exe` if not provided.
|
||||
*/
|
||||
signToolPath?: string;
|
||||
/**
|
||||
* Additional parameters to pass to `signtool.exe`.
|
||||
*
|
||||
* @see Microsoft's {@link https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe SignTool.exe documentation}
|
||||
*/
|
||||
signWithParams?: string | Array<string>;
|
||||
/**
|
||||
* Enables debug logging.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Automatically selects the best signing certificate according to SignTool. Will be passed to `signtool.exe` as `/a`.
|
||||
*
|
||||
* @defaultValue true
|
||||
*/
|
||||
automaticallySelectCertificate?: boolean;
|
||||
/**
|
||||
* Whether or not to sign JavaScript files.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
signJavaScript?: boolean;
|
||||
/**
|
||||
* Hash algorithms to use for signing.
|
||||
*/
|
||||
hashes?: HASHES[];
|
||||
}
|
||||
/**
|
||||
* Custom function that is called sequentially for each file that needs to be signed.
|
||||
*
|
||||
* @param fileToSign Absolute path to the file to sign
|
||||
*
|
||||
* @category Utility
|
||||
*/
|
||||
export type HookFunction = (fileToSign: string) => void | Promise<void>;
|
||||
/**
|
||||
* @category Utility
|
||||
*/
|
||||
export interface OptionalHookOptions {
|
||||
/**
|
||||
* A hook function called for each file that needs to be signed.
|
||||
* Use this for full control over your app's signing logic.
|
||||
* `@electron/windows-sign` will not attempt to sign with SignTool if a custom hook is detected.
|
||||
*/
|
||||
hookFunction?: HookFunction;
|
||||
/**
|
||||
* A path to a JavaScript file, exporting a single function that will be called for each file that needs to be signed.
|
||||
* Use this for full control over your app's signing logic.
|
||||
* `@electron/windows-sign` will not attempt to sign with SignTool if a custom hook is detected.
|
||||
*/
|
||||
hookModulePath?: string;
|
||||
}
|
||||
export interface InternalHookOptions extends OptionalHookOptions {
|
||||
files: Array<string>;
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
export declare function enableDebugging(): void;
|
||||
export declare const log: import("debug").Debugger;
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.log = exports.enableDebugging = void 0;
|
||||
const debug_1 = require("debug");
|
||||
function enableDebugging() {
|
||||
debug_1.debug.enable('electron-windows-sign');
|
||||
}
|
||||
exports.enableDebugging = enableDebugging;
|
||||
exports.log = (0, debug_1.debug)('electron-windows-sign');
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Tries to parse an process.env string to a boolean.
|
||||
* Will understand undefined as the default value
|
||||
* Will understand "false", "False", "fAlse", or "0" as `false`
|
||||
* Will understand everything else as true
|
||||
*
|
||||
* @export
|
||||
* @param {string} name
|
||||
* @return {*} {boolean}
|
||||
*/
|
||||
export declare function booleanFromEnv(name: string): boolean | undefined;
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.booleanFromEnv = void 0;
|
||||
/**
|
||||
* Tries to parse an process.env string to a boolean.
|
||||
* Will understand undefined as the default value
|
||||
* Will understand "false", "False", "fAlse", or "0" as `false`
|
||||
* Will understand everything else as true
|
||||
*
|
||||
* @export
|
||||
* @param {string} name
|
||||
* @return {*} {boolean}
|
||||
*/
|
||||
function booleanFromEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value.toLowerCase() === 'false' || value === '0') {
|
||||
return false;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
exports.booleanFromEnv = booleanFromEnv;
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { SignOptions } from './types';
|
||||
/**
|
||||
* Recursively goes through an entire directory and returns an array
|
||||
* of full paths for files ot sign.
|
||||
*
|
||||
* - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node)
|
||||
* - Microsoft installers (.msi)
|
||||
* - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle)
|
||||
* - Catalog files (.cat)
|
||||
* - Cabinet files (.cab)
|
||||
* - Silverlight applications (.xap)
|
||||
* - Scripts (.vbs, .wsf, .ps1)
|
||||
* If configured:
|
||||
* - JavaScript files (.js)
|
||||
*/
|
||||
export declare function getFilesToSign(options: SignOptions, dir?: string): Array<string>;
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
const IS_PE_REGEX = /\.(exe|dll|sys|efi|scr|node)$/i;
|
||||
const IS_MSI_REGEX = /\.msi$/i;
|
||||
const IS_PACKAGE_REGEX = /\.(appx|appxbundle|msix|msixbundle)$/i;
|
||||
const IS_CATCAB_REGEX = /\.(cat|cab)$/i;
|
||||
const IS_SILVERLIGHT_REGEX = /\.xap$/i;
|
||||
const IS_SCRIPT_REGEX = /\.(vbs|wsf|ps1)$/i;
|
||||
const IS_JS_REGEX = /\.js$/i;
|
||||
/**
|
||||
* Recursively goes through an entire directory and returns an array
|
||||
* of full paths for files ot sign.
|
||||
*
|
||||
* - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node)
|
||||
* - Microsoft installers (.msi)
|
||||
* - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle)
|
||||
* - Catalog files (.cat)
|
||||
* - Cabinet files (.cab)
|
||||
* - Silverlight applications (.xap)
|
||||
* - Scripts (.vbs, .wsf, .ps1)
|
||||
* If configured:
|
||||
* - JavaScript files (.js)
|
||||
*/
|
||||
export function getFilesToSign(options, dir) {
|
||||
if (isSignOptionsForFiles(options)) {
|
||||
return options.files;
|
||||
}
|
||||
dir = dir || options.appDirectory;
|
||||
// Array of file paths to sign
|
||||
const result = [];
|
||||
// Iterate over the app directory, looking for files to sign
|
||||
const files = fs.readdirSync(dir);
|
||||
const regexes = [
|
||||
IS_PE_REGEX,
|
||||
IS_MSI_REGEX,
|
||||
IS_PACKAGE_REGEX,
|
||||
IS_CATCAB_REGEX,
|
||||
IS_SILVERLIGHT_REGEX,
|
||||
IS_SCRIPT_REGEX
|
||||
];
|
||||
if (options.signJavaScript) {
|
||||
regexes.push(IS_JS_REGEX);
|
||||
}
|
||||
for (const file of files) {
|
||||
const fullPath = path.resolve(dir, file);
|
||||
if (fs.statSync(fullPath).isDirectory()) {
|
||||
// If it's a directory, recurse
|
||||
result.push(...getFilesToSign(options, fullPath));
|
||||
}
|
||||
else if (regexes.some((regex) => regex.test(file))) {
|
||||
// If it's a match, add it to the list
|
||||
result.push(fullPath);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function isSignOptionsForFiles(input) {
|
||||
return !!input.files;
|
||||
}
|
||||
Reference in New Issue
Block a user