react-monaco-editor
Version:
Monaco Editor for React
456 lines (443 loc) • 14.4 kB
JavaScript
const Generator = require("yeoman-generator");
const glob = require("glob-all");
const path = require("path");
const Confirm = require("webpack-addons").Confirm;
const List = require("webpack-addons").List;
const Input = require("webpack-addons").Input;
const webpackSchema = require("webpack/schemas/WebpackOptions");
const webpackDevServerSchema = require("webpack-dev-server/lib/optionsSchema.json");
const PROP_TYPES = require("../utils/prop-types");
const getPackageManager = require("../utils/package-manager").getPackageManager;
const npmExists = require("../utils/npm-exists");
const entryQuestions = require("./utils/entry");
/**
*
* Replaces the string with a substring at the given index
* https://gist.github.com/efenacigiray/9367920
*
* @param {String} string - string to be modified
* @param {Number} index - index to replace from
* @param {String} replace - string to replace starting from index
*
* @returns {String} string - The newly mutated string
*
*/
function replaceAt(string, index, replace) {
return string.substring(0, index) + replace + string.substring(index + 1);
}
/**
*
* Checks if the given array has a given property
*
* @param {Array} arr - array to check
* @param {String} prop - property to check existence of
*
* @returns {Boolean} hasProp - Boolean indicating if the property
* is present
*/
const traverseAndGetProperties = (arr, prop) => {
let hasProp = false;
arr.forEach(p => {
if (p[prop]) {
hasProp = true;
}
});
return hasProp;
};
/**
*
* Generator for adding properties
* @class AddGenerator
* @extends Generator
* @returns {Void} After execution, transforms are triggered
*
*/
module.exports = class AddGenerator extends Generator {
constructor(args, opts) {
super(args, opts);
this.dependencies = [];
this.configuration = {
config: {
webpackOptions: {},
topScope: ["const webpack = require('webpack')"]
}
};
}
prompting() {
let done = this.async();
let action;
let self = this;
let manualOrListInput = action =>
Input("actionAnswer", `What do you want to add to ${action}?`);
// first index indicates if it has a deep prop, 2nd indicates what kind of
let isDeepProp = [false, false];
return this.prompt([
List(
"actionType",
"What property do you want to add to?",
Array.from(PROP_TYPES.keys())
)
])
.then(actionTypeAnswer => {
// Set initial prop, like devtool
this.configuration.config.webpackOptions[
actionTypeAnswer.actionType
] = null;
// update the action variable, we're using it later
action = actionTypeAnswer.actionType;
})
.then(() => {
if (action === "entry") {
return this.prompt([
Confirm("entryType", "Will your application have multiple bundles?")
])
.then(entryTypeAnswer => {
// Ask different questions for entry points
return entryQuestions(self, entryTypeAnswer);
})
.then(entryOptions => {
this.configuration.config.webpackOptions.entry = entryOptions;
this.configuration.config.item = action;
});
}
let temp = action;
if (action === "resolveLoader") {
action = "resolve";
}
const webpackSchemaProp = webpackSchema.definitions[action];
/*
* https://github.com/webpack/webpack/blob/next/schemas/WebpackOptions.json
* Find the properties directly in the properties prop, or the anyOf prop
*/
let defOrPropDescription = webpackSchemaProp
? webpackSchemaProp.properties
: webpackSchema.properties[action].properties
? webpackSchema.properties[action].properties
: webpackSchema.properties[action].anyOf
? webpackSchema.properties[action].anyOf.filter(
p => p.properties || p.enum
) // eslint-disable-line
: null;
if (Array.isArray(defOrPropDescription)) {
// Todo: Generalize these to go through the array, then merge enum with props if needed
const hasPropertiesProp = traverseAndGetProperties(
defOrPropDescription,
"properties"
);
const hasEnumProp = traverseAndGetProperties(
defOrPropDescription,
"enum"
);
/* as we know he schema only has two arrays that might hold our values,
* check them for either having arr.enum or arr.properties
*/
if (hasPropertiesProp) {
defOrPropDescription =
defOrPropDescription[0].properties ||
defOrPropDescription[1].properties;
if (!defOrPropDescription) {
defOrPropDescription = defOrPropDescription[0].enum;
}
// TODO: manually implement stats and devtools like sourcemaps
} else if (hasEnumProp) {
const originalPropDesc = defOrPropDescription[0].enum;
// Array -> Object -> Merge objects into one for compat in manualOrListInput
defOrPropDescription = Object.keys(defOrPropDescription[0].enum)
.map(p => {
return Object.assign(
{},
{
[originalPropDesc[p]]: "noop"
}
);
})
.reduce((result, currentObject) => {
for (let key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
result[key] = currentObject[key];
}
}
return result;
}, {});
}
}
// WDS has its own schema, so we gonna need to check that too
const webpackDevserverSchemaProp =
action === "devServer" ? webpackDevServerSchema : null;
// Watch has a boolean arg, but we need to append to it manually
if (action === "watch") {
defOrPropDescription = {
true: {},
false: {}
};
}
if (action === "mode") {
defOrPropDescription = {
development: {},
production: {}
};
}
action = temp;
if (action === "resolveLoader") {
defOrPropDescription = Object.assign(defOrPropDescription, {
moduleExtensions: {}
});
}
// If we've got a schema prop or devServer Schema Prop
if (defOrPropDescription || webpackDevserverSchemaProp) {
// Check for properties in definitions[action] or properties[action]
if (defOrPropDescription) {
if (action !== "devtool") {
// Add the option of adding an own variable if the user wants
defOrPropDescription = Object.assign(defOrPropDescription, {
other: {}
});
} else {
// The schema doesn't have the source maps we can prompt, so add those
defOrPropDescription = Object.assign(defOrPropDescription, {
eval: {},
"cheap-eval-source-map": {},
"cheap-module-eval-source-map": {},
"eval-source-map": {},
"cheap-source-map": {},
"cheap-module-source-map": {},
"inline-cheap-source-map": {},
"inline-cheap-module-source-map": {},
"source-map": {},
"inline-source-map": {},
"hidden-source-map": {},
"nosources-source-map": {}
});
}
manualOrListInput = List(
"actionAnswer",
`What do you want to add to ${action}?`,
Object.keys(defOrPropDescription)
);
// We know we're gonna append some deep prop like module.rule
isDeepProp[0] = true;
} else if (webpackDevserverSchemaProp) {
// Append the custom property option
webpackDevserverSchemaProp.properties = Object.assign(
webpackDevserverSchemaProp.properties,
{
other: {}
}
);
manualOrListInput = List(
"actionAnswer",
`What do you want to add to ${action}?`,
Object.keys(webpackDevserverSchemaProp.properties)
);
// We know we are in a devServer.prop scenario
isDeepProp[0] = true;
} else {
// manual input if non-existent
manualOrListInput = manualOrListInput(action);
}
} else {
manualOrListInput = manualOrListInput(action);
}
return this.prompt([manualOrListInput]);
})
.then(answerToAction => {
if (!answerToAction) {
done();
return;
}
/*
* Plugins got their own logic,
* find the names of each natively plugin and check if it matches
*/
if (action === "plugins") {
const pluginExist = glob
.sync([
"node_modules/webpack/lib/*Plugin.js",
"node_modules/webpack/lib/**/*Plugin.js"
])
.map(p =>
p
.split("/")
.pop()
.replace(".js", "")
)
.find(
p => p.toLowerCase().indexOf(answerToAction.actionAnswer) >= 0
);
if (pluginExist) {
this.configuration.config.item = pluginExist;
const pluginsSchemaPath = glob
.sync([
"node_modules/webpack/schemas/plugins/*Plugin.json",
"node_modules/webpack/schemas/plugins/**/*Plugin.json"
])
.find(
p =>
p
.split("/")
.pop()
.replace(".json", "")
.toLowerCase()
.indexOf(answerToAction.actionAnswer) >= 0
);
if (pluginsSchemaPath) {
const constructorPrefix =
pluginsSchemaPath.indexOf("optimize") >= 0
? "webpack.optimize"
: "webpack";
const resolvePluginsPath = path.resolve(pluginsSchemaPath);
const pluginSchema = resolvePluginsPath
? require(resolvePluginsPath)
: null;
let pluginsSchemaProps = ["other"];
if (pluginSchema) {
Object.keys(pluginSchema)
.filter(p => Array.isArray(pluginSchema[p]))
.forEach(p => {
Object.keys(pluginSchema[p]).forEach(n => {
if (pluginSchema[p][n].properties) {
pluginsSchemaProps = Object.keys(
pluginSchema[p][n].properties
);
}
});
});
}
return this.prompt([
List(
"pluginsPropType",
`What property do you want to add ${pluginExist}?`,
pluginsSchemaProps
)
]).then(pluginsPropAnswer => {
return this.prompt([
Input(
"pluginsPropTypeVal",
`What value should ${pluginExist}.${
pluginsPropAnswer.pluginsPropType
} have?`
)
]).then(valForProp => {
this.configuration.config.webpackOptions[action] = {
[`${constructorPrefix}.${pluginExist}`]: {
[pluginsPropAnswer.pluginsPropType]:
valForProp.pluginsPropTypeVal
}
};
done();
});
});
} else {
this.configuration.config.webpackOptions[
action
] = `new webpack.${pluginExist}`;
done();
}
} else {
// If its not in webpack, check npm
npmExists(answerToAction.actionAnswer).then(p => {
if (p) {
this.dependencies.push(answerToAction.actionAnswer);
const normalizePluginName = answerToAction.actionAnswer.replace(
"-webpack-plugin",
"Plugin"
);
const pluginName = replaceAt(
normalizePluginName,
0,
normalizePluginName.charAt(0).toUpperCase()
);
this.configuration.config.topScope.push(
`const ${pluginName} = require("${
answerToAction.actionAnswer
}")`
);
this.configuration.config.webpackOptions[
action
] = `new ${pluginName}`;
this.configuration.config.item = answerToAction.actionAnswer;
done();
this.runInstall(getPackageManager(), this.dependencies, {
"save-dev": true
});
} else {
console.error(
answerToAction.actionAnswer,
"doesn't exist on NPM or is built in webpack, please check for any misspellings."
);
process.exit(0);
}
});
}
} else {
// If we're in the scenario with a deep-property
if (isDeepProp[0]) {
isDeepProp[1] = answerToAction.actionAnswer;
if (
isDeepProp[1] !== "other" &&
(action === "devtool" || action === "watch" || action === "mode")
) {
this.configuration.config.item = action;
this.configuration.config.webpackOptions[action] =
answerToAction.actionAnswer;
done();
return;
}
// Either we are adding directly at the property, else we're in a prop.theOne scenario
const actionMessage =
isDeepProp[1] === "other"
? `What do you want the key on ${action} to be? (press enter if you want it directly as a value on the property)`
: `What do you want the value of ${isDeepProp[1]} to be?`;
this.prompt([Input("deepProp", actionMessage)]).then(
deepPropAns => {
// The other option needs to be validated of either being empty or not
if (isDeepProp[1] === "other") {
let othersDeepPropKey = deepPropAns.deepProp
? `What do you want the value of ${
deepPropAns.deepProp
} to be?` // eslint-disable-line
: `What do you want to be the value of ${action} to be?`;
// Push the answer to the array we have created, so we can use it later
isDeepProp.push(deepPropAns.deepProp);
this.prompt([Input("deepProp", othersDeepPropKey)]).then(
deepPropAns => {
// Check length, if it has none, add the prop directly on the given action
if (isDeepProp[2].length === 0) {
this.configuration.config.item = action;
this.configuration.config.webpackOptions[action] =
deepPropAns.deepProp;
} else {
// If not, we're adding to something like devServer.myProp
this.configuration.config.item =
action + "." + isDeepProp[2];
this.configuration.config.webpackOptions[action] = {
[isDeepProp[2]]: deepPropAns.deepProp
};
}
done();
}
);
} else {
// We got the schema prop, we've correctly prompted it, and can add it directly
this.configuration.config.item = action + "." + isDeepProp[1];
this.configuration.config.webpackOptions[action] = {
[isDeepProp[1]]: deepPropAns.deepProp
};
done();
}
}
);
} else {
// We're asking for input-only
this.configuration.config.item = action;
this.configuration.config.webpackOptions[action] =
answerToAction.actionAnswer;
done();
}
}
});
}
writing() {
this.config.set("configuration", this.configuration);
}
};