orga-build 0.1.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/LICENSE.org +22 -0
- package/cli.js +31 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -0
- package/index.js +1 -0
- package/lib/build.d.ts +61 -0
- package/lib/build.d.ts.map +1 -0
- package/lib/build.js +231 -0
- package/lib/esbuild/index.d.ts +16 -0
- package/lib/esbuild/index.d.ts.map +1 -0
- package/lib/esbuild/index.js +217 -0
- package/lib/esbuild/raw-loader.d.ts +13 -0
- package/lib/esbuild/raw-loader.d.ts.map +1 -0
- package/lib/esbuild/raw-loader.js +47 -0
- package/lib/esbuild.d.ts +31 -0
- package/lib/esbuild.d.ts.map +1 -0
- package/lib/esbuild.js +57 -0
- package/lib/jsx-loader.d.ts +12 -0
- package/lib/jsx-loader.d.ts.map +1 -0
- package/lib/jsx-loader.js +58 -0
- package/lib/orga-loader.d.ts +3 -0
- package/lib/orga-loader.d.ts.map +1 -0
- package/lib/orga-loader.js +9 -0
- package/lib/raw-loader.d.ts +12 -0
- package/lib/raw-loader.d.ts.map +1 -0
- package/lib/raw-loader.js +31 -0
- package/lib/serve.d.ts +8 -0
- package/lib/serve.d.ts.map +1 -0
- package/lib/serve.js +32 -0
- package/lib/util.d.ts +23 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +61 -0
- package/lib/watch.d.ts +10 -0
- package/lib/watch.d.ts.map +1 -0
- package/lib/watch.js +53 -0
- package/package.json +44 -0
package/LICENSE.org
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 gatsbyjs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/cli.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { argv } from 'node:process'
|
|
4
|
+
import { parseArgs } from 'node:util'
|
|
5
|
+
import { watch } from './lib/watch.js'
|
|
6
|
+
import { loadConfig, clean } from './lib/build.js'
|
|
7
|
+
import { build } from './lib/esbuild/index.js'
|
|
8
|
+
import { serve } from './lib/serve.js'
|
|
9
|
+
|
|
10
|
+
const { values, positionals } = parseArgs({
|
|
11
|
+
args: argv.slice(2),
|
|
12
|
+
options: {
|
|
13
|
+
watch: { type: 'boolean', short: 'w' },
|
|
14
|
+
outDir: { type: 'string', short: 'o', default: 'out' }
|
|
15
|
+
},
|
|
16
|
+
tokens: true,
|
|
17
|
+
allowPositionals: true
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const config = await loadConfig()
|
|
21
|
+
|
|
22
|
+
await build(config)
|
|
23
|
+
|
|
24
|
+
if (positionals.includes('dev')) {
|
|
25
|
+
serve(values.outDir)
|
|
26
|
+
watch('.', new RegExp(`^${config.outDir}`), async () => {
|
|
27
|
+
console.log('rebuilding')
|
|
28
|
+
await clean(config.outDir)
|
|
29
|
+
await build(config)
|
|
30
|
+
})
|
|
31
|
+
}
|
package/index.d.ts
ADDED
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":""}
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { build } from './lib/build.js'
|
package/lib/build.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {typeof defaultConfig} options
|
|
3
|
+
*/
|
|
4
|
+
export function build({ outDir, preBuild, postBuild }: typeof defaultConfig): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* @param {import("fs").PathLike} dir
|
|
7
|
+
*/
|
|
8
|
+
export function clean(dir: import("fs").PathLike): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* @returns {Promise<typeof defaultConfig>}
|
|
11
|
+
*/
|
|
12
|
+
export function loadConfig(): Promise<typeof defaultConfig>;
|
|
13
|
+
export type Page = {
|
|
14
|
+
Content: import("@orgajs/orgx").OrgContent;
|
|
15
|
+
/**
|
|
16
|
+
* - The metadata from the org file export, will be passed to the Layout component
|
|
17
|
+
*/
|
|
18
|
+
metadata: Record<string, any>;
|
|
19
|
+
/**
|
|
20
|
+
* - The absolute path to the org file
|
|
21
|
+
*/
|
|
22
|
+
src: string;
|
|
23
|
+
/**
|
|
24
|
+
* - The slug for the page
|
|
25
|
+
*/
|
|
26
|
+
slug: string;
|
|
27
|
+
};
|
|
28
|
+
export type Layout = import("react").ComponentType<any>;
|
|
29
|
+
export type Pattern = string | RegExp;
|
|
30
|
+
export type BuildContext = {
|
|
31
|
+
/**
|
|
32
|
+
* - The components from the org file
|
|
33
|
+
*/
|
|
34
|
+
components?: import("@orgajs/orgx").OrgComponents;
|
|
35
|
+
/**
|
|
36
|
+
* - The layout component
|
|
37
|
+
*/
|
|
38
|
+
Layout?: Layout | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* - The ignore pattern
|
|
41
|
+
*/
|
|
42
|
+
ignore?: Pattern | Pattern[];
|
|
43
|
+
/**
|
|
44
|
+
* - The build function
|
|
45
|
+
*/
|
|
46
|
+
build: (page: Page & {
|
|
47
|
+
Layout?: Layout | undefined;
|
|
48
|
+
components: Record<string, any>;
|
|
49
|
+
}) => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* - The build function
|
|
52
|
+
*/
|
|
53
|
+
buildHref: (filePath: string, metadata: Record<string, any>) => string;
|
|
54
|
+
};
|
|
55
|
+
declare namespace defaultConfig {
|
|
56
|
+
let outDir: string;
|
|
57
|
+
let preBuild: string[];
|
|
58
|
+
let postBuild: string[];
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["build.js"],"names":[],"mappings":"AAwJA;;GAEG;AACH,uDAFW,OAAO,aAAa,iBAwC9B;AAwBD;;GAEG;AACH,2BAFW,OAAO,IAAI,EAAE,QAAQ,iBAI/B;AAED;;GAEG;AACH,8BAFa,OAAO,CAAC,OAAO,aAAa,CAAC,CAKzC;;aAzMa,OAAO,cAAc,EAAE,UAAU;;;;cACjC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;SACnB,MAAM;;;;UACN,MAAM;;qBAIP,OAAO,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC;sBAIlC,MAAM,GAAG,MAAM;;;;;iBAKd,OAAO,cAAc,EAAE,aAAa;;;;aACpC,MAAM,GAAG,SAAS;;;;aAClB,OAAO,GAAG,OAAO,EAAE;;;;WACnB,CAAC,IAAI,EAAE,IAAI,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC;;;;eAChG,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM;;;;kBA5B7D,MAAM,EAAE;mBAER,MAAM,EAAE"}
|
package/lib/build.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { globby } from 'globby'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import { register } from 'node:module'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { createElement } from 'react'
|
|
6
|
+
import { renderToString } from 'react-dom/server'
|
|
7
|
+
import assert from 'node:assert'
|
|
8
|
+
import { match } from './util.js'
|
|
9
|
+
import { evaluate, build as _build } from './esbuild.js'
|
|
10
|
+
import { $, DefaultLayout } from './util.js'
|
|
11
|
+
|
|
12
|
+
const USE_NODE = false
|
|
13
|
+
|
|
14
|
+
if (USE_NODE) {
|
|
15
|
+
register('./jsx-loader.js', import.meta.url)
|
|
16
|
+
register('./orga-loader.js', import.meta.url)
|
|
17
|
+
register('./raw-loader.js', import.meta.url)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const defaultConfig = {
|
|
21
|
+
outDir: 'out',
|
|
22
|
+
/** @type {string[]} */
|
|
23
|
+
preBuild: [],
|
|
24
|
+
/** @type {string[]} */
|
|
25
|
+
postBuild: []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} Page
|
|
30
|
+
* @property {import('@orgajs/orgx').OrgContent} Content
|
|
31
|
+
* @property {Record<string, any>} metadata - The metadata from the org file export, will be passed to the Layout component
|
|
32
|
+
* @property {string} src - The absolute path to the org file
|
|
33
|
+
* @property {string} slug - The slug for the page
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {import('react').ComponentType<any>} Layout
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {string | RegExp} Pattern
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} BuildContext
|
|
46
|
+
* @property {import('@orgajs/orgx').OrgComponents} [components] - The components from the org file
|
|
47
|
+
* @property {Layout | undefined} [Layout] - The layout component
|
|
48
|
+
* @property {Pattern | Pattern[]} [ignore] - The ignore pattern
|
|
49
|
+
* @property {(page: Page & { Layout?: Layout | undefined, components: Record<string, any> }) => Promise<void>} build - The build function
|
|
50
|
+
* @property {(filePath: string, metadata: Record<string, any>) => string} buildHref - The build function
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Recursively processes a directory to build pages from .org files
|
|
55
|
+
* @param {string} dirPath - The directory path to process
|
|
56
|
+
* @param {BuildContext} context - Build context containing components, layout, and build function
|
|
57
|
+
* @returns {Promise<void>}
|
|
58
|
+
*/
|
|
59
|
+
async function iter(dirPath, context) {
|
|
60
|
+
/** @type {Page[]} */
|
|
61
|
+
const pages = []
|
|
62
|
+
const subdirs = []
|
|
63
|
+
|
|
64
|
+
let components = { ...context.components }
|
|
65
|
+
let Layout = context.Layout
|
|
66
|
+
let ignore = context.ignore || /node_modules/
|
|
67
|
+
if (!Array.isArray(ignore)) {
|
|
68
|
+
ignore = [ignore]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const files = await fs.readdir(dirPath)
|
|
72
|
+
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
if (match(file, ...ignore)) {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
const filePath = path.join(dirPath, file)
|
|
78
|
+
const stat = await fs.stat(filePath)
|
|
79
|
+
|
|
80
|
+
if (stat.isDirectory()) {
|
|
81
|
+
subdirs.push(filePath)
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (match(file, /(.|_)layout.(j|t)sx/)) {
|
|
86
|
+
const InnerLayout = (await _import(filePath)).default
|
|
87
|
+
if (!InnerLayout) continue
|
|
88
|
+
if (Layout !== undefined) {
|
|
89
|
+
const OuterLayout = Layout
|
|
90
|
+
Layout = function Layout(/** @type {any} */ props) {
|
|
91
|
+
return createElement(
|
|
92
|
+
OuterLayout,
|
|
93
|
+
props,
|
|
94
|
+
createElement(InnerLayout, props)
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
Layout = InnerLayout
|
|
99
|
+
}
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if (match(file, /(.|_)components.(j|t)sx$/)) {
|
|
103
|
+
const localComponents = await _import(filePath)
|
|
104
|
+
if (localComponents) {
|
|
105
|
+
components = { ...components, ...localComponents }
|
|
106
|
+
}
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (file.startsWith('.')) continue
|
|
111
|
+
|
|
112
|
+
// write regex to match .org and .tsx, .jsx files, javascript code only
|
|
113
|
+
|
|
114
|
+
if (match(file, /\.(org)$/, /\.(j|t)sx$/)) {
|
|
115
|
+
const module = await _import(filePath)
|
|
116
|
+
const {
|
|
117
|
+
default: /** @type import('@orgajs/orgx').OrgContent */ Content,
|
|
118
|
+
...metadata
|
|
119
|
+
} = module
|
|
120
|
+
pages.push({
|
|
121
|
+
Content,
|
|
122
|
+
metadata,
|
|
123
|
+
slug: context.buildHref(filePath, metadata),
|
|
124
|
+
src: filePath
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await Promise.all(
|
|
130
|
+
pages.map((page) =>
|
|
131
|
+
context.build({
|
|
132
|
+
...page,
|
|
133
|
+
metadata: {
|
|
134
|
+
...page.metadata,
|
|
135
|
+
pages: pages.map((p) => ({ ...p.metadata, slug: p.slug }))
|
|
136
|
+
},
|
|
137
|
+
Layout: Layout,
|
|
138
|
+
components
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// TODO: parallelize
|
|
144
|
+
for (const subdir of subdirs) {
|
|
145
|
+
await iter(subdir, {
|
|
146
|
+
...context,
|
|
147
|
+
Layout,
|
|
148
|
+
components
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {typeof defaultConfig} options
|
|
155
|
+
*/
|
|
156
|
+
export async function build({ outDir, preBuild, postBuild }) {
|
|
157
|
+
for (const cmd of preBuild) {
|
|
158
|
+
console.log(`Running pre-build command: ${cmd}`)
|
|
159
|
+
await $(cmd)
|
|
160
|
+
}
|
|
161
|
+
const start = performance.now()
|
|
162
|
+
const cwd = process.cwd()
|
|
163
|
+
const outFullPath = path.join(cwd, outDir)
|
|
164
|
+
console.log(`Building to ${outFullPath}`)
|
|
165
|
+
|
|
166
|
+
await iter(cwd, {
|
|
167
|
+
buildHref: (filePath) => {
|
|
168
|
+
return `/${path.relative(cwd, filePath).replace(/\.\w+$/, '.html')}`
|
|
169
|
+
},
|
|
170
|
+
ignore: [/node_modules/, 'out'],
|
|
171
|
+
build: async ({ Layout, Content, metadata, src, components }) => {
|
|
172
|
+
assert(Content, 'Content component is required')
|
|
173
|
+
const e = createElement(
|
|
174
|
+
Layout ?? DefaultLayout,
|
|
175
|
+
metadata,
|
|
176
|
+
createElement(Content, { components })
|
|
177
|
+
)
|
|
178
|
+
const code = renderToString(e)
|
|
179
|
+
const filePath = path.relative(cwd, src).replace(/\.\w+$/, '.html')
|
|
180
|
+
const outPath = path.resolve(outFullPath, filePath)
|
|
181
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true })
|
|
182
|
+
const filesize = new Intl.NumberFormat().format(code.length)
|
|
183
|
+
console.log(`${filePath} (${filesize} bytes)`)
|
|
184
|
+
await fs.writeFile(outPath, code, { encoding: 'utf-8', flush: true })
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
const end = performance.now()
|
|
188
|
+
console.log(`Built in ${(end - start).toFixed(2)}ms`)
|
|
189
|
+
|
|
190
|
+
for (const cmd of postBuild) {
|
|
191
|
+
console.log(`Running post-build command: ${cmd}`)
|
|
192
|
+
await $(cmd)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {string[]} files
|
|
198
|
+
*/
|
|
199
|
+
async function _import(...files) {
|
|
200
|
+
const found = await globby(files, {
|
|
201
|
+
cwd: process.cwd(),
|
|
202
|
+
onlyFiles: true
|
|
203
|
+
})
|
|
204
|
+
if (found.length === 0) {
|
|
205
|
+
return null
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const file = found[0]
|
|
209
|
+
const fullPath = path.isAbsolute(file) ? file : path.join(process.cwd(), file)
|
|
210
|
+
const { mtime } = await fs.stat(fullPath)
|
|
211
|
+
if (USE_NODE) {
|
|
212
|
+
return await import(`${fullPath}?version=${mtime.getTime()}`)
|
|
213
|
+
} else {
|
|
214
|
+
return await evaluate(fullPath)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {import("fs").PathLike} dir
|
|
220
|
+
*/
|
|
221
|
+
export async function clean(dir) {
|
|
222
|
+
await fs.rm(dir, { recursive: true })
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @returns {Promise<typeof defaultConfig>}
|
|
227
|
+
*/
|
|
228
|
+
export async function loadConfig() {
|
|
229
|
+
const config = await _import('orga.config.(j|t)s')
|
|
230
|
+
return { ...defaultConfig, ...config }
|
|
231
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Options
|
|
3
|
+
* @property {string} [outDir]
|
|
4
|
+
* @property {string[]} [preBuild]
|
|
5
|
+
* @property {string[]} [postBuild]
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @param {Options} options
|
|
9
|
+
*/
|
|
10
|
+
export function build({ outDir, preBuild, postBuild }: Options): Promise<void>;
|
|
11
|
+
export type Options = {
|
|
12
|
+
outDir?: string;
|
|
13
|
+
preBuild?: string[];
|
|
14
|
+
postBuild?: string[];
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"AAUA;;;;;GAKG;AAEH;;GAEG;AACH,uDAFW,OAAO,iBAuKjB;;aA7Ka,MAAM;eACN,MAAM,EAAE;gBACR,MAAM,EAAE"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import * as esbuild from 'esbuild'
|
|
3
|
+
import esbuildOrga from '@orgajs/esbuild'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { createElement } from 'react'
|
|
6
|
+
import { renderToString } from 'react-dom/server'
|
|
7
|
+
import assert from 'node:assert'
|
|
8
|
+
import { DefaultLayout, $, match } from '../util.js'
|
|
9
|
+
import rawLoader from './raw-loader.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} Options
|
|
13
|
+
* @property {string} [outDir]
|
|
14
|
+
* @property {string[]} [preBuild]
|
|
15
|
+
* @property {string[]} [postBuild]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {Options} options
|
|
20
|
+
*/
|
|
21
|
+
export async function build({ outDir = 'dir', preBuild = [], postBuild = [] }) {
|
|
22
|
+
for (const cmd of preBuild) {
|
|
23
|
+
console.log(`Running pre-build command: ${cmd}`)
|
|
24
|
+
await $(cmd)
|
|
25
|
+
}
|
|
26
|
+
const cwd = process.cwd()
|
|
27
|
+
const start = performance.now()
|
|
28
|
+
|
|
29
|
+
const src = {
|
|
30
|
+
/** @type {string[]} */
|
|
31
|
+
layouts: [],
|
|
32
|
+
/** @type {string[]} */
|
|
33
|
+
components: [],
|
|
34
|
+
/** @type {string[]} */
|
|
35
|
+
pages: []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await walk(cwd, (file) => {
|
|
39
|
+
const fileName = path.basename(file)
|
|
40
|
+
if (match(fileName, /(.|_)layout.(j|t)sx$/)) {
|
|
41
|
+
src.layouts.push(file)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (match(fileName, /(.|_)components.(j|t)sx$/)) {
|
|
45
|
+
src.components.push(file)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
if (match(fileName, /\.(org)$/, /\.(j|t)sx$/)) {
|
|
49
|
+
src.pages.push(file)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const result = await esbuild.build({
|
|
55
|
+
entryPoints: [
|
|
56
|
+
...Object.values(src.layouts),
|
|
57
|
+
...src.components,
|
|
58
|
+
...src.pages
|
|
59
|
+
],
|
|
60
|
+
// entryNames: '[dir]/_/[name]',
|
|
61
|
+
bundle: true,
|
|
62
|
+
format: 'esm',
|
|
63
|
+
platform: 'node',
|
|
64
|
+
target: 'esnext',
|
|
65
|
+
jsx: 'automatic',
|
|
66
|
+
// write: false,
|
|
67
|
+
outdir: '.orga-build/js',
|
|
68
|
+
// splitting: true,
|
|
69
|
+
metafile: true,
|
|
70
|
+
plugins: [esbuildOrga(), rawLoader],
|
|
71
|
+
// external: ['react/jsx-runtime'],
|
|
72
|
+
loader: {
|
|
73
|
+
'.jsx': 'jsx',
|
|
74
|
+
'.tsx': 'tsx'
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
assert(result.metafile, 'metafile not found')
|
|
79
|
+
|
|
80
|
+
let components = {}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {Object} DirInfo
|
|
84
|
+
* @property {string|undefined} layout
|
|
85
|
+
* @property {PageInfo[]} pages
|
|
86
|
+
|
|
87
|
+
* @typedef {Object} PageInfo
|
|
88
|
+
* @property {Record<string, any>} metadata
|
|
89
|
+
* @property {import('react').ComponentType<any>} Content
|
|
90
|
+
* @property {string} src
|
|
91
|
+
* @property {string} file
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/** @type {Record<string, DirInfo>} */
|
|
95
|
+
let map = {}
|
|
96
|
+
// iterate over results to get layout and components
|
|
97
|
+
for (const [file, meta] of Object.entries(result.metafile.outputs)) {
|
|
98
|
+
if (!meta.entryPoint) continue
|
|
99
|
+
const fullSrc = path.join(cwd, meta.entryPoint)
|
|
100
|
+
// get components
|
|
101
|
+
if (src.components.includes(fullSrc)) {
|
|
102
|
+
const module = await import(path.join(cwd, file))
|
|
103
|
+
components = { ...components, ...module }
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const dirPath = path.dirname(fullSrc)
|
|
108
|
+
map[dirPath] = map[dirPath] ?? {
|
|
109
|
+
layout: undefined,
|
|
110
|
+
pages: []
|
|
111
|
+
}
|
|
112
|
+
const dir = map[dirPath]
|
|
113
|
+
|
|
114
|
+
// get layouts
|
|
115
|
+
if (src.layouts.includes(fullSrc)) {
|
|
116
|
+
dir.layout = path.join(cwd, file)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (src.pages.includes(fullSrc)) {
|
|
120
|
+
const fullPath = path.join(cwd, file)
|
|
121
|
+
const { default: Content, ...metadata } = await import(fullPath)
|
|
122
|
+
dir.pages.push({
|
|
123
|
+
metadata: {
|
|
124
|
+
...metadata,
|
|
125
|
+
slug: `/${meta.entryPoint.replace(/\.\w+$/, '.html')}`
|
|
126
|
+
},
|
|
127
|
+
Content,
|
|
128
|
+
src: fullSrc,
|
|
129
|
+
file: fullPath
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const [, content] of Object.entries(map)) {
|
|
135
|
+
for (const page of content.pages) {
|
|
136
|
+
const Layout = await getLayout(page.src)
|
|
137
|
+
const e = createElement(
|
|
138
|
+
Layout ?? DefaultLayout,
|
|
139
|
+
{ ...page.metadata, pages: content.pages.map((p) => p.metadata) },
|
|
140
|
+
createElement(page.Content, { components })
|
|
141
|
+
)
|
|
142
|
+
const html = renderToString(e)
|
|
143
|
+
// write to outDir/file
|
|
144
|
+
const relPath = path.relative(cwd, page.src)
|
|
145
|
+
const outPath = path.join(outDir, relPath)
|
|
146
|
+
const outDirPath = path.dirname(outPath)
|
|
147
|
+
await fs.mkdir(outDirPath, { recursive: true })
|
|
148
|
+
await fs
|
|
149
|
+
.writeFile(outPath.replace(/\.(org|jsx|tsx)$/, '.html'), html)
|
|
150
|
+
.catch(console.error)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const cmd of postBuild) {
|
|
155
|
+
console.log(`Running post-build command: ${cmd}`)
|
|
156
|
+
await $(cmd)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const end = performance.now()
|
|
160
|
+
console.log(`Built in ${(end - start).toFixed(2)}ms`)
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {string} file
|
|
164
|
+
* @returns {Promise<import('react').ComponentType<any>|undefined>}
|
|
165
|
+
*/
|
|
166
|
+
async function getLayout(file) {
|
|
167
|
+
if (file === cwd) return
|
|
168
|
+
const dir = path.dirname(file)
|
|
169
|
+
const ParentLayout = await getLayout(dir)
|
|
170
|
+
const { layout } = map[dir]
|
|
171
|
+
if (layout) {
|
|
172
|
+
const Layout = (await import(layout)).default
|
|
173
|
+
if (ParentLayout) {
|
|
174
|
+
return function (props) {
|
|
175
|
+
return createElement(
|
|
176
|
+
ParentLayout,
|
|
177
|
+
props,
|
|
178
|
+
createElement(Layout, props)
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return Layout
|
|
183
|
+
}
|
|
184
|
+
return ParentLayout
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Iterates over files in a directory recursively in a breadth-first manner.
|
|
190
|
+
* @param {string} dirPath - The path to the directory.
|
|
191
|
+
* @param {(file: string) => void} callback - The callback function to be called for each file.
|
|
192
|
+
*/
|
|
193
|
+
async function walk(dirPath, callback) {
|
|
194
|
+
const queue = [dirPath]
|
|
195
|
+
|
|
196
|
+
const ignore = [/^\./, /node_modules/]
|
|
197
|
+
|
|
198
|
+
while (queue.length > 0) {
|
|
199
|
+
const currentPath = queue.shift()
|
|
200
|
+
if (!currentPath) break
|
|
201
|
+
const files = await fs.readdir(currentPath)
|
|
202
|
+
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
if (match(file, ...ignore)) {
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
const filePath = path.join(currentPath, file)
|
|
208
|
+
const stats = await fs.stat(filePath)
|
|
209
|
+
|
|
210
|
+
if (stats.isDirectory()) {
|
|
211
|
+
queue.push(filePath)
|
|
212
|
+
} else {
|
|
213
|
+
callback(filePath)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default rawLoader;
|
|
2
|
+
declare namespace rawLoader {
|
|
3
|
+
let name: string;
|
|
4
|
+
/**
|
|
5
|
+
* @param {PluginBuild} build
|
|
6
|
+
* Build.
|
|
7
|
+
* @returns {undefined}
|
|
8
|
+
* Nothing.
|
|
9
|
+
*/
|
|
10
|
+
function setup(build: PluginBuild): undefined;
|
|
11
|
+
}
|
|
12
|
+
import type { PluginBuild } from 'esbuild';
|
|
13
|
+
//# sourceMappingURL=raw-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-loader.d.ts","sourceRoot":"","sources":["raw-loader.js"],"names":[],"mappings":";;;IASC;;;;;OAKG;IACH,sBALW,WAAW,GAET,SAAS,CA+BrB;;iCA1C4B,SAAS"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import {PluginBuild} from 'esbuild'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { promises as fs } from 'fs'
|
|
7
|
+
|
|
8
|
+
const rawLoader = {
|
|
9
|
+
name: 'raw',
|
|
10
|
+
/**
|
|
11
|
+
* @param {PluginBuild} build
|
|
12
|
+
* Build.
|
|
13
|
+
* @returns {undefined}
|
|
14
|
+
* Nothing.
|
|
15
|
+
*/
|
|
16
|
+
setup(build) {
|
|
17
|
+
build.onResolve({ filter: /.*\?raw$/ }, (args) => {
|
|
18
|
+
return {
|
|
19
|
+
path: args.path,
|
|
20
|
+
pluginData: {
|
|
21
|
+
isAbsolute: path.isAbsolute(args.path),
|
|
22
|
+
resolveDir: args.resolveDir
|
|
23
|
+
},
|
|
24
|
+
namespace: 'raw-loader'
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
build.onLoad(
|
|
29
|
+
{ filter: /.*\?raw$/, namespace: 'raw-loader' },
|
|
30
|
+
async (args) => {
|
|
31
|
+
const fullPath = args.pluginData.isAbsolute
|
|
32
|
+
? args.path
|
|
33
|
+
: path.join(args.pluginData.resolveDir, args.path)
|
|
34
|
+
const contents = await fs.readFile(
|
|
35
|
+
fullPath.replace(/\?raw$/, ''),
|
|
36
|
+
'utf8'
|
|
37
|
+
)
|
|
38
|
+
return {
|
|
39
|
+
contents,
|
|
40
|
+
loader: 'text'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default rawLoader
|
package/lib/esbuild.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate an org/jsx/tsx/ts file. Returns as a module.
|
|
3
|
+
* It's like a dynamic import, but without relying on nodejs.
|
|
4
|
+
* @param {string} filePath
|
|
5
|
+
* @returns {Promise<any>}
|
|
6
|
+
*/
|
|
7
|
+
export function evaluate(filePath: string): Promise<any>;
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} pattern
|
|
10
|
+
*/
|
|
11
|
+
export function build(pattern: string): Promise<esbuild.BuildResult<{
|
|
12
|
+
entryPoints: string[];
|
|
13
|
+
entryNames: string;
|
|
14
|
+
bundle: true;
|
|
15
|
+
format: "esm";
|
|
16
|
+
platform: "node";
|
|
17
|
+
target: string;
|
|
18
|
+
jsx: "automatic";
|
|
19
|
+
outdir: string;
|
|
20
|
+
plugins: {
|
|
21
|
+
name: string;
|
|
22
|
+
setup: (build: esbuild.PluginBuild) => undefined;
|
|
23
|
+
}[];
|
|
24
|
+
loader: {
|
|
25
|
+
'.jsx': "jsx";
|
|
26
|
+
'.tsx': "tsx";
|
|
27
|
+
};
|
|
28
|
+
}>>;
|
|
29
|
+
export function b(): Promise<void>;
|
|
30
|
+
import * as esbuild from 'esbuild';
|
|
31
|
+
//# sourceMappingURL=esbuild.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esbuild.d.ts","sourceRoot":"","sources":["esbuild.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,mCAHW,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAwBxB;AAED;;GAEG;AACH,+BAFW,MAAM;;;;;;;;;;;;;;;;;IAmBhB;AAED,mCAA4B;yBAvDH,SAAS"}
|
package/lib/esbuild.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import * as esbuild from 'esbuild'
|
|
3
|
+
import esbuildOrga from '@orgajs/esbuild'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Evaluate an org/jsx/tsx/ts file. Returns as a module.
|
|
7
|
+
* It's like a dynamic import, but without relying on nodejs.
|
|
8
|
+
* @param {string} filePath
|
|
9
|
+
* @returns {Promise<any>}
|
|
10
|
+
*/
|
|
11
|
+
export async function evaluate(filePath) {
|
|
12
|
+
const result = await esbuild.build({
|
|
13
|
+
entryPoints: [filePath],
|
|
14
|
+
bundle: true,
|
|
15
|
+
format: 'esm',
|
|
16
|
+
platform: 'node',
|
|
17
|
+
target: 'esnext',
|
|
18
|
+
jsx: 'automatic',
|
|
19
|
+
write: false,
|
|
20
|
+
plugins: [esbuildOrga()],
|
|
21
|
+
loader: {
|
|
22
|
+
'.jsx': 'jsx',
|
|
23
|
+
'.tsx': 'tsx'
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const files = result.outputFiles
|
|
28
|
+
assert(files.length === 1, 'Expected only one output file')
|
|
29
|
+
const code = files[0].text
|
|
30
|
+
return await new Function(
|
|
31
|
+
`return import("data:application/javascript,${encodeURIComponent(code)}")`
|
|
32
|
+
)()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} pattern
|
|
37
|
+
*/
|
|
38
|
+
export async function build(pattern) {
|
|
39
|
+
return await esbuild.build({
|
|
40
|
+
entryPoints: [pattern],
|
|
41
|
+
entryNames: '[dir]/_/[name]',
|
|
42
|
+
bundle: true,
|
|
43
|
+
format: 'esm',
|
|
44
|
+
platform: 'node',
|
|
45
|
+
target: 'esnext',
|
|
46
|
+
jsx: 'automatic',
|
|
47
|
+
// write: false,
|
|
48
|
+
outdir: '.build',
|
|
49
|
+
plugins: [esbuildOrga()],
|
|
50
|
+
loader: {
|
|
51
|
+
'.jsx': 'jsx',
|
|
52
|
+
'.tsx': 'tsx'
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function b() {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} href
|
|
3
|
+
* URL.
|
|
4
|
+
* @param {unknown} context
|
|
5
|
+
* Context.
|
|
6
|
+
* @param {Function} defaultLoad
|
|
7
|
+
* Default `load`.
|
|
8
|
+
* @returns
|
|
9
|
+
* Result.
|
|
10
|
+
*/
|
|
11
|
+
export function load(href: string, context: unknown, defaultLoad: Function): Promise<any>;
|
|
12
|
+
//# sourceMappingURL=jsx-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-loader.d.ts","sourceRoot":"","sources":["jsx-loader.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,2BATW,MAAM,WAEN,OAAO,uCAgCjB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { transform } from 'esbuild'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} href
|
|
7
|
+
* URL.
|
|
8
|
+
* @param {unknown} context
|
|
9
|
+
* Context.
|
|
10
|
+
* @param {Function} defaultLoad
|
|
11
|
+
* Default `load`.
|
|
12
|
+
* @returns
|
|
13
|
+
* Result.
|
|
14
|
+
*/
|
|
15
|
+
export async function load(href, context, defaultLoad) {
|
|
16
|
+
const url = new URL(href)
|
|
17
|
+
|
|
18
|
+
const loader = getLoader(url.pathname)
|
|
19
|
+
if (!loader) {
|
|
20
|
+
return defaultLoad(href, context, defaultLoad)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { code, warnings } = await transform(String(await fs.readFile(url)), {
|
|
24
|
+
format: 'esm',
|
|
25
|
+
loader,
|
|
26
|
+
sourcefile: fileURLToPath(url),
|
|
27
|
+
sourcemap: 'both',
|
|
28
|
+
target: 'esnext',
|
|
29
|
+
jsx: 'automatic',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (warnings) {
|
|
33
|
+
for (const warning of warnings) {
|
|
34
|
+
console.log(warning.location)
|
|
35
|
+
console.log(warning.text)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { format: 'module', shortCircuit: true, source: code }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} filename
|
|
44
|
+
* @returns {import('esbuild').Loader | null}
|
|
45
|
+
*/
|
|
46
|
+
function getLoader(filename) {
|
|
47
|
+
const ext = filename.split('.').pop()
|
|
48
|
+
switch (ext) {
|
|
49
|
+
case 'jsx':
|
|
50
|
+
return 'jsx'
|
|
51
|
+
case 'tsx':
|
|
52
|
+
return 'tsx'
|
|
53
|
+
case 'ts':
|
|
54
|
+
return 'ts'
|
|
55
|
+
default:
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const initialize: (options: Readonly<Options> | null | undefined) => Promise<void>;
|
|
2
|
+
export const load: (href: string, context: import("module").LoadHookContext, nextLoad: NextLoad) => Promise<import("module").LoadFnOutput>;
|
|
3
|
+
//# sourceMappingURL=orga-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orga-loader.d.ts","sourceRoot":"","sources":["orga-loader.js"],"names":[],"mappings":"AAOA,0FAA2C;AAC3C,2IAA+B"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} href
|
|
3
|
+
* URL.
|
|
4
|
+
* @param {unknown} context
|
|
5
|
+
* Context.
|
|
6
|
+
* @param {Function} defaultLoad
|
|
7
|
+
* Default `load`.
|
|
8
|
+
* @returns
|
|
9
|
+
* Result.
|
|
10
|
+
*/
|
|
11
|
+
export function load(href: string, context: unknown, defaultLoad: Function): Promise<any>;
|
|
12
|
+
//# sourceMappingURL=raw-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-loader.d.ts","sourceRoot":"","sources":["raw-loader.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,2BATW,MAAM,WAEN,OAAO,uCAsBjB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} href
|
|
6
|
+
* URL.
|
|
7
|
+
* @param {unknown} context
|
|
8
|
+
* Context.
|
|
9
|
+
* @param {Function} defaultLoad
|
|
10
|
+
* Default `load`.
|
|
11
|
+
* @returns
|
|
12
|
+
* Result.
|
|
13
|
+
*/
|
|
14
|
+
const rawLoader = async (href, context, defaultLoad) => {
|
|
15
|
+
const url = new URL(href)
|
|
16
|
+
const { searchParams } = url
|
|
17
|
+
const raw = searchParams.get('raw')
|
|
18
|
+
if (raw === null || raw === undefined || raw.toLowerCase() === 'false') {
|
|
19
|
+
return defaultLoad(href, context, defaultLoad)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const filePath = fileURLToPath(href)
|
|
23
|
+
const fileContent = readFileSync(filePath, 'utf8')
|
|
24
|
+
return {
|
|
25
|
+
format: 'module',
|
|
26
|
+
shortCircuit: true,
|
|
27
|
+
source: `export default ${JSON.stringify(fileContent)}`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const load = rawLoader
|
package/lib/serve.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["serve.js"],"names":[],"mappings":"AAGA;;GAEG;AACH,2BAFW,MAAM;;;EA2BhB"}
|
package/lib/serve.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import handler from 'serve-handler'
|
|
2
|
+
import http from 'http'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} dir
|
|
6
|
+
*/
|
|
7
|
+
export function serve(dir, port = 3000) {
|
|
8
|
+
/** @type {import('http').Server | null} */
|
|
9
|
+
let server = null
|
|
10
|
+
|
|
11
|
+
function start() {
|
|
12
|
+
server = http
|
|
13
|
+
.createServer((req, res) => {
|
|
14
|
+
return handler(req, res, {
|
|
15
|
+
public: dir,
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
.listen(port)
|
|
19
|
+
console.log(`Server running at http://localhost:${port}/`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stop() {
|
|
23
|
+
server?.close()
|
|
24
|
+
server?.on('close', () => {
|
|
25
|
+
server = null
|
|
26
|
+
return Promise.resolve()
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
start()
|
|
31
|
+
return { start, stop }
|
|
32
|
+
}
|
package/lib/util.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function buildNav(): void;
|
|
2
|
+
/**
|
|
3
|
+
* @param {string} file
|
|
4
|
+
* @param {...RegExp|string} patterns
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function match(file: string, ...patterns: (RegExp | string)[]): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Default layout
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {string|undefined} props.title
|
|
12
|
+
* @param {import('react').ReactNode} props.children
|
|
13
|
+
* @returns {React.JSX.Element}
|
|
14
|
+
*/
|
|
15
|
+
export function DefaultLayout({ title, children }: {
|
|
16
|
+
title: string | undefined;
|
|
17
|
+
children: import("react").ReactNode;
|
|
18
|
+
}): React.JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} cmd
|
|
21
|
+
*/
|
|
22
|
+
export function $(cmd: string): Promise<any>;
|
|
23
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.js"],"names":[],"mappings":"AAGA,iCAA6B;AAE7B;;;;GAIG;AACH,4BAJW,MAAM,eACN,CAAG,MAAM,GAAC,MAAM,GAAA,GACd,OAAO,CASnB;AAED;;;;;;GAMG;AACH,mDAJG;IAAgC,KAAK,EAA7B,MAAM,GAAC,SAAS;IACiB,QAAQ,EAAzC,OAAO,OAAO,EAAE,SAAS;CACjC,GAAU,KAAK,CAAC,GAAG,CAAC,OAAO,CAkB7B;AAED;;GAEG;AACH,uBAFW,MAAM,gBAehB"}
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { exec } from 'node:child_process'
|
|
2
|
+
import { createElement } from 'react'
|
|
3
|
+
|
|
4
|
+
export function buildNav() {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} file
|
|
8
|
+
* @param {...RegExp|string} patterns
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function match(file, ...patterns) {
|
|
12
|
+
return patterns.some((p) => {
|
|
13
|
+
if (p instanceof RegExp) {
|
|
14
|
+
return p.test(file)
|
|
15
|
+
}
|
|
16
|
+
return file === p
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default layout
|
|
22
|
+
* @param {Object} props
|
|
23
|
+
* @param {string|undefined} props.title
|
|
24
|
+
* @param {import('react').ReactNode} props.children
|
|
25
|
+
* @returns {React.JSX.Element}
|
|
26
|
+
*/
|
|
27
|
+
export function DefaultLayout({ title, children }) {
|
|
28
|
+
return createElement(
|
|
29
|
+
'html',
|
|
30
|
+
{ lang: 'en' },
|
|
31
|
+
createElement(
|
|
32
|
+
'head',
|
|
33
|
+
{},
|
|
34
|
+
createElement('meta', { charSet: 'utf-8' }),
|
|
35
|
+
createElement('meta', {
|
|
36
|
+
name: 'viewport',
|
|
37
|
+
content: 'width=device-width, initial-scale=1'
|
|
38
|
+
}),
|
|
39
|
+
title && createElement('title', {}, title)
|
|
40
|
+
),
|
|
41
|
+
createElement('body', {}, children)
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} cmd
|
|
47
|
+
*/
|
|
48
|
+
export async function $(cmd) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
exec(cmd, (err, stdout, stderr) => {
|
|
51
|
+
if (err) {
|
|
52
|
+
reject(err)
|
|
53
|
+
}
|
|
54
|
+
if (stderr) {
|
|
55
|
+
console.error(stderr)
|
|
56
|
+
}
|
|
57
|
+
console.log(stdout)
|
|
58
|
+
resolve(stdout)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
}
|
package/lib/watch.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import("fs").PathLike} dir
|
|
3
|
+
* @param {RegExp} ignore
|
|
4
|
+
* @param {{(event: fs.FileChangeInfo<string>): Promise<void> | void}} onChange
|
|
5
|
+
*/
|
|
6
|
+
export function watch(dir: import("fs").PathLike, ignore: RegExp, onChange: {
|
|
7
|
+
(event: fs.FileChangeInfo<string>): Promise<void> | void;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["watch.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,2BAJW,OAAO,IAAI,EAAE,QAAQ,UACrB,MAAM,YACN;IAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAC,iBA+CpE;eApDc,kBAAkB"}
|
package/lib/watch.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {import("fs").PathLike} dir
|
|
5
|
+
* @param {RegExp} ignore
|
|
6
|
+
* @param {{(event: fs.FileChangeInfo<string>): Promise<void> | void}} onChange
|
|
7
|
+
*/
|
|
8
|
+
export async function watch(dir, ignore, onChange) {
|
|
9
|
+
let busy = false
|
|
10
|
+
let dirty = false
|
|
11
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
12
|
+
let timeout = null
|
|
13
|
+
const delay = 1000
|
|
14
|
+
const defaultIgnorePattern = /node_modules|\.git|\.DS_Store/
|
|
15
|
+
|
|
16
|
+
const watcher = fs.watch(dir, { recursive: true })
|
|
17
|
+
for await (const event of watcher) {
|
|
18
|
+
if (
|
|
19
|
+
event.eventType !== 'change' ||
|
|
20
|
+
event.filename === null ||
|
|
21
|
+
shouldIgnore(event.filename)
|
|
22
|
+
) {
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
console.log(`file changed: ${event.filename}`)
|
|
26
|
+
dirty = true
|
|
27
|
+
if (busy) {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
if (timeout !== null) clearTimeout(timeout)
|
|
31
|
+
timeout = setTimeout(processEvent, delay, event)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {any} event
|
|
36
|
+
*/
|
|
37
|
+
async function processEvent(event) {
|
|
38
|
+
busy = true
|
|
39
|
+
dirty = false
|
|
40
|
+
await onChange(event)
|
|
41
|
+
busy = false
|
|
42
|
+
if (dirty) {
|
|
43
|
+
processEvent(event)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} filename
|
|
49
|
+
*/
|
|
50
|
+
function shouldIgnore(filename) {
|
|
51
|
+
return defaultIgnorePattern.test(filename) || ignore.test(filename)
|
|
52
|
+
}
|
|
53
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "orga-build",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A simple tool that builds org-mode files into a website",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"orga-build": "cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"lib/",
|
|
11
|
+
"cli.js",
|
|
12
|
+
"index.js",
|
|
13
|
+
"index.d.ts",
|
|
14
|
+
"index.d.ts.map"
|
|
15
|
+
],
|
|
16
|
+
"exports": "./index.js",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"orgajs",
|
|
19
|
+
"org-mode",
|
|
20
|
+
"build",
|
|
21
|
+
"website",
|
|
22
|
+
"react"
|
|
23
|
+
],
|
|
24
|
+
"author": "Xiaoxing Hu <hi@xiaoxing.dev>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@orgajs/esbuild": "^1.1.1",
|
|
28
|
+
"esbuild": "^0.24.2",
|
|
29
|
+
"globby": "^14.0.2",
|
|
30
|
+
"react": "^19.0.0",
|
|
31
|
+
"react-dom": "^19.0.0",
|
|
32
|
+
"rehype-katex": "^7.0.1",
|
|
33
|
+
"serve-handler": "^6.1.6",
|
|
34
|
+
"@orgajs/node-loader": "^1.1.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.13.1",
|
|
38
|
+
"@types/react": "^19.0.8",
|
|
39
|
+
"@types/react-dom": "^19.0.3",
|
|
40
|
+
"@types/serve-handler": "^6.1.4",
|
|
41
|
+
"@orgajs/orgx": "^2.5.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {}
|
|
44
|
+
}
|