Improve CLI & CLI Testing (#2245)
* Fix astro --version, astro --help * Improve CLI testing * nit: fix for windows * nit: try different async cli tests * fix: core dev * nit: cleanup core * nit: change port for config test * nit: write config differently than project-root * nit: cleanup AstroDevServer properties
This commit is contained in:
parent
4208640587
commit
b214b095f3
7 changed files with 117 additions and 63 deletions
|
@ -64,7 +64,7 @@ function resolveArgs(flags: Arguments): CLIState {
|
||||||
|
|
||||||
/** Display --help flag */
|
/** Display --help flag */
|
||||||
function printHelp() {
|
function printHelp() {
|
||||||
console.error(` ${colors.bold('astro')} - Futuristic web development tool.
|
console.log(` ${colors.bold('astro')} - Futuristic web development tool.
|
||||||
${colors.bold('Commands:')}
|
${colors.bold('Commands:')}
|
||||||
astro dev Run Astro in development mode.
|
astro dev Run Astro in development mode.
|
||||||
astro build Build a pre-compiled production version of your site.
|
astro build Build a pre-compiled production version of your site.
|
||||||
|
@ -85,8 +85,11 @@ function printHelp() {
|
||||||
|
|
||||||
/** Display --version flag */
|
/** Display --version flag */
|
||||||
async function printVersion() {
|
async function printVersion() {
|
||||||
const pkg = JSON.parse(await fs.promises.readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
const pkgURL = new URL('../../package.json', import.meta.url);
|
||||||
console.error(pkg.version);
|
const pkg = JSON.parse(await fs.promises.readFile(pkgURL, 'utf8'));
|
||||||
|
const pkgVersion = pkg.version;
|
||||||
|
|
||||||
|
console.log(pkgVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merge CLI flags & config options (CLI flags take priority) */
|
/** Merge CLI flags & config options (CLI flags take priority) */
|
||||||
|
@ -105,11 +108,21 @@ export async function cli(args: string[]) {
|
||||||
const options = { ...state.options };
|
const options = { ...state.options };
|
||||||
const projectRoot = options.projectRoot || flags._[3];
|
const projectRoot = options.projectRoot || flags._[3];
|
||||||
|
|
||||||
|
switch (state.cmd) {
|
||||||
|
case 'help':
|
||||||
|
printHelp();
|
||||||
|
return process.exit(0);
|
||||||
|
case 'version':
|
||||||
|
await printVersion();
|
||||||
|
return process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
// logLevel
|
// logLevel
|
||||||
let logging: LogOptions = {
|
let logging: LogOptions = {
|
||||||
dest: defaultLogDestination,
|
dest: defaultLogDestination,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (flags.verbose) logging.level = 'debug';
|
if (flags.verbose) logging.level = 'debug';
|
||||||
if (flags.silent) logging.level = 'silent';
|
if (flags.silent) logging.level = 'silent';
|
||||||
let config: AstroConfig;
|
let config: AstroConfig;
|
||||||
|
@ -118,7 +131,7 @@ export async function cli(args: string[]) {
|
||||||
mergeCLIFlags(config, options);
|
mergeCLIFlags(config, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof z.ZodError) {
|
if (err instanceof z.ZodError) {
|
||||||
console.log(formatConfigError(err));
|
console.error(formatConfigError(err));
|
||||||
} else {
|
} else {
|
||||||
console.error(colors.red((err as any).toString() || err));
|
console.error(colors.red((err as any).toString() || err));
|
||||||
}
|
}
|
||||||
|
@ -126,23 +139,15 @@ export async function cli(args: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (state.cmd) {
|
switch (state.cmd) {
|
||||||
case 'help': {
|
|
||||||
printHelp();
|
|
||||||
process.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'version': {
|
|
||||||
await printVersion();
|
|
||||||
process.exit(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'dev': {
|
case 'dev': {
|
||||||
try {
|
try {
|
||||||
const server = await devServer(config, { logging });
|
await devServer(config, { logging });
|
||||||
|
|
||||||
await new Promise(() => {}); // don’t close dev server
|
await new Promise(() => {}); // don’t close dev server
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwAndExit(err);
|
throwAndExit(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'build': {
|
case 'build': {
|
||||||
|
@ -156,8 +161,7 @@ export async function cli(args: string[]) {
|
||||||
}
|
}
|
||||||
case 'check': {
|
case 'check': {
|
||||||
const ret = await check(config);
|
const ret = await check(config);
|
||||||
process.exit(ret);
|
return process.exit(ret);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case 'preview': {
|
case 'preview': {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -57,27 +57,27 @@ export default async function dev(config: AstroConfig, options: DevOptions = { l
|
||||||
|
|
||||||
/** Dev server */
|
/** Dev server */
|
||||||
export class AstroDevServer {
|
export class AstroDevServer {
|
||||||
app = connect();
|
app: connect.Server = connect();
|
||||||
httpServer: http.Server | undefined;
|
config: AstroConfig;
|
||||||
|
devRoot: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
httpServer: http.Server | undefined;
|
||||||
|
logging: LogOptions;
|
||||||
|
manifest: ManifestData;
|
||||||
|
mostRecentRoute?: RouteData;
|
||||||
|
origin: string;
|
||||||
port: number;
|
port: number;
|
||||||
private config: AstroConfig;
|
routeCache: RouteCache = {};
|
||||||
private logging: LogOptions;
|
site: URL | undefined;
|
||||||
private manifest: ManifestData;
|
url: URL;
|
||||||
private mostRecentRoute?: RouteData;
|
viteServer: vite.ViteDevServer | undefined;
|
||||||
private site: URL | undefined;
|
|
||||||
private devRoot: string;
|
|
||||||
private url: URL;
|
|
||||||
private origin: string;
|
|
||||||
private routeCache: RouteCache = {};
|
|
||||||
private viteServer: vite.ViteDevServer | undefined;
|
|
||||||
|
|
||||||
constructor(config: AstroConfig, options: DevOptions) {
|
constructor(config: AstroConfig, options: DevOptions) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.hostname = config.devOptions.hostname || 'localhost';
|
this.hostname = config.devOptions.hostname || 'localhost';
|
||||||
this.logging = options.logging;
|
this.logging = options.logging;
|
||||||
this.port = config.devOptions.port;
|
this.port = config.devOptions.port;
|
||||||
this.origin = `http://localhost:${this.port}`;
|
this.origin = `http://${this.hostname}:${this.port}`;
|
||||||
this.site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined;
|
this.site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined;
|
||||||
this.devRoot = this.site ? this.site.pathname : '/';
|
this.devRoot = this.site ? this.site.pathname : '/';
|
||||||
this.url = new URL(this.devRoot, this.origin);
|
this.url = new URL(this.devRoot, this.origin);
|
||||||
|
@ -204,7 +204,7 @@ export class AstroDevServer {
|
||||||
public listen(devStart: number): Promise<void> {
|
public listen(devStart: number): Promise<void> {
|
||||||
let showedPortTakenMsg = false;
|
let showedPortTakenMsg = false;
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const listen = () => {
|
const appListen = () => {
|
||||||
this.httpServer = this.app.listen(this.port, this.hostname, () => {
|
this.httpServer = this.app.listen(this.port, this.hostname, () => {
|
||||||
info(this.logging, 'astro', msg.devStart({ startupTime: performance.now() - devStart }));
|
info(this.logging, 'astro', msg.devStart({ startupTime: performance.now() - devStart }));
|
||||||
info(this.logging, 'astro', msg.devHost({ host: `http://${this.hostname}:${this.port}${this.devRoot}` }));
|
info(this.logging, 'astro', msg.devHost({ host: `http://${this.hostname}:${this.port}${this.devRoot}` }));
|
||||||
|
@ -220,7 +220,7 @@ export class AstroDevServer {
|
||||||
showedPortTakenMsg = true; // only print this once
|
showedPortTakenMsg = true; // only print this once
|
||||||
}
|
}
|
||||||
this.port++;
|
this.port++;
|
||||||
return listen(); // retry
|
return appListen(); // retry
|
||||||
} else {
|
} else {
|
||||||
error(this.logging, 'astro', err.stack);
|
error(this.logging, 'astro', err.stack);
|
||||||
this.httpServer?.removeListener('error', onError);
|
this.httpServer?.removeListener('error', onError);
|
||||||
|
@ -228,7 +228,7 @@ export class AstroDevServer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
listen();
|
appListen();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,8 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO
|
||||||
}).pipe(res);
|
}).pipe(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
let port = config.devOptions.port;
|
let { hostname, port } = config.devOptions;
|
||||||
const { hostname } = config.devOptions;
|
|
||||||
let httpServer: http.Server;
|
let httpServer: http.Server;
|
||||||
|
|
||||||
/** Expose dev server to `port` */
|
/** Expose dev server to `port` */
|
||||||
|
|
47
packages/astro/test/cli.test.js
Normal file
47
packages/astro/test/cli.test.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { cli } from './test-utils.js';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
describe('astro cli', () => {
|
||||||
|
it('astro', async () => {
|
||||||
|
const proc = await cli();
|
||||||
|
|
||||||
|
expect(proc.stdout).to.include('astro - Futuristic web development tool');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('astro --version', async () => {
|
||||||
|
const pkgURL = new URL('../package.json', import.meta.url);
|
||||||
|
const pkgVersion = await fs.readFile(pkgURL, 'utf8').then((data) => JSON.parse(data).version);
|
||||||
|
|
||||||
|
const proc = await cli('--version');
|
||||||
|
|
||||||
|
expect(proc.stdout).to.equal(pkgVersion);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('astro dev', async () => {
|
||||||
|
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
||||||
|
|
||||||
|
const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL));
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
|
||||||
|
for await (const chunk of proc.stdout) {
|
||||||
|
stdout += chunk;
|
||||||
|
|
||||||
|
if (chunk.includes('Local:')) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
proc.kill();
|
||||||
|
|
||||||
|
expect(stdout).to.include('Server started');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('astro build', async () => {
|
||||||
|
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
||||||
|
|
||||||
|
const proc = await cli('build', '--project-root', fileURLToPath(projectRootURL));
|
||||||
|
|
||||||
|
expect(proc.stdout).to.include('Done');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,6 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { devCLI, loadFixture } from './test-utils.js';
|
import { cli, loadFixture } from './test-utils.js';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
describe('config', () => {
|
describe('config', () => {
|
||||||
let hostnameFixture;
|
let hostnameFixture;
|
||||||
|
@ -15,41 +16,40 @@ describe('config', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be specified via --hostname flag', async () => {
|
it('can be specified via --hostname flag', async () => {
|
||||||
const cwd = './fixtures/config-hostname/';
|
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
||||||
const cwdURL = new URL(cwd, import.meta.url);
|
const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), '--hostname', '127.0.0.1');
|
||||||
const args = ['--hostname', '127.0.0.1'];
|
|
||||||
const proc = devCLI(cwdURL, args);
|
|
||||||
|
|
||||||
proc.stdout.setEncoding('utf8');
|
let stdout = '';
|
||||||
|
|
||||||
for await (const chunk of proc.stdout) {
|
for await (const chunk of proc.stdout) {
|
||||||
if (/Local:/.test(chunk)) {
|
stdout += chunk;
|
||||||
expect(chunk).to.include('127.0.0.1');
|
|
||||||
break;
|
if (chunk.includes('Local:')) break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proc.kill();
|
proc.kill();
|
||||||
|
|
||||||
|
expect(stdout).to.include('127.0.0.1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('path', () => {
|
describe('path', () => {
|
||||||
it('can be passed via --config', async () => {
|
it('can be passed via --config', async () => {
|
||||||
const cwd = './fixtures/config-path/';
|
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
||||||
const cwdURL = new URL(cwd, import.meta.url);
|
const configFileURL = new URL('./fixtures/config-path/config/my-config.mjs', import.meta.url);
|
||||||
const configPath = new URL('./config/my-config.mjs', cwdURL).pathname;
|
const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), '--config', configFileURL.pathname);
|
||||||
const args = ['--config', configPath];
|
|
||||||
const proc = devCLI(cwdURL, args);
|
|
||||||
|
|
||||||
proc.stdout.setEncoding('utf8');
|
let stdout = '';
|
||||||
|
|
||||||
for await (const chunk of proc.stdout) {
|
for await (const chunk of proc.stdout) {
|
||||||
if (/Server started/.test(chunk)) {
|
stdout += chunk;
|
||||||
break;
|
|
||||||
}
|
if (chunk.includes('Local:')) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
proc.kill();
|
proc.kill();
|
||||||
|
|
||||||
|
expect(stdout).to.include('127.0.0.1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
renderers: [
|
devOptions: {
|
||||||
'@astrojs/renderer-preact'
|
hostname: '127.0.0.1',
|
||||||
]
|
port: 8080,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,11 +99,13 @@ function merge(a, b) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cliURL = new URL('../astro.js', import.meta.url);
|
const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url));
|
||||||
|
|
||||||
/** Start Dev server via CLI */
|
/** Returns a process running the Astro CLI. */
|
||||||
export function devCLI(root, additionalArgs = []) {
|
export function cli(/** @type {string[]} */ ...args) {
|
||||||
const args = [cliURL.pathname, 'dev', '--project-root', root.pathname].concat(additionalArgs);
|
const spawned = execa('node', [cliPath, ...args]);
|
||||||
const proc = execa('node', args);
|
|
||||||
return proc;
|
spawned.stdout.setEncoding('utf8');
|
||||||
|
|
||||||
|
return spawned;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue