Skip to content
This repository was archived by the owner on Sep 12, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/commands/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ class DevCommand extends Command {
}
process.env.NETLIFY_DEV = "true";

let settings = serverSettings(config.dev);
let settings = await serverSettings(config.dev);

if (!(settings && settings.command)) {
this.log(
`${NETLIFYDEV} No dev server detected, using simple static server`
Expand Down
128 changes: 104 additions & 24 deletions src/detect-server.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,130 @@
const path = require("path");
const chalk = require("chalk");
const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`;

const detectors = require("fs")
const inquirer = require("inquirer");
const fs = require("fs");
const detectors = fs
.readdirSync(path.join(__dirname, "detectors"))
.filter(x => x.endsWith(".js")) // only accept .js detector files
.map(det => require(path.join(__dirname, `detectors/${det}`)));

module.exports.serverSettings = devConfig => {
let settings = null;
module.exports.serverSettings = async devConfig => {
let settingsArr = [],
settings = null;
for (const i in detectors) {
settings = detectors[i]();
if (settings) {
break;
const detectorResult = detectors[i]();
if (detectorResult) settingsArr.push(detectorResult);
}
if (settingsArr.length === 1) {
// vast majority of projects will only have one matching detector
settings = settingsArr[0];
settings.args = settings.possibleArgsArrs[0]; // just pick the first one
if (!settings.args) {
console.error(
"empty args assigned, this is an internal Netlify Dev bug, please report your settings and scripts so we can improve"
);
const { scripts } = JSON.parse(
fs.readFileSync("package.json", { encoding: "utf8" })
);
process.exit(1);
}
} else if (settingsArr.length > 1) {
/** multiple matching detectors, make the user choose */
// lazy loading on purpose
inquirer.registerPrompt(
"autocomplete",
require("inquirer-autocomplete-prompt")
);
const fuzzy = require("fuzzy");
const scriptInquirerOptions = formatSettingsArrForInquirer(settingsArr);
const { chosenSetting } = await inquirer.prompt({
name: "chosenSetting",
message: `Multiple possible start commands found`,
type: "autocomplete",
source: async function(_, input) {
if (!input || input === "") {
return scriptInquirerOptions;
}
// only show filtered results
return filterSettings(scriptInquirerOptions, input);
}
});
settings = chosenSetting; // finally! we have a selected option
// TODO: offer to save this setting to netlify.toml so you dont keep doing this

/** utiltities for the inquirer section above */
function filterSettings(scriptInquirerOptions, input) {
const filteredSettings = fuzzy.filter(
input,
scriptInquirerOptions.map(x => x.name)
);
const filteredSettingNames = filteredSettings.map(x =>
input ? x.string : x
);
return scriptInquirerOptions.filter(t =>
filteredSettingNames.includes(t.name)
);
}

/** utiltities for the inquirer section above */
function formatSettingsArrForInquirer(settingsArr) {
let ans = [];
settingsArr.forEach(setting => {
setting.possibleArgsArrs.forEach(args => {
ans.push({
name: `[${chalk.yellow(setting.type)}] ${
setting.command
} ${args.join(" ")}`,
value: { ...setting, args },
short: setting.type + "-" + args.join(" ")
});
});
});
return ans;
}
}

/** everything below assumes we have settled on one detector */

if (devConfig) {
settings = settings || {};
if (devConfig.command) {
assignLoudly(settings, "command", devConfig.command.split(/\s/)[0]);
assignLoudly(settings, "args", devConfig.command.split(/\s/).slice(1));
settings.command = assignLoudly(
devConfig.command,
settings.command.split(/\s/)[0]
);
settings.args = assignLoudly(
devConfig.command,
settings.command.split(/\s/).slice(1)
);
}
if (devConfig.port) {
assignLoudly(settings, "proxyPort", devConfig["port"]);
assignLoudly(
settings,
"urlRegexp",
settings.proxyPort = assignLoudly(devConfig.port, settings.proxyPort);
const regexp =
devConfig.urlRegexp ||
new RegExp(`(http://)([^:]+:)${devConfig.port}(/)?`, "g")
);
new RegExp(`(http://)([^:]+:)${devConfig.port}(/)?`, "g");
settings.urlRegexp = assignLoudly(settings.urlRegexp);
}
assignLoudly(settings, "dist", devConfig["publish"]);
settings.dist = assignLoudly(devConfig.publish, settings.dist);
}

return settings;
};

// mutates the settings field, but tell the user if it does
function assignLoudly(settings, settingsField, newValue) {
if (settings[settingsField] !== newValue) {
// silent if command is exactly same
// if first arg is undefined, use default, but tell user about it in case it is unintentional
function assignLoudly(
optionalValue,
defaultValue,
tellUser = dV =>
console.log(
`${NETLIFYDEV} Overriding ${settingsField} with setting derived from netlify.toml [dev] block: `,
newValue
);
settings[settingsField] === newValue;
dV
)
) {
if (defaultValue === undefined) throw new Error("must have a defaultValue");
if (defaultValue !== optionalValue && optionalValue === undefined) {
tellUser(defaultValue);
return defaultValue;
} else {
return optionalValue;
}
}
4 changes: 2 additions & 2 deletions src/detectors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
port: Number, // e.g. 8888
proxyPort: Number, // e.g. 3000
env: Object, // env variables, see examples
args: String, // e.g 'run develop', so that the combined command is 'npm run develop'
possibleArgsArrs: [[String]], // e.g [['run develop]], so that the combined command is 'npm run develop', but we allow for multiple
urlRegexp: RegExp, // see examples
dist: String // e.g. 'dist' or 'build'
dist: String, // e.g. 'dist' or 'build'
}
```

Expand Down
50 changes: 25 additions & 25 deletions src/detectors/cra.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
const { existsSync, readFileSync } = require("fs");
const {
hasRequiredDeps,
hasRequiredFiles,
getYarnOrNPMCommand,
scanScripts
} = require("./utils/jsdetect");

/**
* detection logic - artificial intelligence!
* */
module.exports = function() {
if (!existsSync("package.json")) {
return false;
}
// REQUIRED FILES
if (!hasRequiredFiles(["package.json"])) return false;
// REQUIRED DEPS
if (!hasRequiredDeps(["react-scripts"])) return false;

const packageSettings = JSON.parse(
readFileSync("package.json", { encoding: "utf8" })
);
const { dependencies, scripts } = packageSettings;
if (!(dependencies && dependencies["react-scripts"])) {
return false;
}
/** everything below now assumes that we are within create-react-app */

const npmCommand =
scripts &&
((scripts.start && "start") ||
(scripts.serve && "serve") ||
(scripts.run && "run"));
const possibleArgsArrs = scanScripts({
preferredScriptsArr: ["start", "serve", "run"],
preferredCommand: "react-scripts start"
});

if (!npmCommand) {
console.error("Couldn't determine the script to run. Use the -c flag.");
process.exit(1);
if (!possibleArgsArrs.length) {
// ofer to run it when the user doesnt have any scripts setup! 🤯
possibleArgsArrs.push(["react-scripts", "start"]);
}

const yarnExists = existsSync("yarn.lock");
return {
type: "create-react-app",
command: yarnExists ? "yarn" : "npm",
port: 8888,
proxyPort: 3000,
command: getYarnOrNPMCommand(),
port: 8888, // the port that the Netlify Dev User will use
proxyPort: 3000, // the port that create-react-app normally outputs
env: { ...process.env, BROWSER: "none", PORT: 3000 },
args:
yarnExists || npmCommand != "start" ? ["run", npmCommand] : [npmCommand],
possibleArgsArrs,
urlRegexp: new RegExp(`(http://)([^:]+:)${3000}(/)?`, "g"),
dist: "dist"
};
Expand Down
16 changes: 11 additions & 5 deletions src/detectors/eleventy.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
const { existsSync } = require("fs");
const {
hasRequiredDeps,
hasRequiredFiles,
scanScripts
} = require("./utils/jsdetect");

module.exports = function() {
if (!existsSync(".eleventy.js")) {
return false;
}
// REQUIRED FILES
if (!hasRequiredFiles(["package.json", ".eleventy.js"])) return false;
// commented this out because we're not sure if we want to require it
// // REQUIRED DEPS
// if (!hasRequiredDeps(["@11y/eleventy"])) return false;

return {
type: "eleventy",
port: 8888,
proxyPort: 8080,
env: { ...process.env },
command: "npx",
args: ["eleventy", "--serve", "--watch"],
possibleArgsArrs: [["eleventy", "--serve", "--watch"]],
urlRegexp: new RegExp(`(http://)([^:]+:)${8080}(/)?`, "g"),
dist: "_site"
};
Expand Down
64 changes: 21 additions & 43 deletions src/detectors/gatsby.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,33 @@
const { existsSync, readFileSync } = require("fs");

const {
hasRequiredDeps,
hasRequiredFiles,
getYarnOrNPMCommand,
scanScripts
} = require("./utils/jsdetect");
module.exports = function() {
if (!existsSync("gatsby-config.js") || !existsSync("package.json")) {
return false;
}
// REQUIRED FILES
if (!hasRequiredFiles(["package.json", "gatsby-config.js"])) return false;
// REQUIRED DEPS
if (!hasRequiredDeps(["gatsby"])) return false;

const packageSettings = JSON.parse(
readFileSync("package.json", { encoding: "utf8" })
);
const { dependencies, scripts } = packageSettings;
if (!(dependencies && dependencies["gatsby"])) {
return false;
}
/** everything below now assumes that we are within gatsby */

const npmCommand =
scripts &&
((scripts.start && "start") ||
(scripts.develop && "develop") ||
(scripts.dev && "dev"));
if (!npmCommand) {
if (!scripts) {
console.error(
"Couldn't determine the package.json script to run for this Gatsby project. Use the --command flag."
);
process.exit(1);
}
// search all the scripts for something that starts with 'gatsby develop'
Object.entries(scripts).forEach(([k, v]) => {
if (v.startsWith("gatsby develop")) {
npmCommand = k;
}
});
if (!npmCommand) {
console.error(
"Couldn't determine the package.json script to run for this Gatsby project. Use the --command flag."
);
process.exit(1);
} else {
console.log("using npm script starting with gatsby develop: ", k);
}
}
const possibleArgsArrs = scanScripts({
preferredScriptsArr: ["start", "develop", "dev"],
preferredCommand: "gatsby develop"
});

const yarnExists = existsSync("yarn.lock");
if (!possibleArgsArrs.length) {
// ofer to run it when the user doesnt have any scripts setup! 🤯
possibleArgsArrs.push(["gatsby", "develop"]);
}
return {
type: "gatsby",
command: yarnExists ? "yarn" : "npm",
command: getYarnOrNPMCommand(),
port: 8888,
proxyPort: 8000,
env: { ...process.env },
args:
yarnExists || npmCommand != "start" ? ["run", npmCommand] : [npmCommand],
possibleArgsArrs,
urlRegexp: new RegExp(`(http://)([^:]+:)${8000}(/)?`, "g"),
dist: "public"
};
Expand Down
2 changes: 1 addition & 1 deletion src/detectors/hugo.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = function() {
proxyPort: 1313,
env: { ...process.env },
command: "hugo",
args: ["server", "-w"],
possibleArgsArrs: [["server", "-w"]],
urlRegexp: new RegExp(`(http://)([^:]+:)${1313}(/)?`, "g"),
dist: "public"
};
Expand Down
2 changes: 1 addition & 1 deletion src/detectors/jekyll.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = function() {
proxyPort: 4000,
env: { ...process.env },
command: "bundle",
args: ["exec", "jekyll", "serve", "-w", "-l"],
possibleArgsArrs: [["exec", "jekyll", "serve", "-w", "-l"]],
urlRegexp: new RegExp(`(http://)([^:]+:)${4000}(/)?`, "g"),
dist: "_site"
};
Expand Down
Loading