From da033e27eada3bbcad5544be282e9edcf4799003 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 22 Apr 2021 08:25:57 -0400 Subject: [PATCH] CLI docs (#121) * Start of cli docs * Document the CLI Also adds support for the `--config` option and `--port` option for the dev server. * Add tests for --config and --port flags * Add port to validateConfig --- README.md | 12 +++++- docs/cli.md | 41 +++++++++++++++++++ package-lock.json | 12 +++--- package.json | 1 + src/@types/astro.ts | 18 +++++++- src/cli.ts | 23 ++++++++--- src/config.ts | 11 +++-- src/dev.ts | 2 +- test/config-path.test.js | 24 +++++++++++ test/config-port.test.js | 29 +++++++++++++ .../fixtures/config-path/config/my-config.mjs | 5 +++ test/fixtures/config-port/astro.config.mjs | 6 +++ test/helpers.js | 9 ++++ 13 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 docs/cli.md create mode 100644 test/config-path.test.js create mode 100644 test/config-port.test.js create mode 100644 test/fixtures/config-path/config/my-config.mjs create mode 100644 test/fixtures/config-port/astro.config.mjs diff --git a/README.md b/README.md index 32ddd7109..92790b883 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,16 @@ export default { }, /** Your public domain, e.g.: https://my-site.dev/ */ site: '', - /** Generate sitemap (set to "false" to disable) */ - sitemap: true, + /** Options specific to `astro build` */ + buildOptions: { + /** Generate sitemap (set to "false" to disable) */ + sitemap: true, + }, + /** Options for the development server run with `astro dev`. */ + devOptions: { + /** The port to run the dev server on. */ + port: 3000 + } }; ``` diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 000000000..c413323d5 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,41 @@ +## 👩🏽‍💻 Command Line Interface + +### Global Flags + +#### `--config path` + +Specify the path to the config file. Defaults to `astro.config.mjs`. Use this if you use a different name for your configuration file or have your config file in another folder. + +```shell +astro --config config/astro.config.mjs dev +``` + +#### `--project-root path` + +Specify the path to the project root. If not specified the current working directory is assumed to be the root. + +The root is used for finding the Astro configuration file. + +```shell +astro --project-root examples/snowpack dev +``` + +#### `--version` + +Print the Astro version number and exit. + +#### `--help` + +Print the help message and exit. + +### Commands + +#### `astro dev` + +Runs the Astro development server. This starts an HTTP server that responds to requests for pages stored in `astro/pages` (or which folder is specified in your [configuration](../README.md##%EF%B8%8F-configuration)). + +__Flags__ + +##### `--port` + +Specifies should port to run on. Defaults to `3000`. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d15448f3d..8032598e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1788,13 +1788,6 @@ "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==" - } } }, "extend": { @@ -2011,6 +2004,11 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", diff --git a/package.json b/package.json index a73e62c43..02bec1423 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "eslint": "^7.22.0", "eslint-config-prettier": "^8.1.0", "eslint-plugin-prettier": "^3.3.1", + "execa": "^5.0.0", "prettier": "^2.2.1", "typescript": "^4.2.3", "uvu": "^0.5.1" diff --git a/src/@types/astro.ts b/src/@types/astro.ts index 4ecab847d..d991dbbcf 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -17,7 +17,23 @@ export interface AstroConfig { /** Public URL base (e.g. 'https://mysite.com'). Used in generating sitemaps and canonical URLs. */ site?: string; /** Generate a sitemap? */ - sitemap: boolean; + buildOptions: { + sitemap: boolean; + }; + devOptions: { + port: number; + projectRoot?: string; + }; +} + +export type AstroUserConfig = Omit & { + buildOptions: { + sitemap: boolean; + }; + devOptions: { + port?: number; + projectRoot?: string; + }; } export interface JsxItem { diff --git a/src/cli.ts b/src/cli.ts index c56a4c098..be0dfe27a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,14 +20,20 @@ type cliCommand = 'help' | 'version' | 'dev' | 'build'; interface CLIState { cmd: cliCommand; options: { + projectRoot?: string; sitemap?: boolean; + port?: number; + config?: string; }; } /** Determine which action the user requested */ function resolveArgs(flags: Arguments): CLIState { const options: CLIState['options'] = { + projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot: undefined, sitemap: typeof flags.sitemap === 'boolean' ? flags.sitemap : undefined, + port: typeof flags.port === 'number' ? flags.port : undefined, + config: typeof flags.config === 'string' ? flags.config : undefined }; if (flags.version) { @@ -52,13 +58,15 @@ function printHelp() { console.error(` ${colors.bold('astro')} - Futuristic web development tool. ${colors.bold('Commands:')} - astro dev Run Astro in development mode. - astro build Build a pre-compiled production version of your site. + astro dev Run Astro in development mode. + astro build Build a pre-compiled production version of your site. ${colors.bold('Flags:')} - --version Show the version number and exit. - --help Show this help message. + --config Specify the path to the Astro config file. + --project-root Specify the path to the project root folder. --no-sitemap Disable sitemap generation (build only). + --version Show the version number and exit. + --help Show this help message. `); } @@ -70,14 +78,17 @@ async function printVersion() { /** Merge CLI flags & config options (CLI flags take priority) */ function mergeCLIFlags(astroConfig: AstroConfig, flags: CLIState['options']) { - if (typeof flags.sitemap === 'boolean') astroConfig.sitemap = flags.sitemap; + if (typeof flags.sitemap === 'boolean') astroConfig.buildOptions.sitemap = flags.sitemap; + if (typeof flags.port === 'number') astroConfig.devOptions.port = flags.port; } /** Handle `astro run` command */ async function runCommand(rawRoot: string, cmd: (a: AstroConfig) => Promise, options: CLIState['options']) { try { - const astroConfig = await loadConfig(rawRoot); + const projectRoot = options.projectRoot || rawRoot; + const astroConfig = await loadConfig(projectRoot, options.config); mergeCLIFlags(astroConfig, options); + return cmd(astroConfig); } catch (err) { console.error(colors.red(err.toString() || err)); diff --git a/src/config.ts b/src/config.ts index 937499923..af618a27f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,6 +24,10 @@ function validateConfig(config: any): void { throw new Error(`[astro config] ${key}: ${JSON.stringify(config[key])}\n Expected boolean, received ${type(config[key])}.`); } } + + if(config.devOptions?.port !== 'number') { + throw new Error(`[astro config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`) + } } /** Set default config values */ @@ -34,7 +38,8 @@ function configDefaults(userConfig?: any): any { if (!config.astroRoot) config.astroRoot = './astro'; if (!config.dist) config.dist = './_site'; if (!config.public) config.public = './public'; - + if (!config.devOptions) config.devOptions = {}; + if (!config.devOptions.port) config.devOptions.port = 3000; if (typeof config.sitemap === 'undefined') config.sitemap = true; return config; @@ -53,13 +58,13 @@ function normalizeConfig(userConfig: any, root: string): AstroConfig { } /** Attempt to load an `astro.config.mjs` file */ -export async function loadConfig(rawRoot: string | undefined): Promise { +export async function loadConfig(rawRoot: string | undefined, configFileName = 'astro.config.mjs'): Promise { if (typeof rawRoot === 'undefined') { rawRoot = process.cwd(); } const root = pathResolve(rawRoot); - const astroConfigPath = pathJoin(root, 'astro.config.mjs'); + const astroConfigPath = pathJoin(root, configFileName); // load let config: any; diff --git a/src/dev.ts b/src/dev.ts index 8c4140259..8f93aabf6 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -8,7 +8,6 @@ import { defaultLogDestination, error, parseError } from './logger.js'; import { createRuntime } from './runtime.js'; const hostname = '127.0.0.1'; -const port = 3000; // Disable snowpack from writing to stdout/err. snowpackLogger.level = 'silent'; @@ -65,6 +64,7 @@ export default async function dev(astroConfig: AstroConfig) { } }); + const port = astroConfig.devOptions.port; server.listen(port, hostname, () => { // eslint-disable-next-line no-console console.log(`Server running at http://${hostname}:${port}/`); diff --git a/test/config-path.test.js b/test/config-path.test.js new file mode 100644 index 000000000..a13e099b3 --- /dev/null +++ b/test/config-path.test.js @@ -0,0 +1,24 @@ +import { suite } from 'uvu'; +import * as assert from 'uvu/assert'; +import { runDevServer } from './helpers.js'; + +const ConfigPath = suite('Config path'); + +const root = new URL('./fixtures/config-path/', import.meta.url); +ConfigPath('can be passed via --config', async (context) => { + const configPath = new URL('./config/my-config.mjs', root).pathname; + const args = ['--config', configPath]; + const process = runDevServer(root, args); + + process.stdout.setEncoding('utf8'); + for await (const chunk of process.stdout) { + if(/Server running at/.test(chunk)) { + break; + } + } + + process.kill(); + assert.ok(true, 'Server started'); +}); + +ConfigPath.run(); diff --git a/test/config-port.test.js b/test/config-port.test.js new file mode 100644 index 000000000..9b5f7b14c --- /dev/null +++ b/test/config-port.test.js @@ -0,0 +1,29 @@ +import { suite } from 'uvu'; +import * as assert from 'uvu/assert'; +import { runDevServer } from './helpers.js'; +import { loadConfig } from '../lib/config.js'; + +const ConfigPort = suite('Config path'); + +const root = new URL('./fixtures/config-port/', import.meta.url); +ConfigPort('can be specified in the astro config', async (context) => { + const astroConfig = await loadConfig(root.pathname); + assert.equal(astroConfig.devOptions.port, 3001); +}); + +ConfigPort('can be specified via --port flag', async (context) => { + const args = ['--port', '3002']; + const process = runDevServer(root, args); + + process.stdout.setEncoding('utf8'); + for await (const chunk of process.stdout) { + if(/Server running at/.test(chunk)) { + assert.ok(/:3002/.test(chunk), 'Using the right port'); + break; + } + } + + process.kill(); +}); + +ConfigPort.run(); diff --git a/test/fixtures/config-path/config/my-config.mjs b/test/fixtures/config-path/config/my-config.mjs new file mode 100644 index 000000000..f50751cfd --- /dev/null +++ b/test/fixtures/config-path/config/my-config.mjs @@ -0,0 +1,5 @@ +export default { + extensions: { + '.jsx': 'preact', + }, +}; diff --git a/test/fixtures/config-port/astro.config.mjs b/test/fixtures/config-port/astro.config.mjs new file mode 100644 index 000000000..61858cdae --- /dev/null +++ b/test/fixtures/config-port/astro.config.mjs @@ -0,0 +1,6 @@ + +export default { + devOptions: { + port: 3001 + } +} \ No newline at end of file diff --git a/test/helpers.js b/test/helpers.js index 8ca42ea11..eb7cabb0b 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -4,6 +4,8 @@ import { readFile } from 'fs/promises'; import { createRuntime } from '../lib/runtime.js'; import { loadConfig } from '../lib/config.js'; import * as assert from 'uvu/assert'; +import execa from 'execa'; + /** setup fixtures for tests */ export function setup(Suite, fixturePath) { let runtime, setupError; @@ -62,3 +64,10 @@ export function setupBuild(Suite, fixturePath) { assert.equal(setupError, undefined); }); } + +const cliURL = new URL('../astro.mjs', import.meta.url); +export function runDevServer(root, additionalArgs = []) { + const args = [cliURL.pathname, 'dev', '--project-root', root.pathname].concat(additionalArgs); + const proc = execa('node', args); + return proc; +} \ No newline at end of file