orga-build 0.9.0 → 0.10.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.
Files changed (66) hide show
  1. package/README.org +282 -33
  2. package/cli.js +4 -4
  3. package/lib/__tests__/build.test.js +441 -22
  4. package/lib/__tests__/dev.test.d.ts +2 -0
  5. package/lib/__tests__/dev.test.d.ts.map +1 -0
  6. package/lib/__tests__/dev.test.js +295 -0
  7. package/lib/__tests__/fixtures.d.ts +8 -0
  8. package/lib/__tests__/fixtures.d.ts.map +1 -0
  9. package/lib/__tests__/fixtures.js +61 -0
  10. package/lib/app.jsx +23 -50
  11. package/lib/build.d.ts +4 -1
  12. package/lib/build.d.ts.map +1 -1
  13. package/lib/build.js +10 -175
  14. package/lib/config.d.ts +5 -3
  15. package/lib/config.d.ts.map +1 -1
  16. package/lib/config.js +8 -16
  17. package/lib/content.d.ts +6 -0
  18. package/lib/dev-ssr.d.ts +14 -0
  19. package/lib/dev-ssr.d.ts.map +1 -0
  20. package/lib/dev-ssr.js +137 -0
  21. package/lib/endpoint.d.ts +5 -0
  22. package/lib/endpoint.d.ts.map +1 -1
  23. package/lib/endpoint.js +1 -0
  24. package/lib/files.d.ts +8 -3
  25. package/lib/files.d.ts.map +1 -1
  26. package/lib/files.js +104 -20
  27. package/lib/fs.d.ts +0 -5
  28. package/lib/fs.d.ts.map +1 -1
  29. package/lib/fs.js +0 -18
  30. package/lib/html.d.ts +37 -0
  31. package/lib/html.d.ts.map +1 -0
  32. package/lib/html.js +101 -0
  33. package/lib/index.html +0 -1
  34. package/lib/island-client.d.ts +5 -0
  35. package/lib/island-client.d.ts.map +1 -0
  36. package/lib/island-client.js +41 -0
  37. package/lib/island.d.ts +18 -0
  38. package/lib/island.d.ts.map +1 -0
  39. package/lib/island.js +146 -0
  40. package/lib/island.jsx +108 -0
  41. package/lib/orga.d.ts +1 -1
  42. package/lib/orga.d.ts.map +1 -1
  43. package/lib/orga.js +54 -16
  44. package/lib/plugin.d.ts +20 -32
  45. package/lib/plugin.d.ts.map +1 -1
  46. package/lib/plugin.js +128 -178
  47. package/lib/prerender.d.ts +11 -0
  48. package/lib/prerender.d.ts.map +1 -0
  49. package/lib/prerender.js +184 -0
  50. package/lib/serve.d.ts.map +1 -1
  51. package/lib/serve.js +5 -23
  52. package/lib/ssr.jsx +9 -14
  53. package/lib/util.d.ts +0 -22
  54. package/lib/util.d.ts.map +1 -1
  55. package/lib/util.js +0 -62
  56. package/lib/vite.d.ts +5 -6
  57. package/lib/vite.d.ts.map +1 -1
  58. package/lib/vite.js +74 -23
  59. package/package.json +5 -8
  60. package/lib/components.d.ts +0 -2
  61. package/lib/components.d.ts.map +0 -1
  62. package/lib/components.js +0 -1
  63. package/lib/csr.jsx +0 -11
  64. package/lib/watch.d.ts +0 -10
  65. package/lib/watch.d.ts.map +0 -1
  66. package/lib/watch.js +0 -53
@@ -3,12 +3,33 @@ import fs from 'node:fs/promises'
3
3
  import path from 'node:path'
4
4
  import { after, before, describe, test } from 'node:test'
5
5
  import { fileURLToPath } from 'node:url'
6
+ import { createBuilder } from 'vite'
6
7
  import { build } from '../build.js'
8
+ import { renderPageHtml } from '../html.js'
9
+ import { orgaBuildPlugin } from '../plugin.js'
10
+ import { writeIslandFixture } from './fixtures.js'
7
11
 
8
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
13
  const fixtureDir = path.join(__dirname, 'fixtures')
10
14
  const outDir = path.join(__dirname, '.test-output')
11
15
 
16
+ /**
17
+ * Build `dir` with plain Vite and only `orgaBuildPlugin`, as a
18
+ * `vite.config.js` would, without the orga-build CLI.
19
+ * @param {string} dir
20
+ * @param {import('vite').InlineConfig} [config]
21
+ */
22
+ async function viteBuild(dir, config = {}) {
23
+ const builder = await createBuilder({
24
+ root: dir,
25
+ configFile: false,
26
+ logLevel: 'silent',
27
+ plugins: orgaBuildPlugin({ root: dir }),
28
+ ...config
29
+ })
30
+ await builder.buildApp()
31
+ }
32
+
12
33
  function markCodeBlocks() {
13
34
  /**
14
35
  * @param {any} tree
@@ -41,6 +62,8 @@ Here's [[file:./docs/index.org][index page]].
41
62
 
42
63
  Here's [[file:more.org][another page]].
43
64
 
65
+ Jump to [[*Hello World][the top]], [[file:more.org::*Some Heading][a heading]] or [[file:./docs/index.org::#cid][an anchor]].
66
+
44
67
  Here's [[mailto:hi@unclex.net][send me an email]].
45
68
  `
46
69
  )
@@ -78,9 +101,7 @@ export function GET() {
78
101
  root: fixtureDir,
79
102
  outDir: outDir,
80
103
  containerClass: [],
81
- vitePlugins: [],
82
- preBuild: [],
83
- postBuild: []
104
+ vitePlugins: []
84
105
  })
85
106
 
86
107
  // Check output exists
@@ -100,19 +121,87 @@ export function GET() {
100
121
  'should rewrite docs/index.org to /docs'
101
122
  )
102
123
  assert.ok(html.includes('href="/more"'), 'should rewrite more.org to /more')
124
+ assert.ok(html.includes('<h1 id="hello-world">'), 'should id headings')
125
+ assert.ok(html.includes('href="#hello-world"'), 'should link in page')
126
+ assert.ok(
127
+ html.includes('href="/more#some-heading"'),
128
+ 'should keep heading search as fragment'
129
+ )
130
+ assert.ok(
131
+ html.includes('href="/docs#cid"'),
132
+ 'should keep custom-id search as fragment'
133
+ )
103
134
  assert.ok(
104
135
  html.includes('href="mailto:hi@unclex.net"'),
105
136
  'should keep mailto protocol in href'
106
137
  )
107
138
  })
108
139
 
109
- test('generates assets directory', async () => {
110
- const assetsDir = path.join(outDir, 'assets')
111
- const assetsExists = await fs
112
- .access(assetsDir)
113
- .then(() => true)
114
- .catch(() => false)
115
- assert.ok(assetsExists, 'assets directory should exist')
140
+ test('ships no JavaScript without islands', async () => {
141
+ const html = await fs.readFile(path.join(outDir, 'index.html'), 'utf-8')
142
+ assert.ok(!html.includes('<script'), html)
143
+ })
144
+
145
+ test("hydrates 'use client' components as islands", async () => {
146
+ const dir = path.join(__dirname, 'fixtures-islands')
147
+ try {
148
+ await writeIslandFixture(dir)
149
+ await viteBuild(dir)
150
+
151
+ const html = await fs.readFile(
152
+ path.join(dir, 'dist', 'index.html'),
153
+ 'utf-8'
154
+ )
155
+ const island = html.match(/<orga-island ([^>]+)>/)?.[1] ?? ''
156
+ const islandSrc = island.match(/src="([^"]+)"/)?.[1] ?? ''
157
+ assert.match(islandSrc, /^\/assets\/.+\.js$/)
158
+ assert.match(island, /export="Counter"/)
159
+ assert.match(island, /props="{&quot;start&quot;:1}"/)
160
+ assert.match(island, /client="load"/)
161
+ assert.match(island, /prefix="island-0-"/)
162
+ assert.match(
163
+ html,
164
+ /<button id="[^"]*island-0-[^"]*">1<\/button>/,
165
+ 'island is prerendered with its own useId prefix'
166
+ )
167
+ const chunk = await fs.readFile(
168
+ path.join(dir, 'dist', islandSrc),
169
+ 'utf-8'
170
+ )
171
+ assert.match(chunk, /\bCounter\b/, 'island chunk keeps its exports')
172
+
173
+ const script =
174
+ html.match(/<script type="module" src="([^"]*island[^"]*\.js)"/)?.[1] ??
175
+ ''
176
+ assert.match(script, /^\/assets\//, html)
177
+ await fs.access(path.join(dir, 'dist', script))
178
+
179
+ const nested = await fs.readFile(
180
+ path.join(dir, 'dist', 'docs', 'index.html'),
181
+ 'utf-8'
182
+ )
183
+ assert.match(nested, /<orga-island [^>]*client="visible"/)
184
+ assert.match(
185
+ nested,
186
+ /<orga-island [^>]*export="MemoCounter" props="{&quot;start&quot;:3,&quot;meta&quot;:{&quot;children&quot;:\[&quot;a&quot;\]}}"/,
187
+ 'nested `children` keys are data; undefined is dropped'
188
+ )
189
+
190
+ const plain = await fs.readFile(
191
+ path.join(dir, 'dist', 'plain', 'index.html'),
192
+ 'utf-8'
193
+ )
194
+ assert.ok(!plain.includes('<script'), 'pages without islands stay static')
195
+
196
+ const page = await fs.readFile(
197
+ path.join(dir, 'dist', 'page', 'index.html'),
198
+ 'utf-8'
199
+ )
200
+ assert.match(page, /<title>Page<\/title>/)
201
+ assert.match(page, /<orga-island [^>]*export="default" props="{}"/)
202
+ } finally {
203
+ await fs.rm(dir, { recursive: true, force: true })
204
+ }
116
205
  })
117
206
 
118
207
  test('processes configured global styles through vite and injects built css', async () => {
@@ -123,9 +212,7 @@ export function GET() {
123
212
  outDir: outDir,
124
213
  containerClass: [],
125
214
  styles: [styleUrl],
126
- vitePlugins: [],
127
- preBuild: [],
128
- postBuild: []
215
+ vitePlugins: []
129
216
  })
130
217
 
131
218
  const html = await fs.readFile(path.join(outDir, 'index.html'), 'utf-8')
@@ -166,9 +253,7 @@ This page verifies custom rehype plugins.`
166
253
  outDir: outDirRehype,
167
254
  containerClass: [],
168
255
  rehypePlugins: [markCodeBlocks],
169
- vitePlugins: [],
170
- preBuild: [],
171
- postBuild: []
256
+ vitePlugins: []
172
257
  })
173
258
 
174
259
  const html = await fs.readFile(
@@ -190,9 +275,7 @@ This page verifies custom rehype plugins.`
190
275
  root: fixtureDir,
191
276
  outDir: outDir,
192
277
  containerClass: [],
193
- vitePlugins: [],
194
- preBuild: [],
195
- postBuild: []
278
+ vitePlugins: []
196
279
  })
197
280
 
198
281
  const rss = await fs.readFile(path.join(outDir, 'rss.xml'), 'utf-8')
@@ -219,9 +302,7 @@ This page verifies custom rehype plugins.`
219
302
  root: fixtureDirConflict,
220
303
  outDir: outDirConflict,
221
304
  containerClass: [],
222
- vitePlugins: [],
223
- preBuild: [],
224
- postBuild: []
305
+ vitePlugins: []
225
306
  }),
226
307
  /Route conflict detected/
227
308
  )
@@ -230,4 +311,342 @@ This page verifies custom rehype plugins.`
230
311
  await fs.rm(fixtureDirConflict, { recursive: true, force: true })
231
312
  }
232
313
  })
314
+
315
+ test("excludes Vite's output directory from content discovery", async () => {
316
+ const dir = path.join(__dirname, 'fixtures-vite-outdir')
317
+ // Dotted chunk names would make bundles look like endpoint routes.
318
+ const buildOnce = () =>
319
+ viteBuild(dir, {
320
+ build: {
321
+ rolldownOptions: {
322
+ output: { entryFileNames: 'assets/[name].[hash].js' }
323
+ }
324
+ }
325
+ })
326
+ try {
327
+ await fs.mkdir(dir, { recursive: true })
328
+ await fs.writeFile(path.join(dir, 'index.org'), '#+title: Home\n\nHome')
329
+
330
+ await buildOnce()
331
+ // The second build sees the first build's output inside the root.
332
+ await buildOnce()
333
+
334
+ const html = await fs.readFile(
335
+ path.join(dir, 'dist', 'index.html'),
336
+ 'utf-8'
337
+ )
338
+ assert.ok(html.includes('<title>Home</title>'))
339
+ } finally {
340
+ await fs.rm(dir, { recursive: true, force: true })
341
+ }
342
+ })
343
+
344
+ test('leaves draft pages out of the build', async () => {
345
+ const dir = path.join(__dirname, 'fixtures-drafts')
346
+ try {
347
+ await fs.mkdir(dir, { recursive: true })
348
+ await fs.writeFile(path.join(dir, 'index.org'), 'Home')
349
+ await fs.writeFile(path.join(dir, 'draft.org'), '#+draft: t\n\nWIP')
350
+ await fs.writeFile(
351
+ path.join(dir, 'published.org'),
352
+ '#+draft: false\n\nDone'
353
+ )
354
+ await fs.writeFile(
355
+ path.join(dir, 'pages.json.ts'),
356
+ `import { getPages } from 'orga-build:content'
357
+ export function GET() {
358
+ return Response.json(getPages().map((page) => page.slug).sort())
359
+ }
360
+ `
361
+ )
362
+
363
+ await viteBuild(dir)
364
+
365
+ const out = path.join(dir, 'dist')
366
+ const slugs = JSON.parse(
367
+ await fs.readFile(path.join(out, 'pages.json'), 'utf-8')
368
+ )
369
+ assert.deepEqual(slugs, ['/', '/published'])
370
+ await fs.access(path.join(out, 'published', 'index.html'))
371
+ await assert.rejects(fs.access(path.join(out, 'draft', 'index.html')))
372
+ } finally {
373
+ await fs.rm(dir, { recursive: true, force: true })
374
+ }
375
+ })
376
+
377
+ test('reads TSX page metadata and exposes site', async () => {
378
+ const dir = path.join(__dirname, 'fixtures-metadata')
379
+ try {
380
+ await fs.mkdir(dir, { recursive: true })
381
+ await fs.writeFile(path.join(dir, 'post.org'), '#+title: Org post\n')
382
+ await fs.writeFile(
383
+ path.join(dir, 'page.tsx'),
384
+ `import { site } from 'orga-build:content'
385
+ export const title = 'TSX page'
386
+ export const tags = ['a', 'b'] as const
387
+ export const order = -1
388
+ export const render = () => title
389
+ export default function Page() {
390
+ return <a href={site + '/page'}>link</a>
391
+ }
392
+ `
393
+ )
394
+ await fs.writeFile(
395
+ path.join(dir, 'draft.jsx'),
396
+ 'export const draft = true\nexport default () => <p />\n'
397
+ )
398
+ await fs.writeFile(
399
+ path.join(dir, 'pages.json.ts'),
400
+ `import { getPages, site } from 'orga-build:content'
401
+ export function GET(ctx) {
402
+ return Response.json({
403
+ site,
404
+ ctxSite: ctx.site,
405
+ url: ctx.url.href,
406
+ pages: getPages()
407
+ .map(({ slug, data }) => ({ slug, data }))
408
+ .sort((a, b) => a.slug.localeCompare(b.slug))
409
+ })
410
+ }
411
+ `
412
+ )
413
+
414
+ await viteBuild(dir, {
415
+ plugins: orgaBuildPlugin({
416
+ root: dir,
417
+ site: 'https://example.com/blog/'
418
+ })
419
+ })
420
+
421
+ const out = path.join(dir, 'dist')
422
+ const json = JSON.parse(
423
+ await fs.readFile(path.join(out, 'pages.json'), 'utf-8')
424
+ )
425
+ assert.deepEqual(json, {
426
+ site: 'https://example.com/blog',
427
+ ctxSite: 'https://example.com/blog',
428
+ url: 'https://example.com/blog/pages.json',
429
+ pages: [
430
+ {
431
+ slug: '/page',
432
+ data: { title: 'TSX page', tags: ['a', 'b'], order: -1 }
433
+ },
434
+ { slug: '/post', data: { title: 'Org post' } }
435
+ ]
436
+ })
437
+ const html = await fs.readFile(
438
+ path.join(out, 'page', 'index.html'),
439
+ 'utf-8'
440
+ )
441
+ assert.ok(html.includes('href="https://example.com/blog/page"'), html)
442
+ for (const site of ['example.com', 'localhost:3000']) {
443
+ assert.throws(
444
+ () => orgaBuildPlugin({ root: dir, site }),
445
+ /"site" must be an absolute http\(s\) URL/
446
+ )
447
+ }
448
+ } finally {
449
+ await fs.rm(dir, { recursive: true, force: true })
450
+ }
451
+ })
452
+
453
+ test('prerenders after a configured builder.buildApp', async () => {
454
+ const dir = path.join(__dirname, 'fixtures-vite-builder')
455
+ try {
456
+ await writeIslandFixture(dir)
457
+
458
+ await viteBuild(dir, {
459
+ builder: {
460
+ // Builds every environment itself, client first (as e.g. the
461
+ // Cloudflare plugin does): that client build predates island
462
+ // discovery and must be redone.
463
+ async buildApp(builder) {
464
+ await builder.build(builder.environments.client)
465
+ await builder.build(builder.environments.ssr)
466
+ }
467
+ }
468
+ })
469
+
470
+ const html = await fs.readFile(
471
+ path.join(dir, 'dist', 'index.html'),
472
+ 'utf-8'
473
+ )
474
+ assert.ok(html.includes('<title>Home</title>'), 'page should survive')
475
+ const islandSrc = html.match(/<orga-island [^>]*src="([^"]+)"/)?.[1]
476
+ assert.ok(islandSrc, 'island is rendered')
477
+ await fs.access(path.join(dir, 'dist', islandSrc))
478
+ } finally {
479
+ await fs.rm(dir, { recursive: true, force: true })
480
+ }
481
+ })
482
+
483
+ test('rejects island props that cannot reach the browser', async () => {
484
+ const dir = path.join(__dirname, 'fixtures-island-props')
485
+ try {
486
+ await writeIslandFixture(dir)
487
+ await fs.writeFile(
488
+ path.join(dir, 'index.org'),
489
+ '#+jsx: <Counter start={1} config={{ onClick: () => {} }} />\n'
490
+ )
491
+ await assert.rejects(() => viteBuild(dir), /prop "onClick" can't be sent/)
492
+ } finally {
493
+ await fs.rm(dir, { recursive: true, force: true })
494
+ }
495
+ })
496
+
497
+ test('emits images and CSS imported by pages', async () => {
498
+ const dir = path.join(__dirname, 'fixtures-page-assets')
499
+ try {
500
+ await fs.mkdir(dir, { recursive: true })
501
+ // Larger than Vite's inline limit, so it is emitted as a file.
502
+ await fs.writeFile(path.join(dir, 'pic.png'), Buffer.alloc(8192, 1))
503
+ await fs.writeFile(path.join(dir, "it's.png"), Buffer.alloc(8192, 2))
504
+ await fs.writeFile(
505
+ path.join(dir, 'index.org'),
506
+ "[[./pic.png]]\n\n[[./it's.png]]\n"
507
+ )
508
+ await fs.writeFile(path.join(dir, 'page.css'), '.page { color: red }\n')
509
+ await fs.writeFile(
510
+ path.join(dir, 'page.tsx'),
511
+ `import './page.css'
512
+ export default function Page() {
513
+ return <p className="page">hi</p>
514
+ }
515
+ `
516
+ )
517
+ await fs.writeFile(
518
+ path.join(dir, 'feed.json.ts'),
519
+ `import pic from './pic.png'
520
+ export function GET() {
521
+ return Response.json({ pic })
522
+ }
523
+ `
524
+ )
525
+ await viteBuild(dir, { build: { sourcemap: true } })
526
+ // The SSR bundle and its source map are not part of the site.
527
+ for (const file of ['ssr.mjs', 'ssr.mjs.map']) {
528
+ await assert.rejects(fs.access(path.join(dir, 'dist', file)), file)
529
+ }
530
+
531
+ const html = await fs.readFile(
532
+ path.join(dir, 'dist', 'index.html'),
533
+ 'utf-8'
534
+ )
535
+ const img = html.match(/<img src="\/(assets\/pic-[^"]+\.png)"/)?.[1] ?? ''
536
+ assert.ok(img, html)
537
+ await fs.access(path.join(dir, 'dist', img))
538
+ assert.equal(html.match(/<img src="\/assets\//g)?.length, 2, html)
539
+
540
+ const feed = JSON.parse(
541
+ await fs.readFile(path.join(dir, 'dist', 'feed.json'), 'utf-8')
542
+ )
543
+ assert.equal(feed.pic, `/${img}`, 'endpoints get real asset URLs too')
544
+
545
+ const page = await fs.readFile(
546
+ path.join(dir, 'dist', 'page', 'index.html'),
547
+ 'utf-8'
548
+ )
549
+ const css =
550
+ page.match(
551
+ /<link rel="stylesheet" href="\/(assets\/[^"]+\.css)"/
552
+ )?.[1] ?? ''
553
+ assert.ok(css, page)
554
+ const built = await fs.readFile(path.join(dir, 'dist', css), 'utf-8')
555
+ assert.ok(built.includes('.page'), built)
556
+ } finally {
557
+ await fs.rm(dir, { recursive: true, force: true })
558
+ }
559
+ })
560
+
561
+ test('leaves external image URLs alone', async () => {
562
+ const dir = path.join(__dirname, 'fixtures-external-assets')
563
+ const srcs = ['https://example.com/a.png', '//cdn.example.com/b.png']
564
+ try {
565
+ await fs.mkdir(dir, { recursive: true })
566
+ await fs.writeFile(
567
+ path.join(dir, 'index.org'),
568
+ srcs.map((src) => `[[${src}]]`).join('\n\n') + '\n'
569
+ )
570
+ await viteBuild(dir)
571
+ const html = await fs.readFile(
572
+ path.join(dir, 'dist', 'index.html'),
573
+ 'utf-8'
574
+ )
575
+ for (const src of srcs) {
576
+ assert.ok(html.includes(`<img src="${src}"`), `${src}: ${html}`)
577
+ }
578
+ } finally {
579
+ await fs.rm(dir, { recursive: true, force: true })
580
+ }
581
+ })
582
+
583
+ test('injects head tags for a page that renders nothing', () => {
584
+ const html = renderPageHtml(
585
+ '<html><head></head><body><div id="root"></div></body></html>',
586
+ { content: '', page: {}, styles: ['/s.css'] }
587
+ )
588
+ assert.match(html, /<link rel="stylesheet" href="\/s.css"><\/head>/)
589
+ })
590
+
591
+ test('rebases relative asset URLs for nested pages', async () => {
592
+ const dir = path.join(__dirname, 'fixtures-vite-base')
593
+ try {
594
+ await writeIslandFixture(dir)
595
+
596
+ await viteBuild(dir, { base: './' })
597
+
598
+ for (const [page, prefix] of [
599
+ ['', './'],
600
+ ['docs', '../']
601
+ ]) {
602
+ const pageDir = path.join(dir, 'dist', page)
603
+ const html = await fs.readFile(
604
+ path.join(pageDir, 'index.html'),
605
+ 'utf-8'
606
+ )
607
+ for (const pattern of [
608
+ /<script type="module"[^>]* src="([^"]+)"/,
609
+ /<orga-island [^>]*src="([^"]+)"/,
610
+ ...(page ? [/<img src="([^"]+)"/] : [])
611
+ ]) {
612
+ const src = html.match(pattern)?.[1] ?? ''
613
+ assert.ok(
614
+ src.startsWith(`${prefix}assets/`),
615
+ `${page || '/'}: ${src}`
616
+ )
617
+ await fs.access(path.join(pageDir, src))
618
+ }
619
+ }
620
+ } finally {
621
+ await fs.rm(dir, { recursive: true, force: true })
622
+ }
623
+ })
624
+
625
+ test('resolves org links under base', async () => {
626
+ const dir = path.join(__dirname, 'fixtures-link-base')
627
+ try {
628
+ await fs.mkdir(path.join(dir, 'docs'), { recursive: true })
629
+ await fs.writeFile(
630
+ path.join(dir, 'index.org'),
631
+ '[[file:docs/a.org::*Part One][a]]\n'
632
+ )
633
+ await fs.writeFile(
634
+ path.join(dir, 'docs', 'a.org'),
635
+ '* Part One\n[[file:../index.org][home]]\n'
636
+ )
637
+
638
+ for (const [base, home, a] of [
639
+ ['/blog/', '/blog/', '/blog/docs/a#part-one'],
640
+ ['./', '../../', './docs/a#part-one']
641
+ ]) {
642
+ await viteBuild(dir, { base })
643
+ const read = (/** @type {string} */ page) =>
644
+ fs.readFile(path.join(dir, 'dist', page, 'index.html'), 'utf-8')
645
+ assert.ok((await read('')).includes(`href="${a}"`), base)
646
+ assert.ok((await read('docs/a')).includes(`href="${home}"`), base)
647
+ }
648
+ } finally {
649
+ await fs.rm(dir, { recursive: true, force: true })
650
+ }
651
+ })
233
652
  })
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dev.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.test.d.ts","sourceRoot":"","sources":["dev.test.js"],"names":[],"mappings":""}