Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'graceful-fs';
import os from 'node:os';
import path from 'node:path';
import createDebug from 'debug';
import { ComboOptions, Options } from './types.js';
import { ProcessedOptionsWithSinglePlatformArch, Options } from './types.js';
import { CreateOptions as AsarOptions } from '@electron/asar';

export const debug = createDebug('electron-packager');
Expand All @@ -13,12 +13,17 @@ export function sanitizeAppName(name: string) {
}

export function generateFinalBasename(
opts: Pick<ComboOptions, 'arch' | 'name' | 'platform'>,
opts: Pick<
ProcessedOptionsWithSinglePlatformArch,
'arch' | 'name' | 'platform'
>,
) {
return `${sanitizeAppName(opts.name!)}-${opts.platform}-${opts.arch}`;
return `${sanitizeAppName(opts.name)}-${opts.platform}-${opts.arch}`;
}

export function generateFinalPath(opts: ComboOptions) {
export function generateFinalPath(
opts: ProcessedOptionsWithSinglePlatformArch,
) {
return path.join(opts.out || process.cwd(), generateFinalBasename(opts));
}

Expand Down Expand Up @@ -50,7 +55,9 @@ export function subOptionWarning(
properties[parameter] = value;
}

export function createAsarOpts(opts: ComboOptions): false | AsarOptions {
export function createAsarOpts(
opts: ProcessedOptionsWithSinglePlatformArch,
): false | AsarOptions {
let asarOptions;
if (opts.asar === true) {
asarOptions = {};
Expand All @@ -73,7 +80,9 @@ export function ensureArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}

export function isPlatformMac(platform: ComboOptions['platform']) {
export function isPlatformMac(
platform: ProcessedOptionsWithSinglePlatformArch['platform'],
) {
return platform === 'darwin' || platform === 'mas';
}

Expand Down
50 changes: 34 additions & 16 deletions src/copy-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import { isJunk } from 'junk';
import path from 'node:path';
import { isModule, Pruner } from './prune.js';
import { officialPlatformArchCombos } from './targets.js';
import { ComboOptions, Options } from './types.js';
import {
ProcessedOptionsWithSinglePlatformArch,
OfficialArch,
OfficialPlatform,
Options,
ProcessedOptions,
} from './types.js';
import { CopyOptions } from 'node:fs';

const DEFAULT_IGNORES = [
Expand All @@ -22,25 +28,35 @@ const DEFAULT_IGNORES = [
'/node_gyp_bins($|/)',
];

export function populateIgnoredPaths(opts: Options) {
(opts as Options & { originalIgnore: Options['ignore'] }).originalIgnore =
opts.ignore;
export function populateIgnoredPaths(
opts: Options,
): Pick<ProcessedOptions, 'ignore' | 'originalIgnore'> {
const originalIgnore = opts.ignore;
let ignore: ProcessedOptions['ignore'];

if (typeof opts.ignore !== 'function') {
if (opts.ignore) {
opts.ignore = [...ensureArray(opts.ignore), ...DEFAULT_IGNORES];
} else {
opts.ignore = [...DEFAULT_IGNORES];
}
const ignoreArray = opts.ignore
? [...ensureArray(opts.ignore), ...DEFAULT_IGNORES]
: [...DEFAULT_IGNORES];
if (process.platform === 'linux') {
opts.ignore.push(baseTempDir(opts));
ignoreArray.push(baseTempDir(opts));
}
ignore = ignoreArray;

debug('Ignored path regular expressions:', opts.ignore);
debug('Ignored path regular expressions:', ignore);
} else {
ignore = opts.ignore;
}

return {
ignore,
originalIgnore,
};
}

export function generateIgnoredOutDirs(opts: ComboOptions): string[] {
export function generateIgnoredOutDirs(
opts: ProcessedOptionsWithSinglePlatformArch,
): string[] {
const normalizedOut = opts.out ? path.resolve(opts.out) : null;
const ignoredOutDirs: string[] = [];

Expand All @@ -50,9 +66,9 @@ export function generateIgnoredOutDirs(opts: ComboOptions): string[] {
)) {
for (const arch of archs) {
const basenameOpts = {
arch: arch,
arch: arch as OfficialArch,
name: opts.name,
platform: platform,
platform: platform as OfficialPlatform,
};
ignoredOutDirs.push(
path.join(process.cwd(), generateFinalBasename(basenameOpts)),
Expand All @@ -69,7 +85,7 @@ export function generateIgnoredOutDirs(opts: ComboOptions): string[] {
}

function generateFilterFunction(
ignore: Exclude<ComboOptions['ignore'], undefined>,
ignore: Exclude<ProcessedOptionsWithSinglePlatformArch['ignore'], undefined>,
): (file: string) => boolean {
if (typeof ignore === 'function') {
return (file) => !ignore(file);
Expand All @@ -82,7 +98,9 @@ function generateFilterFunction(
}
}

export function userPathFilter(opts: ComboOptions): CopyOptions['filter'] {
export function userPathFilter(
opts: ProcessedOptionsWithSinglePlatformArch,
): CopyOptions['filter'] {
const filterFunc = generateFilterFunction(opts.ignore || []);
const ignoredOutDirs = generateIgnoredOutDirs(opts);
const pruner = opts.prune ? new Pruner(opts.dir, Boolean(opts.quiet)) : null;
Expand Down
13 changes: 7 additions & 6 deletions src/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { createPlatformArchPairs } from './targets.js';
import {
DownloadOptions,
IgnoreFunc,
OfficialArch,
OfficialPlatform,
Options,
SupportedArch,
SupportedPlatform,
} from './types.js';

export function createDownloadOpts(
opts: Options,
platform: SupportedPlatform,
arch: SupportedArch,
platform: OfficialPlatform,
arch: OfficialArch,
): DownloadOptions {
const downloadOpts = { ...opts.download };

Expand All @@ -42,8 +42,8 @@ export function createDownloadOpts(

export function createDownloadCombos(
opts: Options,
selectedPlatforms: SupportedPlatform[],
selectedArchs: SupportedArch[],
selectedPlatforms: OfficialPlatform[],
selectedArchs: OfficialArch[],
ignoreFunc?: IgnoreFunc,
) {
const platformArchPairs = createPlatformArchPairs(
Expand All @@ -66,6 +66,7 @@ export async function downloadElectronZip(downloadOpts: DownloadOptions) {
downloadOpts.arch === 'armv7l' &&
semver.lt(downloadOpts.version, '1.0.0')
) {
//@ts-expect-error: this used to be a valid arch for Electron 0.x
downloadOpts.arch = 'arm';
}
debug(`Downloading Electron with options ${JSON.stringify(downloadOpts)}`);
Expand Down
62 changes: 30 additions & 32 deletions src/infer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import parseAuthor from 'parse-author';
import path from 'node:path';
import resolve, { AsyncOpts } from 'resolve';
import { debug } from './common.js';
import { Options, SupportedPlatform } from './types.js';
import { OfficialPlatform, Options, ProcessedOptions } from './types.js';

function isMissingRequiredProperty(props: string[]) {
return props.some(
Expand Down Expand Up @@ -59,66 +59,69 @@ function resolvePromise(id: string, options: AsyncOpts) {
);
}

async function getVersion(
opts: Options,
electronProp: GetPackageInfoResultSourceItem,
) {
async function getVersion(electronProp: GetPackageInfoResultSourceItem) {
const [, packageName] = electronProp.prop.split('.');
const src = electronProp.src;

const pkg = (
await resolvePromise(packageName, { basedir: path.dirname(src) })
)[1];
debug(`Inferring target Electron version from ${packageName} in ${src}`);
opts.electronVersion = pkg.version;
return pkg.version;
}

async function handleMetadata(
opts: Options,
result: GetPackageInfoResult,
): Promise<void> {
if (result.values.productName) {
): Promise<Partial<ProcessedOptions>> {
const processedValues: Partial<ProcessedOptions> = {};

if (typeof result.values.productName === 'string') {
debug(
`Inferring application name from ${result.source.productName.prop} in ${result.source.productName.src}`,
);
opts.name = result.values.productName as string;
processedValues.name = result.values.productName;
}

if (result.values.version) {
if (typeof result.values.version === 'string') {
debug(`Inferring appVersion from version in ${result.source.version.src}`);
opts.appVersion = result.values.version as string;
}

if (result.values.author && !opts.win32metadata) {
opts.win32metadata = {};
processedValues.appVersion = result.values.version;
}

if (result.values.author) {
const author = result.values.author as string | { name: string };
const win32metadata = opts.win32metadata || {};

debug(
`Inferring win32metadata.CompanyName from author in ${result.source.author.src}`,
);
if (typeof author === 'string') {
opts.win32metadata!.CompanyName = parseAuthor(author).name;
win32metadata.CompanyName = parseAuthor(author).name;
} else if (author.name) {
opts.win32metadata!.CompanyName = author.name;
win32metadata.CompanyName = author.name;
} else {
debug(
'Cannot infer win32metadata.CompanyName from author, no name found',
);
}
processedValues.win32metadata = win32metadata;
}

// eslint-disable-next-line no-prototype-builtins
if (result.values.hasOwnProperty('dependencies.electron')) {
return getVersion(opts, result.source['dependencies.electron']);
} else {
return Promise.resolve();
if (
Object.prototype.hasOwnProperty.call(result.values, 'dependencies.electron')
) {
processedValues.electronVersion = await getVersion(
result.source['dependencies.electron'],
);
}

return processedValues;
}

function handleMissingProperties(opts: Options, err: GetPackageInfoError) {
function handleMissingProperties(
opts: Options,
err: GetPackageInfoError,
): Promise<Partial<ProcessedOptions>> {
const missingProps = err.missingProps.map((prop) => {
return Array.isArray(prop) ? prop[0] : prop;
});
Expand All @@ -136,10 +139,10 @@ function handleMissingProperties(opts: Options, err: GetPackageInfoError) {
}

export async function getMetadataFromPackageJSON(
platforms: SupportedPlatform[],
platforms: OfficialPlatform[],
opts: Options,
dir: string,
): Promise<void> {
): Promise<Partial<ProcessedOptions>> {
const props: Array<string | string[]> = [];

if (!opts.name) {
Expand Down Expand Up @@ -169,15 +172,10 @@ export async function getMetadataFromPackageJSON(
props.push('author');
}

// Name and version provided, no need to infer
if (props.length === 0) {
return Promise.resolve();
}

// Search package.json files to infer name and version from
try {
const result = await getPackageInfo(props, dir);
return handleMetadata(opts, result);
const packageInfo = await getPackageInfo(props, dir);
return handleMetadata(opts, packageInfo);
} catch (e) {
const err = e as GetPackageInfoError;

Expand Down
30 changes: 17 additions & 13 deletions src/mac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import path from 'node:path';
import plist, { PlistValue } from 'plist';
import { notarize, NotarizeOptions } from '@electron/notarize';
import { ElectronMacPlatform, sign, SignOptions } from '@electron/osx-sign';
import { ComboOptions } from './types.js';
import { ProcessedOptionsWithSinglePlatformArch } from './types.js';
import { generateAssetCatalogForIcon } from './icon-composer.js';

type NSUsageDescription = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[key in `NS${string}UsageDescription`]: string;
};

Expand Down Expand Up @@ -60,7 +59,10 @@ export class MacApp extends App implements Plists {
helperRendererPlist: Plists['helperRendererPlist'];
loginHelperPlist: Plists['loginHelperPlist'];

constructor(opts: ComboOptions, templatePath: string) {
constructor(
opts: ProcessedOptionsWithSinglePlatformArch,
templatePath: string,
) {
super(opts, templatePath);

this.appName = opts.name as string;
Expand Down Expand Up @@ -203,7 +205,9 @@ export class MacApp extends App implements Plists {

async extendPlist(
basePlist: BasePList,
propsOrFilename: ComboOptions['extendInfo' | 'extendHelperInfo'],
propsOrFilename: ProcessedOptionsWithSinglePlatformArch[
| 'extendInfo'
| 'extendHelperInfo'],
) {
if (!propsOrFilename) {
return Promise.resolve();
Expand Down Expand Up @@ -511,10 +515,7 @@ export class MacApp extends App implements Plists {
const platform = this.opts.platform;
const version = this.opts.electronVersion;

if (
(platform === 'all' || platform === 'mas') &&
osxSignOpt === undefined
) {
if (platform === 'mas' && osxSignOpt === undefined) {
warning(
'signing is required for mas builds. Provide the osx-sign option, ' +
'or manually sign the app later.',
Expand Down Expand Up @@ -588,7 +589,7 @@ export { MacApp as App };
* https://developer.apple.com/library/mac/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070
*/
export function filterCFBundleIdentifier(
identifier: ComboOptions['appBundleId'],
identifier: ProcessedOptionsWithSinglePlatformArch['appBundleId'],
) {
return identifier!.replace(/ /g, '-').replace(/[^a-zA-Z0-9.-]/g, '');
}
Expand All @@ -604,10 +605,13 @@ type CreateSignOptsResult = Mutable<
>;

export function createSignOpts(
properties: Exclude<ComboOptions['osxSign'], undefined>,
platform: ComboOptions['platform'],
properties: Exclude<
ProcessedOptionsWithSinglePlatformArch['osxSign'],
undefined
>,
platform: ProcessedOptionsWithSinglePlatformArch['platform'],
app: string,
version: ComboOptions['electronVersion'],
version: ProcessedOptionsWithSinglePlatformArch['electronVersion'],
quiet?: boolean,
): CreateSignOptsResult {
// use default sign opts if osx-sign is true, otherwise clone osx-sign object
Expand Down Expand Up @@ -658,7 +662,7 @@ export function createSignOpts(
type CreateNotarizeOptsResult = Exclude<NotarizeOptions, { tool?: 'legacy' }>;

export function createNotarizeOpts(
properties: ComboOptions['osxNotarize'],
properties: ProcessedOptionsWithSinglePlatformArch['osxNotarize'],
appBundleId: string,
appPath: string,
quiet: boolean,
Expand Down
Loading
Loading