diff options
| -rw-r--r-- | .changeset/thirty-fans-know.md | 6 | ||||
| -rw-r--r-- | packages/astro-parser/src/interfaces.ts | 2 | ||||
| -rw-r--r-- | packages/astro-parser/src/utils/error.ts | 29 | ||||
| -rw-r--r-- | packages/astro/src/dev.ts | 1 | ||||
| -rw-r--r-- | packages/astro/src/logger.ts | 2 | ||||
| -rw-r--r-- | packages/astro/src/runtime.ts | 44 | 
6 files changed, 61 insertions, 23 deletions
| diff --git a/.changeset/thirty-fans-know.md b/.changeset/thirty-fans-know.md new file mode 100644 index 000000000..ff8010931 --- /dev/null +++ b/.changeset/thirty-fans-know.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'astro-parser': patch +--- + +Improve error display for missing local files diff --git a/packages/astro-parser/src/interfaces.ts b/packages/astro-parser/src/interfaces.ts index 40eeb04f5..f3fa5da9d 100644 --- a/packages/astro-parser/src/interfaces.ts +++ b/packages/astro-parser/src/interfaces.ts @@ -1,5 +1,5 @@  import type { SourceMap } from 'magic-string'; -export type { CompileError } from './utils/error'; +export { CompileError } from './utils/error';  export interface BaseNode {    start: number; diff --git a/packages/astro-parser/src/utils/error.ts b/packages/astro-parser/src/utils/error.ts index 8ebb5b093..00eed866f 100644 --- a/packages/astro-parser/src/utils/error.ts +++ b/packages/astro-parser/src/utils/error.ts @@ -5,14 +5,23 @@ import get_code_frame from './get_code_frame.js';  export class CompileError extends Error {    code: string; -  start: { line: number; column: number };    end: { line: number; column: number }; -  pos: number;    filename: string;    frame: string; +  start: { line: number; column: number }; + +  constructor({ code, filename, start, end, message }: { code: string; filename: string; start: number; message: string; end?: number }) { +    super(message); + +    this.start = locate(code, start, { offsetLine: 1 }); +    this.end = locate(code, end || start, { offsetLine: 1 }); +    this.filename = filename; +    this.message = message; +    this.frame = get_code_frame(code, this.start.line - 1, this.start.column); +  }    toString() { -    return `${this.message} (${this.start.line}:${this.start.column})\n${this.frame}`; +    return `${this.filename}:${this.start.line}:${this.start.column}\n\t${this.message}\n${this.frame}`;    }  } @@ -21,26 +30,14 @@ export default function error(    message: string,    props: {      name: string; -    code: string;      source: string;      filename: string;      start: number;      end?: number;    }  ): never { -  const err = new CompileError(message); +  const err = new CompileError({ message, start: props.start, end: props.end, filename: props.filename });    err.name = props.name; -  const start = locate(props.source, props.start, { offsetLine: 1 }); -  const end = locate(props.source, props.end || props.start, { offsetLine: 1 }); - -  err.code = props.code; -  err.start = start; -  err.end = end; -  err.pos = props.start; -  err.filename = props.filename; - -  err.frame = get_code_frame(props.source, start.line - 1, start.column); -    throw err;  } diff --git a/packages/astro/src/dev.ts b/packages/astro/src/dev.ts index 7f61492d7..5d1f20cff 100644 --- a/packages/astro/src/dev.ts +++ b/packages/astro/src/dev.ts @@ -66,6 +66,7 @@ export default async function dev(astroConfig: AstroConfig) {          break;        }        case 500: { +        res.setHeader('Content-Type', 'text/html;charset=utf-8');          switch (result.type) {            case 'parse-error': {              const err = result.error; diff --git a/packages/astro/src/logger.ts b/packages/astro/src/logger.ts index 282e8506e..84572e86e 100644 --- a/packages/astro/src/logger.ts +++ b/packages/astro/src/logger.ts @@ -131,7 +131,7 @@ export function parseError(opts: LogOptions, err: CompileError) {      'parse-error',      ` - ${underline(bold(grey(`${err.filename}:${err.start.line}:${err.start.column}`)))} + ${underline(bold(grey(`${err.filename || ''}:${err.start.line}:${err.start.column}`)))}   ${bold(red(`š
 ${err.message}`))} diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts index 2beb9d7f3..a73df33d6 100644 --- a/packages/astro/src/runtime.ts +++ b/packages/astro/src/runtime.ts @@ -1,16 +1,16 @@  import 'source-map-support/register.js'; -import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig } from 'snowpack'; -import type { CompileError } from 'astro-parser';  import type { LogOptions } from './logger';  import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro';  import resolve from 'resolve'; -import { existsSync } from 'fs'; +import { existsSync, promises as fs } from 'fs';  import { fileURLToPath, pathToFileURL } from 'url';  import { posix as path } from 'path';  import { performance } from 'perf_hooks'; +import { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig, NotFoundError } from 'snowpack'; +import { CompileError } from 'astro-parser';  import { loadConfiguration, logger as snowpackLogger, startServer as startSnowpackServer } from 'snowpack'; -import { canonicalURL, stopTimer } from './build/util.js'; +import { canonicalURL, getSrcPath, stopTimer } from './build/util.js';  import { debug, info } from './logger.js';  import { searchForPage } from './search.js';  import snowpackExternals from './external.js'; @@ -40,7 +40,7 @@ type LoadResultSuccess = {  };  type LoadResultNotFound = { statusCode: 404; error: Error; collectionInfo?: CollectionInfo };  type LoadResultRedirect = { statusCode: 301 | 302; location: string; collectionInfo?: CollectionInfo }; -type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: CompileError } | { type: 'unknown'; error: Error }); +type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: CompileError } | { type: 'not-found'; error: CompileError } | { type: 'unknown'; error: Error });  export type LoadResult = (LoadResultSuccess | LoadResultNotFound | LoadResultRedirect | LoadResultError) & { collectionInfo?: CollectionInfo }; @@ -242,6 +242,40 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro          error: err,        };      } + +    if (err instanceof NotFoundError && rawPathname) { +      const fileMatch = err.toString().match(/\(([^\)]+)\)/); +      const missingFile: string | undefined = (fileMatch && fileMatch[1].replace(/^\/_astro/, '').replace(/\.proxy\.js$/, '')) || undefined; +      const distPath = path.extname(rawPathname) ? rawPathname : rawPathname.replace(/\/?$/, '/index.html'); +      const srcFile = getSrcPath(distPath, { astroConfig: config.astroConfig }); +      const code = existsSync(srcFile) ? await fs.readFile(srcFile, 'utf8') : ''; + +      // try and find the import statement within the module. this is a bit hacky, as we donāt know the line, but +      // given that we know this is for sure a ānot foundā error, and we know what file is erring, +      // we can make some safe assumptions about how to locate the line in question +      let start = 0; +      const segments = missingFile ? missingFile.split('/').filter((segment) => !!segment) : []; +      while (segments.length) { +        const importMatch = code.indexOf(segments.join('/')); +        if (importMatch >= 0) { +          start = importMatch; +          break; +        } +        segments.shift(); +      } + +      return { +        statusCode: 500, +        type: 'not-found', +        error: new CompileError({ +          code, +          filename: srcFile.pathname, +          start, +          message: `Could not find${missingFile ? ` "${missingFile}"` : ' file'}`, +        }), +      }; +    } +      return {        statusCode: 500,        type: 'unknown', | 
