orga-build 0.8.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 +296 -41
  2. package/cli.js +4 -4
  3. package/lib/__tests__/build.test.js +444 -23
  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 +12 -176
  14. package/lib/config.d.ts +9 -3
  15. package/lib/config.d.ts.map +1 -1
  16. package/lib/config.js +14 -18
  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 +10 -3
  25. package/lib/files.d.ts.map +1 -1
  26. package/lib/files.js +111 -23
  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 +25 -32
  45. package/lib/plugin.d.ts.map +1 -1
  46. package/lib/plugin.js +130 -170
  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 +7 -6
  57. package/lib/vite.d.ts.map +1 -1
  58. package/lib/vite.js +75 -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
package/README.org CHANGED
@@ -4,7 +4,9 @@ A simple tool that builds org-mode files into a website.
4
4
 
5
5
  * Architecture
6
6
 
7
- orga-build is built on top of Vite with a Vite-native architecture. The dev server uses Vite's native =createServer().listen()= pattern (no custom Express wrapper), which ensures maximum compatibility with the Vite ecosystem, including plugins like Cloudflare Workers.
7
+ orga-build is built on top of Vite (8+) with a Vite-native architecture. The dev server uses Vite's native =createServer().listen()= pattern (no custom Express wrapper), which ensures maximum compatibility with the Vite ecosystem, including plugins like Cloudflare Workers.
8
+
9
+ The build uses Vite's Environment API: =orgaBuildPlugin= configures an =ssr= and a =client= environment, and its =buildApp= hook builds both and prerenders every page and endpoint to static HTML. Pages ship no JavaScript unless they use an [[*Interactive Components (Islands)][island]]. The CLI is a thin wrapper, so =vite build= with the plugin produces the same site.
8
10
 
9
11
  ** Key Design Principles
10
12
 
@@ -31,30 +33,26 @@ export default {
31
33
 
32
34
  Note: Do NOT add =orgaBuildPlugin()= to =vitePlugins= - it's already included by the CLI. Only add external plugins.
33
35
 
34
- For advanced users integrating directly with Vite (without the orga-build CLI), use =orgaBuildPlugin= in your =vite.config.js=:
36
+ For advanced users integrating directly with Vite (without the orga-build CLI), use =orgaBuildPlugin= in your =vite.config.js=. =vite= then serves the site with SSR and =vite build= builds the static site:
35
37
 
36
38
  #+begin_src javascript
37
39
  // vite.config.js (advanced - direct Vite integration)
38
40
  import { cloudflare } from '@cloudflare/vite-plugin'
39
- import { orgaBuildPlugin, alias } from 'orga-build'
41
+ import { orgaBuildPlugin } from 'orga-build'
40
42
 
41
43
  export default {
42
44
  plugins: [
43
45
  cloudflare(),
44
- ...orgaBuildPlugin({ root: 'pages', containerClass: [] })
45
- ],
46
- resolve: { alias }
46
+ ...orgaBuildPlugin({ root: 'pages', outDir: 'out', containerClass: [] })
47
+ ]
47
48
  }
48
49
  #+end_src
49
50
 
50
51
  ** Default HTML Template
51
52
 
52
- If your project doesn't have an =index.html= file, orga-build provides a default template that:
53
- - Sets up React rendering
54
- - Enables client-side routing
55
- - Works in both dev and production builds
53
+ If your project doesn't have an =index.html= file, orga-build provides a default template: a bare HTML document with a =<div id="root">= that pages are rendered into.
56
54
 
57
- To customize the HTML shell, create your own =index.html= in your project root.
55
+ To customize the HTML shell, create your own =index.html= in your project root. Keep =<div id="root"></div>=; Vite processes the rest like any =index.html= (bundling linked scripts and stylesheets), and =%orga.<key>%= placeholders are filled from each page's exports (e.g. =%orga.title%=).
58
56
 
59
57
  * Installation
60
58
 
@@ -62,45 +60,64 @@ To customize the HTML shell, create your own =index.html= in your project root.
62
60
  npm install orga-build
63
61
  #+end_src
64
62
 
63
+ * Usage
64
+
65
+ #+begin_src bash
66
+ orga-build dev # dev server with live reload
67
+ orga-build # build the static site into outDir
68
+ orga-build --outDir dist # override outDir (also -o)
69
+ #+end_src
70
+
65
71
  * Configuration
66
72
 
67
- orga-build uses =orga.config.js= (or =orga.config.mjs=) as the primary configuration file. This file should be placed in your project root.
73
+ orga-build uses =orga.config.js= (or =orga.config.mjs=) as the primary configuration file. This file should be placed in your project root. It is optional; a config file that fails to load fails the command.
68
74
 
69
75
  #+begin_src javascript
70
76
  // orga.config.js
71
77
  export default {
72
- // Directory containing your .org files (default: 'pages')
78
+ // Directory containing your .org files (default: '.', the project root)
73
79
  root: 'pages',
74
80
 
75
- // Output directory for production build (default: 'out')
81
+ // Output directory for production build (default: '.out')
76
82
  outDir: 'out',
77
83
 
84
+ // Absolute URL the site is served from, for canonical URLs, feeds and
85
+ // sitemaps. Include the path if the site isn't at the domain root.
86
+ site: 'https://example.com',
87
+
78
88
  // CSS class(es) to wrap rendered org content
79
89
  containerClass: ['prose', 'prose-lg'],
80
90
 
81
- // Global stylesheet URLs (explicit, no magic files)
82
- // These are injected in dev SSR <head> and imported by the client entry.
83
- styles: ['/style.css'],
91
+ // Global stylesheets — paths relative to orga.config.js (leading / optional).
92
+ // Linked from the HTML shell; Vite processes them (with HMR in dev).
93
+ styles: ['pages/style.css'],
84
94
 
85
95
  // Extra rehype plugins appended to orga-build defaults
86
96
  // Useful for syntax highlighting (e.g. rehype-pretty-code).
87
97
  rehypePlugins: [],
88
98
 
89
99
  // Additional Vite plugins
90
- vitePlugins: []
100
+ vitePlugins: [],
101
+
102
+ // Glob patterns (relative to root) to exclude from content scanning.
103
+ // Useful for generated or declaration files that must live inside root
104
+ // but should not be treated as pages or endpoints.
105
+ exclude: ['**/*.d.ts']
91
106
  }
92
107
  #+end_src
93
108
 
94
109
  ** Configuration Options
95
110
 
96
- | Option | Type | Default | Description |
97
- |----------------+-------------------+---------+------------------------------------------------------|
98
- | =root= | =string= | ='pages'= | Directory containing content files |
99
- | =outDir= | =string= | ='out'= | Output directory for production build |
100
- | =containerClass= | =string \vert string[]= | =[]= | CSS class(es) for content wrapper |
101
- | =styles= | =string[]= | =[]= | Global stylesheet URLs injected/imported explicitly |
102
- | =rehypePlugins= | =PluggableList= | =[]= | Extra rehype plugins appended to orga-build defaults |
103
- | =vitePlugins= | =PluginOption[]= | =[]= | Additional Vite plugins |
111
+ | Option | Type | Default | Description |
112
+ |----------------+-------------------+---------+-----------------------------------------------------------------|
113
+ | =root= | =string= | ='.'= | Directory containing content files |
114
+ | =outDir= | =string= | ='.out'= | Output directory for production build |
115
+ | =site= | =string= | none | Absolute URL the site is served from, see [[*Site URL][Site URL]] |
116
+ | =containerClass= | =string \vert string[]= | =[]= | CSS class(es) for content wrapper |
117
+ | =styles= | =string[]= | =[]= | Stylesheets to link; paths relative to =orga.config.js= |
118
+ | =exclude= | =string[]= | =[]= | Glob patterns (relative to =root=) excluded from content scanning |
119
+ | =rehypePlugins= | =PluggableList= | =[]= | Extra rehype plugins appended to orga-build defaults |
120
+ | =vitePlugins= | =PluginOption[]= | =[]= | Additional Vite plugins |
104
121
 
105
122
  ** Syntax Highlighting Example
106
123
 
@@ -126,7 +143,7 @@ Page routes are discovered from =.org=, =.tsx=, and =.jsx= files.
126
143
 
127
144
  At build time, page routes are emitted as HTML:
128
145
 
129
- - =/about= -> =out/about/index.html=
146
+ - =/about= -> =.out/about/index.html=
130
147
 
131
148
  ** Endpoint Routes
132
149
 
@@ -146,13 +163,65 @@ export async function GET(ctx) {
146
163
  }
147
164
  #+end_src
148
165
 
166
+ =ctx= has:
167
+
168
+ - =url=: the request URL. At build time it is =site + route= (or =http://localhost= + route without =site=).
169
+ - =site=: the configured =site=, see [[*Site URL][Site URL]]
170
+ - =mode=: ='dev'= or ='build'=
171
+ - =route.route=: the route, e.g. ='/rss.xml'=
172
+
149
173
  At build time, endpoint routes are emitted to exact filenames:
150
174
 
151
- - =/rss.xml= -> =out/rss.xml=
152
- - =/api/data.json= -> =out/api/data.json=
175
+ - =/rss.xml= -> =.out/rss.xml=
176
+ - =/api/data.json= -> =.out/api/data.json=
153
177
 
154
178
  Route conflicts (same final route path) fail fast during dev/build startup.
155
179
 
180
+ ** Links
181
+
182
+ Links to =.org= files point at their pages, under Vite's =base=. Every heading gets an =id=: its =CUSTOM_ID= property when set, otherwise a slug of its text (=* Getting Started= → =#getting-started=).
183
+
184
+ | Link | Goes to |
185
+ |----------------------------------------+------------------------------|
186
+ | =[[file:guide.org]]= | =/guide= |
187
+ | =[[file:guide.org::*Getting Started]]= | =/guide#getting-started= |
188
+ | =[[file:guide.org::#install]]= | =/guide#install= |
189
+ | =[[*Getting Started]]= | heading on the same page |
190
+ | =[[#install]]= | =CUSTOM_ID= on the same page |
191
+
192
+ A =::*Heading= link to another file assumes that heading's id is the slug of its text. If the heading has a =CUSTOM_ID=, link to it with =::#id= instead. =id:= links aren't supported yet.
193
+
194
+ * Interactive Components (Islands)
195
+
196
+ Pages are static HTML. To run a component in the browser, start its module with React's ='use client'= directive, like in any React framework:
197
+
198
+ #+begin_src tsx
199
+ // _components.tsx
200
+ 'use client'
201
+ import { useState } from 'react'
202
+
203
+ export function Counter({ start }: { start: number }) {
204
+ const [count, setCount] = useState(start)
205
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
206
+ }
207
+ #+end_src
208
+
209
+ #+begin_example
210
+ #+jsx: <Counter start={1} />
211
+ #+end_example
212
+
213
+ The component is still prerendered. On pages that use it, the browser imports just that module (plus a shared React chunk) and hydrates it in place; every other page ships no JavaScript. Only components become islands, recognized by their PascalCase name (or the default export); hooks and other exports of a ='use client'= module stay plain values. A page file (=.tsx=) can start with ='use client'= too, which makes the whole page an island.
214
+
215
+ Props cross to the browser as JSON, so they must be serializable: no functions and no =children=. Components rendered /inside/ an island are already client code and have no such limit.
216
+
217
+ An island is wrapped in an =<orga-island>= element, so it can't sit where HTML allows only specific children, such as directly inside =<table>=, =<tbody>=, =<tr>=, =<select>= or =<ul>=. Make the island the whole table or list instead.
218
+
219
+ Islands hydrate as soon as the page loads. Pass =client="visible"= to defer hydration until the island scrolls into view, which also defers loading React:
220
+
221
+ #+begin_example
222
+ #+jsx: <Counter start={1} client="visible" />
223
+ #+end_example
224
+
156
225
  * TypeScript Setup
157
226
 
158
227
  If you're using TypeScript and want type support for the =orga-build:content= virtual module, you need to add a reference to the type definitions.
@@ -218,9 +287,9 @@ const writing = getPages('writing')
218
287
  // Get entries in a nested path
219
288
  const posts2025 = getPages('content/writing/2025')
220
289
 
221
- // Filter out drafts
222
- const published = getPages('writing', (entry) => {
223
- return entry.data['draft'] !== 'true'
290
+ // Filter by metadata
291
+ const featured = getPages('writing', (entry) => {
292
+ return entry.data['featured'] === 'true'
224
293
  })
225
294
  #+end_src
226
295
 
@@ -262,6 +331,10 @@ const entries = getEntries([
262
331
  ])
263
332
  #+end_src
264
333
 
334
+ *** =site=
335
+
336
+ The configured =site=, without a trailing slash, or =undefined=. See [[*Site URL][Site URL]].
337
+
265
338
  *** Aliases
266
339
 
267
340
  For Astro familiarity:
@@ -279,7 +352,7 @@ interface ContentEntry {
279
352
  path: string // e.g., 'writing' or 'content/writing/2025'
280
353
  filePath: string // absolute source file path
281
354
  ext: 'org' | 'tsx' | 'jsx' // file extension
282
- data: Record<string, unknown> // metadata from org headers
355
+ data: Record<string, unknown> // org keywords, or TSX/JSX named exports
283
356
  }
284
357
  #+end_src
285
358
 
@@ -305,7 +378,48 @@ This becomes:
305
378
  }
306
379
  #+end_src
307
380
 
308
- For =.tsx= and =.jsx= files, the =data= field is currently empty in v1.
381
+ For =.tsx= and =.jsx= files, =data= holds the page's named exports whose values are literals: strings, numbers, booleans, =null=, templates without =${}=, and arrays of those. Other exports (functions, computed values) are skipped, since pages aren't run to read them.
382
+
383
+ #+begin_src tsx
384
+ export const title = 'About'
385
+ export const date = '2025-01-15'
386
+ export const tags = ['meta', 'site']
387
+
388
+ export default function About() {
389
+ return <h1>{title}</h1>
390
+ }
391
+ #+end_src
392
+
393
+ ** Drafts
394
+
395
+ Mark a page as a draft with =#+draft: t= (or =true=, =yes=), or in a TSX/JSX
396
+ page with =export const draft = true=. The dev server
397
+ serves drafts so you can preview them; the build leaves them out entirely: no
398
+ HTML, and no entry in =getPages=, so feeds, sitemaps, and index pages skip
399
+ them without filtering.
400
+
401
+ ** Site URL
402
+
403
+ Set =site= in the config to the absolute URL the site is served from. It is
404
+ exported from =orga-build:content= and passed to endpoints as =ctx.site=,
405
+ without a trailing slash, so a page's absolute URL is =site + page.slug=:
406
+
407
+ #+begin_src tsx
408
+ // _layout.tsx
409
+ import { site } from 'orga-build:content'
410
+
411
+ export default function Layout({ slug, children }) {
412
+ const share = 'https://bsky.app/intent/compose?text=' + encodeURIComponent(site + slug)
413
+ return (
414
+ <>
415
+ {children}
416
+ <a href={share}>Share</a>
417
+ </>
418
+ )
419
+ }
420
+ #+end_src
421
+
422
+ If the site is served under a path, include it: =site: 'https://example.com/blog'=.
309
423
 
310
424
  ** Path Matching Behavior
311
425
 
@@ -332,9 +446,7 @@ Path matching works hierarchically:
332
446
  import { getPages } from 'orga-build:content'
333
447
 
334
448
  export default function BlogIndex() {
335
- const posts = getPages('writing', (entry) => {
336
- return entry.data['draft'] !== 'true'
337
- }).sort((a, b) => {
449
+ const posts = getPages('writing').sort((a, b) => {
338
450
  // Sort by date descending
339
451
  return String(b.data['date']).localeCompare(String(a.data['date']))
340
452
  })
@@ -384,12 +496,155 @@ export default function Post({ slug }: { slug: string }) {
384
496
  }
385
497
  #+end_src
386
498
 
387
- * Development
499
+ * Feeds and Sitemaps
500
+
501
+ There is no built-in feed or sitemap. Write them as [[*Endpoint Routes][endpoints]] built on =getPages=, and adapt them to your site. Both need =site= for absolute URLs. Drafts are already left out of =getPages= in the build.
502
+
503
+ ** RSS Feed
504
+
505
+ This feed lists the pages under =writing/= that have a =#+date:=, newest first. It reads the =YYYY-MM-DD= part of the date, so both =2025-01-15= and =<2025-01-15 Wed>= work.
506
+
507
+ #+begin_src ts
508
+ // rss.xml.ts
509
+ import { getPages, site } from 'orga-build:content'
510
+
511
+ const escape = (value: unknown) =>
512
+ String(value).replace(/[<>&'"]/g, (c) => `&#${c.charCodeAt(0)};`)
513
+
514
+ export function GET() {
515
+ if (!site) throw new Error('Set "site" in orga.config.js to build the feed')
516
+
517
+ const posts = getPages('writing')
518
+ .flatMap((page) => {
519
+ const date = String(page.data['date'] ?? '').match(/\d{4}-\d{2}-\d{2}/)
520
+ return date ? [{ page, date: date[0] }] : []
521
+ })
522
+ .sort((a, b) => b.date.localeCompare(a.date))
523
+
524
+ const items = posts.map(({ page, date }) => {
525
+ const url = escape(site + page.slug)
526
+ return `
527
+ <item>
528
+ <title>${escape(page.data['title'] ?? page.id)}</title>
529
+ <link>${url}</link>
530
+ <guid>${url}</guid>
531
+ <pubDate>${new Date(date).toUTCString()}</pubDate>
532
+ </item>`
533
+ })
534
+
535
+ return new Response(
536
+ `<?xml version="1.0" encoding="UTF-8"?>
537
+ <rss version="2.0">
538
+ <channel>
539
+ <title>My Site</title>
540
+ <link>${escape(site + '/')}</link>
541
+ <description>Latest writing</description>${items.join('')}
542
+ </channel>
543
+ </rss>
544
+ `,
545
+ { headers: { 'content-type': 'application/xml; charset=utf-8' } }
546
+ )
547
+ }
548
+ #+end_src
549
+
550
+ Feed readers look for the feed in the page's =<head>=. Layouts render inside =<body>=, so add the link to the =<head>= of your own [[*Default HTML Template][index.html]] instead, with the feed's full URL (=site= + =/rss.xml=):
551
+
552
+ #+begin_src html
553
+ <link rel="alternate" type="application/rss+xml" href="https://example.com/rss.xml" />
554
+ #+end_src
555
+
556
+ ** Sitemap
557
+
558
+ #+begin_src ts
559
+ // sitemap.xml.ts
560
+ import { getPages, site } from 'orga-build:content'
561
+
562
+ const escape = (value: unknown) =>
563
+ String(value).replace(/[<>&'"]/g, (c) => `&#${c.charCodeAt(0)};`)
564
+
565
+ export function GET() {
566
+ if (!site) throw new Error('Set "site" in orga.config.js to build the sitemap')
567
+
568
+ const urls = getPages().map(
569
+ (page) => `
570
+ <url><loc>${escape(site + page.slug)}</loc></url>`
571
+ )
572
+
573
+ return new Response(
574
+ `<?xml version="1.0" encoding="UTF-8"?>
575
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls.join('')}
576
+ </urlset>
577
+ `,
578
+ { headers: { 'content-type': 'application/xml; charset=utf-8' } }
579
+ )
580
+ }
581
+ #+end_src
582
+
583
+ ** robots.txt
584
+
585
+ A small endpoint, which can also point crawlers at the sitemap:
586
+
587
+ #+begin_src ts
588
+ // robots.txt.ts
589
+ import { site } from 'orga-build:content'
590
+
591
+ export function GET() {
592
+ if (!site) throw new Error('Set "site" in orga.config.js to build robots.txt')
593
+ return new Response(`User-agent: *\nAllow: /\nSitemap: ${site}/sitemap.xml\n`)
594
+ }
595
+ #+end_src
596
+
597
+ * Search with Pagefind
598
+
599
+ The build output is plain static HTML, so [[https://pagefind.app][Pagefind]] can index it without any orga-build integration. Install it and run it on =outDir= (default =.out=) after each build:
600
+
601
+ #+begin_src bash
602
+ npm install -D pagefind
603
+ #+end_src
604
+
605
+ #+begin_src json
606
+ {
607
+ "scripts": {
608
+ "build": "orga-build && pagefind --site .out"
609
+ }
610
+ }
611
+ #+end_src
612
+
613
+ Pagefind writes its index and UI to =.out/pagefind/=. Pagefind already skips =<nav>= and =<footer>= elements. To also leave out headers, sidebars and other page chrome, mark the content area with =data-pagefind-body= in your root =_layout.jsx=, and Pagefind indexes only that. Then add the search UI from the same layout. It is made of web components, so it doesn't need an island:
614
+
615
+ #+begin_src jsx
616
+ // pages/_layout.jsx
617
+ export default function Layout({ children }) {
618
+ return (
619
+ <>
620
+ <header>
621
+ <pagefind-modal-trigger></pagefind-modal-trigger>
622
+ <pagefind-modal></pagefind-modal>
623
+ </header>
624
+ <main data-pagefind-body>{children}</main>
625
+ <link href="/pagefind/pagefind-component-ui.css" rel="stylesheet" />
626
+ <script src="/pagefind/pagefind-component-ui.js" type="module"></script>
627
+ </>
628
+ )
629
+ }
630
+ #+end_src
631
+
632
+ Pages without =data-pagefind-body= are left out of the index, so put it in a layout that wraps every page you want searchable.
633
+
634
+ The index only exists after a build, so search doesn't work in =orga-build dev=. To try it locally, run =npx pagefind --site .out --serve= after building.
388
635
 
389
- ** TODO Items
636
+ In a =_layout.tsx=, declare the elements so TypeScript accepts them:
390
637
 
391
- - resolve relative path in links and images
392
- - monitor file changes and cache properly
638
+ #+begin_src tsx
639
+ declare module 'react' {
640
+ namespace JSX {
641
+ interface IntrinsicElements {
642
+ 'pagefind-modal-trigger': React.HTMLAttributes<HTMLElement>
643
+ 'pagefind-modal': React.HTMLAttributes<HTMLElement>
644
+ }
645
+ }
646
+ }
647
+ #+end_src
393
648
 
394
649
  * License
395
650
 
package/cli.js CHANGED
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import path from 'node:path'
3
4
  import { argv } from 'node:process'
4
5
  import { parseArgs } from 'node:util'
5
6
  import { build } from './lib/build.js'
6
7
  import { loadConfig } from './lib/config.js'
7
8
  import { serve } from './lib/serve.js'
8
9
 
9
- const { positionals } = parseArgs({
10
+ const { values, positionals } = parseArgs({
10
11
  args: argv.slice(2),
11
12
  options: {
12
- watch: { type: 'boolean', short: 'w' },
13
- outDir: { type: 'string', short: 'o', default: '.out' }
13
+ outDir: { type: 'string', short: 'o' }
14
14
  },
15
- tokens: true,
16
15
  allowPositionals: true
17
16
  })
18
17
 
@@ -20,6 +19,7 @@ const { config, projectRoot } = await loadConfig(
20
19
  'orga.config.js',
21
20
  'orga.config.mjs'
22
21
  )
22
+ if (values.outDir) config.outDir = path.resolve(values.outDir)
23
23
 
24
24
  await (positionals.includes('dev')
25
25
  ? serve(config, 3000, projectRoot)