diff --git a/README.md b/README.md index ca66182..4167be3 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,49 @@ Netlify CLI plugin for local dev experience. ## What is Netlify Dev? -Netlify Dev brings the power of Netlify's Edge Logic layer, serverless functions and [add-on ecosystem](#using-add-ons) to your local laptop. It runs Netlify's production routing engine in a local dev server to make all redirects, proxy rules, function routes or add-on routes available locally and injects the correct environment variables from your site environment, installed add-ons or your netlify.toml file into your build and function environment. +Netlify Dev brings the power of Netlify's Edge Logic layer, [serverless functions](#netlify-functions) and [add-on ecosystem](#using-add-ons) to your local machine. It runs Netlify's production routing engine in a local dev server to make all redirects, proxy rules, function routes or add-on routes available locally and injects the correct environment variables from your site environment, installed add-ons or your netlify.toml file into your build and function environment. It automatically detects common tools like Gatsby, Hugo, React Static, Eleventy, and more, to give a zero config setup for your local dev server and can help scaffolding new functions as you work on them. +## Prerequisites + +There are just two: + +- You should be [logged in on Netlify CLI](https://www.netlify.com/docs/cli/#authentication) +- Your project should be linked to a `siteID` on Netlify (using [netlify init](https://www.netlify.com/docs/cli/#continuous-deployment) or [netlify link](https://www.netlify.com/docs/cli/#linking-and-unlinking-sites)). You can confirm this has been done if you have a `.netlify` folder with a `state.json` file containing your `siteID`. + +This is how we pull down your build environment variables and manage your addons on your local machine. + ## Usage - `netlify dev` start a local dev server for the build tool you're using - `netlify dev:exec ` runs a shell command within the netlify dev environment - `netlify functions:create` bootstrap a new function +As these commands are expected to be frequently used, it may be helpful to define aliases in your terminal (Mac: [bash](https://jonsuh.com/blog/bash-command-line-shortcuts/), [zsh](https://askubuntu.com/questions/758496/how-to-make-a-permanent-alias-in-oh-my-zsh), Windows: [doskey](https://stackoverflow.com/questions/20530996/aliases-in-windows-command-prompt), [registry](https://stackoverflow.com/questions/20530996/aliases-in-windows-command-prompt)) to your personal preference. For example: + +```bash +## ~/.zshrc +alias ndeploy="netlify deploy --prod" +alias nd="netlify dev" +alias ndl="netlify dev --live" +alias nfc="netlify functions:create" +alias ndx="netlify dev:exec " +``` + ## Using the beta Currently the Netlify dev plugin is in private beta. You'll need to follow these steps to enable it: Make sure Netlify CLI is installed and up to date: -``` +```bash npm install -g netlify-cli ``` Then clone and activate the plugin: -``` +```bash git clone git@github.com:netlify/netlify-dev-plugin.git cd netlify-dev-plugin npm install @@ -161,7 +181,7 @@ Add-ons are a way for Netlify users to extend the functionality of their Jamstac To try out an add-on with Netlify dev, run the `netlify addons:create` command: -``` +```bash netlify addons:create fauna ``` @@ -175,8 +195,10 @@ After you have installed an add-on, it will be visible with the `netlify addons: To share your ongoing dev session with a coworker, just run Netlify Dev with a `--live` flag: -``` +```bash netlify dev --live ``` You will get a URL that looks like `https://clever-cray-2aa156-6639f3.netlify.live/`. This can be accessed by anyone as long as you keep your session open. + +> Note: there are currently known issues with ending the live session alongside your webdevserver. We are working on fixing it. In the mean time you can run `ps aux | grep live-tunnel` and kill these sessions manually. diff --git a/src/commands/dev/exec.js b/src/commands/dev/exec.js index b1bf1d2..3053a32 100644 --- a/src/commands/dev/exec.js +++ b/src/commands/dev/exec.js @@ -8,7 +8,10 @@ class ExecCommand extends Command { async run() { const { site, api } = this.netlify; if (site.id) { - const accessToken = await this.authenticate(); + console.log( + `${NETLIFYDEV} Checking your site's environment variables...` + ); + const accessToken = api.accessToken; const { addEnvVariables } = require("../../utils/dev"); await addEnvVariables(api, site, accessToken); } else { diff --git a/src/commands/dev/index.js b/src/commands/dev/index.js index 55de465..25e14a1 100644 --- a/src/commands/dev/index.js +++ b/src/commands/dev/index.js @@ -170,6 +170,7 @@ function startDevServer(settings, log, error) { class DevCommand extends Command { async run() { + this.log(`${NETLIFYDEV} Starting Netlify Dev...`); const { flags, args } = this.parse(DevCommand); const { api, site, config } = this.netlify; const functionsDir = @@ -180,7 +181,6 @@ class DevCommand extends Command { let accessToken = api.accessToken; if (site.id && !flags.offline) { - accessToken = await this.authenticate(); const { addEnvVariables } = require("../../utils/dev"); addonUrls = await addEnvVariables(api, site, accessToken); } @@ -189,7 +189,7 @@ class DevCommand extends Command { let settings = serverSettings(config.dev); if (!(settings && settings.command)) { this.log( - "[Netlify Dev] No dev server detected, using simple static server" + `${NETLIFYDEV} No dev server detected, using simple static server` ); const dist = (config.dev && config.dev.publish) || @@ -240,7 +240,7 @@ class DevCommand extends Command { live: flags.live || false }); - const banner = chalk.bold(`Netlify Dev Server now ready on ${url}`); + const banner = chalk.bold(`${NETLIFYDEV} Server now ready on ${url}`); this.log( boxen(banner, { padding: 1, diff --git a/src/commands/functions/build.js b/src/commands/functions/build.js index d613ac5..04e8c3a 100644 --- a/src/commands/functions/build.js +++ b/src/commands/functions/build.js @@ -2,6 +2,8 @@ const fs = require("fs"); const { flags } = require("@oclif/command"); const Command = require("@netlify/cli-utils"); const { zipFunctions } = require("@netlify/zip-it-and-ship-it"); +const chalk = require("chalk"); +const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`; class FunctionsBuildCommand extends Command { async run() { @@ -12,27 +14,29 @@ class FunctionsBuildCommand extends Command { const dst = flags.functions || config.build.functions; if (src === dst) { - this.log("Source and destination for function build can't be the same"); + this.log( + `${NETLIFYDEV} Source and destination for function build can't be the same` + ); process.exit(1); } if (!src || !dst) { if (!src) this.log( - "You must specify a source folder with a --src flag or a functionsSource field in your config" + `${NETLIFYDEV} Error: You must specify a source folder with a --src flag or a functionsSource field in your config` ); if (!dst) this.log( - "You must specify a destination functions folder with a --functions flag or a functions field in your config" + `${NETLIFYDEV} Error: You must specify a destination functions folder with a --functions flag or a functions field in your config` ); process.exit(1); } fs.mkdirSync(dst, { recursive: true }); - this.log("Building functions"); + this.log(`${NETLIFYDEV} Building functions`); zipFunctions(src, dst, { skipGo: true }); - this.log("Functions built to ", dst); + this.log(`${NETLIFYDEV} Functions built to `, dst); } } diff --git a/src/commands/functions/create.js b/src/commands/functions/create.js index e7846e7..d67397d 100644 --- a/src/commands/functions/create.js +++ b/src/commands/functions/create.js @@ -12,6 +12,8 @@ const cp = require("child_process"); const { createAddon } = require("netlify/src/addons"); const ora = require("ora"); const { track } = require("@netlify/cli-utils/src/utils/telemetry"); +const chalk = require("chalk"); +const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`; const templatesDir = path.resolve(__dirname, "../../functions-templates"); @@ -161,7 +163,6 @@ async function pickTemplate() { const filteredTemplateNames = filteredTemplates.map(x => input ? x.string : x ); - // console.log({ filteredTemplateNames }) return registry .filter(t => filteredTemplateNames.includes(t.name + t.description)) .map(t => { @@ -203,15 +204,17 @@ function ensureFunctionDirExists(flags, config) { const functionsDir = flags.functions || (config.build && config.build.functions); if (!functionsDir) { - this.log("No functions folder specified in netlify.toml or as an argument"); + this.log( + `${NETLIFYDEV} No functions folder specified in netlify.toml or as an argument` + ); process.exit(1); } if (!fs.existsSync(functionsDir)) { this.log( - `functions folder ${functionsDir} specified in netlify.toml but folder not found, creating it...` + `${NETLIFYDEV} functions folder ${functionsDir} specified in netlify.toml but folder not found, creating it...` ); fs.mkdirSync(functionsDir); - this.log(`functions folder ${functionsDir} created`); + this.log(`${NETLIFYDEV} functions folder ${functionsDir} created`); } return functionsDir; } @@ -227,7 +230,7 @@ async function downloadFromURL(flags, args, functionsDir) { fs.lstatSync(fnFolder + ".js").isFile() ) { this.log( - `A single file version of the function ${name} already exists at ${fnFolder}.js` + `${NETLIFYDEV} Warning: A single file version of the function ${name} already exists at ${fnFolder}.js. Terminating without further action.` ); process.exit(1); } @@ -254,9 +257,11 @@ async function downloadFromURL(flags, args, functionsDir) { }) ); - this.log(`installing dependencies for ${nameToUse}...`); + this.log(`${NETLIFYDEV} Installing dependencies for ${nameToUse}...`); cp.exec("npm i", { cwd: path.join(functionsDir, nameToUse) }, () => { - this.log(`installing dependencies for ${nameToUse} complete `); + this.log( + `${NETLIFYDEV} Installing dependencies for ${nameToUse} complete ` + ); }); // read, execute, and delete function template file if exists @@ -296,16 +301,13 @@ async function scaffoldFromTemplate(flags, args, functionsDir) { try { await downloadFromURL.call(this, flags, args, functionsDir); } catch (err) { - console.error("Error downloading from URL: " + flags.url); + console.error(`${NETLIFYDEV} Error downloading from URL: ` + flags.url); console.error(err); process.exit(1); } } else if (chosentemplate === "report") { console.log( - "opening in browser: https://github.com/netlify/netlify-dev-plugin/issues/new" - ); - require("../../utils/openBrowser.js")( - "https://github.com/netlify/netlify-dev-plugin/issues/new" + `${NETLIFYDEV} Open in browser: https://github.com/netlify/netlify-dev-plugin/issues/new` ); } else { const { @@ -323,7 +325,7 @@ async function scaffoldFromTemplate(flags, args, functionsDir) { } const name = await getNameFromArgs(args, flags, templateName); - this.log(`Creating function ${name}`); + this.log(`${NETLIFYDEV} Creating function ${name}`); const functionPath = ensureFunctionPathIsOk.call( this, functionsDir, @@ -338,7 +340,7 @@ async function scaffoldFromTemplate(flags, args, functionsDir) { copy(pathToTemplate, functionPath, vars, async (err, createdFiles) => { if (err) throw err; createdFiles.forEach(filePath => { - this.log(`Created ${filePath}`); + this.log(`${NETLIFYDEV} Created ${filePath}`); require("fs").chmodSync(path.resolve(filePath), 0o777); if (filePath.includes("package.json")) hasPackageJSON = true; }); @@ -377,12 +379,12 @@ async function installAddons(addons = [], fnPath) { ); return false; } - console.log("checking Netlify APIs..."); + console.log(`${NETLIFYDEV} checking Netlify APIs...`); return api.getSite({ siteId }).then(async siteData => { - const accessToken = await this.authenticate(); + const accessToken = api.accessToken; const arr = addons.map(({ addonName, addonDidInstall }) => { - console.log("installing addon: " + addonName); + console.log(`${NETLIFYDEV} installing addon: ` + addonName); // will prompt for configs if not supplied - we do not yet allow for addon configs supplied by `netlify functions:create` command and may never do so return createSiteAddon( accessToken, @@ -423,7 +425,9 @@ async function installAddons(addons = [], fnPath) { function ensureFunctionPathIsOk(functionsDir, flags, name) { const functionPath = path.join(functionsDir, name); if (fs.existsSync(functionPath)) { - this.log(`Function ${functionPath} already exists, cancelling...`); + this.log( + `${NETLIFYDEV} Function ${functionPath} already exists, cancelling...` + ); process.exit(1); } return functionPath; diff --git a/src/commands/functions/serve.js b/src/commands/functions/serve.js index 55f4e88..8c82ee2 100644 --- a/src/commands/functions/serve.js +++ b/src/commands/functions/serve.js @@ -1,8 +1,10 @@ const { Command, flags } = require("@oclif/command"); +const chalk = require("chalk"); +const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`; class FunctionsServeCommand extends Command { async run() { - this.log(`serve a function`); + this.log(`${NETLIFYDEV} NOT IMPLEMENTED YET: serve a function`); } } diff --git a/src/commands/functions/update.js b/src/commands/functions/update.js index 0ef4202..ce91812 100644 --- a/src/commands/functions/update.js +++ b/src/commands/functions/update.js @@ -1,8 +1,10 @@ const { Command, flags } = require("@oclif/command"); +const chalk = require("chalk"); +const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`; class FunctionsUpdateCommand extends Command { async run() { - this.log(`update a function`); + this.log(`${NETLIFYDEV} NOT IMPLEMENTED YET: update a function`); } } diff --git a/src/live-tunnel.js b/src/live-tunnel.js index 2a780e3..cdd5f26 100644 --- a/src/live-tunnel.js +++ b/src/live-tunnel.js @@ -4,6 +4,8 @@ const os = require("os"); const path = require("path"); const execa = require("execa"); const { fetchLatest, updateAvailable } = require("gh-release-fetch"); +const chalk = require("chalk"); +const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`; async function createTunnel(siteId, netlifyApiToken, log) { await installTunnelClient(log); @@ -14,7 +16,7 @@ async function createTunnel(siteId, netlifyApiToken, log) { ); process.exit(1); } - log("Creating Live Tunnel for " + siteId); + log(`${NETLIFYDEV} Creating Live Tunnel for ` + siteId); const url = `https://api.netlify.com/api/v1/live_sessions?site_id=${siteId}`; const response = await fetch(url, { @@ -71,7 +73,7 @@ async function installTunnelClient(log) { return; } - log("Installing Live Tunnel Client"); + log(`${NETLIFYDEV} Installing Live Tunnel Client`); const win = isWindows(); const platform = win ? "windows" : process.platform; diff --git a/src/utils/dev.js b/src/utils/dev.js index 830f4f0..b2fe437 100644 --- a/src/utils/dev.js +++ b/src/utils/dev.js @@ -9,9 +9,9 @@ const NETLIFYDEV = `[${chalk.cyan("Netlify Dev")}]`; * * ``` * // usage example - * const { site } = this.netlify + * const { site, api } = this.netlify * if (site.id) { - * const accessToken = await this.authenticate() + * const accessToken = api.accessToken * const addonUrls = await addEnvVariables(site, accessToken) * // addonUrls is only for startProxy in netlify dev:index * }