Skip to content

Commit

Permalink
misc(generator): allow local paths to generators
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanonelson committed Feb 26, 2018
1 parent 4cf5e17 commit 0f0fe3e
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 17 deletions.
6 changes: 5 additions & 1 deletion SCAFFOLDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ Before writing a `webpack-cli` scaffold, think about what you're trying to achie

## webpack-addons-yourpackage

In order for `webpack-cli` to compile your package, it relies on a prefix of `webpack-addons`. The package must also be published on npm. If you are curious about how you can create your very own `addon`, please read [How do I compose a webpack-addon?](https://github.com/ev1stensberg/webpack-addons-demo).
In order for `webpack-cli` to compile your package, it must be available on npm or on your local filesystem.

If the package is on npm, its name must have a prefix of `webpack-addons`.

If the package is on your local filesystem, it can be named whatever you want. Pass the name of the package as a relative path to its root directory.

## API

Expand Down
4 changes: 3 additions & 1 deletion lib/commands/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ function serve() {
: [];

if (hasDevServerDep.length) {
let WDSPath = getRootPathModule("node_modules/webpack-dev-server/bin/webpack-dev-server.js");
let WDSPath = getRootPathModule(
"node_modules/webpack-dev-server/bin/webpack-dev-server.js"
);
if (!WDSPath) {
console.log(
"\n",
Expand Down
25 changes: 19 additions & 6 deletions lib/utils/npm-packages-exists.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use strict";
const chalk = require("chalk");
const fs = require("fs");
const npmExists = require("./npm-exists");
const resolvePackages = require("./resolve-packages").resolvePackages;

Expand All @@ -14,8 +15,22 @@ const resolvePackages = require("./resolve-packages").resolvePackages;

module.exports = function npmPackagesExists(pkg) {
let acceptedPackages = [];

function resolvePackagesIfReady() {
if (acceptedPackages.length === pkg.length)
return resolvePackages(acceptedPackages);
}

pkg.forEach(addon => {
//eslint-disable-next-line
// The addon is a path to a local folder; no validation is necessary
if (fs.existsSync(addon)) {
acceptedPackages.push(addon);
resolvePackagesIfReady();
return;
}

// The addon is on npm; validate name and existence
// eslint-disable-next-line
if (addon.length <= 14 || addon.slice(0, 14) !== "webpack-addons") {
throw new TypeError(
chalk.bold(`${addon} isn't a valid name.\n`) +
Expand All @@ -24,11 +39,12 @@ module.exports = function npmPackagesExists(pkg) {
)
);
}

npmExists(addon)
.then(moduleExists => {
if (!moduleExists) {
Error.stackTraceLimit = 0;
throw new TypeError("Package isn't registered on npm.");
throw new TypeError(`Cannot resolve location of package ${addon}.`);
}
if (moduleExists) {
acceptedPackages.push(addon);
Expand All @@ -38,9 +54,6 @@ module.exports = function npmPackagesExists(pkg) {
console.error(err.stack || err);
process.exit(0);
})
.then(_ => {
if (acceptedPackages.length === pkg.length)
return resolvePackages(acceptedPackages);
});
.then(resolvePackagesIfReady);
});
};
31 changes: 31 additions & 0 deletions lib/utils/npm-packages-exists.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const fs = require("fs");
const npmPackagesExists = require("./npm-packages-exists");

jest.mock("fs");
jest.mock("./npm-exists");
jest.mock("./resolve-packages");

const mockResolvePackages = require("./resolve-packages").resolvePackages;

describe("npmPackagesExists", () => {
test("resolves packages when they are available on the local filesystem", () => {
fs.existsSync.mockReturnValueOnce(true);
npmPackagesExists(["./testpkg"]);
expect(mockResolvePackages.mock.calls[mockResolvePackages.mock.calls.length - 1][0]).toEqual(["./testpkg"]);
});

test("throws a TypeError when an npm package name doesn't include the prefix", () => {
fs.existsSync.mockReturnValueOnce(false);
expect(() => npmPackagesExists(["my-webpack-addon"])).toThrowError(TypeError);
});

test("resolves packages when they are available on npm", done => {
fs.existsSync.mockReturnValueOnce(false);
require("./npm-exists").mockImplementation(() => Promise.resolve(true));
npmPackagesExists(["webpack-addons-foobar"]);
setTimeout(() => {
expect(mockResolvePackages.mock.calls[mockResolvePackages.mock.calls.length - 1][0]).toEqual(["webpack-addons-foobar"]);
done();
}, 10);
});
});
22 changes: 22 additions & 0 deletions lib/utils/package-exists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use strict";

const fs = require("fs");
const npmExists = require("./npm-exists");

/**
*
* Checks whether a package exists locally or on npm
*
* @param {String} pkg - Name or location of package
* @returns {Promise} exists - Returns true or false,
* based on if it exists or not
*/

module.exports = function packageExists(pkg) {
const isValidLocalPath = fs.existsSync(pkg);
if (isValidLocalPath) {
return Promise.resolve(true);
}

return npmExists(pkg);
};
30 changes: 29 additions & 1 deletion lib/utils/package-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ function spawnYarn(pkg, isNew) {
*/

function spawnChild(pkg) {
const pkgPath = path.resolve(globalPath, pkg);
const rootPath = getPathToGlobalPackages();
const pkgPath = path.resolve(rootPath, pkg);
const packageManager = getPackageManager();
const isNew = !fs.existsSync(pkgPath);

Expand All @@ -71,7 +72,34 @@ function getPackageManager() {
return "yarn";
}

/**
*
* Returns the path to globally installed
* npm packages, depending on the available
* package manager determined by `getPackageManager`
*
* @returns {String} path - Path to global node_modules folder
*/
function getPathToGlobalPackages() {
const manager = getPackageManager();

if (manager === "yarn") {
try {
const yarnDir = spawn
.sync("yarn", ["global", "dir"])
.stdout.toString()
.trim();
return path.join(yarnDir, "node_modules");
} catch (e) {
// Default to the global npm path below
}
}

return globalPath;
}

module.exports = {
getPackageManager,
getPathToGlobalPackages,
spawnChild
};
30 changes: 28 additions & 2 deletions lib/utils/package-manager.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ describe("package-manager", () => {
);
}

function mockSpawnErrorTwice() {
mockSpawnErrorOnce();
mockSpawnErrorOnce();
}

spawn.sync.mockReturnValue(defaultSyncResult);

it("should return 'yarn' from getPackageManager if it's installed", () => {
Expand Down Expand Up @@ -65,7 +70,7 @@ describe("package-manager", () => {
it("should spawn npm install from spawnChild", () => {
const packageName = "some-pkg";

mockSpawnErrorOnce();
mockSpawnErrorTwice();
packageManager.spawnChild(packageName);
expect(spawn.sync).toHaveBeenLastCalledWith(
"npm",
Expand All @@ -77,7 +82,7 @@ describe("package-manager", () => {
it("should spawn npm update from spawnChild", () => {
const packageName = "some-pkg";

mockSpawnErrorOnce();
mockSpawnErrorTwice();
fs.existsSync.mockReturnValueOnce(true);

packageManager.spawnChild(packageName);
Expand All @@ -87,4 +92,25 @@ describe("package-manager", () => {
{ stdio: "inherit" }
);
});

it("should return the yarn global dir from getPathToGlobalPackages if yarn is installed", () => {
const yarnDir = "/Users/test/.config/yarn/global";
// Mock confirmation that yarn is installed
spawn.sync.mockReturnValueOnce(defaultSyncResult);
// Mock stdout of `yarn global dir`
spawn.sync.mockReturnValueOnce({
stdout: {
toString: () => `${yarnDir}\n`
}
});
const globalPath = packageManager.getPathToGlobalPackages();
const expected = `${yarnDir}/node_modules`;
expect(globalPath).toBe(expected);
});

it("should return the npm global dir from getPathToGlobalPackages if yarn is not installed", () => {
mockSpawnErrorOnce();
const globalPath = packageManager.getPathToGlobalPackages();
expect(globalPath).toBe(require("global-modules"));
});
});
34 changes: 28 additions & 6 deletions lib/utils/resolve-packages.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"use strict";

const fs = require("fs");
const path = require("path");
const chalk = require("chalk");
const globalPath = require("global-modules");

const creator = require("../init/index").creator;

const getPathToGlobalPackages = require("./package-manager")
.getPathToGlobalPackages;
const spawnChild = require("./package-manager").spawnChild;

/**
Expand Down Expand Up @@ -41,10 +43,33 @@ function resolvePackages(pkg) {

let packageLocations = [];

function invokeGeneratorIfReady() {
if (packageLocations.length === pkg.length)
return creator(packageLocations);
}

pkg.forEach(addon => {
// Resolve paths to modules on local filesystem
if (fs.existsSync(addon)) {
let absolutePath = addon;

try {
absolutePath = path.resolve(process.cwd(), addon);
require.resolve(absolutePath);
packageLocations.push(absolutePath);
} catch (err) {
console.log(`Cannot find a valid npm module at ${absolutePath}.`);
}

invokeGeneratorIfReady();
return;
}

// Resolve modules on npm registry
processPromise(spawnChild(addon))
.then(_ => {
try {
const globalPath = getPathToGlobalPackages();
packageLocations.push(path.resolve(globalPath, addon));
} catch (err) {
console.log("Package wasn't validated correctly..");
Expand All @@ -55,15 +80,12 @@ function resolvePackages(pkg) {
}
})
.catch(err => {
console.log("Package Coudln't be installed, aborting..");
console.log("Package couldn't be installed, aborting..");
console.log("\nReason: \n");
console.error(chalk.bold.red(err));
process.exitCode = 1;
})
.then(_ => {
if (packageLocations.length === pkg.length)
return creator(packageLocations);
});
.then(invokeGeneratorIfReady);
});
}

Expand Down

0 comments on commit 0f0fe3e

Please sign in to comment.