methanol 0.0.7 → 0.0.9

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.md CHANGED
@@ -44,6 +44,11 @@ export default () => ({
44
44
  // optional: code highlighting (Starry Night)
45
45
  starryNight: false,
46
46
 
47
+ // optional: site metadata
48
+ site: {
49
+ base: '/docs/'
50
+ },
51
+
47
52
  // optional: theme sources
48
53
  theme: {
49
54
  sources: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methanol",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Static site generator powered by rEFui and MDX",
5
5
  "main": "./index.js",
6
6
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  "refurbish": "^0.1.8",
40
40
  "rehype-slug": "^6.0.0",
41
41
  "rehype-starry-night": "^2.2.0",
42
+ "remark-gfm": "^4.0.1",
42
43
  "unist-util-visit": "^5.0.0",
43
44
  "vite": "^7.3.0",
44
45
  "yargs": "^18.0.0"
package/src/assets.js CHANGED
@@ -27,4 +27,4 @@ const __dirname = dirname(__filename)
27
27
 
28
28
  export const INJECT_SCRIPT = readFileSync(resolve(__dirname, './virtual-module/inject.js'), 'utf-8')
29
29
  export const LOADER_SCRIPT = readFileSync(resolve(__dirname, './virtual-module/loader.js'), 'utf-8')
30
- export const PAGEFIND_SCRIPT = readFileSync(resolve(__dirname, './virtual-module/pagefind.js'), 'utf-8')
30
+ export const PAGEFIND_LOADER_SCRIPT = readFileSync(resolve(__dirname, './virtual-module/pagefind-loader.js'), 'utf-8')
@@ -61,12 +61,13 @@ const collectHtmlFiles = async (dir, basePath = '') => {
61
61
  }
62
62
 
63
63
  export const buildHtmlEntries = async () => {
64
+ await resolveUserViteConfig('build') // Prepare `base`
64
65
  if (state.INTERMEDIATE_DIR) {
65
66
  await rm(state.INTERMEDIATE_DIR, { recursive: true, force: true })
66
67
  await ensureDir(state.INTERMEDIATE_DIR)
67
68
  }
68
69
 
69
- const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build'
70
+ const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
70
71
  const stageLogger = createStageLogger(logEnabled)
71
72
  const themeComponentsDir = state.THEME_COMPONENTS_DIR
72
73
  const themeEnv = state.THEME_ENV
@@ -163,6 +164,10 @@ export const buildHtmlEntries = async () => {
163
164
  }
164
165
 
165
166
  export const runViteBuild = async (entry, htmlCache) => {
167
+ const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
168
+ const stageLogger = createStageLogger(logEnabled)
169
+ const token = stageLogger.start('Building bundle')
170
+
166
171
  if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
167
172
  await preparePublicAssets({
168
173
  themeDir: state.THEME_ASSETS_DIR,
@@ -174,8 +179,9 @@ export const runViteBuild = async (entry, htmlCache) => {
174
179
  const baseConfig = {
175
180
  configFile: false,
176
181
  root: state.PAGES_DIR,
177
- base: '/',
182
+ appType: 'mpa',
178
183
  publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
184
+ logLevel: cli.CLI_VERBOSE ? 'info' : 'silent',
179
185
  build: {
180
186
  outDir: state.DIST_DIR,
181
187
  emptyOutDir: true,
@@ -197,4 +203,5 @@ export const runViteBuild = async (entry, htmlCache) => {
197
203
  const userConfig = await resolveUserViteConfig('build')
198
204
  const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
199
205
  await viteBuild(finalConfig)
206
+ stageLogger.end(token)
200
207
  }
package/src/config.js CHANGED
@@ -24,9 +24,11 @@ import { resolve, isAbsolute, extname, basename } from 'path'
24
24
  import { pathToFileURL } from 'url'
25
25
  import { mergeConfig } from 'vite'
26
26
  import { cli, state } from './state.js'
27
+ import { logger } from './logger.js'
27
28
  import { HTMLRenderer } from './renderer.js'
28
29
  import { rewindEnv } from './components.js'
29
30
  import { env as createEnv } from './rewind.js'
31
+ import { cached, cachedStr } from './utils.js'
30
32
  import defaultTheme from '../themes/default/index.js'
31
33
 
32
34
  const CONFIG_FILENAMES = [
@@ -79,6 +81,52 @@ const resolveThemePublicDir = (root, value) => {
79
81
  return isAbsolute(value) ? value : resolve(root, value)
80
82
  }
81
83
 
84
+ let devBaseWarningShown = false
85
+
86
+ const normalizeSiteBase = (value) => {
87
+ if (value == null) return null
88
+ if (typeof value !== 'string') return null
89
+ const trimmed = value.trim()
90
+ if (!trimmed || trimmed === './') return '/'
91
+ if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
92
+ return trimmed
93
+ }
94
+ let base = trimmed
95
+ if (!base.startsWith('/')) {
96
+ base = `/${base}`
97
+ }
98
+ if (!base.endsWith('/')) {
99
+ base = `${base}/`
100
+ }
101
+ return base
102
+ }
103
+
104
+ const normalizeViteBase = (value) => {
105
+ if (!value || value === '/' || value === './') return '/'
106
+ if (typeof value !== 'string') return '/'
107
+ let base = value.trim()
108
+ if (!base || base === './') return '/'
109
+ if (base.startsWith('http://') || base.startsWith('https://')) {
110
+ try {
111
+ base = new URL(base).pathname
112
+ } catch {
113
+ return '/'
114
+ }
115
+ }
116
+ if (!base.startsWith('/')) return '/'
117
+ if (!base.endsWith('/')) {
118
+ base = `${base}/`
119
+ }
120
+ return base
121
+ }
122
+
123
+ const warnDevBase = (value) => {
124
+ if (devBaseWarningShown) return
125
+ devBaseWarningShown = true
126
+ const label = value ? ` (received "${value}")` : ''
127
+ logger.warn(`Methanol: \`base\`${label} is disabled in dev mode due to module resolution inconsistencies in Vite. Using "/".\n`)
128
+ }
129
+
82
130
  const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key)
83
131
  const normalizeSources = (value, root) => {
84
132
  if (!value) return []
@@ -216,11 +264,20 @@ export const applyConfig = async (config, mode) => {
216
264
  state.ROOT_DIR = root
217
265
  const configSiteName = cli.CLI_SITE_NAME ?? config.site?.name ?? null
218
266
  state.SITE_NAME = configSiteName || basename(root) || 'Methanol Site'
219
- state.USER_SITE = config.site && typeof config.site === 'object' ? { ...config.site } : null
267
+ const userSite = config.site && typeof config.site === 'object' ? { ...config.site } : null
268
+ const siteBase = normalizeSiteBase(cli.CLI_BASE || userSite?.base)
269
+ state.SITE_BASE = siteBase
270
+ if (userSite) {
271
+ if (siteBase == null) {
272
+ delete userSite.base
273
+ } else {
274
+ userSite.base = siteBase
275
+ }
276
+ }
277
+ state.USER_SITE = userSite
220
278
  if (mode) {
221
279
  state.CURRENT_MODE = mode
222
280
  }
223
- // config.paths / config.dirs are intentionally ignored (deprecated)
224
281
 
225
282
  const pagesDirValue = cli.CLI_PAGES_DIR || config.pagesDir
226
283
  const componentsDirValue = cli.CLI_COMPONENTS_DIR || config.componentsDir
@@ -336,6 +393,9 @@ export const applyConfig = async (config, mode) => {
336
393
  state.THEME_POST_BUILD_HOOKS = normalizeHooks(state.USER_THEME?.postBuild)
337
394
  state.THEME_PRE_BUNDLE_HOOKS = normalizeHooks(state.USER_THEME?.preBundle)
338
395
  state.THEME_POST_BUNDLE_HOOKS = normalizeHooks(state.USER_THEME?.postBundle)
396
+ if (hasOwn(config, 'gfm')) {
397
+ state.GFM_ENABLED = config.gfm !== false
398
+ }
339
399
  const starryNight = resolveStarryNightConfig(config.starryNight)
340
400
  const cliCodeHighlighting = cli.CLI_CODE_HIGHLIGHTING
341
401
  if (cliCodeHighlighting != null) {
@@ -392,6 +452,7 @@ export const resolveUserViteConfig = async (command) => {
392
452
  if (state.RESOLVED_VITE_CONFIG !== undefined) {
393
453
  return state.RESOLVED_VITE_CONFIG
394
454
  }
455
+
395
456
  const resolveConfig = async (config) => {
396
457
  if (!config) return null
397
458
  if (typeof config === 'function') {
@@ -407,16 +468,85 @@ export const resolveUserViteConfig = async (command) => {
407
468
  }
408
469
  return config || null
409
470
  }
471
+
410
472
  const themeConfig = await resolveConfig(state.USER_THEME.vite)
411
473
  const userConfig = await resolveConfig(state.USER_VITE_CONFIG)
474
+
412
475
  if (!themeConfig && !userConfig) {
413
- state.RESOLVED_VITE_CONFIG = null
414
- return null
476
+ if (state.SITE_BASE) {
477
+ state.RESOLVED_VITE_CONFIG = { base: state.SITE_BASE }
478
+ } else {
479
+ state.RESOLVED_VITE_CONFIG = null
480
+ }
481
+ state.VITE_BASE = normalizeViteBase(state.RESOLVED_VITE_CONFIG?.base || state.SITE_BASE || '/')
482
+ if (command === 'serve') {
483
+ if (state.VITE_BASE !== '/' || (state.SITE_BASE && state.SITE_BASE !== '/')) {
484
+ warnDevBase(state.RESOLVED_VITE_CONFIG?.base || state.SITE_BASE || '')
485
+ }
486
+ if (state.RESOLVED_VITE_CONFIG) {
487
+ state.RESOLVED_VITE_CONFIG.base = '/'
488
+ }
489
+ state.VITE_BASE = '/'
490
+ }
491
+ return state.RESOLVED_VITE_CONFIG
415
492
  }
493
+
416
494
  state.RESOLVED_VITE_CONFIG = themeConfig
417
495
  ? userConfig
418
496
  ? mergeConfig(themeConfig, userConfig)
419
497
  : themeConfig
420
498
  : userConfig
499
+
500
+ const userHasBase = userConfig && hasOwn(userConfig, 'base')
501
+ if (state.SITE_BASE && (cli.CLI_BASE || !userHasBase)) {
502
+ state.RESOLVED_VITE_CONFIG.base = state.SITE_BASE
503
+ }
504
+
505
+ state.VITE_BASE = normalizeViteBase(state.RESOLVED_VITE_CONFIG?.base || state.SITE_BASE || '/')
506
+
507
+ if (command === 'serve') {
508
+ if (state.VITE_BASE !== '/' || (state.SITE_BASE && state.SITE_BASE !== '/')) {
509
+ warnDevBase(state.RESOLVED_VITE_CONFIG?.base || state.SITE_BASE || '')
510
+ }
511
+ state.RESOLVED_VITE_CONFIG.base = '/'
512
+ state.VITE_BASE = '/'
513
+ }
514
+
421
515
  return state.RESOLVED_VITE_CONFIG
422
516
  }
517
+
518
+ export const resolveBasePrefix = cached(() => {
519
+ const value = state.VITE_BASE || state.SITE_BASE || '/'
520
+ if (!value || value === '/' || value === './') return ''
521
+ if (typeof value !== 'string') return ''
522
+ let base = value.trim()
523
+ if (!base || base === '/' || base === './') return ''
524
+ if (base.startsWith('http://') || base.startsWith('https://')) {
525
+ try {
526
+ base = new URL(base).pathname
527
+ } catch {
528
+ return ''
529
+ }
530
+ }
531
+ if (!base.startsWith('/')) return ''
532
+ if (base.endsWith('/')) base = base.slice(0, -1)
533
+ return base
534
+ })
535
+
536
+ export const withBase = cachedStr((value) => {
537
+ if (!value || typeof value !== 'string') return value
538
+ if (
539
+ value.startsWith('http://') ||
540
+ value.startsWith('https://') ||
541
+ value.startsWith('//') ||
542
+ value.startsWith('data:') ||
543
+ value.startsWith('mailto:') ||
544
+ value.startsWith('#')
545
+ ) {
546
+ return value
547
+ }
548
+ if (!value.startsWith('/')) return value
549
+ const prefix = resolveBasePrefix()
550
+ if (!prefix || value.startsWith(`${prefix}/`)) return value
551
+ return `${prefix}${value}`
552
+ })
package/src/dev-server.js CHANGED
@@ -49,6 +49,7 @@ export const runViteDev = async () => {
49
49
  const baseConfig = {
50
50
  configFile: false,
51
51
  root: state.PAGES_DIR,
52
+ appType: 'mpa',
52
53
  publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
53
54
  server: {
54
55
  fs: {
@@ -66,6 +67,8 @@ export const runViteDev = async () => {
66
67
  }
67
68
  const userConfig = await resolveUserViteConfig('serve')
68
69
  const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
70
+ const devBase = state.VITE_BASE || '/'
71
+ const devBasePrefix = devBase === '/' ? '' : devBase.slice(0, -1)
69
72
  if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
70
73
  await preparePublicAssets({
71
74
  themeDir: state.THEME_ASSETS_DIR,
@@ -169,7 +172,14 @@ export const runViteDev = async () => {
169
172
  }
170
173
 
171
174
  const resolvePageFile = (routePath) => {
172
- const name = routePath === '/' ? 'index' : routePath.slice(1)
175
+ let name = 'index'
176
+ if (routePath && routePath !== '/') {
177
+ if (routePath.endsWith('/')) {
178
+ name = `${routePath.slice(1, -1)}/index`
179
+ } else {
180
+ name = routePath.slice(1)
181
+ }
182
+ }
173
183
  const mdxPath = resolve(state.PAGES_DIR, `${name}.mdx`)
174
184
  if (existsSync(mdxPath)) return mdxPath
175
185
  const mdPath = resolve(state.PAGES_DIR, `${name}.md`)
@@ -181,6 +191,8 @@ export const runViteDev = async () => {
181
191
  const candidates = []
182
192
  if (pathname === '/' || pathname === '') {
183
193
  candidates.push('/index.html')
194
+ } else if (pathname.endsWith('/')) {
195
+ candidates.push(`${pathname}index.html`)
184
196
  } else if (pathname.endsWith('.html')) {
185
197
  candidates.push(pathname)
186
198
  } else {
@@ -196,7 +208,7 @@ export const runViteDev = async () => {
196
208
  if (hasMdx) return false
197
209
  const baseName = basename(relativePath, '.html')
198
210
  if (baseName.startsWith('_') || baseName.startsWith('.')) return false
199
- const excludedDirs = pagesContext?.excludedDirs
211
+ const excludedDirs = pagesContext.excludedDirs
200
212
  if (excludedDirs?.size) {
201
213
  const dir = relativePath.split('/').slice(0, -1).join('/')
202
214
  for (const excludedDir of excludedDirs) {
@@ -206,9 +218,9 @@ export const runViteDev = async () => {
206
218
  }
207
219
  }
208
220
  }
209
- const excludedRoutes = pagesContext?.excludedRoutes
221
+ const excludedRoutes = pagesContext.excludedRoutes
210
222
  if (excludedRoutes?.has(requestedPath)) return false
211
- const excludedDirPaths = pagesContext?.excludedDirPaths
223
+ const excludedDirPaths = pagesContext.excludedDirPaths
212
224
  if (excludedDirPaths?.size) {
213
225
  for (const dirPath of excludedDirPaths) {
214
226
  if (requestedPath === dirPath || requestedPath.startsWith(`${dirPath}/`)) {
@@ -223,9 +235,6 @@ export const runViteDev = async () => {
223
235
  if (!req.url || req.method !== 'GET') {
224
236
  return next()
225
237
  }
226
- if (req.url.startsWith('/@vite') || req.url.startsWith('/__vite')) {
227
- return next()
228
- }
229
238
 
230
239
  const url = new URL(req.url, 'http://methanol')
231
240
  let pathname = url.pathname
@@ -233,7 +242,20 @@ export const runViteDev = async () => {
233
242
  pathname = decodeURIComponent(pathname)
234
243
  } catch {}
235
244
  const originalPathname = pathname
236
- const hasTrailingSlash = originalPathname.endsWith('/') && originalPathname !== '/'
245
+ if (devBase !== '/') {
246
+ const baseNoSlash = devBasePrefix
247
+ if (originalPathname === baseNoSlash) {
248
+ pathname = '/'
249
+ } else if (originalPathname.startsWith(devBase)) {
250
+ pathname = originalPathname.slice(devBase.length - 1)
251
+ } else {
252
+ return next()
253
+ }
254
+ }
255
+
256
+ if (pathname.startsWith('/@vite') || pathname.startsWith('/__vite')) {
257
+ return next()
258
+ }
237
259
 
238
260
  if (pathname.includes('.') && !pathname.endsWith('.html')) {
239
261
  return next()
@@ -251,15 +273,11 @@ export const runViteDev = async () => {
251
273
  routePath = '/'
252
274
  }
253
275
  }
254
- if (routePath.endsWith('/') && routePath !== '/') {
255
- routePath = routePath.slice(0, -1)
256
- }
257
-
258
276
  const requestedPath = routePath
259
277
  const isExcludedPath = () => {
260
- const excludedRoutes = pagesContext?.excludedRoutes
278
+ const excludedRoutes = pagesContext.excludedRoutes
261
279
  if (excludedRoutes?.has(requestedPath)) return true
262
- const excludedDirPaths = pagesContext?.excludedDirPaths
280
+ const excludedDirPaths = pagesContext.excludedDirPaths
263
281
  if (excludedDirPaths?.size) {
264
282
  for (const dirPath of excludedDirPaths) {
265
283
  if (requestedPath === dirPath || requestedPath.startsWith(`${dirPath}/`)) {
@@ -269,10 +287,8 @@ export const runViteDev = async () => {
269
287
  }
270
288
  return false
271
289
  }
272
- const notFoundPage = pagesContext?.pagesByRoute?.get('/404') ?? null
273
- const pageMeta = hasTrailingSlash
274
- ? (pagesContext?.pagesByRouteIndex?.get(requestedPath) ?? pagesContext?.pagesByRoute?.get(requestedPath) ?? null)
275
- : (pagesContext?.pagesByRoute?.get(requestedPath) ?? null)
290
+ const notFoundPage = pagesContext.pagesByRoute.get('/404')
291
+ let pageMeta = pagesContext.pagesByRoute.get(requestedPath)
276
292
  let filePath = pageMeta?.filePath || resolvePageFile(requestedPath)
277
293
  const hasMdx = Boolean(pageMeta) || existsSync(filePath)
278
294
  let status = 200
@@ -291,7 +307,9 @@ export const runViteDev = async () => {
291
307
  }
292
308
  try {
293
309
  const html = await readFile(candidate, 'utf-8')
294
- const candidateUrl = `/${relativePath}`
310
+ const candidateUrl = devBasePrefix
311
+ ? `${devBasePrefix}/${relativePath}`
312
+ : `/${relativePath}`
295
313
  const transformed = await server.transformIndexHtml(candidateUrl, html)
296
314
  res.statusCode = 200
297
315
  res.setHeader('Content-Type', 'text/html')
@@ -326,6 +344,8 @@ export const runViteDev = async () => {
326
344
  } else {
327
345
  return next()
328
346
  }
347
+ } else if (requestedPath.endsWith('/index')) {
348
+ renderRoutePath = requestedPath.slice(0, -5) // remove last 'index'
329
349
  }
330
350
 
331
351
  try {
@@ -338,6 +358,8 @@ export const runViteDev = async () => {
338
358
  return
339
359
  }
340
360
 
361
+ pageMeta ??= pagesContext.getPageByRoute(renderRoutePath, { filePath })
362
+
341
363
  const html = await renderHtml({
342
364
  routePath: renderRoutePath,
343
365
  filePath,
@@ -374,16 +396,18 @@ export const runViteDev = async () => {
374
396
  await server.listen()
375
397
  server.printUrls()
376
398
 
377
- const invalidateRewindInject = () => {
378
- const registryModule = server.moduleGraph.getModuleById('\0/.methanol_virtual_module/registry.js')
379
- if (registryModule) {
380
- server.moduleGraph.invalidateModule(registryModule)
381
- }
382
- const injectModule = server.moduleGraph.getModuleById('\0/.methanol_virtual_module/inject.js')
383
- if (injectModule) {
384
- server.moduleGraph.invalidateModule(injectModule)
399
+ const _invalidate = (id) => {
400
+ const _module = server.moduleGraph.getModuleById(id)
401
+ if (_module) {
402
+ server.moduleGraph.invalidateModule(_module)
385
403
  }
386
404
  }
405
+ const invalidateRewindInject = () => {
406
+ _invalidate('\0/.methanol_virtual_module/registry.js')
407
+ _invalidate('\0methanol:registry')
408
+ _invalidate('\0/.methanol_virtual_module/inject.js')
409
+ _invalidate('\0methanol:inject')
410
+ }
387
411
 
388
412
  let queue = Promise.resolve()
389
413
  const enqueue = (task) => {
package/src/logger.js ADDED
@@ -0,0 +1,80 @@
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
+ const supportColor =
22
+ typeof process !== 'undefined' &&
23
+ process.stdout &&
24
+ (process.stdout.isTTY || process.env.FORCE_COLOR)
25
+
26
+ const formatter = (open, close, replace = open) =>
27
+ supportColor
28
+ ? (input) => {
29
+ const string = '' + input
30
+ const index = string.indexOf(close, open.length)
31
+ return ~index
32
+ ? open + getReplace(string, index, close, replace) + close
33
+ : open + string + close
34
+ }
35
+ : (input) => '' + input
36
+
37
+ const getReplace = (string, index, close, replace) => {
38
+ const head = string.substring(0, index) + replace
39
+ const tail = string.substring(index + close.length)
40
+ const next = tail.indexOf(close)
41
+ return ~next ? head + getReplace(tail, next, close, replace) : head + tail
42
+ }
43
+
44
+ export const style = {
45
+ reset: formatter('\x1b[0m', '\x1b[0m'),
46
+ bold: formatter('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'),
47
+ dim: formatter('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m'),
48
+ italic: formatter('\x1b[3m', '\x1b[23m'),
49
+ underline: formatter('\x1b[4m', '\x1b[24m'),
50
+ inverse: formatter('\x1b[7m', '\x1b[27m'),
51
+ hidden: formatter('\x1b[8m', '\x1b[28m'),
52
+ strikethrough: formatter('\x1b[9m', '\x1b[29m'),
53
+
54
+ black: formatter('\x1b[30m', '\x1b[39m'),
55
+ red: formatter('\x1b[31m', '\x1b[39m'),
56
+ green: formatter('\x1b[32m', '\x1b[39m'),
57
+ yellow: formatter('\x1b[33m', '\x1b[39m'),
58
+ blue: formatter('\x1b[34m', '\x1b[39m'),
59
+ magenta: formatter('\x1b[35m', '\x1b[39m'),
60
+ cyan: formatter('\x1b[36m', '\x1b[39m'),
61
+ white: formatter('\x1b[37m', '\x1b[39m'),
62
+ gray: formatter('\x1b[90m', '\x1b[39m'),
63
+
64
+ bgBlack: formatter('\x1b[40m', '\x1b[49m'),
65
+ bgRed: formatter('\x1b[41m', '\x1b[49m'),
66
+ bgGreen: formatter('\x1b[42m', '\x1b[49m'),
67
+ bgYellow: formatter('\x1b[43m', '\x1b[49m'),
68
+ bgBlue: formatter('\x1b[44m', '\x1b[49m'),
69
+ bgMagenta: formatter('\x1b[45m', '\x1b[49m'),
70
+ bgCyan: formatter('\x1b[46m', '\x1b[49m'),
71
+ bgWhite: formatter('\x1b[47m', '\x1b[49m')
72
+ }
73
+
74
+ export const logger = {
75
+ info: (msg) => console.log(`${style.blue('ℹ')} ${msg}`),
76
+ success: (msg) => console.log(`${style.green('✔')} ${msg}`),
77
+ warn: (msg) => console.log(`${style.yellow('⚠')} ${msg}`),
78
+ error: (msg) => console.log(`${style.red('✖')} ${msg}`),
79
+ dim: (msg) => console.log(style.dim(msg))
80
+ }
package/src/main.js CHANGED
@@ -26,13 +26,14 @@ import { runVitePreview } from './preview-server.js'
26
26
  import { cli, state } from './state.js'
27
27
  import { HTMLRenderer } from './renderer.js'
28
28
  import { readFile } from 'fs/promises'
29
+ import { style, logger } from './logger.js'
29
30
 
30
31
  const printBanner = async () => {
31
32
  try {
32
33
  const pkgUrl = new URL('../package.json', import.meta.url)
33
34
  const raw = await readFile(pkgUrl, 'utf-8')
34
35
  const pkg = JSON.parse(raw)
35
- const version = pkg?.version ? `v${pkg.version}` : ''
36
+ const version = `v${pkg.version}`
36
37
  const isTty = Boolean(process.stdout && process.stdout.isTTY)
37
38
  const label = `Methanol ${version}`.trim()
38
39
  if (isTty) {
@@ -63,6 +64,7 @@ const main = async () => {
63
64
  const config = await loadUserConfig(mode, cli.CLI_CONFIG_PATH)
64
65
  await applyConfig(config, mode)
65
66
  const userSite = state.USER_SITE || {}
67
+ const siteBase = state.VITE_BASE ?? userSite.base ?? null
66
68
  const hookContext = {
67
69
  mode,
68
70
  root: state.ROOT_DIR,
@@ -73,6 +75,7 @@ const main = async () => {
73
75
  HTMLRenderer,
74
76
  site: {
75
77
  ...userSite,
78
+ base: siteBase,
76
79
  name: state.SITE_NAME,
77
80
  root: state.ROOT_DIR,
78
81
  pagesDir: state.PAGES_DIR,
@@ -105,6 +108,7 @@ const main = async () => {
105
108
  return
106
109
  }
107
110
  if (isBuild) {
111
+ const startTime = performance.now()
108
112
  await runHooks(state.USER_PRE_BUILD_HOOKS)
109
113
  await runHooks(state.THEME_PRE_BUILD_HOOKS)
110
114
  const { entry, htmlCache, pagesContext } = await buildHtmlEntries()
@@ -127,6 +131,11 @@ const main = async () => {
127
131
  }
128
132
  await runHooks(state.THEME_POST_BUILD_HOOKS, buildContext)
129
133
  await runHooks(state.USER_POST_BUILD_HOOKS, buildContext)
134
+ const endTime = performance.now()
135
+ const duration = endTime - startTime
136
+ const timeString = duration > 1000 ? `${(duration / 1000).toFixed(2)}s` : `${Math.round(duration)}ms`
137
+ console.log()
138
+ logger.success(`Build completed in ${style.bold(timeString)}.`)
130
139
  return
131
140
  }
132
141
  cli.showHelp()