methanol 0.0.0 → 0.0.1

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 (45) hide show
  1. package/.editorconfig +19 -0
  2. package/.prettierrc +10 -0
  3. package/LICENSE +203 -0
  4. package/banner.txt +6 -0
  5. package/bin/methanol.js +24 -0
  6. package/index.js +22 -0
  7. package/package.json +42 -9
  8. package/src/assets.js +30 -0
  9. package/src/build-system.js +200 -0
  10. package/src/components.js +145 -0
  11. package/src/config.js +355 -0
  12. package/src/dev-server.js +559 -0
  13. package/src/main.js +87 -0
  14. package/src/mdx.js +254 -0
  15. package/src/node-loader.js +88 -0
  16. package/src/pagefind.js +99 -0
  17. package/src/pages.js +638 -0
  18. package/src/preview-server.js +58 -0
  19. package/src/public-assets.js +73 -0
  20. package/src/register-loader.js +29 -0
  21. package/src/rehype-plugins/link-resolve.js +89 -0
  22. package/src/rehype-plugins/methanol-ctx.js +89 -0
  23. package/src/renderer.js +25 -0
  24. package/src/rewind.js +117 -0
  25. package/src/stage-logger.js +59 -0
  26. package/src/state.js +159 -0
  27. package/src/virtual-module/inject.js +30 -0
  28. package/src/virtual-module/loader.js +116 -0
  29. package/src/virtual-module/pagefind.js +108 -0
  30. package/src/vite-plugins.js +173 -0
  31. package/themes/default/components/ThemeColorSwitch.client.jsx +95 -0
  32. package/themes/default/components/ThemeColorSwitch.static.jsx +23 -0
  33. package/themes/default/components/ThemeSearchBox.client.jsx +287 -0
  34. package/themes/default/components/ThemeSearchBox.static.jsx +41 -0
  35. package/themes/default/components/ThemeToCContainer.client.jsx +154 -0
  36. package/themes/default/components/ThemeToCContainer.static.jsx +61 -0
  37. package/themes/default/components/pre.client.jsx +84 -0
  38. package/themes/default/components/pre.jsx +27 -0
  39. package/themes/default/heading.jsx +35 -0
  40. package/themes/default/index.js +50 -0
  41. package/themes/default/page.jsx +249 -0
  42. package/themes/default/pages/404.mdx +8 -0
  43. package/themes/default/pages/index.mdx +9 -0
  44. package/themes/default/public/logo.png +0 -0
  45. package/themes/default/resources/style.css +1089 -0
@@ -0,0 +1,559 @@
1
+ /* Copyright Yukino Song, SudoMaker Ltd.
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements. See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership. The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied. See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ */
20
+
21
+ import { existsSync } from 'fs'
22
+ import { resolve, dirname, extname, join, basename } from 'path'
23
+ import { fileURLToPath } from 'url'
24
+ import chokidar from 'chokidar'
25
+ import { createServer, mergeConfig } from 'vite'
26
+ import { refurbish } from 'refurbish/vite'
27
+ import { state, cli } from './state.js'
28
+ import { resolveUserViteConfig } from './config.js'
29
+ import {
30
+ buildComponentRegistry,
31
+ buildComponentEntry,
32
+ invalidateRegistryEntry,
33
+ bumpComponentImportNonce,
34
+ isComponentFile,
35
+ isClientComponent,
36
+ COMPONENT_EXTENSIONS
37
+ } from './components.js'
38
+ import { buildPagesContext, buildPageEntry, routePathFromFile } from './pages.js'
39
+ import { compilePageMdx, renderHtml } from './mdx.js'
40
+ import { methanolResolverPlugin } from './vite-plugins.js'
41
+ import { copyPublicDir } from './public-assets.js'
42
+
43
+ export const runViteDev = async () => {
44
+ const baseFsAllow = [state.ROOT_DIR, state.USER_THEME.root].filter(Boolean)
45
+ const baseConfig = {
46
+ configFile: false,
47
+ root: state.PAGES_DIR,
48
+ publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
49
+ server: {
50
+ fs: {
51
+ allow: baseFsAllow
52
+ }
53
+ },
54
+ esbuild: {
55
+ jsx: 'automatic',
56
+ jsxImportSource: 'refui'
57
+ },
58
+ resolve: {
59
+ dedupe: ['refui', 'methanol']
60
+ },
61
+ plugins: [methanolResolverPlugin(), refurbish()]
62
+ }
63
+ const userConfig = await resolveUserViteConfig('serve')
64
+ const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
65
+ if (state.STATIC_DIR !== false) {
66
+ await copyPublicDir({
67
+ sourceDir: state.THEME_PUBLIC_DIR,
68
+ targetDir: state.STATIC_DIR,
69
+ label: 'theme public'
70
+ })
71
+ }
72
+ if (cli.CLI_PORT != null) {
73
+ finalConfig.server = { ...(finalConfig.server || {}), port: cli.CLI_PORT }
74
+ }
75
+ if (cli.CLI_HOST !== null) {
76
+ finalConfig.server = { ...(finalConfig.server || {}), host: cli.CLI_HOST }
77
+ }
78
+ if (baseFsAllow.length) {
79
+ const fsConfig = finalConfig.server?.fs || {}
80
+ const allow = Array.isArray(fsConfig.allow) ? fsConfig.allow : []
81
+ for (const dir of baseFsAllow) {
82
+ if (!allow.includes(dir)) {
83
+ allow.push(dir)
84
+ }
85
+ }
86
+ finalConfig.server = {
87
+ ...(finalConfig.server || {}),
88
+ fs: {
89
+ ...fsConfig,
90
+ allow
91
+ }
92
+ }
93
+ }
94
+ const server = await createServer(finalConfig)
95
+
96
+ const themeComponentsDir = state.THEME_COMPONENTS_DIR
97
+ const themeEnv = state.THEME_ENV
98
+ const themeRegistry = themeComponentsDir
99
+ ? await buildComponentRegistry({
100
+ componentsDir: themeComponentsDir,
101
+ client: themeEnv.client
102
+ })
103
+ : { components: {} }
104
+ const themeComponents = {
105
+ ...(themeRegistry.components || {}),
106
+ ...(state.USER_THEME.components || {})
107
+ }
108
+ const initialRegistry = await buildComponentRegistry()
109
+ let components = initialRegistry.components
110
+ const componentSources = new Map(initialRegistry.sources)
111
+ let pagesContext = null
112
+ let pagesContextToken = 0
113
+ const setPagesContext = (next) => {
114
+ pagesContext = next
115
+ pagesContextToken += 1
116
+ }
117
+ setPagesContext(await buildPagesContext({ compileAll: false }))
118
+ const htmlCache = new Map()
119
+ let htmlCacheEpoch = 0
120
+
121
+ const invalidateHtmlCache = () => {
122
+ htmlCacheEpoch += 1
123
+ htmlCache.clear()
124
+ }
125
+
126
+ const refreshPagesContext = async () => {
127
+ setPagesContext(await buildPagesContext({ compileAll: false }))
128
+ }
129
+
130
+ const runInitialCompile = async () => {
131
+ const token = pagesContextToken
132
+ try {
133
+ const nextContext = await buildPagesContext({ compileAll: true })
134
+ if (token !== pagesContextToken) {
135
+ return
136
+ }
137
+ setPagesContext(nextContext)
138
+ invalidateHtmlCache()
139
+ reload()
140
+ } catch (err) {
141
+ console.error(err)
142
+ }
143
+ }
144
+
145
+ const resolvePageFile = (routePath) => {
146
+ const name = routePath === '/' ? 'index' : routePath.slice(1)
147
+ const mdxPath = resolve(state.PAGES_DIR, `${name}.mdx`)
148
+ if (existsSync(mdxPath)) return mdxPath
149
+ const mdPath = resolve(state.PAGES_DIR, `${name}.md`)
150
+ if (existsSync(mdPath)) return mdPath
151
+ return mdxPath
152
+ }
153
+
154
+ const htmlMiddleware = async (req, res, next) => {
155
+ if (!req.url || req.method !== 'GET') {
156
+ return next()
157
+ }
158
+ if (req.url.startsWith('/@vite') || req.url.startsWith('/__vite')) {
159
+ return next()
160
+ }
161
+
162
+ const url = new URL(req.url, 'http://methanol')
163
+ let pathname = url.pathname
164
+ try {
165
+ pathname = decodeURIComponent(pathname)
166
+ } catch {}
167
+ const originalPathname = pathname
168
+ const hasTrailingSlash = originalPathname.endsWith('/') && originalPathname !== '/'
169
+
170
+ if (pathname.includes('.') && !pathname.endsWith('.html')) {
171
+ return next()
172
+ }
173
+
174
+ const accept = req.headers.accept || ''
175
+ if (!pathname.endsWith('.html') && !accept.includes('text/html')) {
176
+ return next()
177
+ }
178
+
179
+ let routePath = pathname
180
+ if (routePath.endsWith('.html')) {
181
+ routePath = routePath.slice(0, -'.html'.length)
182
+ if (routePath === '') {
183
+ routePath = '/'
184
+ }
185
+ }
186
+ if (routePath.endsWith('/') && routePath !== '/') {
187
+ routePath = routePath.slice(0, -1)
188
+ }
189
+
190
+ const requestedPath = routePath
191
+ const isExcludedPath = () => {
192
+ const excludedRoutes = pagesContext?.excludedRoutes
193
+ if (excludedRoutes?.has(requestedPath)) return true
194
+ const excludedDirPaths = pagesContext?.excludedDirPaths
195
+ if (excludedDirPaths?.size) {
196
+ for (const dirPath of excludedDirPaths) {
197
+ if (requestedPath === dirPath || requestedPath.startsWith(`${dirPath}/`)) {
198
+ return true
199
+ }
200
+ }
201
+ }
202
+ return false
203
+ }
204
+ const notFoundPage = pagesContext?.pagesByRoute?.get('/404') ?? null
205
+ const pageMeta = hasTrailingSlash
206
+ ? (pagesContext?.pagesByRouteIndex?.get(requestedPath) ?? pagesContext?.pagesByRoute?.get(requestedPath) ?? null)
207
+ : (pagesContext?.pagesByRoute?.get(requestedPath) ?? null)
208
+ let filePath = pageMeta?.filePath || resolvePageFile(requestedPath)
209
+ let status = 200
210
+ let renderRoutePath = requestedPath
211
+
212
+ if (isExcludedPath()) {
213
+ if (notFoundPage) {
214
+ filePath = notFoundPage.filePath
215
+ renderRoutePath = '/404'
216
+ status = 404
217
+ } else {
218
+ return next()
219
+ }
220
+ } else if (requestedPath === '/404' && notFoundPage) {
221
+ filePath = notFoundPage.filePath
222
+ renderRoutePath = '/404'
223
+ status = 404
224
+ } else if (!pageMeta && !existsSync(filePath)) {
225
+ if (notFoundPage) {
226
+ filePath = notFoundPage.filePath
227
+ renderRoutePath = '/404'
228
+ status = 404
229
+ } else {
230
+ return next()
231
+ }
232
+ }
233
+
234
+ try {
235
+ const renderEpoch = htmlCacheEpoch
236
+ const cacheEntry = htmlCache.get(renderRoutePath)
237
+ if (cacheEntry && cacheEntry.filePath === filePath && cacheEntry.epoch === htmlCacheEpoch) {
238
+ res.statusCode = status
239
+ res.setHeader('Content-Type', 'text/html')
240
+ res.end(cacheEntry.html)
241
+ return
242
+ }
243
+
244
+ const html = await renderHtml({
245
+ routePath: renderRoutePath,
246
+ filePath,
247
+ components: {
248
+ ...themeComponents,
249
+ ...components
250
+ },
251
+ pagesContext,
252
+ pageMeta
253
+ })
254
+ if (renderEpoch === htmlCacheEpoch) {
255
+ htmlCache.set(renderRoutePath, {
256
+ html,
257
+ filePath,
258
+ epoch: renderEpoch
259
+ })
260
+ }
261
+ res.statusCode = status
262
+ res.setHeader('Content-Type', 'text/html')
263
+ res.end(html)
264
+ } catch (err) {
265
+ console.error(err)
266
+ res.statusCode = 500
267
+ res.end('Internal Server Error')
268
+ }
269
+ }
270
+
271
+ if (Array.isArray(server.middlewares.stack)) {
272
+ server.middlewares.stack.unshift({ route: '', handle: htmlMiddleware })
273
+ } else {
274
+ server.middlewares.use(htmlMiddleware)
275
+ }
276
+
277
+ await server.listen()
278
+ server.printUrls()
279
+
280
+ const invalidateRewindInject = () => {
281
+ const registryModule = server.moduleGraph.getModuleById('\0/.methanol_virtual_module/registry.js')
282
+ if (registryModule) {
283
+ server.moduleGraph.invalidateModule(registryModule)
284
+ }
285
+ const injectModule = server.moduleGraph.getModuleById('\0/.methanol_virtual_module/inject.js')
286
+ if (injectModule) {
287
+ server.moduleGraph.invalidateModule(injectModule)
288
+ }
289
+ }
290
+
291
+ let queue = Promise.resolve()
292
+ const enqueue = (task) => {
293
+ queue = queue.then(task).catch((err) => {
294
+ console.error(err)
295
+ })
296
+ return queue
297
+ }
298
+
299
+ const reload = () => {
300
+ server.ws.send({ type: 'full-reload' })
301
+ }
302
+
303
+ runInitialCompile()
304
+
305
+ const refreshPages = async () => {
306
+ await refreshPagesContext()
307
+ invalidateHtmlCache()
308
+ reload()
309
+ }
310
+
311
+ const getExportName = (filePath) => basename(filePath).split('.')[0]
312
+
313
+ const findComponentExt = (dir, exportName) => {
314
+ for (const ext of COMPONENT_EXTENSIONS) {
315
+ if (
316
+ existsSync(join(dir, `${exportName}${ext}`)) ||
317
+ existsSync(join(dir, `${exportName}.client${ext}`)) ||
318
+ existsSync(join(dir, `${exportName}.static${ext}`))
319
+ ) {
320
+ return ext
321
+ }
322
+ }
323
+ return null
324
+ }
325
+
326
+ const updateComponentEntry = async (filePath, { fallback = false } = {}) => {
327
+ bumpComponentImportNonce()
328
+ const exportName = getExportName(filePath)
329
+ const dir = dirname(filePath)
330
+ let ext = extname(filePath)
331
+ let { component, hasClient, staticPath } = await buildComponentEntry({
332
+ dir,
333
+ exportName,
334
+ ext
335
+ })
336
+ if (!component && fallback) {
337
+ ext = findComponentExt(dir, exportName)
338
+ if (ext) {
339
+ ;({ component, hasClient, staticPath } = await buildComponentEntry({
340
+ dir,
341
+ exportName,
342
+ ext
343
+ }))
344
+ }
345
+ }
346
+ if (!component) {
347
+ delete components[exportName]
348
+ componentSources.delete(exportName)
349
+ invalidateRegistryEntry(exportName)
350
+ return { hasClient: false }
351
+ }
352
+ if (!hasClient) {
353
+ invalidateRegistryEntry(exportName)
354
+ }
355
+ components[exportName] = component
356
+ if (staticPath) {
357
+ componentSources.set(exportName, staticPath)
358
+ }
359
+ return { hasClient }
360
+ }
361
+
362
+ const pageWatchPaths = [state.PAGES_DIR, state.THEME_PAGES_DIR].filter(Boolean)
363
+ const pageWatcher = chokidar.watch(pageWatchPaths, {
364
+ ignoreInitial: true
365
+ })
366
+
367
+ const PAGE_UPDATE_DEBOUNCE_MS = 30
368
+ const pageUpdateTimers = new Map()
369
+
370
+ const schedulePageUpdate = (filePath, kind) => {
371
+ const existing = pageUpdateTimers.get(filePath)
372
+ if (existing?.timer) {
373
+ clearTimeout(existing.timer)
374
+ }
375
+ const entry = {
376
+ kind,
377
+ timer: setTimeout(() => {
378
+ pageUpdateTimers.delete(filePath)
379
+ enqueue(() => handlePageUpdate(filePath, kind))
380
+ }, PAGE_UPDATE_DEBOUNCE_MS)
381
+ }
382
+ pageUpdateTimers.set(filePath, entry)
383
+ }
384
+
385
+ const resolveWatchedSource = (filePath) => {
386
+ const inUserPages = routePathFromFile(filePath, state.PAGES_DIR)
387
+ if (inUserPages) {
388
+ return { pagesDir: state.PAGES_DIR, source: 'user', routePath: inUserPages }
389
+ }
390
+ if (state.THEME_PAGES_DIR) {
391
+ const inThemePages = routePathFromFile(filePath, state.THEME_PAGES_DIR)
392
+ if (inThemePages) {
393
+ return { pagesDir: state.THEME_PAGES_DIR, source: 'theme', routePath: inThemePages }
394
+ }
395
+ }
396
+ return null
397
+ }
398
+
399
+ const updatePageEntry = async (filePath, resolved) => {
400
+ if (!pagesContext || !resolved) return false
401
+ pagesContext.clearDerivedTitle?.(filePath)
402
+ const nextEntry = await buildPageEntry({
403
+ filePath,
404
+ pagesDir: resolved.pagesDir,
405
+ source: resolved.source
406
+ })
407
+ if (!nextEntry) return false
408
+ const prevEntry = pagesContext.pages?.find?.((page) => page.filePath === filePath) || null
409
+ if (!prevEntry) return false
410
+ if (prevEntry.exclude !== nextEntry.exclude) return false
411
+ if (prevEntry.isIndex !== nextEntry.isIndex || prevEntry.dir !== nextEntry.dir) return false
412
+ Object.assign(prevEntry, nextEntry)
413
+ prevEntry.mdxComponent = null
414
+ prevEntry.toc = null
415
+ pagesContext.refreshPagesTree?.()
416
+ pagesContext.refreshLanguages?.()
417
+ if (prevEntry.frontmatter?.title == null) {
418
+ if (prevEntry.content && prevEntry.content.trim().length) {
419
+ await compilePageMdx(prevEntry, pagesContext)
420
+ // Avoid caching a potentially stale render; recompile on request.
421
+ prevEntry.mdxComponent = null
422
+ }
423
+ }
424
+ return true
425
+ }
426
+
427
+ const isUserHeadAsset = (filePath) => {
428
+ const name = basename(filePath)
429
+ if (name !== 'style.css' && name !== 'index.js' && name !== 'index.ts') {
430
+ return false
431
+ }
432
+ const root = resolve(state.PAGES_DIR || '')
433
+ return root && resolve(dirname(filePath)) === root
434
+ }
435
+
436
+ const handlePageUpdate = async (filePath, kind) => {
437
+ if (isUserHeadAsset(filePath)) {
438
+ invalidateHtmlCache()
439
+ reload()
440
+ return
441
+ }
442
+ const resolved = resolveWatchedSource(filePath)
443
+ if (kind === 'unlink') {
444
+ if (resolved?.routePath) {
445
+ htmlCache.delete(resolved.routePath)
446
+ } else {
447
+ return
448
+ }
449
+ await refreshPages()
450
+ return
451
+ }
452
+ const updated = await updatePageEntry(filePath, resolved)
453
+ if (updated) {
454
+ invalidateHtmlCache()
455
+ reload()
456
+ return
457
+ }
458
+ if (!resolved?.routePath) {
459
+ return
460
+ }
461
+ await refreshPages()
462
+ }
463
+
464
+ pageWatcher.on('change', (filePath) => {
465
+ schedulePageUpdate(filePath, 'change')
466
+ })
467
+
468
+ pageWatcher.on('add', (filePath) => {
469
+ schedulePageUpdate(filePath, 'add')
470
+ })
471
+ pageWatcher.on('unlink', (filePath) => {
472
+ schedulePageUpdate(filePath, 'unlink')
473
+ })
474
+ pageWatcher.on('addDir', () => {
475
+ enqueue(refreshPages)
476
+ })
477
+
478
+ pageWatcher.on('unlinkDir', () => {
479
+ enqueue(refreshPages)
480
+ })
481
+
482
+ const componentWatcher = chokidar.watch(state.COMPONENTS_DIR, {
483
+ ignoreInitial: true
484
+ })
485
+
486
+ componentWatcher.on('add', (filePath) => {
487
+ if (!isComponentFile(filePath)) {
488
+ return
489
+ }
490
+ if (isClientComponent(filePath)) {
491
+ enqueue(async () => {
492
+ const { hasClient } = await updateComponentEntry(filePath)
493
+ if (hasClient) {
494
+ invalidateRewindInject()
495
+ }
496
+ invalidateHtmlCache()
497
+ reload()
498
+ })
499
+ return
500
+ }
501
+ enqueue(async () => {
502
+ const { hasClient } = await updateComponentEntry(filePath)
503
+ invalidateHtmlCache()
504
+ if (hasClient) {
505
+ invalidateRewindInject()
506
+ }
507
+ reload()
508
+ })
509
+ })
510
+
511
+ componentWatcher.on('change', (filePath) => {
512
+ if (!isComponentFile(filePath)) {
513
+ return
514
+ }
515
+ if (isClientComponent(filePath)) {
516
+ enqueue(async () => {
517
+ await updateComponentEntry(filePath)
518
+ invalidateHtmlCache()
519
+ })
520
+ return
521
+ }
522
+ enqueue(async () => {
523
+ const { hasClient } = await updateComponentEntry(filePath)
524
+ invalidateHtmlCache()
525
+ if (hasClient) {
526
+ invalidateRewindInject()
527
+ }
528
+ reload()
529
+ })
530
+ })
531
+
532
+ componentWatcher.on('unlink', (filePath) => {
533
+ if (!isComponentFile(filePath)) return
534
+ if (isClientComponent(filePath)) {
535
+ enqueue(async () => {
536
+ await updateComponentEntry(filePath, { fallback: true })
537
+ invalidateRewindInject()
538
+ invalidateHtmlCache()
539
+ reload()
540
+ })
541
+ return
542
+ }
543
+ const exportName = getExportName(filePath)
544
+ const currentSource = componentSources.get(exportName)
545
+ if (currentSource && currentSource !== filePath && existsSync(currentSource)) {
546
+ return
547
+ }
548
+ enqueue(async () => {
549
+ const { hasClient } = await updateComponentEntry(filePath, {
550
+ fallback: true
551
+ })
552
+ invalidateHtmlCache()
553
+ if (hasClient) {
554
+ invalidateRewindInject()
555
+ }
556
+ reload()
557
+ })
558
+ })
559
+ }
package/src/main.js ADDED
@@ -0,0 +1,87 @@
1
+ /* Copyright Yukino Song, SudoMaker Ltd.
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements. See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership. The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied. See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ */
20
+
21
+ import { loadUserConfig, applyConfig } from './config.js'
22
+ import { runViteDev } from './dev-server.js'
23
+ import { buildHtmlEntries, runViteBuild } from './build-system.js'
24
+ import { runPagefind } from './pagefind.js'
25
+ import { runVitePreview } from './preview-server.js'
26
+ import { cli, state } from './state.js'
27
+ import { readFile } from 'fs/promises'
28
+
29
+ const printBanner = async () => {
30
+ try {
31
+ const pkgUrl = new URL('../package.json', import.meta.url)
32
+ const raw = await readFile(pkgUrl, 'utf-8')
33
+ const pkg = JSON.parse(raw)
34
+ const version = pkg?.version ? `v${pkg.version}` : ''
35
+ const isTty = Boolean(process.stdout && process.stdout.isTTY)
36
+ const label = `Methanol ${version}`.trim()
37
+ if (isTty) {
38
+ const bannerUrl = new URL('../banner.txt', import.meta.url)
39
+ const banner = await readFile(bannerUrl, 'utf-8')
40
+ console.log(banner.trimEnd())
41
+ console.log(`\n\t${label}\n`)
42
+ } else {
43
+ console.log(label)
44
+ }
45
+ } catch {
46
+ console.log('Methanol')
47
+ }
48
+ }
49
+
50
+ const main = async () => {
51
+ await printBanner()
52
+ const command = cli.command
53
+ if (!command) {
54
+ cli.showHelp()
55
+ process.exit(1)
56
+ }
57
+ const normalizedCommand = command === 'preview' ? 'serve' : command
58
+ const isDev = normalizedCommand === 'dev'
59
+ const isPreview = normalizedCommand === 'serve'
60
+ const isBuild = normalizedCommand === 'build'
61
+ const mode = isDev ? 'development' : 'production'
62
+ const config = await loadUserConfig(mode, cli.CLI_CONFIG_PATH)
63
+ await applyConfig(config, mode)
64
+ if (isDev) {
65
+ await runViteDev()
66
+ return
67
+ }
68
+ if (isPreview) {
69
+ await runVitePreview()
70
+ return
71
+ }
72
+ if (isBuild) {
73
+ const { entry, htmlCache } = await buildHtmlEntries()
74
+ await runViteBuild(entry, htmlCache)
75
+ if (state.PAGEFIND_ENABLED) {
76
+ await runPagefind()
77
+ }
78
+ return
79
+ }
80
+ cli.showHelp()
81
+ process.exit(1)
82
+ }
83
+
84
+ main().catch((err) => {
85
+ console.error(err)
86
+ process.exit(1)
87
+ })