Proyecto App Linux
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { createRequire } from 'module';
|
||||
import * as path from 'path';
|
||||
|
||||
const WORKER_RELATIVE_PATH = [
|
||||
'dist',
|
||||
'apps',
|
||||
'electron-backend',
|
||||
'workers',
|
||||
] as const;
|
||||
|
||||
const NATIVE_MODULE_RELATIVE_PATHS = [
|
||||
['app.asar.unpacked', 'node_modules'],
|
||||
['app.asar.unpacked', 'electron-backend', 'node_modules'],
|
||||
['app.asar.unpacked', 'dist', 'apps', 'electron-backend', 'node_modules'],
|
||||
] as const;
|
||||
|
||||
type FileExists = (filePath: string) => boolean;
|
||||
type ModuleRequireFactory = typeof createRequire;
|
||||
type NodeModuleApi = {
|
||||
globalPaths: string[];
|
||||
_initPaths?: () => void;
|
||||
};
|
||||
|
||||
export interface WorkerBootstrapData {
|
||||
nativeModuleSearchPaths?: string[];
|
||||
}
|
||||
|
||||
export interface ResolveWorkerRuntimeBootstrapOptions {
|
||||
isPackaged: boolean;
|
||||
workerFilename: string;
|
||||
developmentWorkerDir: string;
|
||||
resourcesPath?: string;
|
||||
appPath?: string;
|
||||
fileExists?: FileExists;
|
||||
}
|
||||
|
||||
export interface WorkerRuntimeBootstrap {
|
||||
workerPath: string;
|
||||
workerPathCandidates: string[];
|
||||
nativeModuleSearchPaths?: string[];
|
||||
}
|
||||
|
||||
export interface LoadNativeModuleOptions<TModule> {
|
||||
moduleName: string;
|
||||
searchPaths: string[];
|
||||
loggerLabel: string;
|
||||
fallbackRequire?: () => TModule;
|
||||
fileExists?: FileExists;
|
||||
requireFactory?: ModuleRequireFactory;
|
||||
}
|
||||
|
||||
function dedupePaths(paths: Array<string | undefined>): string[] {
|
||||
return [...new Set(paths.filter((value): value is string => Boolean(value)))];
|
||||
}
|
||||
|
||||
export function registerNativeModuleSearchPaths(
|
||||
searchPaths: string[],
|
||||
options?: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
moduleApi?: NodeModuleApi;
|
||||
}
|
||||
): string[] {
|
||||
const env = options?.env ?? process.env;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const moduleApi =
|
||||
options?.moduleApi ??
|
||||
(require('module') as NodeModuleApi);
|
||||
|
||||
const mergedPaths = dedupePaths([
|
||||
...searchPaths,
|
||||
...(env.NODE_PATH?.split(path.delimiter) ?? []),
|
||||
...moduleApi.globalPaths,
|
||||
]);
|
||||
|
||||
if (mergedPaths.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
env.NODE_PATH = mergedPaths.join(path.delimiter);
|
||||
|
||||
if (typeof moduleApi._initPaths === 'function') {
|
||||
moduleApi._initPaths();
|
||||
} else {
|
||||
moduleApi.globalPaths.splice(0, moduleApi.globalPaths.length, ...mergedPaths);
|
||||
}
|
||||
|
||||
for (let index = mergedPaths.length - 1; index >= 0; index -= 1) {
|
||||
const currentPath = mergedPaths[index];
|
||||
|
||||
if (!moduleApi.globalPaths.includes(currentPath)) {
|
||||
moduleApi.globalPaths.unshift(currentPath);
|
||||
}
|
||||
}
|
||||
|
||||
return mergedPaths;
|
||||
}
|
||||
|
||||
export function getPackagedResourceRoots(options: {
|
||||
resourcesPath?: string;
|
||||
appPath?: string;
|
||||
}): string[] {
|
||||
return dedupePaths([
|
||||
options.resourcesPath,
|
||||
options.appPath ? path.dirname(options.appPath) : undefined,
|
||||
]);
|
||||
}
|
||||
|
||||
export function getNativeModuleSearchPaths(options: {
|
||||
resourcesPath?: string;
|
||||
appPath?: string;
|
||||
}): string[] {
|
||||
return dedupePaths(
|
||||
getPackagedResourceRoots(options).flatMap((root) =>
|
||||
NATIVE_MODULE_RELATIVE_PATHS.map((segments) =>
|
||||
path.join(root, ...segments)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function getPackagedWorkerPathCandidates(options: {
|
||||
workerFilename: string;
|
||||
resourcesPath?: string;
|
||||
appPath?: string;
|
||||
}): string[] {
|
||||
return dedupePaths(
|
||||
getPackagedResourceRoots(options).map((root) =>
|
||||
path.join(root, ...WORKER_RELATIVE_PATH, options.workerFilename)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function createWorkerResolutionError(
|
||||
workerFilename: string,
|
||||
workerPathCandidates: string[]
|
||||
): Error {
|
||||
const error = new Error(
|
||||
[
|
||||
`Unable to resolve worker "${workerFilename}".`,
|
||||
'Tried:',
|
||||
...workerPathCandidates.map((candidate) => `- ${candidate}`),
|
||||
].join('\n')
|
||||
);
|
||||
error.name = 'WorkerPathResolutionError';
|
||||
return error;
|
||||
}
|
||||
|
||||
export function resolveWorkerRuntimeBootstrap(
|
||||
options: ResolveWorkerRuntimeBootstrapOptions
|
||||
): WorkerRuntimeBootstrap {
|
||||
const fileExists = options.fileExists ?? existsSync;
|
||||
|
||||
if (!options.isPackaged) {
|
||||
const workerPath = path.join(
|
||||
options.developmentWorkerDir,
|
||||
options.workerFilename
|
||||
);
|
||||
const workerPathCandidates = [workerPath];
|
||||
|
||||
if (!fileExists(workerPath)) {
|
||||
throw createWorkerResolutionError(
|
||||
options.workerFilename,
|
||||
workerPathCandidates
|
||||
);
|
||||
}
|
||||
|
||||
return { workerPath, workerPathCandidates };
|
||||
}
|
||||
|
||||
const workerPathCandidates = getPackagedWorkerPathCandidates(options);
|
||||
const workerPath = workerPathCandidates.find((candidate) =>
|
||||
fileExists(candidate)
|
||||
);
|
||||
|
||||
if (!workerPath) {
|
||||
throw createWorkerResolutionError(
|
||||
options.workerFilename,
|
||||
workerPathCandidates
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
workerPath,
|
||||
workerPathCandidates,
|
||||
nativeModuleSearchPaths: getNativeModuleSearchPaths(options),
|
||||
};
|
||||
}
|
||||
|
||||
export function getWorkerDataNativeModuleSearchPaths(
|
||||
value: unknown
|
||||
): string[] {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!('nativeModuleSearchPaths' in value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const searchPaths = value.nativeModuleSearchPaths;
|
||||
if (!Array.isArray(searchPaths)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return dedupePaths(
|
||||
searchPaths.filter(
|
||||
(searchPath): searchPath is string => typeof searchPath === 'string'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function loadNativeModuleFromSearchPaths<TModule>(
|
||||
options: LoadNativeModuleOptions<TModule>
|
||||
): TModule {
|
||||
const fileExists = options.fileExists ?? existsSync;
|
||||
const requireFactory = options.requireFactory ?? createRequire;
|
||||
const attemptedPaths: string[] = [];
|
||||
const failedPaths: string[] = [];
|
||||
|
||||
for (const searchPath of dedupePaths(options.searchPaths)) {
|
||||
attemptedPaths.push(searchPath);
|
||||
|
||||
if (!fileExists(searchPath)) {
|
||||
failedPaths.push(`${searchPath} (missing)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const nativeRequire = requireFactory(
|
||||
path.join(searchPath, 'index.js')
|
||||
);
|
||||
return nativeRequire(options.moduleName) as TModule;
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
failedPaths.push(`${searchPath} (${message})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.fallbackRequire) {
|
||||
try {
|
||||
return options.fallbackRequire();
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
failedPaths.push(`require(${options.moduleName}) (${message})`);
|
||||
}
|
||||
}
|
||||
|
||||
const error = new Error(
|
||||
[
|
||||
`${options.loggerLabel} Unable to load native module "${options.moduleName}".`,
|
||||
'Tried:',
|
||||
...attemptedPaths.map((searchPath) => `- ${searchPath}`),
|
||||
...(failedPaths.length > 0 ? ['Failures:', ...failedPaths.map((failure) => `- ${failure}`)] : []),
|
||||
].join('\n')
|
||||
);
|
||||
error.name = 'NativeModuleResolutionError';
|
||||
throw error;
|
||||
}
|
||||
Reference in New Issue
Block a user