Skip to content

Commit

Permalink
feat: implement better errors all over, new errors pages+refined logi…
Browse files Browse the repository at this point in the history
…c, debounce watch
  • Loading branch information
JulianCataldo committed Aug 25, 2024
1 parent 09ac5dd commit 522df77
Show file tree
Hide file tree
Showing 26 changed files with 811 additions and 303 deletions.
24 changes: 23 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
# See: https://github.com/JulianCataldo/gh-actions

# For matrix setup:
# https://github.com/withastro/astro/blob/main/.github/workflows/ci.yml
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow

name: CI / Release

on:
Expand Down Expand Up @@ -28,13 +32,31 @@ permissions:
jobs:
release:
name: CI / Release
runs-on: ubuntu-latest

permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance

runs-on: ubuntu-latest
# TODO:
# runs-on: ${{ matrix.os }}
# timeout-minutes: 25
# # needs: build
# strategy:
# matrix:
# OS: [ubuntu-latest]
# NODE_VERSION: [18, 20]
# include:
# - os: macos-14
# NODE_VERSION: 18
# - os: windows-latest
# NODE_VERSION: 18
# fail-fast: false
# env:
# NODE_VERSION: ${{ matrix.NODE_VERSION }}

steps:
# MARK: Setup GH Action

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineRoute } from '@gracile/gracile/route';
import * as Route from '@gracile/gracile/_internals/route';
import { RequestMethod } from '@gracile/gracile/_internals/route';

const basic = defineRoute({
handler: {
Expand All @@ -8,7 +8,7 @@ const basic = defineRoute({
{
url,
param1: url.searchParams.get("foo"),
[Route.RequestMethod.GET]: "ok",
[RequestMethod.GET]: "ok",
// TODO: When middleware are implemented, mock this properly
locals: { requestIdLength: locals.requestId.length }
},
Expand All @@ -26,7 +26,7 @@ const basic = defineRoute({
{
url,
param1: url.searchParams.get("foo"),
[Route.RequestMethod.POST]: "ok",
[RequestMethod.POST]: "ok",
body: await request.json(),
locals: { requestIdLength: locals.requestId.length }
},
Expand All @@ -38,7 +38,7 @@ const basic = defineRoute({
{
url,
param1: url.searchParams.get("foo"),
[Route.RequestMethod.PUT]: "ok",
[RequestMethod.PUT]: "ok",
body: await request.json(),
locals: { requestIdLength: locals.requestId.length }
},
Expand All @@ -50,7 +50,7 @@ const basic = defineRoute({
{
url,
param1: url.searchParams.get("foo"),
[Route.RequestMethod.QUERY]: "ok",
[RequestMethod.QUERY]: "ok",
body: await request.json(),
locals: { requestIdLength: locals.requestId.length }
},
Expand All @@ -62,7 +62,7 @@ const basic = defineRoute({
{
url,
param1: url.searchParams.get("foo"),
[Route.RequestMethod.DELETE]: "ok",
[RequestMethod.DELETE]: "ok",
locals: { requestIdLength: locals.requestId.length }
},
{ status: 200, statusText: "DONE", headers: { bar: "baz" } }
Expand All @@ -73,7 +73,7 @@ const basic = defineRoute({
{
url,
param1: url.searchParams.get("foo"),
[Route.RequestMethod.PATCH]: "ok",
[RequestMethod.PATCH]: "ok",
body: await request.json(),
locals: { requestIdLength: locals.requestId.length }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,6 @@ options) {
return routeModule;
};
}
// TODO: remove this, use `Response.json instead`? Or keep for old envs?
// export function jsonResponse(data: any, init?: ResponseInit) {
// return new Response(JSON.stringify(data), {
// ...init,
// headers: { 'Content-Type': 'application/json' },
// });
// }
// NOTE: Useful?
// export function notFound(statusText = '404 - Not found') {
// return new Response(null, {
// status: 404,
// statusText,
// });
// }

const routeImports = new Map(
[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { URLPattern } from '@gracile/gracile/url-pattern';
import { createGracileHandler } from '@gracile/gracile/_internals/server-runtime';
import { createLogger } from '@gracile/gracile/_internals/logger';

const routes = new Map([
[
Expand Down Expand Up @@ -204,6 +205,8 @@ const routeAssets = new Map([
]
]);

createLogger();

const handler = createGracileHandler({
root: process.cwd(),
routes,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,20 @@
<!-- -->

<!doctype html>
<html lang="en">
<head>
<script type="module" src="/@vite/client"></script>

<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>Error</title>
</head>
<body>
<title>500: TypeError | Internal Server Error</title>
<style>
html {
color-scheme: dark;
color-scheme: light dark;
font-size: 16px;
line-height: 1.23rem;
font-family: system-ui;
}
body {
padding: 1rem;
}

pre {
padding: 1rem;

overflow-y: auto;
}
button {
font-size: 2rem;
}

h1 {
color: tomato;
}
</style>

<main>
<h1>😵 An error has occurred!</h1>
<button id="reload">Reload</button>
<!-- -->
<hr />

<pre>
Error: !!! OH NO !!! I AM A FAKE ERROR !!!
at RouteModule.template (__REPLACED_FOR_TESTS__)
at renderRouteTemplate (__REPLACED_FOR_TESTS__)
at async middleware (__REPLACED_FOR_TESTS__)
at async nodeHandler (__REPLACED_FOR_TESTS__)</pre
>
<!-- <pre>$ {e.name}</pre> -->
<!-- <pre>$ {e.message}</pre> -->
<!-- <pre>$ {e.cause}</pre> -->
<hr />
</main>

<script>
reload.addEventListener("click", () => document.location.reload());
</script>
<script type="module" src="/@vite/client"></script>
</head>
<body>
<h1>500: TypeError | Internal Server Error</h1>
</body>
</html>
3 changes: 2 additions & 1 deletion integration/__fixtures__/static-site/src/routes/throws.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { defineRoute } from '@gracile/gracile/route';
import { html } from '@gracile/gracile/server-html';
// import { GracileUserError } from '@gracile/gracile/error';

import { document } from '../documents/document-minimal.js';

export default defineRoute({
document: (context) => document({ ...context, title: 'Gracile - Oh no' }),

template: (context) => {
throw new Error('!!! OH NO !!! I AM A FAKE ERROR !!!');
throw new TypeError('!!! OH NO !!! I AM A FAKE ERROR !!!');
html`
<h1>⚠️ Arrrrrhh !!</h1>
Expand Down
3 changes: 3 additions & 0 deletions integration/manual-tests.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# pnpm tsx --test -C test src/addons/client-router.test.ts

# set -e

pnpm tsx --test -C test src/routes-premises.test.ts

pnpm tsx --test -C test src/addons/metadata.test.ts
Expand Down
5 changes: 5 additions & 0 deletions integration/src/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { fileURLToPath } from 'node:url';

import { constants as serverConstants } from '@gracile/engine/server/constants';
import * as internalLogger from '@gracile/gracile/_internals/logger';
import * as route from '@gracile/gracile/_internals/route';
import * as routeModule from '@gracile/gracile/_internals/route-module';
import * as serverRuntime from '@gracile/gracile/_internals/server-runtime';
import * as document from '@gracile/gracile/document';
import { env as envFromNodeConditions } from '@gracile/gracile/env';
Expand Down Expand Up @@ -150,5 +152,8 @@ describe('gracile package should do its exports correctly', () => {

test('internals', () => {
assert.equal(typeof internalLogger.createLogger, 'function');
assert.equal(typeof route.RequestMethod, 'object');
assert.equal(typeof routeModule.RouteModule, 'function');
assert.equal(typeof serverRuntime.createGracileHandler, 'function');
});
});
12 changes: 7 additions & 5 deletions integration/src/server-express/_common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,15 @@ async function tests(mode: string, item: string, writeActual: boolean) {
));
// TODO: Test with "accept: json" when implemented

await it(`load an error page when a route throws - ${item}`, async () =>
await it(`load an error page when a route throws - ${item}`, async () => {
const ressource = await fetchResource(ADDRESS, ['throws']);

assert.equal(
(await fetchResource(ADDRESS, ['throws'])).includes(
'Error: !!! OH NO !!! I AM A FAKE ERROR !!!',
),
// <vite-error-overlay> should take over just after (its a client only component) in DEV
ressource.includes('500: Error | Internal Server Error'),
true,
));
);
});

await it(`should redirect`, async () =>
checkResponse(
Expand Down
48 changes: 25 additions & 23 deletions packages/engine/src/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getLogger } from '@gracile/internal-utils/logger/helpers';
import c from 'picocolors';
import { type ViteDevServer } from 'vite';

import { collectRoutes } from '../routes/collect.js';
import { collectRoutes, WATCHED_FILES_REGEX } from '../routes/collect.js';
import type { RoutesManifest } from '../routes/route.js';
import {
createGracileHandler,
Expand All @@ -14,7 +14,7 @@ import { generateRoutesTypings } from './route-typings.js';
export async function createDevHandler({
routes,
vite,
gracileConfig,dd
gracileConfig,
}: {
routes: RoutesManifest;
vite: ViteDevServer;
Expand All @@ -27,34 +27,36 @@ export async function createDevHandler({

const root = vite.config.root;

logger.info(c.dim('\nCreating handler…'), { timestamp: true });
logger.info('');
logger.info(c.dim('Creating the request handler…'), { timestamp: true });

await collectRoutes(routes, root, gracileConfig.routes?.exclude);
const collect = async () => {
await collectRoutes(routes, root, gracileConfig.routes?.exclude);

if (gracileConfig.experimental?.generateRoutesTypings)
generateRoutesTypings(root, routes).catch((error) =>
logger.error(String(error)),
);
if (gracileConfig.experimental?.generateRoutesTypings)
await generateRoutesTypings(root, routes).catch((error) =>
logger.error(String(error)),
);
};

await collect();

let wait: ReturnType<typeof setTimeout>;
vite.watcher.on('all', (event, file) => {
// console.log({ event });
if (
file.match(
/\/src\/routes\/(.*)\.(ts|js|css|scss|sass|less|styl|stylus)$/,
) &&
file.match(WATCHED_FILES_REGEX) &&
['add', 'unlink'].includes(event)
)
collectRoutes(routes, root, gracileConfig.routes?.exclude)
.then(() => {
vite.hot.send('vite:invalidate');

if (gracileConfig.experimental?.generateRoutesTypings)
generateRoutesTypings(root, routes).catch((error) =>
logger.error(String(error)),
);
})
.catch((e) => logger.error(String(e)));
//
) {
clearTimeout(wait);
wait = setTimeout(() => {
collect()
.then(() => vite.hot.send('vite:invalidate'))
.catch((error) => logger.error(String(error)));
}, 100);
}
});

//

// NOTE: Wrong place?
Expand Down
Loading

0 comments on commit 522df77

Please sign in to comment.