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.
- package/README.org +296 -41
- package/cli.js +4 -4
- package/lib/__tests__/build.test.js +444 -23
- package/lib/__tests__/dev.test.d.ts +2 -0
- package/lib/__tests__/dev.test.d.ts.map +1 -0
- package/lib/__tests__/dev.test.js +295 -0
- package/lib/__tests__/fixtures.d.ts +8 -0
- package/lib/__tests__/fixtures.d.ts.map +1 -0
- package/lib/__tests__/fixtures.js +61 -0
- package/lib/app.jsx +23 -50
- package/lib/build.d.ts +4 -1
- package/lib/build.d.ts.map +1 -1
- package/lib/build.js +12 -176
- package/lib/config.d.ts +9 -3
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +14 -18
- package/lib/content.d.ts +6 -0
- package/lib/dev-ssr.d.ts +14 -0
- package/lib/dev-ssr.d.ts.map +1 -0
- package/lib/dev-ssr.js +137 -0
- package/lib/endpoint.d.ts +5 -0
- package/lib/endpoint.d.ts.map +1 -1
- package/lib/endpoint.js +1 -0
- package/lib/files.d.ts +10 -3
- package/lib/files.d.ts.map +1 -1
- package/lib/files.js +111 -23
- package/lib/fs.d.ts +0 -5
- package/lib/fs.d.ts.map +1 -1
- package/lib/fs.js +0 -18
- package/lib/html.d.ts +37 -0
- package/lib/html.d.ts.map +1 -0
- package/lib/html.js +101 -0
- package/lib/index.html +0 -1
- package/lib/island-client.d.ts +5 -0
- package/lib/island-client.d.ts.map +1 -0
- package/lib/island-client.js +41 -0
- package/lib/island.d.ts +18 -0
- package/lib/island.d.ts.map +1 -0
- package/lib/island.js +146 -0
- package/lib/island.jsx +108 -0
- package/lib/orga.d.ts +1 -1
- package/lib/orga.d.ts.map +1 -1
- package/lib/orga.js +54 -16
- package/lib/plugin.d.ts +25 -32
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +130 -170
- package/lib/prerender.d.ts +11 -0
- package/lib/prerender.d.ts.map +1 -0
- package/lib/prerender.js +184 -0
- package/lib/serve.d.ts.map +1 -1
- package/lib/serve.js +5 -23
- package/lib/ssr.jsx +9 -14
- package/lib/util.d.ts +0 -22
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +0 -62
- package/lib/vite.d.ts +7 -6
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +75 -23
- package/package.json +5 -8
- package/lib/components.d.ts +0 -2
- package/lib/components.d.ts.map +0 -1
- package/lib/components.js +0 -1
- package/lib/csr.jsx +0 -11
- package/lib/watch.d.ts +0 -10
- package/lib/watch.d.ts.map +0 -1
- package/lib/watch.js +0 -53
package/lib/config.d.ts
CHANGED
|
@@ -9,20 +9,26 @@ export function loadConfig(...files: string[]): Promise<{
|
|
|
9
9
|
export type Config = {
|
|
10
10
|
outDir: string;
|
|
11
11
|
root: string;
|
|
12
|
-
preBuild: string[];
|
|
13
|
-
postBuild: string[];
|
|
14
12
|
/**
|
|
15
13
|
* - Array of Vite plugins
|
|
16
14
|
*/
|
|
17
15
|
vitePlugins: import("vite").PluginOption[];
|
|
18
16
|
containerClass: string[] | string;
|
|
19
17
|
/**
|
|
20
|
-
* - Global stylesheet URLs
|
|
18
|
+
* - Global stylesheet URLs linked from the HTML shell
|
|
21
19
|
*/
|
|
22
20
|
styles?: string[];
|
|
23
21
|
/**
|
|
24
22
|
* - Extra rehype plugins appended to orga-build defaults
|
|
25
23
|
*/
|
|
26
24
|
rehypePlugins?: import("unified").PluggableList;
|
|
25
|
+
/**
|
|
26
|
+
* - Glob patterns for files to exclude from content scanning
|
|
27
|
+
*/
|
|
28
|
+
exclude?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* - Absolute URL the site is served from, e.g. `https://example.com`
|
|
31
|
+
*/
|
|
32
|
+
site?: string | undefined;
|
|
27
33
|
};
|
|
28
34
|
//# sourceMappingURL=config.d.ts.map
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.js"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,qCAHW,MAAM,EAAE,GACN,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CA4C5D;;YAnEa,MAAM;UACN,MAAM;;;;iBACN,OAAO,MAAM,EAAE,YAAY,EAAE;oBAC7B,MAAM,EAAE,GAAC,MAAM;;;;aACf,MAAM,EAAE;;;;oBACR,OAAO,SAAS,EAAE,aAAa;;;;cAC/B,MAAM,EAAE;;;;WACR,MAAM,GAAG,SAAS"}
|
package/lib/config.js
CHANGED
|
@@ -5,24 +5,23 @@ import path from 'node:path'
|
|
|
5
5
|
* @typedef {Object} Config
|
|
6
6
|
* @property {string} outDir
|
|
7
7
|
* @property {string} root
|
|
8
|
-
* @property {string[]} preBuild
|
|
9
|
-
* @property {string[]} postBuild
|
|
10
8
|
* @property {import('vite').PluginOption[]} vitePlugins - Array of Vite plugins
|
|
11
9
|
* @property {string[]|string} containerClass
|
|
12
|
-
* @property {string[]} [styles] - Global stylesheet URLs
|
|
10
|
+
* @property {string[]} [styles] - Global stylesheet URLs linked from the HTML shell
|
|
13
11
|
* @property {import('unified').PluggableList} [rehypePlugins] - Extra rehype plugins appended to orga-build defaults
|
|
12
|
+
* @property {string[]} [exclude] - Glob patterns for files to exclude from content scanning
|
|
13
|
+
* @property {string | undefined} [site] - Absolute URL the site is served from, e.g. `https://example.com`
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/** @type {Config} */
|
|
17
17
|
const defaultConfig = {
|
|
18
18
|
outDir: '.out',
|
|
19
19
|
root: '.',
|
|
20
|
-
preBuild: [],
|
|
21
|
-
postBuild: [],
|
|
22
20
|
vitePlugins: [],
|
|
23
21
|
containerClass: [],
|
|
24
22
|
styles: [],
|
|
25
|
-
rehypePlugins: []
|
|
23
|
+
rehypePlugins: [],
|
|
24
|
+
exclude: []
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
@@ -51,24 +50,21 @@ export async function loadConfig(...files) {
|
|
|
51
50
|
continue
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
break
|
|
61
|
-
} catch (err) {
|
|
62
|
-
// Config file exists but has errors
|
|
63
|
-
console.error(`Error loading config from ${file}:`, err)
|
|
64
|
-
}
|
|
53
|
+
// A broken config file fails the command rather than falling back to defaults.
|
|
54
|
+
const module = await import(filePath)
|
|
55
|
+
// Support both default export (recommended) and named exports
|
|
56
|
+
result = { ...defaultConfig, ...(module.default || module) }
|
|
57
|
+
configPath = filePath
|
|
58
|
+
break
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
result.root = resolveConfigPath(result.root)
|
|
68
62
|
result.outDir = resolveConfigPath(result.outDir)
|
|
69
63
|
const styles = result.styles
|
|
70
64
|
result.styles = Array.isArray(styles)
|
|
71
|
-
? styles
|
|
65
|
+
? styles
|
|
66
|
+
.filter((v) => typeof v === 'string')
|
|
67
|
+
.map((v) => '/' + v.replace(/^\/+/, ''))
|
|
72
68
|
: []
|
|
73
69
|
return {
|
|
74
70
|
config: result,
|
package/lib/content.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ declare module 'orga-build:content' {
|
|
|
8
8
|
data: Record<string, unknown>
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The configured `site` (absolute URL, no trailing slash), or `undefined`.
|
|
13
|
+
* A page's absolute URL is `site + page.slug`.
|
|
14
|
+
*/
|
|
15
|
+
export const site: string | undefined
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
18
|
* Get all content entries matching a path pattern
|
|
13
19
|
* @param path - Optional path prefix to filter by (e.g., 'writing', 'content/writing/2025')
|
package/lib/dev-ssr.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serves pages and endpoints in dev, rendering each request on the server
|
|
3
|
+
* (matching Astro/SvelteKit behaviour):
|
|
4
|
+
* - Endpoint routes are answered from their `GET`/`HEAD` handlers
|
|
5
|
+
* - Pages are SSR-rendered through the `ssr` environment's module runner, so
|
|
6
|
+
* edits are picked up without restarting
|
|
7
|
+
* - Unknown routes fall through to Vite, which answers 404 like the build
|
|
8
|
+
* - Only GET/HEAD requests that accept HTML are rendered; assets pass through
|
|
9
|
+
*
|
|
10
|
+
* @param {string | undefined} site - Absolute site URL, passed to endpoints
|
|
11
|
+
* @returns {import('vite').Plugin}
|
|
12
|
+
*/
|
|
13
|
+
export function devSsrPlugin(site: string | undefined): import("vite").Plugin;
|
|
14
|
+
//# sourceMappingURL=dev-ssr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-ssr.d.ts","sourceRoot":"","sources":["dev-ssr.js"],"names":[],"mappings":"AASA;;;;;;;;;;;GAWG;AACH,mCAHW,MAAM,GAAG,SAAS,GAChB,OAAO,MAAM,EAAE,MAAM,CAqHjC"}
|
package/lib/dev-ssr.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { isCSSRequest, isRunnableDevEnvironment, normalizePath } from 'vite'
|
|
4
|
+
import { resolveEndpointResponse } from './endpoint.js'
|
|
5
|
+
import { readIndexHtml, renderPageHtml } from './html.js'
|
|
6
|
+
import { islandRuntimeId } from './vite.js'
|
|
7
|
+
|
|
8
|
+
const ssrEntry = fileURLToPath(new URL('./ssr.jsx', import.meta.url))
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serves pages and endpoints in dev, rendering each request on the server
|
|
12
|
+
* (matching Astro/SvelteKit behaviour):
|
|
13
|
+
* - Endpoint routes are answered from their `GET`/`HEAD` handlers
|
|
14
|
+
* - Pages are SSR-rendered through the `ssr` environment's module runner, so
|
|
15
|
+
* edits are picked up without restarting
|
|
16
|
+
* - Unknown routes fall through to Vite, which answers 404 like the build
|
|
17
|
+
* - Only GET/HEAD requests that accept HTML are rendered; assets pass through
|
|
18
|
+
*
|
|
19
|
+
* @param {string | undefined} site - Absolute site URL, passed to endpoints
|
|
20
|
+
* @returns {import('vite').Plugin}
|
|
21
|
+
*/
|
|
22
|
+
export function devSsrPlugin(site) {
|
|
23
|
+
return {
|
|
24
|
+
name: 'orga-build:dev-ssr',
|
|
25
|
+
// Run before other plugins' middlewares (e.g. Cloudflare) so HTML
|
|
26
|
+
// navigation requests are not answered with a 404 first.
|
|
27
|
+
enforce: 'pre',
|
|
28
|
+
|
|
29
|
+
configureServer(server) {
|
|
30
|
+
const ssr = server.environments.ssr
|
|
31
|
+
if (!isRunnableDevEnvironment(ssr)) {
|
|
32
|
+
server.config.logger.warn(
|
|
33
|
+
'[orga-build] the "ssr" environment is not runnable in this process; dev SSR is disabled'
|
|
34
|
+
)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The browser imports islands straight from source, served by Vite
|
|
39
|
+
// under its `base`. The shell's own URLs are rebased by Vite in
|
|
40
|
+
// `transformIndexHtml`; these are injected afterwards.
|
|
41
|
+
const { root, base } = server.config
|
|
42
|
+
/** @param {string} src */
|
|
43
|
+
const islandUrl = (src) =>
|
|
44
|
+
// Outside the root (`..`, or another drive on Windows): Vite's /@fs/ form.
|
|
45
|
+
src.startsWith('..') || path.isAbsolute(src)
|
|
46
|
+
? `${base}@fs/${normalizePath(path.resolve(root, src)).replace(/^\//, '')}`
|
|
47
|
+
: base + src
|
|
48
|
+
const islandScript = base + islandRuntimeId.slice(1)
|
|
49
|
+
// CSS imported by server-rendered code never reaches the browser's
|
|
50
|
+
// module graph: link each file, which Vite serves as plain CSS.
|
|
51
|
+
const styles = () =>
|
|
52
|
+
[...ssr.moduleGraph.idToModuleMap.keys()]
|
|
53
|
+
.filter(
|
|
54
|
+
(id) => isCSSRequest(id) && path.isAbsolute(id) && !id.includes('?')
|
|
55
|
+
)
|
|
56
|
+
.map((id) => islandUrl(normalizePath(path.relative(root, id))))
|
|
57
|
+
|
|
58
|
+
server.middlewares.use(async (req, res, next) => {
|
|
59
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
60
|
+
return next()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// This runs before Vite's own middlewares, so `base` is still in the URL.
|
|
64
|
+
const url = req.url || '/'
|
|
65
|
+
const requestPath = url.split('?')[0]
|
|
66
|
+
if (!requestPath.startsWith(base)) return next()
|
|
67
|
+
// Directory-style URLs (`/docs/`) are the same page as `/docs`.
|
|
68
|
+
const pathname = requestPath
|
|
69
|
+
.slice(base.length - 1)
|
|
70
|
+
.replace(/(.)\/+$/, '$1')
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// The runner follows the module graph, so stale modules are never served.
|
|
74
|
+
const { render, pages, endpoints } = await ssr.runner.import(ssrEntry)
|
|
75
|
+
|
|
76
|
+
// Endpoint routes are handled first and bypass HTML rendering.
|
|
77
|
+
const endpointModule = endpoints?.[pathname]
|
|
78
|
+
if (endpointModule) {
|
|
79
|
+
const ctx = {
|
|
80
|
+
url: new URL(url, `http://${req.headers.host || 'localhost'}`),
|
|
81
|
+
params: {},
|
|
82
|
+
mode: /** @type {'dev'} */ ('dev'),
|
|
83
|
+
route: { route: pathname },
|
|
84
|
+
site
|
|
85
|
+
}
|
|
86
|
+
const response = await resolveEndpointResponse(
|
|
87
|
+
endpointModule,
|
|
88
|
+
ctx,
|
|
89
|
+
req.method
|
|
90
|
+
)
|
|
91
|
+
res.statusCode = response.status
|
|
92
|
+
response.headers.forEach((headerValue, headerName) => {
|
|
93
|
+
res.setHeader(headerName, headerValue)
|
|
94
|
+
})
|
|
95
|
+
if (req.method === 'HEAD') {
|
|
96
|
+
res.end()
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
res.end(Buffer.from(await response.arrayBuffer()))
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Only handle browser-like navigation requests.
|
|
104
|
+
// Don't match generic */* accepts to avoid hijacking API requests.
|
|
105
|
+
const accept = req.headers.accept || ''
|
|
106
|
+
if (!accept.includes('text/html')) {
|
|
107
|
+
return next()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Don't intercept asset requests (files with extensions)
|
|
111
|
+
if (pathname !== '/' && /\.\w+$/.test(pathname)) {
|
|
112
|
+
return next()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!pages[pathname]) return next()
|
|
116
|
+
|
|
117
|
+
const template = await server.transformIndexHtml(
|
|
118
|
+
url,
|
|
119
|
+
await readIndexHtml(server.config.root)
|
|
120
|
+
)
|
|
121
|
+
const html = renderPageHtml(template, {
|
|
122
|
+
content: render(pathname, islandUrl),
|
|
123
|
+
page: pages[pathname],
|
|
124
|
+
islandScript,
|
|
125
|
+
styles: styles()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
res.statusCode = 200
|
|
129
|
+
res.setHeader('Content-Type', 'text/html')
|
|
130
|
+
res.end(html)
|
|
131
|
+
} catch (e) {
|
|
132
|
+
next(e)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @property {Record<string, string>} params
|
|
5
5
|
* @property {'dev' | 'build'} mode
|
|
6
6
|
* @property {{ route: string }} route
|
|
7
|
+
* @property {string | undefined} site - The configured `site`, without a trailing slash
|
|
7
8
|
*/
|
|
8
9
|
/**
|
|
9
10
|
* @param {Record<string, any>} endpointModule
|
|
@@ -19,5 +20,9 @@ export type EndpointContext = {
|
|
|
19
20
|
route: {
|
|
20
21
|
route: string;
|
|
21
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* - The configured `site`, without a trailing slash
|
|
25
|
+
*/
|
|
26
|
+
site: string | undefined;
|
|
22
27
|
};
|
|
23
28
|
//# sourceMappingURL=endpoint.d.ts.map
|
package/lib/endpoint.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["endpoint.js"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["endpoint.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wDALW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,OACnB,eAAe,WACf,MAAM,GACJ,OAAO,CAAC,QAAQ,CAAC,CAoC7B;;SA/Ca,GAAG;YACH,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;UACtB,KAAK,GAAG,OAAO;WACf;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE;;;;UACjB,MAAM,GAAG,SAAS"}
|
package/lib/endpoint.js
CHANGED
package/lib/files.d.ts
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
* @param {string} dir
|
|
3
3
|
* @param {object} [options]
|
|
4
4
|
* @param {string} [options.outDir] - Output directory to exclude from file discovery
|
|
5
|
+
* @param {string[]} [options.exclude] - Additional glob patterns to exclude from file discovery
|
|
6
|
+
* @param {boolean} [options.drafts] - Include pages marked `#+draft:` (for previews in dev)
|
|
5
7
|
*/
|
|
6
|
-
export function setup(dir: string, { outDir }?: {
|
|
8
|
+
export function setup(dir: string, { outDir, exclude, drafts }?: {
|
|
7
9
|
outDir?: string | undefined;
|
|
10
|
+
exclude?: string[] | undefined;
|
|
11
|
+
drafts?: boolean | undefined;
|
|
8
12
|
}): {
|
|
9
13
|
pages: (() => Promise<Record<string, Page>>) & {
|
|
10
14
|
invalidate: () => void;
|
|
@@ -31,11 +35,14 @@ export function setup(dir: string, { outDir }?: {
|
|
|
31
35
|
*/
|
|
32
36
|
export function getSlugFromContentFilePath(contentFilePath: string): string;
|
|
33
37
|
export type Page = {
|
|
38
|
+
/**
|
|
39
|
+
* Path to the page data file
|
|
40
|
+
*/
|
|
34
41
|
dataPath: string;
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
43
|
+
* Metadata: an org page's keywords, or a TSX/JSX page's literal named exports
|
|
37
44
|
*/
|
|
38
|
-
|
|
45
|
+
data: Record<string, unknown>;
|
|
39
46
|
};
|
|
40
47
|
export type EndpointRoute = {
|
|
41
48
|
route: string;
|
package/lib/files.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["files.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["files.js"],"names":[],"mappings":"AAsFA;;;;;;GAMG;AACH,2BANW,MAAM,gCAEd;IAAyB,MAAM;IACJ,OAAO;IACR,MAAM;CAClC;;0BA8QqD,IAAI;;iBAlH7C,MAAM;;0BAkHmC,IAAI;;sBA5G7C,MAAM;;0BA4GmC,IAAI;;;0BAAJ,IAAI;;;0BAAJ,IAAI;;;EAvGzD;AA4HD;;;GAGG;AACH,4DAFW,MAAM,UAoBhB;;;;;cA7Ya,MAAM;;;;UAEN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;WAMvB,MAAM;cACN,MAAM;;;QAKN,MAAM;UACN,MAAM;UACN,MAAM;cACN,MAAM;SACN,KAAK,GAAG,KAAK,GAAG,KAAK;UACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
|
package/lib/files.js
CHANGED
|
@@ -2,12 +2,14 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { globby } from 'globby'
|
|
4
4
|
import { getSettings } from 'orga'
|
|
5
|
+
import { parseSync } from 'vite'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {Object} Page
|
|
8
9
|
* @property {string} dataPath
|
|
9
|
-
* @property {string} [title]
|
|
10
10
|
* Path to the page data file
|
|
11
|
+
* @property {Record<string, unknown>} data
|
|
12
|
+
* Metadata: an org page's keywords, or a TSX/JSX page's literal named exports
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -86,8 +88,10 @@ function getContentId(slug) {
|
|
|
86
88
|
* @param {string} dir
|
|
87
89
|
* @param {object} [options]
|
|
88
90
|
* @param {string} [options.outDir] - Output directory to exclude from file discovery
|
|
91
|
+
* @param {string[]} [options.exclude] - Additional glob patterns to exclude from file discovery
|
|
92
|
+
* @param {boolean} [options.drafts] - Include pages marked `#+draft:` (for previews in dev)
|
|
89
93
|
*/
|
|
90
|
-
export function setup(dir, { outDir } = {}) {
|
|
94
|
+
export function setup(dir, { outDir, exclude = [], drafts = false } = {}) {
|
|
91
95
|
const outDirRelative = outDir ? path.relative(dir, outDir) : null
|
|
92
96
|
// Only exclude outDir if it's inside the root (not an external path like ../out)
|
|
93
97
|
const outDirExclude =
|
|
@@ -104,7 +108,8 @@ export function setup(dir, { outDir } = {}) {
|
|
|
104
108
|
'!**/.*/**',
|
|
105
109
|
'!**/.*',
|
|
106
110
|
'!node_modules/**',
|
|
107
|
-
...(outDirExclude ? [outDirExclude] : [])
|
|
111
|
+
...(outDirExclude ? [outDirExclude] : []),
|
|
112
|
+
...exclude.map((p) => `!${p}`)
|
|
108
113
|
],
|
|
109
114
|
{ cwd: dir }
|
|
110
115
|
)
|
|
@@ -123,7 +128,10 @@ export function setup(dir, { outDir } = {}) {
|
|
|
123
128
|
|
|
124
129
|
if (pageSlug) {
|
|
125
130
|
assertUniqueRoute(routeOwners, pageSlug, 'page', absolutePath)
|
|
126
|
-
|
|
131
|
+
const data = await readMetadata(absolutePath)
|
|
132
|
+
if (drafts || !isDraft(data)) {
|
|
133
|
+
pages[pageSlug] = { dataPath: absolutePath, data }
|
|
134
|
+
}
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
if (endpointRoute) {
|
|
@@ -155,7 +163,8 @@ export function setup(dir, { outDir } = {}) {
|
|
|
155
163
|
'!**/.*/**',
|
|
156
164
|
'!**/.*',
|
|
157
165
|
'!node_modules/**',
|
|
158
|
-
...(outDirExclude ? [outDirExclude] : [])
|
|
166
|
+
...(outDirExclude ? [outDirExclude] : []),
|
|
167
|
+
...exclude.map((p) => `!${p}`)
|
|
159
168
|
],
|
|
160
169
|
{
|
|
161
170
|
cwd: dir
|
|
@@ -179,7 +188,8 @@ export function setup(dir, { outDir } = {}) {
|
|
|
179
188
|
'!**/.*/**',
|
|
180
189
|
'!**/.*',
|
|
181
190
|
'!node_modules/**',
|
|
182
|
-
...(outDirExclude ? [outDirExclude] : [])
|
|
191
|
+
...(outDirExclude ? [outDirExclude] : []),
|
|
192
|
+
...exclude.map((p) => `!${p}`)
|
|
183
193
|
],
|
|
184
194
|
{
|
|
185
195
|
cwd: dir
|
|
@@ -203,29 +213,13 @@ export function setup(dir, { outDir } = {}) {
|
|
|
203
213
|
// Derive id from the slug (last segment or 'index')
|
|
204
214
|
const id = getContentId(slug)
|
|
205
215
|
|
|
206
|
-
/** @type {Record<string, unknown>} */
|
|
207
|
-
let data = {}
|
|
208
|
-
|
|
209
|
-
// Extract metadata from .org files
|
|
210
|
-
if (ext === 'org') {
|
|
211
|
-
try {
|
|
212
|
-
const content = await readFile(filePath, 'utf-8')
|
|
213
|
-
data = getSettings(content)
|
|
214
|
-
} catch (/** @type {any} */ error) {
|
|
215
|
-
console.warn(
|
|
216
|
-
`Failed to read metadata from ${filePath}:`,
|
|
217
|
-
error?.message || error
|
|
218
|
-
)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
216
|
entries.push({
|
|
223
217
|
id,
|
|
224
218
|
slug,
|
|
225
219
|
path: derivedPath,
|
|
226
220
|
filePath,
|
|
227
221
|
ext,
|
|
228
|
-
data
|
|
222
|
+
data: pageData.data
|
|
229
223
|
})
|
|
230
224
|
}
|
|
231
225
|
|
|
@@ -265,6 +259,100 @@ export function setup(dir, { outDir } = {}) {
|
|
|
265
259
|
}
|
|
266
260
|
}
|
|
267
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Read a page's metadata: an org file's keywords, or the named exports of a
|
|
264
|
+
* TSX/JSX page whose values are literals (`export const title = 'Hi'`).
|
|
265
|
+
* @param {string} filePath
|
|
266
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
267
|
+
*/
|
|
268
|
+
async function readMetadata(filePath) {
|
|
269
|
+
try {
|
|
270
|
+
const text = await readFile(filePath, 'utf-8')
|
|
271
|
+
return filePath.endsWith('.org')
|
|
272
|
+
? getSettings(text)
|
|
273
|
+
: readLiteralExports(filePath, text)
|
|
274
|
+
} catch (/** @type {any} */ error) {
|
|
275
|
+
console.warn(
|
|
276
|
+
`Failed to read metadata from ${filePath}:`,
|
|
277
|
+
error?.message || error
|
|
278
|
+
)
|
|
279
|
+
return {}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Named exports whose values can be read without running the module. Others
|
|
285
|
+
* are skipped; Vite reports syntax errors when it builds the page.
|
|
286
|
+
* @param {string} filePath
|
|
287
|
+
* @param {string} text
|
|
288
|
+
*/
|
|
289
|
+
function readLiteralExports(filePath, text) {
|
|
290
|
+
const { program, errors } = parseSync(filePath, text)
|
|
291
|
+
/** @type {Record<string, unknown>} */
|
|
292
|
+
const data = {}
|
|
293
|
+
if (errors.length) return data
|
|
294
|
+
for (const node of program.body) {
|
|
295
|
+
if (
|
|
296
|
+
node.type !== 'ExportNamedDeclaration' ||
|
|
297
|
+
node.declaration?.type !== 'VariableDeclaration'
|
|
298
|
+
) {
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
for (const { id, init } of node.declaration.declarations) {
|
|
302
|
+
if (id.type !== 'Identifier' || !init) continue
|
|
303
|
+
const value = literalValue(init)
|
|
304
|
+
if (value !== notLiteral) data[id.name] = value
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return data
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const notLiteral = Symbol('notLiteral')
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The value of a literal expression: a string, number (signed too), boolean
|
|
314
|
+
* or `null`, a template without placeholders, or an array of those.
|
|
315
|
+
* @param {any} node
|
|
316
|
+
* @returns {unknown}
|
|
317
|
+
*/
|
|
318
|
+
function literalValue(node) {
|
|
319
|
+
switch (node.type) {
|
|
320
|
+
case 'Literal':
|
|
321
|
+
// Regexes and bigints have no JSON form.
|
|
322
|
+
return node.regex || node.bigint ? notLiteral : node.value
|
|
323
|
+
case 'TemplateLiteral':
|
|
324
|
+
return node.expressions.length ? notLiteral : node.quasis[0].value.cooked
|
|
325
|
+
case 'TSAsExpression':
|
|
326
|
+
case 'TSSatisfiesExpression':
|
|
327
|
+
return literalValue(node.expression)
|
|
328
|
+
case 'UnaryExpression': {
|
|
329
|
+
const value = literalValue(node.argument)
|
|
330
|
+
if (typeof value !== 'number') return notLiteral
|
|
331
|
+
return node.operator === '-'
|
|
332
|
+
? -value
|
|
333
|
+
: node.operator === '+'
|
|
334
|
+
? value
|
|
335
|
+
: notLiteral
|
|
336
|
+
}
|
|
337
|
+
case 'ArrayExpression': {
|
|
338
|
+
const values = node.elements.map((/** @type {any} */ element) =>
|
|
339
|
+
element ? literalValue(element) : notLiteral
|
|
340
|
+
)
|
|
341
|
+
return values.includes(notLiteral) ? notLiteral : values
|
|
342
|
+
}
|
|
343
|
+
default:
|
|
344
|
+
return notLiteral
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Whether a page is marked `#+draft: t` (or `true`, `yes`).
|
|
350
|
+
* @param {Record<string, unknown>} data
|
|
351
|
+
*/
|
|
352
|
+
function isDraft(data) {
|
|
353
|
+
return /^(t|true|yes)$/i.test(String(data.draft ?? '').trim())
|
|
354
|
+
}
|
|
355
|
+
|
|
268
356
|
/**
|
|
269
357
|
* Creates a cached version of an async function that will only execute once
|
|
270
358
|
* and return the cached result on subsequent calls. The returned function
|
package/lib/fs.d.ts
CHANGED
|
@@ -9,11 +9,6 @@ export function resolvePath(rootPath: string): string;
|
|
|
9
9
|
* @returns {Promise<boolean>}
|
|
10
10
|
*/
|
|
11
11
|
export function exists(path: string): Promise<boolean>;
|
|
12
|
-
/**
|
|
13
|
-
* @param {string} dir
|
|
14
|
-
* @returns {Promise<void>}
|
|
15
|
-
*/
|
|
16
|
-
export function emptyDir(dir: string): Promise<void>;
|
|
17
12
|
/**
|
|
18
13
|
* @param {string} path
|
|
19
14
|
*/
|
package/lib/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["fs.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,CAclB;AAED;;;GAGG;AACH,6BAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAS5B;AAED
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["fs.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,CAclB;AAED;;;GAGG;AACH,6BAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAS5B;AAED;;GAEG;AACH,gCAFW,MAAM,iBAUhB;AAED;;;GAGG;AACH,0BAHW,MAAM,QACN,MAAM,iBAKhB"}
|
package/lib/fs.js
CHANGED
|
@@ -33,24 +33,6 @@ export async function exists(path) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* @param {string} dir
|
|
38
|
-
* @returns {Promise<void>}
|
|
39
|
-
*/
|
|
40
|
-
export async function emptyDir(dir) {
|
|
41
|
-
/** @type {string[]} */
|
|
42
|
-
let items = []
|
|
43
|
-
try {
|
|
44
|
-
items = await fs.readdir(dir)
|
|
45
|
-
} catch {
|
|
46
|
-
await fs.mkdir(dir, { recursive: true })
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
await Promise.all(
|
|
50
|
-
items.map((item) => fs.rm(`${dir}/${item}`, { recursive: true }))
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
/**
|
|
55
37
|
* @param {string} path
|
|
56
38
|
*/
|
package/lib/html.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the HTML shell: the user's `index.html` in the Vite root, or the
|
|
3
|
+
* default one shipped with orga-build.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} root - Vite root
|
|
6
|
+
*/
|
|
7
|
+
export function readIndexHtml(root: string): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Makes `<root>/index.html` always loadable, falling back to the default shell,
|
|
10
|
+
* so Vite can use it as the client build entry. Vite then bundles its scripts
|
|
11
|
+
* and stylesheets and injects the hashed asset tags itself.
|
|
12
|
+
*
|
|
13
|
+
* Global styles are added as `<link>` tags, which Vite serves (with HMR) in
|
|
14
|
+
* dev and bundles in build.
|
|
15
|
+
*
|
|
16
|
+
* @param {string[]} [styles]
|
|
17
|
+
* @returns {import('vite').Plugin}
|
|
18
|
+
*/
|
|
19
|
+
export function htmlShellPlugin(styles?: string[]): import("vite").Plugin;
|
|
20
|
+
/**
|
|
21
|
+
* Fill a processed HTML shell with a server-rendered page. The island runtime
|
|
22
|
+
* is only linked when the page has an island, so other pages ship no script.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} template - HTML shell, already transformed by Vite
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {string | undefined} options.content - Rendered page markup
|
|
27
|
+
* @param {Record<string, unknown> | undefined} options.page - Page module exports, used for `%orga.*%` placeholders
|
|
28
|
+
* @param {string | undefined} [options.islandScript] - URL of the island runtime
|
|
29
|
+
* @param {string[]} [options.styles] - URLs of stylesheets imported by server-rendered code
|
|
30
|
+
*/
|
|
31
|
+
export function renderPageHtml(template: string, { content, page, islandScript, styles }: {
|
|
32
|
+
content: string | undefined;
|
|
33
|
+
page: Record<string, unknown> | undefined;
|
|
34
|
+
islandScript?: string | undefined;
|
|
35
|
+
styles?: string[] | undefined;
|
|
36
|
+
}): string;
|
|
37
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["html.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,oCAFW,MAAM,mBAMhB;AAED;;;;;;;;;;GAUG;AACH,yCAHW,MAAM,EAAE,GACN,OAAO,MAAM,EAAE,MAAM,CAqCjC;AAED;;;;;;;;;;GAUG;AACH,yCAPW,MAAM,2CAEd;IAAoC,OAAO,EAAnC,MAAM,GAAG,SAAS;IAC2B,IAAI,EAAjD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IACN,YAAY,GAAzC,MAAM,GAAG,SAAS;IACC,MAAM;CACnC,UAsBA"}
|