davaux 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BASELINE.md +169 -0
- package/CLAUDE.md +518 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/ROADMAP.md +198 -0
- package/build.mjs +101 -0
- package/client/control.ts +247 -0
- package/client/hydrate.ts +37 -0
- package/client/index.ts +19 -0
- package/client/jsx-runtime.ts +209 -0
- package/client/resource.ts +122 -0
- package/client/signal.ts +211 -0
- package/client/store.ts +110 -0
- package/client/useHead.ts +63 -0
- package/dist/build/config.d.ts +3 -0
- package/dist/build/config.d.ts.map +1 -0
- package/dist/build/config.js +38 -0
- package/dist/build/config.js.map +7 -0
- package/dist/build/index.d.ts +2 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +13 -0
- package/dist/build/index.js.map +7 -0
- package/dist/build/plugins.d.ts +7 -0
- package/dist/build/plugins.d.ts.map +1 -0
- package/dist/build/plugins.js +85 -0
- package/dist/build/plugins.js.map +7 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +7 -0
- package/dist/client/control.d.ts +49 -0
- package/dist/client/control.d.ts.map +1 -0
- package/dist/client/control.js +154 -0
- package/dist/client/control.js.map +7 -0
- package/dist/client/hydrate.d.ts +7 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/hydrate.js +23 -0
- package/dist/client/hydrate.js.map +7 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +32 -0
- package/dist/client/index.js.map +7 -0
- package/dist/client/jsx-runtime.d.ts +40 -0
- package/dist/client/jsx-runtime.d.ts.map +1 -0
- package/dist/client/jsx-runtime.js +139 -0
- package/dist/client/jsx-runtime.js.map +7 -0
- package/dist/client/resource.d.ts +31 -0
- package/dist/client/resource.d.ts.map +1 -0
- package/dist/client/resource.js +64 -0
- package/dist/client/resource.js.map +7 -0
- package/dist/client/signal.d.ts +90 -0
- package/dist/client/signal.d.ts.map +1 -0
- package/dist/client/signal.js +115 -0
- package/dist/client/signal.js.map +7 -0
- package/dist/client/store.d.ts +26 -0
- package/dist/client/store.d.ts.map +1 -0
- package/dist/client/store.js +63 -0
- package/dist/client/store.js.map +7 -0
- package/dist/client/useHead.d.ts +28 -0
- package/dist/client/useHead.d.ts.map +1 -0
- package/dist/client/useHead.js +33 -0
- package/dist/client/useHead.js.map +7 -0
- package/dist/config.d.ts +182 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +7 -0
- package/dist/create-multisite.d.ts +2 -0
- package/dist/create-multisite.d.ts.map +1 -0
- package/dist/create-multisite.js +291 -0
- package/dist/create-multisite.js.map +7 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +179 -0
- package/dist/create.js.map +7 -0
- package/dist/dev/blueprints.d.ts +11 -0
- package/dist/dev/blueprints.d.ts.map +1 -0
- package/dist/dev/blueprints.js +65 -0
- package/dist/dev/blueprints.js.map +7 -0
- package/dist/dev/components.d.ts +19 -0
- package/dist/dev/components.d.ts.map +1 -0
- package/dist/dev/components.js +87 -0
- package/dist/dev/components.js.map +7 -0
- package/dist/dev/insert.d.ts +11 -0
- package/dist/dev/insert.d.ts.map +1 -0
- package/dist/dev/insert.js +160 -0
- package/dist/dev/insert.js.map +7 -0
- package/dist/dev/remove.d.ts +53 -0
- package/dist/dev/remove.d.ts.map +1 -0
- package/dist/dev/remove.js +518 -0
- package/dist/dev/remove.js.map +7 -0
- package/dist/dev/watch.d.ts +26 -0
- package/dist/dev/watch.d.ts.map +1 -0
- package/dist/dev/watch.js +2905 -0
- package/dist/dev/watch.js.map +7 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +7 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +191 -0
- package/dist/generate.js.map +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/island.d.ts +24 -0
- package/dist/island.d.ts.map +1 -0
- package/dist/island.js +15 -0
- package/dist/island.js.map +7 -0
- package/dist/jsx-runtime.d.ts +406 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx-runtime.js +90 -0
- package/dist/jsx-runtime.js.map +7 -0
- package/dist/link.d.ts +27 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +29 -0
- package/dist/link.js.map +7 -0
- package/dist/oml/fragment.d.ts +16 -0
- package/dist/oml/fragment.d.ts.map +1 -0
- package/dist/oml/fragment.js +26 -0
- package/dist/oml/fragment.js.map +7 -0
- package/dist/oml/index.d.ts +11 -0
- package/dist/oml/index.d.ts.map +1 -0
- package/dist/oml/index.js +21 -0
- package/dist/oml/index.js.map +7 -0
- package/dist/oml/jsx-runtime.d.ts +34 -0
- package/dist/oml/jsx-runtime.d.ts.map +1 -0
- package/dist/oml/jsx-runtime.js +59 -0
- package/dist/oml/jsx-runtime.js.map +7 -0
- package/dist/oml/jsx.d.ts +14 -0
- package/dist/oml/jsx.d.ts.map +1 -0
- package/dist/oml/jsx.js +96 -0
- package/dist/oml/jsx.js.map +7 -0
- package/dist/oml/page.d.ts +7 -0
- package/dist/oml/page.d.ts.map +1 -0
- package/dist/oml/page.js +6 -0
- package/dist/oml/page.js.map +7 -0
- package/dist/oml/render.d.ts +13 -0
- package/dist/oml/render.d.ts.map +1 -0
- package/dist/oml/render.js +117 -0
- package/dist/oml/render.js.map +7 -0
- package/dist/oml/types.d.ts +79 -0
- package/dist/oml/types.d.ts.map +1 -0
- package/dist/oml/types.js +64 -0
- package/dist/oml/types.js.map +7 -0
- package/dist/router/handler.d.ts +53 -0
- package/dist/router/handler.d.ts.map +1 -0
- package/dist/router/handler.js +342 -0
- package/dist/router/handler.js.map +7 -0
- package/dist/router/matcher.d.ts +21 -0
- package/dist/router/matcher.d.ts.map +1 -0
- package/dist/router/matcher.js +28 -0
- package/dist/router/matcher.js.map +7 -0
- package/dist/router/scanner.d.ts +17 -0
- package/dist/router/scanner.d.ts.map +1 -0
- package/dist/router/scanner.js +197 -0
- package/dist/router/scanner.js.map +7 -0
- package/dist/server/index.d.ts +23 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +29 -0
- package/dist/server/index.js.map +7 -0
- package/dist/signal.d.ts +15 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +29 -0
- package/dist/signal.js.map +7 -0
- package/dist/ssg.d.ts +45 -0
- package/dist/ssg.d.ts.map +1 -0
- package/dist/ssg.js +175 -0
- package/dist/ssg.js.map +7 -0
- package/dist/test/actions.test.d.ts +2 -0
- package/dist/test/actions.test.d.ts.map +1 -0
- package/dist/test/body-limits.test.d.ts +2 -0
- package/dist/test/body-limits.test.d.ts.map +1 -0
- package/dist/test/errors.test.d.ts +2 -0
- package/dist/test/errors.test.d.ts.map +1 -0
- package/dist/test/fixtures/routes/[id].page.d.ts +4 -0
- package/dist/test/fixtures/routes/[id].page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_error.d.ts +3 -0
- package/dist/test/fixtures/routes/_error.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_global.d.ts +3 -0
- package/dist/test/fixtures/routes/_global.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout-template.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout-template.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout_scripts.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout_scripts.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_middleware.d.ts +3 -0
- package/dist/test/fixtures/routes/_middleware.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_redirect301_mw.d.ts +3 -0
- package/dist/test/fixtures/routes/_redirect301_mw.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_redirect_mw.d.ts +3 -0
- package/dist/test/fixtures/routes/_redirect_mw.d.ts.map +1 -0
- package/dist/test/fixtures/routes/about.page.d.ts +3 -0
- package/dist/test/fixtures/routes/about.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/action.page.d.ts +6 -0
- package/dist/test/fixtures/routes/action.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/form-all.post.d.ts +3 -0
- package/dist/test/fixtures/routes/api/form-all.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/form-limited.post.d.ts +6 -0
- package/dist/test/fixtures/routes/api/form-limited.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/response-obj.get.d.ts +3 -0
- package/dist/test/fixtures/routes/api/response-obj.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/upload.post.d.ts +12 -0
- package/dist/test/fixtures/routes/api/upload.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/users.get.d.ts +6 -0
- package/dist/test/fixtures/routes/api/users.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/xml.get.d.ts +3 -0
- package/dist/test/fixtures/routes/api/xml.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/auth/_middleware.d.ts +3 -0
- package/dist/test/fixtures/routes/auth/_middleware.d.ts.map +1 -0
- package/dist/test/fixtures/routes/auth/protected.page.d.ts +3 -0
- package/dist/test/fixtures/routes/auth/protected.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/index.page.d.ts +3 -0
- package/dist/test/fixtures/routes/index.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/oml.page.d.ts +3 -0
- package/dist/test/fixtures/routes/oml.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/redirect.page.d.ts +3 -0
- package/dist/test/fixtures/routes/redirect.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/ssg/[slug].page.d.ts +5 -0
- package/dist/test/fixtures/routes/ssg/[slug].page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/ssg/server.page.d.ts +4 -0
- package/dist/test/fixtures/routes/ssg/server.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/state.page.d.ts +3 -0
- package/dist/test/fixtures/routes/state.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/throw.page.d.ts +3 -0
- package/dist/test/fixtures/routes/throw.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts +3 -0
- package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts.map +1 -0
- package/dist/test/helpers.d.ts +37 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/layouts.test.d.ts +2 -0
- package/dist/test/layouts.test.d.ts.map +1 -0
- package/dist/test/middleware.test.d.ts +2 -0
- package/dist/test/middleware.test.d.ts.map +1 -0
- package/dist/test/multipart.test.d.ts +2 -0
- package/dist/test/multipart.test.d.ts.map +1 -0
- package/dist/test/oml-routing.test.d.ts +2 -0
- package/dist/test/oml-routing.test.d.ts.map +1 -0
- package/dist/test/oml.test.d.ts +2 -0
- package/dist/test/oml.test.d.ts.map +1 -0
- package/dist/test/redirects.test.d.ts +2 -0
- package/dist/test/redirects.test.d.ts.map +1 -0
- package/dist/test/routing.test.d.ts +2 -0
- package/dist/test/routing.test.d.ts.map +1 -0
- package/dist/test/ssg.test.d.ts +2 -0
- package/dist/test/ssg.test.d.ts.map +1 -0
- package/dist/test/web-response.test.d.ts +2 -0
- package/dist/test/web-response.test.d.ts.map +1 -0
- package/dist/types.d.ts +314 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +292 -0
- package/dist/types.js.map +7 -0
- package/package.json +103 -0
- package/pka.config.json +32 -0
- package/src/build/config.ts +42 -0
- package/src/build/index.ts +6 -0
- package/src/build/plugins.ts +118 -0
- package/src/cli.ts +502 -0
- package/src/config.ts +197 -0
- package/src/create-multisite.ts +310 -0
- package/src/create.ts +194 -0
- package/src/dev/blueprints.ts +75 -0
- package/src/dev/components.ts +108 -0
- package/src/dev/insert.ts +221 -0
- package/src/dev/remove.ts +677 -0
- package/src/dev/watch.ts +3098 -0
- package/src/env.d.ts +5 -0
- package/src/errors.ts +64 -0
- package/src/generate.ts +228 -0
- package/src/index.ts +67 -0
- package/src/island.ts +47 -0
- package/src/jsx-runtime.d.ts +408 -0
- package/src/jsx-runtime.d.ts.map +1 -0
- package/src/jsx-runtime.ts +536 -0
- package/src/link.ts +49 -0
- package/src/oml/fragment.ts +54 -0
- package/src/oml/index.ts +21 -0
- package/src/oml/jsx-runtime.ts +121 -0
- package/src/oml/jsx.ts +151 -0
- package/src/oml/page.ts +13 -0
- package/src/oml/render.ts +181 -0
- package/src/oml/types.ts +159 -0
- package/src/router/handler.ts +515 -0
- package/src/router/matcher.ts +52 -0
- package/src/router/scanner.ts +272 -0
- package/src/server/index.ts +49 -0
- package/src/signal.ts +39 -0
- package/src/ssg.ts +253 -0
- package/src/test/actions.test.ts +40 -0
- package/src/test/body-limits.test.ts +83 -0
- package/src/test/errors.test.ts +53 -0
- package/src/test/fixtures/routes/[id].page.ts +3 -0
- package/src/test/fixtures/routes/_error.ts +6 -0
- package/src/test/fixtures/routes/_global.ts +8 -0
- package/src/test/fixtures/routes/_layout-template.ts +7 -0
- package/src/test/fixtures/routes/_layout.ts +7 -0
- package/src/test/fixtures/routes/_layout_scripts.ts +8 -0
- package/src/test/fixtures/routes/_middleware.ts +8 -0
- package/src/test/fixtures/routes/_redirect301_mw.ts +5 -0
- package/src/test/fixtures/routes/_redirect_mw.ts +5 -0
- package/src/test/fixtures/routes/about.page.ts +6 -0
- package/src/test/fixtures/routes/action.page.ts +11 -0
- package/src/test/fixtures/routes/api/form-all.post.ts +5 -0
- package/src/test/fixtures/routes/api/form-limited.post.ts +6 -0
- package/src/test/fixtures/routes/api/response-obj.get.ts +17 -0
- package/src/test/fixtures/routes/api/upload.post.ts +14 -0
- package/src/test/fixtures/routes/api/users.get.ts +3 -0
- package/src/test/fixtures/routes/api/xml.get.ts +5 -0
- package/src/test/fixtures/routes/auth/_middleware.ts +11 -0
- package/src/test/fixtures/routes/auth/protected.page.ts +3 -0
- package/src/test/fixtures/routes/index.page.ts +3 -0
- package/src/test/fixtures/routes/oml.page.ts +7 -0
- package/src/test/fixtures/routes/redirect.page.ts +3 -0
- package/src/test/fixtures/routes/ssg/[slug].page.ts +8 -0
- package/src/test/fixtures/routes/ssg/server.page.ts +5 -0
- package/src/test/fixtures/routes/state.page.ts +4 -0
- package/src/test/fixtures/routes/throw.page.ts +5 -0
- package/src/test/fixtures/routes/wiki/[...slug].page.ts +3 -0
- package/src/test/helpers.ts +132 -0
- package/src/test/layouts.test.ts +76 -0
- package/src/test/middleware.test.ts +69 -0
- package/src/test/multipart.test.ts +91 -0
- package/src/test/oml-routing.test.ts +59 -0
- package/src/test/oml.test.ts +429 -0
- package/src/test/redirects.test.ts +32 -0
- package/src/test/routing.test.ts +118 -0
- package/src/test/ssg.test.ts +273 -0
- package/src/test/web-response.test.ts +33 -0
- package/src/types.ts +670 -0
- package/tsconfig.client.json +17 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
import { layout, makeApp, request, route } from './helpers.js'
|
|
4
|
+
|
|
5
|
+
describe('routing', () => {
|
|
6
|
+
test('GET / returns 200 with page content', async () => {
|
|
7
|
+
const app = makeApp({ routes: [route('index.page.ts', '/')] })
|
|
8
|
+
const { status, body } = await request(app, { url: '/' })
|
|
9
|
+
assert.equal(status, 200)
|
|
10
|
+
assert.ok(body.includes('Home'))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('GET /about returns 200', async () => {
|
|
14
|
+
const app = makeApp({ routes: [route('about.page.ts', '/about')] })
|
|
15
|
+
const { status, body } = await request(app, { url: '/about' })
|
|
16
|
+
assert.equal(status, 200)
|
|
17
|
+
assert.ok(body.includes('About'))
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('dynamic params are extracted and passed to the handler', async () => {
|
|
21
|
+
const app = makeApp({ routes: [route('[id].page.ts', '/:id')] })
|
|
22
|
+
const { status, body } = await request(app, { url: '/42' })
|
|
23
|
+
assert.equal(status, 200)
|
|
24
|
+
assert.ok(body.includes('42'))
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('unmatched URL returns 404', async () => {
|
|
28
|
+
const app = makeApp({ routes: [route('index.page.ts', '/')] })
|
|
29
|
+
const { status } = await request(app, { url: '/does-not-exist' })
|
|
30
|
+
assert.equal(status, 404)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('static route takes priority over dynamic route at same depth', async () => {
|
|
34
|
+
const app = makeApp({
|
|
35
|
+
routes: [route('about.page.ts', '/about'), route('[id].page.ts', '/:id')],
|
|
36
|
+
})
|
|
37
|
+
// /about should match the static route, not the dynamic one
|
|
38
|
+
const { body } = await request(app, { url: '/about' })
|
|
39
|
+
assert.ok(body.includes('About'))
|
|
40
|
+
assert.ok(!body.includes('Item:'))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('POST to page without action returns 405', async () => {
|
|
44
|
+
const app = makeApp({ routes: [route('index.page.ts', '/')] })
|
|
45
|
+
const { status } = await request(app, { method: 'POST', url: '/' })
|
|
46
|
+
assert.equal(status, 405)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('GET API route returns JSON', async () => {
|
|
50
|
+
const app = makeApp({ routes: [route('api/users.get.ts', '/api/users', 'get')] })
|
|
51
|
+
const { status, body } = await request(app, { url: '/api/users' })
|
|
52
|
+
assert.equal(status, 200)
|
|
53
|
+
const data = JSON.parse(body) as unknown[]
|
|
54
|
+
assert.ok(Array.isArray(data))
|
|
55
|
+
assert.equal(data.length, 1)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('HEAD request matches page route', async () => {
|
|
59
|
+
const app = makeApp({ routes: [route('index.page.ts', '/')] })
|
|
60
|
+
const { status } = await request(app, { method: 'HEAD', url: '/' })
|
|
61
|
+
assert.equal(status, 200)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('layout wraps page content', async () => {
|
|
65
|
+
const app = makeApp({
|
|
66
|
+
routes: [route('about.page.ts', '/about')],
|
|
67
|
+
layouts: [layout('_layout.ts')],
|
|
68
|
+
})
|
|
69
|
+
const { status, body } = await request(app, { url: '/about' })
|
|
70
|
+
assert.equal(status, 200)
|
|
71
|
+
assert.ok(body.startsWith('<!DOCTYPE html>') || body.startsWith('<html'))
|
|
72
|
+
assert.ok(body.includes('About'))
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('catch-all route matches a single segment', async () => {
|
|
76
|
+
const app = makeApp({ routes: [route('wiki/[...slug].page.ts', '/wiki/*slug')] })
|
|
77
|
+
const { status, body } = await request(app, { url: '/wiki/about' })
|
|
78
|
+
assert.equal(status, 200)
|
|
79
|
+
assert.ok(body.includes('Wiki: about'))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('catch-all route matches multiple segments', async () => {
|
|
83
|
+
const app = makeApp({ routes: [route('wiki/[...slug].page.ts', '/wiki/*slug')] })
|
|
84
|
+
const { status, body } = await request(app, { url: '/wiki/about/team/members' })
|
|
85
|
+
assert.equal(status, 200)
|
|
86
|
+
assert.ok(body.includes('Wiki: about/team/members'))
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('static route takes priority over catch-all at the same prefix', async () => {
|
|
90
|
+
const app = makeApp({
|
|
91
|
+
routes: [
|
|
92
|
+
route('about.page.ts', '/wiki/about'),
|
|
93
|
+
route('wiki/[...slug].page.ts', '/wiki/*slug'),
|
|
94
|
+
],
|
|
95
|
+
})
|
|
96
|
+
const { body } = await request(app, { url: '/wiki/about' })
|
|
97
|
+
assert.ok(body.includes('About'))
|
|
98
|
+
assert.ok(!body.includes('Wiki:'))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('dynamic route takes priority over catch-all', async () => {
|
|
102
|
+
const app = makeApp({
|
|
103
|
+
routes: [route('[id].page.ts', '/wiki/:id'), route('wiki/[...slug].page.ts', '/wiki/*slug')],
|
|
104
|
+
})
|
|
105
|
+
const { body } = await request(app, { url: '/wiki/42' })
|
|
106
|
+
assert.ok(body.includes('Item: 42'))
|
|
107
|
+
assert.ok(!body.includes('Wiki:'))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('ctx.head.title set in page is available in layout', async () => {
|
|
111
|
+
const app = makeApp({
|
|
112
|
+
routes: [route('about.page.ts', '/about')],
|
|
113
|
+
layouts: [layout('_layout.ts')],
|
|
114
|
+
})
|
|
115
|
+
const { body } = await request(app, { url: '/about' })
|
|
116
|
+
assert.ok(body.includes('<title>About</title>'))
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readdir, readFile, rm } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { after, describe, test } from 'node:test'
|
|
7
|
+
import { generateStatic } from '../ssg.js'
|
|
8
|
+
import { fix, layout, makeScan, route } from './helpers.js'
|
|
9
|
+
|
|
10
|
+
describe('static generation', () => {
|
|
11
|
+
const outDir = join(tmpdir(), `davaux-ssg-test-${Date.now()}`)
|
|
12
|
+
|
|
13
|
+
after(async () => {
|
|
14
|
+
await rm(outDir, { recursive: true, force: true })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('renders a static route to out/<path>/index.html', async () => {
|
|
18
|
+
const scan = makeScan({ routes: [route('about.page.ts', '/about')] })
|
|
19
|
+
await generateStatic({
|
|
20
|
+
outDir,
|
|
21
|
+
distDir: outDir,
|
|
22
|
+
publicDir: '/nonexistent',
|
|
23
|
+
scan,
|
|
24
|
+
clientScripts: [],
|
|
25
|
+
clientStylesheets: [],
|
|
26
|
+
})
|
|
27
|
+
assert.ok(existsSync(join(outDir, 'about', 'index.html')))
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('renders the root route to out/index.html', async () => {
|
|
31
|
+
const scan = makeScan({ routes: [route('index.page.ts', '/')] })
|
|
32
|
+
await generateStatic({
|
|
33
|
+
outDir,
|
|
34
|
+
distDir: outDir,
|
|
35
|
+
publicDir: '/nonexistent',
|
|
36
|
+
scan,
|
|
37
|
+
clientScripts: [],
|
|
38
|
+
clientStylesheets: [],
|
|
39
|
+
})
|
|
40
|
+
assert.ok(existsSync(join(outDir, 'index.html')))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('rendered HTML includes DOCTYPE', async () => {
|
|
44
|
+
const scan = makeScan({ routes: [route('index.page.ts', '/')] })
|
|
45
|
+
await generateStatic({
|
|
46
|
+
outDir,
|
|
47
|
+
distDir: outDir,
|
|
48
|
+
publicDir: '/nonexistent',
|
|
49
|
+
scan,
|
|
50
|
+
clientScripts: [],
|
|
51
|
+
clientStylesheets: [],
|
|
52
|
+
})
|
|
53
|
+
const html = await readFile(join(outDir, 'index.html'), 'utf-8')
|
|
54
|
+
assert.ok(html.startsWith('<!DOCTYPE html>') || html.startsWith('<html'))
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('dynamic route with getStaticPaths renders one file per path', async () => {
|
|
58
|
+
const scan = makeScan({ routes: [route('ssg/[slug].page.ts', '/ssg/:slug')] })
|
|
59
|
+
await generateStatic({
|
|
60
|
+
outDir,
|
|
61
|
+
distDir: outDir,
|
|
62
|
+
publicDir: '/nonexistent',
|
|
63
|
+
scan,
|
|
64
|
+
clientScripts: [],
|
|
65
|
+
clientStylesheets: [],
|
|
66
|
+
})
|
|
67
|
+
assert.ok(existsSync(join(outDir, 'ssg', 'hello', 'index.html')))
|
|
68
|
+
assert.ok(existsSync(join(outDir, 'ssg', 'world', 'index.html')))
|
|
69
|
+
const html = await readFile(join(outDir, 'ssg', 'hello', 'index.html'), 'utf-8')
|
|
70
|
+
assert.ok(html.includes('hello'))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('dynamic route without getStaticPaths is skipped', async () => {
|
|
74
|
+
// [id].page.ts has no getStaticPaths export
|
|
75
|
+
const scan = makeScan({ routes: [route('[id].page.ts', '/:id')] })
|
|
76
|
+
const before = new Set(existsSync(outDir) ? await readdir(outDir, { recursive: true }) : [])
|
|
77
|
+
await generateStatic({
|
|
78
|
+
outDir,
|
|
79
|
+
distDir: outDir,
|
|
80
|
+
publicDir: '/nonexistent',
|
|
81
|
+
scan,
|
|
82
|
+
clientScripts: [],
|
|
83
|
+
clientStylesheets: [],
|
|
84
|
+
})
|
|
85
|
+
const after = existsSync(outDir) ? await readdir(outDir, { recursive: true }) : []
|
|
86
|
+
const newFiles = after.filter((f) => !before.has(f as string))
|
|
87
|
+
assert.equal(newFiles.length, 0)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('route with prerender=false is skipped during static generation', async () => {
|
|
91
|
+
const scan = makeScan({ routes: [route('ssg/server.page.ts', '/ssg/server')] })
|
|
92
|
+
const before = new Set(existsSync(outDir) ? await readdir(outDir, { recursive: true }) : [])
|
|
93
|
+
await generateStatic({
|
|
94
|
+
outDir,
|
|
95
|
+
distDir: outDir,
|
|
96
|
+
publicDir: '/nonexistent',
|
|
97
|
+
scan,
|
|
98
|
+
clientScripts: [],
|
|
99
|
+
clientStylesheets: [],
|
|
100
|
+
})
|
|
101
|
+
const after = existsSync(outDir) ? await readdir(outDir, { recursive: true }) : []
|
|
102
|
+
const newFiles = after.filter((f) => !before.has(f as string))
|
|
103
|
+
assert.equal(newFiles.length, 0)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('API routes are not rendered', async () => {
|
|
107
|
+
const scan = makeScan({ routes: [route('api/users.get.ts', '/api/users', 'get')] })
|
|
108
|
+
const beforeFiles = existsSync(outDir) ? await readdir(outDir) : []
|
|
109
|
+
await generateStatic({
|
|
110
|
+
outDir,
|
|
111
|
+
distDir: outDir,
|
|
112
|
+
publicDir: '/nonexistent',
|
|
113
|
+
scan,
|
|
114
|
+
clientScripts: [],
|
|
115
|
+
clientStylesheets: [],
|
|
116
|
+
})
|
|
117
|
+
const afterFiles = await readdir(outDir)
|
|
118
|
+
// No new HTML files created for API routes
|
|
119
|
+
const newHtml = afterFiles.filter((f) => f.endsWith('.html') && !beforeFiles.includes(f))
|
|
120
|
+
assert.equal(newHtml.length, 0)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('SSG config options', () => {
|
|
125
|
+
const outDir = join(tmpdir(), `davaux-ssg-opts-${Date.now()}`)
|
|
126
|
+
|
|
127
|
+
after(async () => {
|
|
128
|
+
await rm(outDir, { recursive: true, force: true })
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('trailingSlash: never writes <path>.html instead of <path>/index.html', async () => {
|
|
132
|
+
const scan = makeScan({ routes: [route('about.page.ts', '/about')] })
|
|
133
|
+
await generateStatic({
|
|
134
|
+
outDir,
|
|
135
|
+
distDir: outDir,
|
|
136
|
+
publicDir: '/nonexistent',
|
|
137
|
+
scan,
|
|
138
|
+
clientScripts: [],
|
|
139
|
+
clientStylesheets: [],
|
|
140
|
+
trailingSlash: 'never',
|
|
141
|
+
})
|
|
142
|
+
assert.ok(existsSync(join(outDir, 'about.html')), 'about.html should exist')
|
|
143
|
+
assert.ok(!existsSync(join(outDir, 'about', 'index.html')), 'about/index.html should not exist')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('trailingSlash: always (default) writes <path>/index.html', async () => {
|
|
147
|
+
const scan = makeScan({ routes: [route('about.page.ts', '/about')] })
|
|
148
|
+
await generateStatic({
|
|
149
|
+
outDir,
|
|
150
|
+
distDir: outDir,
|
|
151
|
+
publicDir: '/nonexistent',
|
|
152
|
+
scan,
|
|
153
|
+
clientScripts: [],
|
|
154
|
+
clientStylesheets: [],
|
|
155
|
+
trailingSlash: 'always',
|
|
156
|
+
})
|
|
157
|
+
assert.ok(existsSync(join(outDir, 'about', 'index.html')))
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('basePath is prepended to injected script URLs in generated HTML', async () => {
|
|
161
|
+
const scan = makeScan({
|
|
162
|
+
routes: [route('index.page.ts', '/')],
|
|
163
|
+
layouts: [layout('_layout_scripts.ts')],
|
|
164
|
+
})
|
|
165
|
+
await generateStatic({
|
|
166
|
+
outDir,
|
|
167
|
+
distDir: outDir,
|
|
168
|
+
publicDir: '/nonexistent',
|
|
169
|
+
scan,
|
|
170
|
+
clientScripts: ['/_davaux/islands.js'],
|
|
171
|
+
clientStylesheets: ['/_davaux/styles.css'],
|
|
172
|
+
basePath: '/docs',
|
|
173
|
+
})
|
|
174
|
+
const html = await readFile(join(outDir, 'index.html'), 'utf-8')
|
|
175
|
+
assert.ok(
|
|
176
|
+
html.includes('/docs/_davaux/islands.js'),
|
|
177
|
+
'basePath should be prepended to script URL',
|
|
178
|
+
)
|
|
179
|
+
assert.ok(
|
|
180
|
+
html.includes('/docs/_davaux/styles.css'),
|
|
181
|
+
'basePath should be prepended to stylesheet URL',
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('notFound: generates 404.html when error page exists', async () => {
|
|
186
|
+
const errorPage = fix('_error.ts')
|
|
187
|
+
const scan = makeScan({ routes: [route('index.page.ts', '/')], errorPage })
|
|
188
|
+
await generateStatic({
|
|
189
|
+
outDir,
|
|
190
|
+
distDir: outDir,
|
|
191
|
+
publicDir: '/nonexistent',
|
|
192
|
+
scan,
|
|
193
|
+
clientScripts: [],
|
|
194
|
+
clientStylesheets: [],
|
|
195
|
+
})
|
|
196
|
+
assert.ok(
|
|
197
|
+
existsSync(join(outDir, '404.html')),
|
|
198
|
+
'404.html should be generated when errorPage exists',
|
|
199
|
+
)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test('notFound: false suppresses 404.html even when error page exists', async () => {
|
|
203
|
+
const errorPage = fix('_error.ts')
|
|
204
|
+
const scan = makeScan({ routes: [route('index.page.ts', '/')], errorPage })
|
|
205
|
+
const before = existsSync(join(outDir, '404.html'))
|
|
206
|
+
await generateStatic({
|
|
207
|
+
outDir,
|
|
208
|
+
distDir: outDir,
|
|
209
|
+
publicDir: '/nonexistent',
|
|
210
|
+
scan,
|
|
211
|
+
clientScripts: [],
|
|
212
|
+
clientStylesheets: [],
|
|
213
|
+
notFound: false,
|
|
214
|
+
})
|
|
215
|
+
const after = existsSync(join(outDir, '404.html'))
|
|
216
|
+
// If it didn't exist before, it shouldn't exist now
|
|
217
|
+
if (!before) assert.ok(!after, '404.html should not be generated when notFound: false')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('sitemap: generates sitemap.xml with correct URLs', async () => {
|
|
221
|
+
const scan = makeScan({
|
|
222
|
+
routes: [route('index.page.ts', '/'), route('about.page.ts', '/about')],
|
|
223
|
+
})
|
|
224
|
+
await generateStatic({
|
|
225
|
+
outDir,
|
|
226
|
+
distDir: outDir,
|
|
227
|
+
publicDir: '/nonexistent',
|
|
228
|
+
scan,
|
|
229
|
+
clientScripts: [],
|
|
230
|
+
clientStylesheets: [],
|
|
231
|
+
sitemap: { baseUrl: 'https://example.com' },
|
|
232
|
+
})
|
|
233
|
+
assert.ok(existsSync(join(outDir, 'sitemap.xml')))
|
|
234
|
+
const xml = await readFile(join(outDir, 'sitemap.xml'), 'utf-8')
|
|
235
|
+
assert.ok(xml.includes('<loc>https://example.com/</loc>'))
|
|
236
|
+
assert.ok(xml.includes('<loc>https://example.com/about/</loc>'))
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('sitemap: respects trailingSlash: never in generated URLs', async () => {
|
|
240
|
+
const scan = makeScan({ routes: [route('about.page.ts', '/about')] })
|
|
241
|
+
await generateStatic({
|
|
242
|
+
outDir,
|
|
243
|
+
distDir: outDir,
|
|
244
|
+
publicDir: '/nonexistent',
|
|
245
|
+
scan,
|
|
246
|
+
clientScripts: [],
|
|
247
|
+
clientStylesheets: [],
|
|
248
|
+
sitemap: { baseUrl: 'https://example.com' },
|
|
249
|
+
trailingSlash: 'never',
|
|
250
|
+
})
|
|
251
|
+
const xml = await readFile(join(outDir, 'sitemap.xml'), 'utf-8')
|
|
252
|
+
assert.ok(
|
|
253
|
+
xml.includes('<loc>https://example.com/about</loc>'),
|
|
254
|
+
'no trailing slash in sitemap URL',
|
|
255
|
+
)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('sitemap: includes basePath in generated URLs', async () => {
|
|
259
|
+
const scan = makeScan({ routes: [route('about.page.ts', '/about')] })
|
|
260
|
+
await generateStatic({
|
|
261
|
+
outDir,
|
|
262
|
+
distDir: outDir,
|
|
263
|
+
publicDir: '/nonexistent',
|
|
264
|
+
scan,
|
|
265
|
+
clientScripts: [],
|
|
266
|
+
clientStylesheets: [],
|
|
267
|
+
sitemap: { baseUrl: 'https://example.com' },
|
|
268
|
+
basePath: '/docs',
|
|
269
|
+
})
|
|
270
|
+
const xml = await readFile(join(outDir, 'sitemap.xml'), 'utf-8')
|
|
271
|
+
assert.ok(xml.includes('<loc>https://example.com/docs/about/</loc>'))
|
|
272
|
+
})
|
|
273
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
import { makeApp, request, route } from './helpers.js'
|
|
4
|
+
|
|
5
|
+
describe('Web Response support in defineHandler', () => {
|
|
6
|
+
const app = makeApp({ routes: [route('api/response-obj.get.ts', '/api/resp', 'get')] })
|
|
7
|
+
|
|
8
|
+
test('Response.json() sets status and JSON body', async () => {
|
|
9
|
+
const { status, body, headers } = await request(app, { url: '/api/resp?shape=created' })
|
|
10
|
+
assert.equal(status, 201)
|
|
11
|
+
assert.deepEqual(JSON.parse(body), { created: true })
|
|
12
|
+
assert.ok(headers['content-type']?.includes('application/json'))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('Response.json() with default status 200', async () => {
|
|
16
|
+
const { status, body } = await request(app, { url: '/api/resp' })
|
|
17
|
+
assert.equal(status, 200)
|
|
18
|
+
assert.deepEqual(JSON.parse(body), { ok: true })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('new Response() with custom content-type', async () => {
|
|
22
|
+
const { status, body, headers } = await request(app, { url: '/api/resp?shape=text' })
|
|
23
|
+
assert.equal(status, 200)
|
|
24
|
+
assert.equal(body, 'hello plain')
|
|
25
|
+
assert.equal(headers['content-type'], 'text/plain')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('new Response(null, { status: 204 }) sends empty body', async () => {
|
|
29
|
+
const { status, body } = await request(app, { url: '/api/resp?shape=nocontent' })
|
|
30
|
+
assert.equal(status, 204)
|
|
31
|
+
assert.equal(body, '')
|
|
32
|
+
})
|
|
33
|
+
})
|