-
Notifications
You must be signed in to change notification settings - Fork 176
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support yarn workspaces (#250)
Co-authored-by: Mark Lee <[email protected]>
- Loading branch information
Showing
11 changed files
with
263 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
}); | ||
} | ||
|
||
return null | ||
} | ||
|
||
export function locateElectronModule(): string | null { | ||
const siblingModules: string[] | null = locateSiblingModules(); | ||
if (siblingModules.length > 0) { | ||
return siblingModules[0]; | ||
} | ||
export async function locateElectronModule(projectRootPath?: string): Promise<string | null> { | ||
for (const moduleName of electronModuleNames) { | ||
const electronPath = await searchForModule(process.cwd(), moduleName, projectRootPath)[0]; | ||
|
||
const requiredModules = locateModulesByRequire(); | ||
if (requiredModules && requiredModules.length > 0) { | ||
return requiredModules[0]; | ||
if (electronPath && await fs.pathExists(path.join(electronPath, 'package.json'))) { | ||
return electronPath; | ||
} | ||
} | ||
|
||
return null; | ||
return locateModuleByRequire(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"private": true, | ||
"workspaces": [ | ||
"child-workspace" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.