Co-authored-by: Mark Lee <malept@users.noreply.github.com>
This commit is contained in:
小飞侠 2020-01-30 01:18:23 +08:00 коммит произвёл GitHub
Родитель 94cb04d9a4
Коммит 367e08129a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 264 добавлений и 49 удалений

Просмотреть файл

@ -7,6 +7,7 @@ import ora from 'ora';
import * as argParser from 'yargs';
import { rebuild, ModuleType } from './rebuild';
import { getProjectRootPath } from './search-module';
import { locateElectronModule } from './electron-locator';
const yargs = argParser
@ -68,7 +69,8 @@ process.on('unhandledRejection', handler);
(async (): Promise<void> => {
const electronModulePath = argv.e ? path.resolve(process.cwd(), (argv.e as string)) : locateElectronModule();
const projectRootPath = await getProjectRootPath(process.cwd());
const electronModulePath = argv.e ? path.resolve(process.cwd(), (argv.e as string)) : await locateElectronModule(projectRootPath);
let electronModuleVersion = argv.v as string;
if (!electronModuleVersion) {
@ -128,6 +130,7 @@ process.on('unhandledRejection', handler);
mode: argv.p ? 'parallel' : (argv.s ? 'sequential' : undefined),
debug: argv.b as boolean,
prebuildTagPrefix: (argv.prebuildTagPrefix as string) || 'v',
projectRootPath,
});
const lifecycle = rebuilder.lifecycle;

Просмотреть файл

@ -1,38 +1,31 @@
import * as fs from 'fs';
import * as fs from 'fs-extra';
import * as path from 'path';
import { searchForModule } from './search-module';
const electronModuleNames = ['electron', 'electron-prebuilt', 'electron-prebuilt-compile'];
const relativeNodeModulesDir = path.resolve(__dirname, '..', '..');
function locateModules(pathMapper: (moduleName: string) => string | null): string[] {
const possibleModulePaths = electronModuleNames.map(pathMapper);
return possibleModulePaths.filter((modulePath) => modulePath && fs.existsSync(path.join(modulePath, 'package.json'))) as string[];
}
function locateSiblingModules(): string[] {
return locateModules((moduleName) => path.join(relativeNodeModulesDir, moduleName));
}
function locateModulesByRequire(): string[] | null {
return locateModules((moduleName) => {
async function locateModuleByRequire(): Promise<string | null> {
for (const moduleName of electronModuleNames) {
try {
return path.resolve(require.resolve(path.join(moduleName, 'package.json')), '..');
} catch (error) {
return null;
const modulePath = path.resolve(require.resolve(path.join(moduleName, 'package.json')), '..');
if (await fs.pathExists(path.join(modulePath, 'package.json'))) {
return modulePath;
}
} catch (_error) { // eslint-disable-line no-empty
}
});
}
export function locateElectronModule(): string | null {
const siblingModules: string[] | null = locateSiblingModules();
if (siblingModules.length > 0) {
return siblingModules[0];
}
const requiredModules = locateModulesByRequire();
if (requiredModules && requiredModules.length > 0) {
return requiredModules[0];
return null
}
export async function locateElectronModule(projectRootPath?: string): Promise<string | null> {
for (const moduleName of electronModuleNames) {
const electronPath = await searchForModule(process.cwd(), moduleName, projectRootPath)[0];
if (electronPath && await fs.pathExists(path.join(electronPath, 'package.json'))) {
return electronPath;
}
}
return null;
return locateModuleByRequire();
}

Просмотреть файл

@ -9,6 +9,7 @@ import * as os from 'os';
import * as path from 'path';
import { readPackageJson } from './read-package-json';
import { lookupModuleState, cacheModuleState } from './cache';
import { searchForModule, searchForNodeModules } from './search-module';
export type ModuleType = 'prod' | 'dev' | 'optional';
export type RebuildMode = 'sequential' | 'parallel';
@ -27,6 +28,7 @@ export interface RebuildOptions {
useCache?: boolean;
cachePath?: string;
prebuildTagPrefix?: string;
projectRootPath?: string;
}
export type HashTree = { [path: string]: string | HashTree };
@ -84,6 +86,7 @@ class Rebuilder {
public useCache: boolean;
public cachePath: string;
public prebuildTagPrefix: string;
public projectRootPath?: string;
constructor(options: RebuilderOptions) {
this.lifecycle = options.lifecycle;
@ -105,6 +108,7 @@ class Rebuilder {
console.warn('[WARNING]: Electron Rebuild has force enabled and cache enabled, force take precedence and the cache will not be used.');
this.useCache = false;
}
this.projectRootPath = options.projectRootPath;
if (typeof this.electronVersion === 'number') {
if (`${this.electronVersion}`.split('.').length === 1) {
@ -118,7 +122,7 @@ class Rebuilder {
}
this.ABI = nodeAbi.getAbi(this.electronVersion, 'electron');
this.prodDeps = this.extraModules.reduce((acc, x) => acc.add(x), new Set<string>());
this.prodDeps = this.extraModules.reduce((acc: Set<string>, x: string) => acc.add(x), new Set<string>());
this.rebuilds = [];
this.realModulePaths = new Set();
this.realNodeModulesPaths = new Set();
@ -158,14 +162,28 @@ class Rebuilder {
for (const key of depKeys) {
this.prodDeps[key] = true;
markWaiters.push(this.markChildrenAsProdDeps(path.resolve(this.buildPath, 'node_modules', key)));
const modulePaths: string[] = 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);
await this.rebuildAllModulesIn(path.resolve(this.buildPath, 'node_modules'));
const nodeModulesPaths = await searchForNodeModules(
this.buildPath,
this.projectRootPath
);
for (const nodeModulesPath of nodeModulesPaths) {
await this.rebuildAllModulesIn(nodeModulesPath);
}
this.rebuilds.push(() => this.rebuildModuleAt(this.buildPath));
if (this.mode !== 'sequential') {
@ -448,17 +466,13 @@ class Rebuilder {
}
async findModule(moduleName: string, fromDir: string, foundFn: ((p: string) => Promise<void>)): Promise<void[]> {
let targetDir = fromDir;
const foundFns = [];
while (targetDir !== path.dirname(this.buildPath)) {
const testPath = path.resolve(targetDir, 'node_modules', moduleName);
if (await fs.pathExists(testPath)) {
foundFns.push(foundFn(testPath));
}
targetDir = path.dirname(targetDir);
}
const testPaths = await searchForModule(
fromDir,
moduleName,
this.projectRootPath
);
const foundFns = testPaths.map(testPath => foundFn(testPath));
return Promise.all(foundFns);
}

86
src/search-module.ts Normal file
Просмотреть файл

@ -0,0 +1,86 @@
import * as fs from 'fs-extra';
import * as path from 'path';
async function shouldContinueSearch(traversedPath: string, rootPath?: string, stopAtPackageJSON?: boolean): Promise<boolean> {
if (rootPath) {
return Promise.resolve(traversedPath !== path.dirname(rootPath));
} else if (stopAtPackageJSON) {
return fs.pathExists(path.join(traversedPath, 'package.json'));
} else {
return true;
}
}
type PathGeneratorFunction = (traversedPath: string) => string;
async function traverseAncestorDirectories(
cwd: string,
pathGenerator: PathGeneratorFunction,
rootPath?: string,
maxItems?: number,
stopAtPackageJSON?: boolean
): Promise<string[]> {
const paths: string[] = [];
let traversedPath = path.resolve(cwd);
while (await shouldContinueSearch(traversedPath, rootPath, stopAtPackageJSON)) {
const generatedPath = pathGenerator(traversedPath);
if (await fs.pathExists(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: string,
moduleName: string,
rootPath?: string
): Promise<string[]> {
const pathGenerator: PathGeneratorFunction = (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: string, rootPath?: string): Promise<string[]> {
const pathGenerator: PathGeneratorFunction = (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.
*
* @param cwd the initial directory to traverse
*/
export async function getProjectRootPath(cwd: string): Promise<string> {
for (const lockFilename of ['yarn.lock', 'package-lock.json']) {
const pathGenerator: PathGeneratorFunction = (traversedPath) => path.join(traversedPath, lockFilename);
const lockPaths = await traverseAncestorDirectories(cwd, pathGenerator, undefined, 1)
if (lockPaths.length > 0) {
return path.dirname(lockPaths[0]);
}
}
return cwd;
}

Просмотреть файл

@ -16,11 +16,11 @@ const install: ((s: string) => Promise<void>) = packageCommand.bind(null, 'insta
const uninstall: ((s: string) => Promise<void>) = packageCommand.bind(null, 'uninstall');
const testElectronCanBeFound = (): void => {
it('should return a valid path', () => {
const electronPath = locateElectronModule();
it('should return a valid path', async () => {
const electronPath = await locateElectronModule();
expect(electronPath).to.be.a('string');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(fs.existsSync(electronPath!)).to.be.equal(true);
expect(await fs.pathExists(electronPath!)).to.be.equal(true);
});
};
@ -31,10 +31,10 @@ describe('locateElectronModule', function() {
it('should return null when electron is not installed', async () => {
await fs.remove(path.resolve(__dirname, '..', 'node_modules', 'electron'));
expect(locateElectronModule()).to.be.equal(null);
expect(await locateElectronModule()).to.be.equal(null);
});
describe('with electron-prebuilt installed', () => {
describe('with electron-prebuilt installed', async () => {
before(() => install('electron-prebuilt'));
testElectronCanBeFound();
@ -42,13 +42,13 @@ describe('locateElectronModule', function() {
after(() => uninstall('electron-prebuilt'));
});
describe('with electron installed', () => {
before(() => install('electron'));
describe('with electron installed', async () => {
before(() => install('electron@^5.0.13'));
testElectronCanBeFound();
after(() => uninstall('electron'));
});
after(() => install('electron'));
after(() => install('electron@^5.0.13'));
});

Просмотреть файл

@ -0,0 +1 @@
{}

Просмотреть файл

@ -0,0 +1 @@
{}

Просмотреть файл

@ -0,0 +1,16 @@
{
"name": "workspace-app",
"productName": "Workspace App",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"ffi-napi": "2.4.5"
},
"dependencies": {
"ref-napi": "1.4.2"
}
}

Просмотреть файл

@ -0,0 +1,6 @@
{
"private": true,
"workspaces": [
"child-workspace"
]
}

Просмотреть файл

@ -0,0 +1,46 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
import { spawnPromise } from 'spawn-rx';
import { expectNativeModuleToBeRebuilt, expectNativeModuleToNotBeRebuilt } from './helpers/rebuild';
import { rebuild } from '../src/rebuild';
import { getProjectRootPath } from '../src/search-module';
describe('rebuild for yarn workspace', function() {
this.timeout(2 * 60 * 1000);
const testModulePath = path.resolve(os.tmpdir(), 'electron-rebuild-test');
describe('core behavior', () => {
before(async () => {
await fs.remove(testModulePath);
await fs.copy(path.resolve(__dirname, 'fixture/workspace-test'), testModulePath);
await spawnPromise('yarn', [], {
cwd: testModulePath,
stdio: 'ignore'
});
const projectRootPath = await getProjectRootPath(path.join(testModulePath, 'workspace-test', 'child-workspace'));
await rebuild({
buildPath: path.resolve(testModulePath, 'child-workspace'),
electronVersion: '5.0.13',
arch: process.arch,
projectRootPath
});
});
it('should have rebuilt top level prod dependencies', async () => {
await expectNativeModuleToBeRebuilt(testModulePath, 'ref-napi');
});
it('should not have rebuilt top level devDependencies', async () => {
await expectNativeModuleToNotBeRebuilt(testModulePath, 'ffi-napi');
});
after(async () => {
await fs.remove(testModulePath);
});
});
});

49
test/search-module.ts Normal file
Просмотреть файл

@ -0,0 +1,49 @@
import { expect } from 'chai';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { getProjectRootPath } from '../src/search-module';
let baseDir: string;
async function createTempDir(): Promise<void> {
baseDir = await fs.mkdtemp(path.join(os.tmpdir(), 'electron-rebuild-test-'));
}
async function removeTempDir(): Promise<void> {
await fs.remove(baseDir);
}
describe('search-module', () => {
describe('getProjectRootPath', () => {
describe('multi-level workspace', () => {
for (const lockFile of ['yarn.lock', 'package-lock.json']) {
describe(lockFile, () => {
before(async () => {
await createTempDir();
await fs.copy(path.resolve(__dirname, 'fixture', 'multi-level-workspace'), baseDir);
await fs.ensureFile(path.join(baseDir, lockFile));
});
it('finds the folder with the lockfile', async () => {
const packageDir = path.join(baseDir, 'packages', 'bar');
expect(await getProjectRootPath(packageDir)).to.equal(baseDir);
});
after(removeTempDir);
});
}
});
describe('no workspace', () => {
before(createTempDir);
it('returns the input directory if a lockfile cannot be found', async () => {
expect(await getProjectRootPath(baseDir)).to.equal(baseDir);
});
after(removeTempDir);
});
});
});