methanol 0.0.21 → 0.0.22

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/bin/methanol.js CHANGED
@@ -20,5 +20,4 @@
20
20
  * under the License.
21
21
  */
22
22
 
23
- import '../src/register-loader.js'
24
- import('../src/main.js')
23
+ import('../src/entry.js')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methanol",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "Static site generator powered by rEFui and MDX",
5
5
  "main": "./index.js",
6
6
  "type": "module",
@@ -29,21 +29,23 @@
29
29
  "@mdx-js/mdx": "^3.1.1",
30
30
  "@sindresorhus/fnv1a": "^3.1.0",
31
31
  "@stefanprobst/rehype-extract-toc": "^3.0.0",
32
- "@wooorm/starry-night": "^3.8.0",
32
+ "@wooorm/starry-night": "^3.9.0",
33
33
  "chokidar": "^5.0.0",
34
34
  "esbuild": "^0.27.2",
35
+ "fast-glob": "^3.3.3",
35
36
  "gray-matter": "^4.0.3",
36
37
  "hast-util-is-element": "^3.0.0",
38
+ "htmlparser2": "^10.1.0",
37
39
  "json5": "^2.2.3",
38
40
  "null-prototype-object": "^1.2.5",
41
+ "picomatch": "^4.0.3",
39
42
  "refui": "^0.17.1",
40
43
  "refurbish": "^0.1.8",
41
44
  "rehype-slug": "^6.0.0",
42
45
  "rehype-starry-night": "^2.2.0",
43
46
  "remark-gfm": "^4.0.1",
44
- "unist-util-visit": "^5.0.0",
47
+ "unist-util-visit": "^5.1.0",
45
48
  "vite": "^7.3.1",
46
- "vite-plugin-pwa": "^1.2.0",
47
49
  "workbox-core": "^7.4.0",
48
50
  "workbox-routing": "^7.4.0",
49
51
  "workbox-strategies": "^7.4.0",
package/src/base.js ADDED
@@ -0,0 +1,36 @@
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
+ export const normalizeBasePrefix = (value) => {
22
+ if (!value || value === '/' || value === './') return ''
23
+ if (typeof value !== 'string') return ''
24
+ let base = value.trim()
25
+ if (!base || base === '/' || base === './') return ''
26
+ if (base.startsWith('http://') || base.startsWith('https://')) {
27
+ try {
28
+ base = new URL(base).pathname
29
+ } catch {
30
+ return ''
31
+ }
32
+ }
33
+ if (!base.startsWith('/')) return ''
34
+ if (base.endsWith('/')) base = base.slice(0, -1)
35
+ return base
36
+ }
@@ -18,20 +18,22 @@
18
18
  * under the License.
19
19
  */
20
20
 
21
+ import { existsSync } from 'fs'
21
22
  import { writeFile, mkdir, rm, readFile, readdir, stat } from 'fs/promises'
22
- import { resolve, dirname, join } from 'path'
23
+ import { resolve, dirname, join, basename } from 'path'
24
+ import { createHash } from 'crypto'
23
25
  import { fileURLToPath } from 'url'
24
26
  import { build as viteBuild, mergeConfig, normalizePath } from 'vite'
25
- import { VitePWA } from 'vite-plugin-pwa'
26
27
  import { state, cli } from './state.js'
27
28
  import { resolveUserViteConfig } from './config.js'
28
29
  import { buildPagesContext } from './pages.js'
29
30
  import { selectFeedPages } from './feed.js'
30
31
  import { buildComponentRegistry } from './components.js'
31
32
  import { createBuildWorkers, runWorkerStage, terminateWorkers } from './workers/build-pool.js'
32
- import { methanolVirtualHtmlPlugin, methanolResolverPlugin } from './vite-plugins.js'
33
+ import { methanolResolverPlugin } from './vite-plugins.js'
33
34
  import { createStageLogger } from './stage-logger.js'
34
35
  import { preparePublicAssets } from './public-assets.js'
36
+ export { scanHtmlEntries, rewriteHtmlEntries } from './html/build-html.js'
35
37
 
36
38
  const __filename = fileURLToPath(import.meta.url)
37
39
  const __dirname = dirname(__filename)
@@ -40,11 +42,29 @@ const ensureDir = async (dir) => {
40
42
  await mkdir(dir, { recursive: true })
41
43
  }
42
44
 
45
+ const ensureSwEntry = async () => {
46
+ const entriesDir = resolve(resolveMethanolDir(), ENTRY_DIR)
47
+ await ensureDir(entriesDir)
48
+ const swEntryPath = resolve(entriesDir, 'sw-entry.js')
49
+ const swSource = normalizePath(resolve(__dirname, 'client', 'sw.js'))
50
+ await writeFile(swEntryPath, `import ${JSON.stringify(swSource)}\n`)
51
+ return swEntryPath
52
+ }
53
+
54
+ const INLINE_DIR = 'inline'
55
+ const ENTRY_DIR = 'entries'
56
+ const WRITE_CONCURRENCY_LIMIT = 32
57
+
58
+ const resolveMethanolDir = () => resolve(state.PAGES_DIR, '.methanol')
59
+
43
60
  const isHtmlFile = (name) => name.endsWith('.html')
44
61
  const collectHtmlFiles = async (dir, basePath = '') => {
45
62
  const entries = await readdir(dir)
46
63
  const files = []
47
64
  for (const entry of entries.sort()) {
65
+ if (entry.startsWith('.') || entry.startsWith('_')) {
66
+ continue
67
+ }
48
68
  const fullPath = resolve(dir, entry)
49
69
  const stats = await stat(fullPath)
50
70
  if (stats.isDirectory()) {
@@ -65,11 +85,18 @@ const collectHtmlFiles = async (dir, basePath = '') => {
65
85
  return files
66
86
  }
67
87
 
68
- export const buildHtmlEntries = async () => {
88
+ const hashKey = (value) =>
89
+ createHash('md5').update(value).digest('hex')
90
+
91
+ const makeInputKey = (prefix, value) => `${prefix}-${hashKey(value).slice(0, 12)}`
92
+
93
+ export const buildHtmlEntries = async (options = {}) => {
94
+ const keepWorkers = Boolean(options.keepWorkers)
69
95
  await resolveUserViteConfig('build') // Prepare `base`
70
- if (state.INTERMEDIATE_DIR) {
71
- await rm(state.INTERMEDIATE_DIR, { recursive: true, force: true })
72
- await ensureDir(state.INTERMEDIATE_DIR)
96
+ const htmlStageDir = state.INTERMEDIATE_DIR || resolve(state.PAGES_DIR, '.methanol/html')
97
+ if (htmlStageDir) {
98
+ await rm(htmlStageDir, { recursive: true, force: true })
99
+ await ensureDir(htmlStageDir)
73
100
  }
74
101
 
75
102
  const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
@@ -84,8 +111,13 @@ export const buildHtmlEntries = async () => {
84
111
  }
85
112
  await buildComponentRegistry()
86
113
  const pagesContext = await buildPagesContext({ compileAll: false })
87
- const entry = {}
88
- const htmlCache = new Map()
114
+ const htmlEntries = []
115
+ const htmlEntryNames = new Set()
116
+ const inlineDir = resolve(resolveMethanolDir(), INLINE_DIR)
117
+ await rm(inlineDir, { recursive: true, force: true })
118
+ await ensureDir(inlineDir)
119
+ const renderScans = new Map()
120
+ const renderScansById = new Map()
89
121
  const resolveOutputName = (page) => {
90
122
  if (page.routePath === '/') return 'index'
91
123
  if (page.isIndex && page.dir) {
@@ -94,15 +126,16 @@ export const buildHtmlEntries = async () => {
94
126
  return page.routePath.slice(1)
95
127
  }
96
128
 
97
- const pages = pagesContext.pages || []
129
+ const pages = pagesContext.pagesAll || pagesContext.pages || []
98
130
  const totalPages = pages.length
99
131
  const { workers, assignments } = createBuildWorkers(totalPages)
132
+ const writeConcurrency = Math.max(1, Math.floor(WRITE_CONCURRENCY_LIMIT / workers.length))
100
133
  const excludedRoutes = Array.from(pagesContext.excludedRoutes || [])
101
134
  const excludedDirs = Array.from(pagesContext.excludedDirs || [])
102
135
  const rssContent = new Map()
103
- const intermediateOutputs = []
104
136
  let feedIds = []
105
137
  let feedAssignments = null
138
+ let completedRun = false
106
139
  try {
107
140
  await runWorkerStage({
108
141
  workers,
@@ -193,7 +226,9 @@ export const buildHtmlEntries = async () => {
193
226
  type: 'render',
194
227
  stage: 'render',
195
228
  ids: assignments[index],
196
- feedIds: feedAssignments ? feedAssignments[index] : []
229
+ feedIds: feedAssignments ? feedAssignments[index] : [],
230
+ htmlStageDir,
231
+ writeConcurrency
197
232
  }
198
233
  })),
199
234
  onProgress: (count) => {
@@ -205,14 +240,15 @@ export const buildHtmlEntries = async () => {
205
240
  if (!result || typeof result.id !== 'number') return
206
241
  const page = pages[result.id]
207
242
  if (!page) return
208
- const html = result.html
209
- const name = resolveOutputName(page)
210
- const id = normalizePath(resolve(state.VIRTUAL_HTML_OUTPUT_ROOT, `${name}.html`))
211
- entry[name] = id
212
- htmlCache.set(id, html)
213
- if (state.INTERMEDIATE_DIR) {
214
- intermediateOutputs.push({ name, id })
243
+ if (result.scan) {
244
+ renderScansById.set(result.id, result.scan)
215
245
  }
246
+ const name = resolveOutputName(page)
247
+ const outPath = htmlStageDir
248
+ ? (result.stagePath || resolve(htmlStageDir, `${name}.html`))
249
+ : `${name}.html`
250
+ htmlEntryNames.add(name)
251
+ htmlEntries.push({ name, routePath: page.routePath, stagePath: outPath, source: 'rendered' })
216
252
  if (result.feedContent != null) {
217
253
  const key = page.path || page.routePath
218
254
  if (key) {
@@ -222,16 +258,18 @@ export const buildHtmlEntries = async () => {
222
258
  }
223
259
  })
224
260
  stageLogger.end(renderToken)
261
+
262
+ for (const [id, scan] of renderScansById.entries()) {
263
+ const page = pages[id]
264
+ if (!page || !scan) continue
265
+ const name = resolveOutputName(page)
266
+ const stagePath = htmlStageDir ? resolve(htmlStageDir, `${name}.html`) : `${name}.html`
267
+ renderScans.set(stagePath, scan)
268
+ }
269
+ completedRun = true
225
270
  } finally {
226
- await terminateWorkers(workers)
227
- }
228
- if (state.INTERMEDIATE_DIR) {
229
- for (const output of intermediateOutputs) {
230
- const html = htmlCache.get(output.id)
231
- if (typeof html !== 'string') continue
232
- const outPath = resolve(state.INTERMEDIATE_DIR, `${output.name}.html`)
233
- await ensureDir(dirname(outPath))
234
- await writeFile(outPath, html)
271
+ if (!keepWorkers || !completedRun) {
272
+ await terminateWorkers(workers)
235
273
  }
236
274
  }
237
275
 
@@ -255,27 +293,133 @@ export const buildHtmlEntries = async () => {
255
293
  }
256
294
  const name = file.relativePath.replace(/\.html$/, '')
257
295
  const outputName = name === 'index' ? 'index' : name
258
- if (entry[outputName]) {
296
+ if (htmlEntryNames.has(outputName)) {
259
297
  continue
260
298
  }
261
299
  const html = await readFile(file.fullPath, 'utf-8')
262
- const id = normalizePath(resolve(state.VIRTUAL_HTML_OUTPUT_ROOT, `${outputName}.html`))
263
- entry[outputName] = id
264
- htmlCache.set(id, html)
265
- if (state.INTERMEDIATE_DIR) {
266
- const outPath = resolve(state.INTERMEDIATE_DIR, file.relativePath)
300
+ const outPath = htmlStageDir ? resolve(htmlStageDir, file.relativePath) : null
301
+ if (outPath) {
267
302
  await ensureDir(dirname(outPath))
268
303
  await writeFile(outPath, html)
269
304
  }
305
+ htmlEntryNames.add(outputName)
306
+ htmlEntries.push({
307
+ name: outputName,
308
+ routePath: outputName === 'index'
309
+ ? '/'
310
+ : outputName.endsWith('/index')
311
+ ? `/${outputName.slice(0, -'/index'.length)}/`
312
+ : `/${outputName}`,
313
+ stagePath: outPath,
314
+ inputPath: file.fullPath,
315
+ source: 'static'
316
+ })
270
317
  }
271
318
 
272
- return { entry, htmlCache, pagesContext, rssContent }
319
+ return {
320
+ htmlEntries,
321
+ htmlStageDir,
322
+ pagesContext,
323
+ rssContent,
324
+ renderScans,
325
+ renderScansById,
326
+ workers: keepWorkers ? workers : null,
327
+ assignments: keepWorkers ? assignments : null
328
+ }
329
+ }
330
+
331
+ export const rewriteHtmlEntriesInWorkers = async ({
332
+ pages = [],
333
+ htmlStageDir,
334
+ manifest,
335
+ scanResult,
336
+ renderScansById,
337
+ onProgress,
338
+ workers: existingWorkers = null,
339
+ assignments: existingAssignments = null
340
+ }) => {
341
+ const totalPages = pages.length
342
+ if (!totalPages) return
343
+ const useExisting = Array.isArray(existingWorkers) && Array.isArray(existingAssignments)
344
+ const { workers, assignments } = useExisting
345
+ ? { workers: existingWorkers, assignments: existingAssignments }
346
+ : createBuildWorkers(totalPages)
347
+ try {
348
+ if (!useExisting) {
349
+ await runWorkerStage({
350
+ workers,
351
+ stage: 'setPagesLite',
352
+ messages: workers.map((worker) => ({
353
+ worker,
354
+ message: {
355
+ type: 'setPagesLite',
356
+ stage: 'setPagesLite',
357
+ pages
358
+ }
359
+ }))
360
+ })
361
+ }
362
+
363
+ const entryModules = Array.isArray(scanResult?.entryModules) ? scanResult.entryModules : []
364
+ const commonScripts = Array.isArray(scanResult?.commonScripts) ? scanResult.commonScripts : []
365
+ const commonEntry = scanResult?.commonScriptEntry?.manifestKey
366
+ ? manifest?.[scanResult.commonScriptEntry.manifestKey] || manifest?.[`/${scanResult.commonScriptEntry.manifestKey}`]
367
+ : null
368
+
369
+ await runWorkerStage({
370
+ workers,
371
+ stage: 'rewrite',
372
+ messages: workers.map((worker, index) => {
373
+ const ids = assignments[index] || []
374
+ const scans = {}
375
+ if (renderScansById) {
376
+ for (const id of ids) {
377
+ const scan = renderScansById.get(id)
378
+ if (scan) scans[id] = scan
379
+ }
380
+ }
381
+ return {
382
+ worker,
383
+ message: {
384
+ type: 'rewrite',
385
+ stage: 'rewrite',
386
+ ids,
387
+ htmlStageDir,
388
+ manifest,
389
+ entryModules,
390
+ commonScripts,
391
+ commonEntry,
392
+ scans
393
+ }
394
+ }
395
+ }),
396
+ onProgress: (count) => {
397
+ if (typeof onProgress === 'function') {
398
+ onProgress(count, totalPages)
399
+ }
400
+ }
401
+ })
402
+ } finally {
403
+ if (!useExisting) {
404
+ await terminateWorkers(workers)
405
+ }
406
+ }
273
407
  }
274
408
 
275
- export const runViteBuild = async (entry, htmlCache) => {
409
+ export const runViteBuild = async (inputs) => {
276
410
  const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
277
411
  const stageLogger = createStageLogger(logEnabled)
278
412
  const token = stageLogger.start('Building bundle')
413
+ const rewriteOptions = inputs.rewrite || null
414
+ const preWrite = typeof inputs.preWrite === 'function' ? inputs.preWrite : null
415
+ const postWrite = typeof inputs.postWrite === 'function' ? inputs.postWrite : null
416
+ let manifestData = null
417
+ let bundleEnded = false
418
+ const endBundleStage = () => {
419
+ if (bundleEnded) return
420
+ bundleEnded = true
421
+ stageLogger.end(token)
422
+ }
279
423
 
280
424
  if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
281
425
  await preparePublicAssets({
@@ -285,20 +429,53 @@ export const runViteBuild = async (entry, htmlCache) => {
285
429
  })
286
430
  }
287
431
  const copyPublicDirEnabled = state.STATIC_DIR !== false
288
- const manifestConfig = state.PWA_OPTIONS?.manifest || {}
289
- const resolvedManifest = { name: state.SITE_NAME, short_name: state.SITE_NAME, ...manifestConfig }
432
+ const entryModules = Array.isArray(inputs.entryModules) ? inputs.entryModules : []
433
+ const entryInputs = entryModules
434
+ .filter((entry) => entry && entry.kind !== 'style')
435
+ .map((entry) => entry.fsPath)
436
+ .filter(Boolean)
437
+ .sort()
438
+ const htmlEntries = Array.isArray(inputs.htmlEntries) ? inputs.htmlEntries : []
439
+ const htmlInputs = htmlEntries
440
+ .filter((entry) => entry?.source === 'static' && entry.inputPath)
441
+ .map((entry) => entry.inputPath)
442
+ .sort()
443
+ if (cli.CLI_VERBOSE && entryInputs.length === 0) {
444
+ console.log('Vite pipeline: no wrapper entries detected (no module scripts/stylesheets found)')
445
+ }
446
+ const rollupInput = {}
447
+ for (const entryPath of entryInputs) {
448
+ const normalized = normalizePath(entryPath)
449
+ rollupInput[makeInputKey('chunk', normalized)] = normalized
450
+ }
451
+ for (const htmlPath of htmlInputs) {
452
+ const normalized = normalizePath(htmlPath)
453
+ rollupInput[makeInputKey('html', normalized)] = normalized
454
+ }
455
+ let swEntryPath = null
456
+ if (state.PWA_ENABLED) {
457
+ swEntryPath = await ensureSwEntry()
458
+ if (swEntryPath) {
459
+ const normalized = normalizePath(swEntryPath)
460
+ rollupInput['sw'] = normalized
461
+ }
462
+ }
290
463
  const baseConfig = {
291
464
  configFile: false,
292
465
  root: state.PAGES_DIR,
293
- appType: 'mpa',
466
+ appType: 'custom',
294
467
  publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
295
468
  logLevel: cli.CLI_VERBOSE ? 'info' : 'silent',
296
469
  build: {
297
470
  outDir: state.DIST_DIR,
298
471
  emptyOutDir: true,
299
472
  rollupOptions: {
300
- input: entry
473
+ input: rollupInput,
474
+ output: {
475
+ entryFileNames: (chunk) => (chunk.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js')
476
+ }
301
477
  },
478
+ manifest: true,
302
479
  copyPublicDir: copyPublicDirEnabled,
303
480
  minify: true
304
481
  },
@@ -309,42 +486,115 @@ export const runViteBuild = async (entry, htmlCache) => {
309
486
  resolve: {
310
487
  dedupe: ['refui', 'methanol']
311
488
  },
312
- plugins: [
313
- methanolVirtualHtmlPlugin(htmlCache),
314
- methanolResolverPlugin(),
315
- state.PWA_ENABLED
316
- ? VitePWA({
317
- injectRegister: 'auto',
318
- registerType: 'autoUpdate',
319
- strategies: 'injectManifest',
320
- srcDir: resolve(__dirname, 'client'),
321
- filename: 'sw.js',
322
- manifest: resolvedManifest,
323
- injectManifest: {
324
- globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
325
- ...(state.PWA_OPTIONS?.injectManifest || {})
326
- }
327
- })
328
- : null
329
- ]
489
+ plugins: [methanolResolverPlugin()]
330
490
  }
331
491
  const userConfig = await resolveUserViteConfig('build')
332
492
  const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
333
493
 
334
- const originalLog = console.log
335
- const originalWarn = console.warn
336
- if (!cli.CLI_VERBOSE) {
337
- console.log = () => {}
338
- console.warn = () => {}
494
+ // Keep the pipeline deterministic: do not let user configs override the build root/output/inputs.
495
+ finalConfig.root = state.PAGES_DIR
496
+ finalConfig.appType = 'custom'
497
+ finalConfig.publicDir = state.STATIC_DIR === false ? false : state.STATIC_DIR
498
+ finalConfig.build = {
499
+ ...(finalConfig.build || {}),
500
+ outDir: state.DIST_DIR,
501
+ emptyOutDir: true,
502
+ manifest: finalConfig.build?.manifest === undefined ? true : finalConfig.build.manifest,
503
+ copyPublicDir: copyPublicDirEnabled,
504
+ rollupOptions: {
505
+ ...((finalConfig.build && finalConfig.build.rollupOptions) || {}),
506
+ input: rollupInput,
507
+ output: (() => {
508
+ const existing = finalConfig.build?.rollupOptions?.output
509
+ const outputConfig = Array.isArray(existing) ? existing[0] || {} : existing || {}
510
+ return {
511
+ ...outputConfig,
512
+ entryFileNames: (chunk) => (chunk.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js')
513
+ }
514
+ })()
515
+ }
516
+ }
517
+
518
+ const manifestFileName = typeof finalConfig.build?.manifest === 'string'
519
+ ? finalConfig.build.manifest
520
+ : '.vite/manifest.json'
521
+ const manifestPath = resolve(state.DIST_DIR, manifestFileName.replace(/^\//, ''))
522
+ const manifestEnabled = finalConfig.build?.manifest !== false
523
+ let rewriteDone = false
524
+ let preWriteDone = false
525
+ let postWriteDone = false
526
+ const loadManifest = async () => {
527
+ if (!manifestEnabled) return null
528
+ if (!existsSync(manifestPath)) return null
529
+ const raw = await readFile(manifestPath, 'utf-8')
530
+ return JSON.parse(raw)
531
+ }
532
+ const runPreWrite = async () => {
533
+ if (rewriteOptions) {
534
+ endBundleStage()
535
+ }
536
+ if (preWrite) {
537
+ await preWrite({ manifest: manifestData })
538
+ }
539
+ }
540
+ const runPostWrite = async () => {
541
+ if (postWrite) {
542
+ await postWrite({ manifest: manifestData })
543
+ }
544
+ }
545
+ const runRewrite = async () => {
546
+ if (!rewriteOptions || rewriteDone || !manifestData) return
547
+ const rewriteToken = logEnabled ? stageLogger.start('Rewriting HTML') : null
548
+ try {
549
+ await rewriteHtmlEntriesInWorkers({
550
+ ...rewriteOptions,
551
+ manifest: manifestData,
552
+ onProgress: (done, total) => {
553
+ if (!rewriteToken) return
554
+ stageLogger.update(rewriteToken, `Rewriting HTML [${done}/${total}]`)
555
+ }
556
+ })
557
+ } finally {
558
+ rewriteDone = true
559
+ if (rewriteToken) {
560
+ stageLogger.end(rewriteToken)
561
+ }
562
+ }
563
+ }
564
+
565
+ const postBundlePlugin = {
566
+ name: 'methanol:post-bundle',
567
+ apply: 'build',
568
+ enforce: 'post',
569
+ async writeBundle() {
570
+ manifestData = await loadManifest()
571
+ await runPreWrite()
572
+ await runRewrite()
573
+ await runPostWrite()
574
+ }
339
575
  }
340
576
 
577
+ finalConfig.plugins = [...(finalConfig.plugins || []), postBundlePlugin]
578
+
579
+ await viteBuild(finalConfig)
580
+ endBundleStage()
581
+ const methanolManifestDir = resolve(state.PAGES_DIR, '.methanol')
582
+ const methanolManifestPath = resolve(methanolManifestDir, 'manifest.json')
341
583
  try {
342
- await viteBuild(finalConfig)
343
- } finally {
344
- if (!cli.CLI_VERBOSE) {
345
- console.log = originalLog
346
- console.warn = originalWarn
584
+ const parsed = manifestData || (manifestEnabled ? JSON.parse(await readFile(manifestPath, 'utf-8')) : null)
585
+ if (!parsed) return {}
586
+ await ensureDir(methanolManifestDir)
587
+ await writeFile(methanolManifestPath, JSON.stringify(parsed, null, 2))
588
+ await rm(manifestPath, { force: true })
589
+ const manifestDir = dirname(manifestPath)
590
+ if (basename(manifestDir) === '.vite') {
591
+ await rm(manifestDir, { recursive: true, force: true })
592
+ }
593
+ return parsed
594
+ } catch (error) {
595
+ if (cli.CLI_VERBOSE) {
596
+ console.log(`Vite pipeline: failed to read manifest at ${manifestPath}`)
347
597
  }
598
+ return {}
348
599
  }
349
- stageLogger.end(token)
350
600
  }