mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
feat(cli): add --watch
to astro check
command (#6356)
* feat(cli): add `--watch` to `astro check` command * chore: refactor in a leaner way, logic not changed * chore: lint * chore: revert changes in sync command * chore: tweak server settings * test: add one test case * chore: increase timeout * test: predictable testing * chore: add changeset * chore: code suggestions * code suggestions * chore: use directly `chokidar` * chore: tweak code * fix: open documents first * chore: disable test * chore: code suggestions * chore: code suggestions * Apply suggestions from code review Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> * code suggestions * chore: rebase --------- Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
This commit is contained in:
parent
1291afc09d
commit
75921b3cd9
13 changed files with 472 additions and 74 deletions
5
.changeset/six-moons-taste.md
Normal file
5
.changeset/six-moons-taste.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': minor
|
||||
---
|
||||
|
||||
Added a new `--watch` flag to the command `astro check`
|
|
@ -113,6 +113,7 @@
|
|||
"@types/yargs-parser": "^21.0.0",
|
||||
"acorn": "^8.8.1",
|
||||
"boxen": "^6.2.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"ci-info": "^3.3.1",
|
||||
"common-ancestor-path": "^1.0.1",
|
||||
"cookie": "^0.5.0",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable no-console */
|
||||
import { AstroCheck, DiagnosticSeverity } from '@astrojs/language-server';
|
||||
import { AstroCheck, DiagnosticSeverity, GetDiagnosticsResult } from '@astrojs/language-server';
|
||||
import glob from 'fast-glob';
|
||||
import * as fs from 'fs';
|
||||
import { bold, dim, red, yellow } from 'kleur/colors';
|
||||
|
@ -11,18 +11,77 @@ import type { AstroSettings } from '../../@types/astro';
|
|||
import type { LogOptions } from '../../core/logger/core.js';
|
||||
import { printHelp } from '../../core/messages.js';
|
||||
import { printDiagnostic } from './print.js';
|
||||
import type { Arguments as Flags } from 'yargs-parser';
|
||||
import { debug, info } from '../../core/logger/core.js';
|
||||
import type { ProcessExit, SyncOptions } from '../../core/sync';
|
||||
import fsMod from 'fs';
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
import { join } from 'node:path';
|
||||
|
||||
interface Result {
|
||||
type DiagnosticResult = {
|
||||
errors: number;
|
||||
// The language server cannot actually return any warnings at the moment, but we'll keep this here for future use
|
||||
warnings: number;
|
||||
hints: number;
|
||||
};
|
||||
|
||||
export type CheckPayload = {
|
||||
/**
|
||||
* Flags passed via CLI
|
||||
*/
|
||||
flags: Flags;
|
||||
|
||||
/**
|
||||
* Logging options
|
||||
*/
|
||||
logging: LogOptions;
|
||||
};
|
||||
|
||||
type CheckFlags = {
|
||||
/**
|
||||
* Whether the `check` command should watch for `.astro` and report errors
|
||||
* @default {false}
|
||||
*/
|
||||
watch: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Types of response emitted by the checker
|
||||
*/
|
||||
export enum CheckResult {
|
||||
/**
|
||||
* Operation finished without errors
|
||||
*/
|
||||
ExitWithSuccess,
|
||||
/**
|
||||
* Operation finished with errors
|
||||
*/
|
||||
ExitWithError,
|
||||
/**
|
||||
* The consumer should not terminate the operation
|
||||
*/
|
||||
Listen,
|
||||
}
|
||||
|
||||
const ASTRO_GLOB_PATTERN = '**/*.astro';
|
||||
|
||||
/**
|
||||
* Checks `.astro` files for possible errors.
|
||||
*
|
||||
* If the `--watch` flag is provided, the command runs indefinitely and provides diagnostics
|
||||
* when `.astro` files are modified.
|
||||
*
|
||||
* Every time an astro files is modified, content collections are also generated.
|
||||
*
|
||||
* @param {AstroSettings} settings
|
||||
* @param {CheckPayload} options Options passed {@link AstroChecker}
|
||||
* @param {Flags} options.flags Flags coming from the CLI
|
||||
* @param {LogOptions} options.logging Logging options
|
||||
*/
|
||||
export async function check(
|
||||
settings: AstroSettings,
|
||||
{ logging, flags }: { logging: LogOptions; flags: Arguments }
|
||||
) {
|
||||
{ logging, flags }: CheckPayload
|
||||
): Promise<AstroChecker | undefined> {
|
||||
if (flags.help || flags.h) {
|
||||
printHelp({
|
||||
commandName: 'astro check',
|
||||
|
@ -34,66 +93,256 @@ export async function check(
|
|||
});
|
||||
return;
|
||||
}
|
||||
console.log(bold('astro check'));
|
||||
const checkFlags = parseFlags(flags);
|
||||
if (checkFlags.watch) {
|
||||
info(logging, 'check', 'Checking files in watch mode');
|
||||
} else {
|
||||
info(logging, 'check', 'Checking files');
|
||||
}
|
||||
|
||||
const { syncCli } = await import('../../core/sync/index.js');
|
||||
const syncRet = await syncCli(settings, { logging, fs, flags });
|
||||
// early exit on sync failure
|
||||
if (syncRet === 1) return syncRet;
|
||||
|
||||
const root = settings.config.root;
|
||||
|
||||
const spinner = ora(` Getting diagnostics for Astro files in ${fileURLToPath(root)}…`).start();
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
let checker = new AstroCheck(
|
||||
const diagnosticChecker = new AstroCheck(
|
||||
root.toString(),
|
||||
require.resolve('typescript/lib/tsserverlibrary.js', { paths: [root.toString()] })
|
||||
require.resolve('typescript/lib/tsserverlibrary.js', {
|
||||
paths: [root.toString()],
|
||||
})
|
||||
);
|
||||
const filesCount = await openAllDocuments(root, [], checker);
|
||||
|
||||
let diagnostics = await checker.getDiagnostics();
|
||||
|
||||
spinner.succeed();
|
||||
|
||||
let result: Result = {
|
||||
errors: 0,
|
||||
warnings: 0,
|
||||
hints: 0,
|
||||
};
|
||||
|
||||
diagnostics.forEach((diag) => {
|
||||
diag.diagnostics.forEach((d) => {
|
||||
console.log(printDiagnostic(diag.fileUri, diag.text, d));
|
||||
|
||||
switch (d.severity) {
|
||||
case DiagnosticSeverity.Error: {
|
||||
result.errors++;
|
||||
break;
|
||||
}
|
||||
case DiagnosticSeverity.Warning: {
|
||||
result.warnings++;
|
||||
break;
|
||||
}
|
||||
case DiagnosticSeverity.Hint: {
|
||||
result.hints++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return new AstroChecker({
|
||||
syncCli,
|
||||
settings,
|
||||
fileSystem: fs,
|
||||
logging,
|
||||
diagnosticChecker,
|
||||
isWatchMode: checkFlags.watch,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
[
|
||||
bold(`Result (${filesCount} file${filesCount === 1 ? '' : 's'}): `),
|
||||
bold(red(`${result.errors} ${result.errors === 1 ? 'error' : 'errors'}`)),
|
||||
bold(yellow(`${result.warnings} ${result.warnings === 1 ? 'warning' : 'warnings'}`)),
|
||||
dim(`${result.hints} ${result.hints === 1 ? 'hint' : 'hints'}\n`),
|
||||
].join(`\n${dim('-')} `)
|
||||
);
|
||||
type CheckerConstructor = {
|
||||
diagnosticChecker: AstroCheck;
|
||||
|
||||
const exitCode = result.errors ? 1 : 0;
|
||||
return exitCode;
|
||||
isWatchMode: boolean;
|
||||
|
||||
syncCli: (settings: AstroSettings, options: SyncOptions) => Promise<ProcessExit>;
|
||||
|
||||
settings: Readonly<AstroSettings>;
|
||||
|
||||
logging: Readonly<LogOptions>;
|
||||
|
||||
fileSystem: typeof fsMod;
|
||||
};
|
||||
|
||||
/**
|
||||
* Responsible to check files - classic or watch mode - and report diagnostics.
|
||||
*
|
||||
* When in watch mode, the class does a whole check pass, and then starts watching files.
|
||||
* When a change occurs to an `.astro` file, the checker builds content collections again and lint all the `.astro` files.
|
||||
*/
|
||||
export class AstroChecker {
|
||||
readonly #diagnosticsChecker: AstroCheck;
|
||||
readonly #shouldWatch: boolean;
|
||||
readonly #syncCli: (settings: AstroSettings, opts: SyncOptions) => Promise<ProcessExit>;
|
||||
|
||||
readonly #settings: AstroSettings;
|
||||
|
||||
readonly #logging: LogOptions;
|
||||
readonly #fs: typeof fsMod;
|
||||
#watcher?: FSWatcher;
|
||||
|
||||
#filesCount: number;
|
||||
#updateDiagnostics: NodeJS.Timeout | undefined;
|
||||
|
||||
constructor({
|
||||
diagnosticChecker,
|
||||
isWatchMode,
|
||||
syncCli,
|
||||
settings,
|
||||
fileSystem,
|
||||
logging,
|
||||
}: CheckerConstructor) {
|
||||
this.#diagnosticsChecker = diagnosticChecker;
|
||||
this.#shouldWatch = isWatchMode;
|
||||
this.#syncCli = syncCli;
|
||||
this.#logging = logging;
|
||||
this.#settings = settings;
|
||||
this.#fs = fileSystem;
|
||||
this.#filesCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all `.astro` files once and then finishes the operation.
|
||||
*/
|
||||
public async check(): Promise<CheckResult> {
|
||||
return await this.#checkAllFiles(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all `.astro` files and then start watching for changes.
|
||||
*/
|
||||
public async watch(): Promise<CheckResult> {
|
||||
await this.#checkAllFiles(true);
|
||||
await this.#watch();
|
||||
return CheckResult.Listen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the watch. It terminates the inner server.
|
||||
*/
|
||||
public async stop() {
|
||||
await this.#watcher?.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the checker should run in watch mode
|
||||
*/
|
||||
public get isWatchMode(): boolean {
|
||||
return this.#shouldWatch;
|
||||
}
|
||||
|
||||
async #openDocuments() {
|
||||
this.#filesCount = await openAllDocuments(
|
||||
this.#settings.config.root,
|
||||
[],
|
||||
this.#diagnosticsChecker
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint all `.astro` files, and report the result in console. Operations executed, in order:
|
||||
* 1. Compile content collections.
|
||||
* 2. Optionally, traverse the file system for `.astro` files and saves their paths.
|
||||
* 3. Get diagnostics for said files and print the result in console.
|
||||
*
|
||||
* @param openDocuments Whether the operation should open all `.astro` files
|
||||
*/
|
||||
async #checkAllFiles(openDocuments: boolean): Promise<CheckResult> {
|
||||
const processExit = await this.#syncCli(this.#settings, {
|
||||
logging: this.#logging,
|
||||
fs: this.#fs,
|
||||
});
|
||||
// early exit on sync failure
|
||||
if (processExit === 1) return processExit;
|
||||
|
||||
let spinner = ora(
|
||||
` Getting diagnostics for Astro files in ${fileURLToPath(this.#settings.config.root)}…`
|
||||
).start();
|
||||
|
||||
if (openDocuments) {
|
||||
await this.#openDocuments();
|
||||
}
|
||||
|
||||
let diagnostics = await this.#diagnosticsChecker.getDiagnostics();
|
||||
|
||||
spinner.succeed();
|
||||
|
||||
let brokenDownDiagnostics = this.#breakDownDiagnostics(diagnostics);
|
||||
this.#logDiagnosticsSeverity(brokenDownDiagnostics);
|
||||
return brokenDownDiagnostics.errors > 0
|
||||
? CheckResult.ExitWithError
|
||||
: CheckResult.ExitWithSuccess;
|
||||
}
|
||||
|
||||
#checkForDiagnostics() {
|
||||
clearTimeout(this.#updateDiagnostics);
|
||||
// @ematipico: I am not sure of `setTimeout`. I would rather use a debounce but let's see if this works.
|
||||
// Inspiration from `svelte-check`.
|
||||
this.#updateDiagnostics = setTimeout(async () => await this.#checkAllFiles(false), 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is responsible to attach events to the server watcher
|
||||
*/
|
||||
async #watch() {
|
||||
const { default: chokidar } = await import('chokidar');
|
||||
this.#watcher = chokidar.watch(
|
||||
join(fileURLToPath(this.#settings.config.root), ASTRO_GLOB_PATTERN),
|
||||
{
|
||||
ignored: ['**/node_modules/**'],
|
||||
ignoreInitial: true,
|
||||
}
|
||||
);
|
||||
|
||||
this.#watcher.on('add', (file) => {
|
||||
this.#addDocument(file);
|
||||
this.#filesCount += 1;
|
||||
this.#checkForDiagnostics();
|
||||
});
|
||||
this.#watcher.on('change', (file) => {
|
||||
this.#addDocument(file);
|
||||
this.#checkForDiagnostics();
|
||||
});
|
||||
this.#watcher.on('unlink', (file) => {
|
||||
this.#diagnosticsChecker.removeDocument(file);
|
||||
this.#filesCount -= 1;
|
||||
this.#checkForDiagnostics();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a document to the diagnostics checker
|
||||
* @param filePath Path to the file
|
||||
*/
|
||||
#addDocument(filePath: string) {
|
||||
const text = fs.readFileSync(filePath, 'utf-8');
|
||||
this.#diagnosticsChecker.upsertDocument({
|
||||
uri: pathToFileURL(filePath).toString(),
|
||||
text,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the result of the various diagnostics
|
||||
*
|
||||
* @param result Result emitted by AstroChecker.#breakDownDiagnostics
|
||||
*/
|
||||
#logDiagnosticsSeverity(result: Readonly<DiagnosticResult>) {
|
||||
info(
|
||||
this.#logging,
|
||||
'diagnostics',
|
||||
[
|
||||
bold(`Result (${this.#filesCount} file${this.#filesCount === 1 ? '' : 's'}): `),
|
||||
bold(red(`${result.errors} ${result.errors === 1 ? 'error' : 'errors'}`)),
|
||||
bold(yellow(`${result.warnings} ${result.warnings === 1 ? 'warning' : 'warnings'}`)),
|
||||
dim(`${result.hints} ${result.hints === 1 ? 'hint' : 'hints'}\n`),
|
||||
].join(`\n${dim('-')} `)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* It loops through all diagnostics and break down diagnostics that are errors, warnings or hints.
|
||||
*/
|
||||
#breakDownDiagnostics(diagnostics: Readonly<GetDiagnosticsResult[]>): DiagnosticResult {
|
||||
let result: DiagnosticResult = {
|
||||
errors: 0,
|
||||
warnings: 0,
|
||||
hints: 0,
|
||||
};
|
||||
|
||||
diagnostics.forEach((diag) => {
|
||||
diag.diagnostics.forEach((d) => {
|
||||
info(this.#logging, 'diagnostics', `\n ${printDiagnostic(diag.fileUri, diag.text, d)}`);
|
||||
|
||||
switch (d.severity) {
|
||||
case DiagnosticSeverity.Error: {
|
||||
result.errors++;
|
||||
break;
|
||||
}
|
||||
case DiagnosticSeverity.Warning: {
|
||||
result.warnings++;
|
||||
break;
|
||||
}
|
||||
case DiagnosticSeverity.Hint: {
|
||||
result.hints++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,13 +353,14 @@ async function openAllDocuments(
|
|||
filePathsToIgnore: string[],
|
||||
checker: AstroCheck
|
||||
): Promise<number> {
|
||||
const files = await glob('**/*.astro', {
|
||||
const files = await glob(ASTRO_GLOB_PATTERN, {
|
||||
cwd: fileURLToPath(workspaceUri),
|
||||
ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`)),
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
for (const file of files) {
|
||||
debug('check', `Adding file ${file} to the list of files to check.`);
|
||||
const text = fs.readFileSync(file, 'utf-8');
|
||||
checker.upsertDocument({
|
||||
uri: pathToFileURL(file).toString(),
|
||||
|
@ -120,3 +370,12 @@ async function openAllDocuments(
|
|||
|
||||
return files.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse flags and sets defaults
|
||||
*/
|
||||
function parseFlags(flags: Flags): CheckFlags {
|
||||
return {
|
||||
watch: flags.watch ?? false,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { enableVerboseLogging, nodeLogDestination } from '../core/logger/node.js
|
|||
import { formatConfigErrorMessage, formatErrorMessage, printHelp } from '../core/messages.js';
|
||||
import * as event from '../events/index.js';
|
||||
import { eventConfigError, eventError, telemetry } from '../events/index.js';
|
||||
import { check } from './check/index.js';
|
||||
import { check, CheckResult } from './check/index.js';
|
||||
import { openInBrowser } from './open.js';
|
||||
|
||||
type Arguments = yargs.Arguments;
|
||||
|
@ -222,15 +222,24 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
|
|||
}
|
||||
|
||||
case 'check': {
|
||||
const ret = await check(settings, { logging, flags });
|
||||
return process.exit(ret);
|
||||
// We create a server to start doing our operations
|
||||
const checkServer = await check(settings, { flags, logging });
|
||||
if (checkServer) {
|
||||
if (checkServer.isWatchMode) {
|
||||
await checkServer.watch();
|
||||
return await new Promise(() => {}); // lives forever
|
||||
} else {
|
||||
let checkResult = await checkServer.check();
|
||||
return process.exit(checkResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'sync': {
|
||||
const { syncCli } = await import('../core/sync/index.js');
|
||||
|
||||
const ret = await syncCli(settings, { logging, fs, flags });
|
||||
return process.exit(ret);
|
||||
const result = await syncCli(settings, { logging, fs, flags });
|
||||
return process.exit(result);
|
||||
}
|
||||
|
||||
case 'preview': {
|
||||
|
|
|
@ -2,19 +2,24 @@ import { dim } from 'kleur/colors';
|
|||
import type fsMod from 'node:fs';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import { createServer } from 'vite';
|
||||
import { Arguments } from 'yargs-parser';
|
||||
import type { AstroSettings } from '../../@types/astro';
|
||||
import { createContentTypesGenerator } from '../../content/index.js';
|
||||
import { globalContentConfigObserver } from '../../content/utils.js';
|
||||
import { runHookConfigSetup } from '../../integrations/index.js';
|
||||
import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js';
|
||||
import { getTimeStat } from '../build/util.js';
|
||||
import { createVite } from '../create-vite.js';
|
||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||
import { info, LogOptions } from '../logger/core.js';
|
||||
import { printHelp } from '../messages.js';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import { createVite } from '../create-vite.js';
|
||||
|
||||
type ProcessExit = 0 | 1;
|
||||
export type ProcessExit = 0 | 1;
|
||||
|
||||
export type SyncOptions = {
|
||||
logging: LogOptions;
|
||||
fs: typeof fsMod;
|
||||
};
|
||||
|
||||
export async function syncCli(
|
||||
settings: AstroSettings,
|
||||
|
@ -40,9 +45,20 @@ export async function syncCli(
|
|||
return sync(resolvedSettings, { logging, fs });
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate content collection types, and then returns the process exit signal.
|
||||
*
|
||||
* A non-zero process signal is emitted in case there's an error while generating content collection types.
|
||||
*
|
||||
* @param {SyncOptions} options
|
||||
* @param {AstroSettings} settings Astro settings
|
||||
* @param {typeof fsMod} options.fs The file system
|
||||
* @param {LogOptions} options.logging Logging options
|
||||
* @return {Promise<ProcessExit>}
|
||||
*/
|
||||
export async function sync(
|
||||
settings: AstroSettings,
|
||||
{ logging, fs }: { logging: LogOptions; fs: typeof fsMod }
|
||||
{ logging, fs }: SyncOptions
|
||||
): Promise<ProcessExit> {
|
||||
const timerStart = performance.now();
|
||||
// Needed to load content config
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { expect } from 'chai';
|
||||
import { cli, parseCliDevStart, cliServerLogSetup } from './test-utils.js';
|
||||
import { promises as fs } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { isIPv4 } from 'net';
|
||||
import { cli, parseCliDevStart, cliServerLogSetup, loadFixture } from './test-utils.js';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { promises as fs, readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { isIPv4 } from 'node:net';
|
||||
import { join } from 'node:path';
|
||||
import { Writable } from 'node:stream';
|
||||
|
||||
describe('astro cli', () => {
|
||||
const cliServerLogSetupWithFixture = (flags, cmd) => {
|
||||
|
@ -15,6 +18,50 @@ describe('astro cli', () => {
|
|||
expect(proc.exitCode).to.equal(0);
|
||||
});
|
||||
|
||||
// Flaky test, in CI it exceeds the timeout most of the times
|
||||
it.skip('astro check --watch reports errors on modified files', async () => {
|
||||
let messageResolve;
|
||||
const messagePromise = new Promise((resolve) => {
|
||||
messageResolve = resolve;
|
||||
});
|
||||
const oneErrorContent = 'foobar';
|
||||
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
const fixture = await loadFixture({
|
||||
root: './fixtures/astro-check-watch/',
|
||||
});
|
||||
const logs = [];
|
||||
|
||||
const checkServer = await fixture.check({
|
||||
flags: { watch: true },
|
||||
logging: {
|
||||
level: 'info',
|
||||
dest: new Writable({
|
||||
objectMode: true,
|
||||
write(event, _, callback) {
|
||||
logs.push({ ...event, message: stripAnsi(event.message) });
|
||||
if (event.message.includes('1 error')) {
|
||||
messageResolve(logs);
|
||||
}
|
||||
callback();
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
await checkServer.watch();
|
||||
const pagePath = join(fileURLToPath(fixture.config.root), 'src/pages/index.astro');
|
||||
const pageContent = readFileSync(pagePath, 'utf-8');
|
||||
await fs.writeFile(pagePath, oneErrorContent);
|
||||
const messages = await messagePromise;
|
||||
await fs.writeFile(pagePath, pageContent);
|
||||
await checkServer.stop();
|
||||
const diagnostics = messages.filter(
|
||||
(m) => m.type === 'diagnostics' && m.message.includes('Result')
|
||||
);
|
||||
expect(diagnostics[0].message).to.include('0 errors');
|
||||
expect(diagnostics[1].message).to.include('1 error');
|
||||
}).timeout(35000);
|
||||
|
||||
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);
|
||||
|
@ -144,7 +191,9 @@ describe('astro cli i18n', () => {
|
|||
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
|
||||
let error = null;
|
||||
try {
|
||||
const proc = cli('dev', '--root', fileURLToPath(projectRootURL), { env: { LANG: locale } });
|
||||
const proc = cli('dev', '--root', fileURLToPath(projectRootURL), {
|
||||
env: { LANG: locale },
|
||||
});
|
||||
await parseCliDevStart(proc);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
|
6
packages/astro/test/fixtures/astro-check-watch/astro.config.mjs
vendored
Normal file
6
packages/astro/test/fixtures/astro-check-watch/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [],
|
||||
});
|
8
packages/astro/test/fixtures/astro-check-watch/package.json
vendored
Normal file
8
packages/astro/test/fixtures/astro-check-watch/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/astro-check-watch",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
1
packages/astro/test/fixtures/astro-check-watch/src/pages/index.astro
vendored
Normal file
1
packages/astro/test/fixtures/astro-check-watch/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<h1></h1>
|
2
packages/astro/test/fixtures/astro-check-watch/tsconfig.json
vendored
Normal file
2
packages/astro/test/fixtures/astro-check-watch/tsconfig.json
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Having a `tsconfig.json`, even empty speeds up the test massively since TypeScript does not have to look for one
|
||||
{}
|
|
@ -12,6 +12,7 @@ import { createSettings } from '../dist/core/config/index.js';
|
|||
import dev from '../dist/core/dev/index.js';
|
||||
import { nodeLogDestination } from '../dist/core/logger/node.js';
|
||||
import preview from '../dist/core/preview/index.js';
|
||||
import { check } from '../dist/cli/check/index.js';
|
||||
|
||||
// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16
|
||||
polyfill(globalThis, {
|
||||
|
@ -24,6 +25,8 @@ polyfill(globalThis, {
|
|||
* @typedef {import('../src/@types/astro').AstroConfig} AstroConfig
|
||||
* @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer
|
||||
* @typedef {import('../src/core/app/index').App} App
|
||||
* @typedef {import('../src/cli/check/index').AstroChecker} AstroChecker
|
||||
* @typedef {import('../src/cli/check/index').CheckPayload} CheckPayload
|
||||
*
|
||||
*
|
||||
* @typedef {Object} Fixture
|
||||
|
@ -39,6 +42,24 @@ polyfill(globalThis, {
|
|||
* @property {() => Promise<void>} clean
|
||||
* @property {() => Promise<App>} loadTestAdapterApp
|
||||
* @property {() => Promise<void>} onNextChange
|
||||
* @property {(opts: CheckPayload) => Promise<AstroChecker>} check
|
||||
*
|
||||
* This function returns an instance of the Check
|
||||
*
|
||||
*
|
||||
* When used in a test suite:
|
||||
* ```js
|
||||
* let fixture = await loadFixture({
|
||||
* root: './fixtures/astro-check-watch/',
|
||||
* });
|
||||
* ```
|
||||
* `opts` will override the options passed to the `AstroChecker`
|
||||
*
|
||||
* ```js
|
||||
* let { check, stop, watch } = fixture.check({
|
||||
* flags: { watch: true },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
/** @type {import('../src/core/logger/core').LogOptions} */
|
||||
|
@ -148,6 +169,9 @@ export async function loadFixture(inlineConfig) {
|
|||
return build(settings, { logging, telemetry, ...opts });
|
||||
},
|
||||
sync: (opts) => sync(settings, { logging, fs, ...opts }),
|
||||
check: async (opts) => {
|
||||
return await check(settings, { logging, ...opts });
|
||||
},
|
||||
startDevServer: async (opts = {}) => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
devServer = await dev(settings, { logging, telemetry, ...opts });
|
||||
|
@ -160,7 +184,11 @@ export async function loadFixture(inlineConfig) {
|
|||
fetch: (url, init) => fetch(resolveUrl(url), init),
|
||||
preview: async (opts = {}) => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
const previewServer = await preview(settings, { logging, telemetry, ...opts });
|
||||
const previewServer = await preview(settings, {
|
||||
logging,
|
||||
telemetry,
|
||||
...opts,
|
||||
});
|
||||
config.server.host = parseAddressToHost(previewServer.host); // update host
|
||||
config.server.port = previewServer.port; // update port
|
||||
return previewServer;
|
||||
|
@ -177,7 +205,11 @@ export async function loadFixture(inlineConfig) {
|
|||
cwd: fileURLToPath(config.outDir),
|
||||
}),
|
||||
clean: async () => {
|
||||
await fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true });
|
||||
await fs.promises.rm(config.outDir, {
|
||||
maxRetries: 10,
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
},
|
||||
loadTestAdapterApp: async (streaming) => {
|
||||
const url = new URL(`./server/entry.mjs?id=${fixtureId}`, config.outDir);
|
||||
|
@ -250,7 +282,9 @@ const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url));
|
|||
|
||||
/** Returns a process running the Astro CLI. */
|
||||
export function cli(/** @type {string[]} */ ...args) {
|
||||
const spawned = execa('node', [cliPath, ...args], { env: { ASTRO_TELEMETRY_DISABLED: true } });
|
||||
const spawned = execa('node', [cliPath, ...args], {
|
||||
env: { ASTRO_TELEMETRY_DISABLED: true },
|
||||
});
|
||||
|
||||
spawned.stdout.setEncoding('utf8');
|
||||
|
||||
|
|
2
packages/webapi/mod.d.ts
vendored
2
packages/webapi/mod.d.ts
vendored
|
@ -10,4 +10,4 @@ interface PolyfillOptions {
|
|||
override?: Record<string, {
|
||||
(...args: any[]): any;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -455,6 +455,7 @@ importers:
|
|||
boxen: ^6.2.1
|
||||
chai: ^4.3.6
|
||||
cheerio: ^1.0.0-rc.11
|
||||
chokidar: ^3.5.3
|
||||
ci-info: ^3.3.1
|
||||
common-ancestor-path: ^1.0.1
|
||||
cookie: ^0.5.0
|
||||
|
@ -521,6 +522,7 @@ importers:
|
|||
'@types/yargs-parser': 21.0.0
|
||||
acorn: 8.8.2
|
||||
boxen: 6.2.1
|
||||
chokidar: 3.5.3
|
||||
ci-info: 3.7.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 0.5.0
|
||||
|
@ -1237,6 +1239,12 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-check-watch:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-children:
|
||||
specifiers:
|
||||
'@astrojs/preact': workspace:*
|
||||
|
|
Loading…
Reference in a new issue