@@ -4,7 +4,7 @@ import {copyFile, readFile, rm, stat, writeFile} from "node:fs/promises";
44import { basename , dirname , extname , join } from "node:path/posix" ;
55import type { Config } from "./config.js" ;
66import { getDuckDBManifest } from "./duckdb.js" ;
7- import { CliError } from "./error.js" ;
7+ import { CliError , enoent } from "./error.js" ;
88import { getClientPath , prepareOutput } from "./files.js" ;
99import { findModule , getModuleHash , readJavaScript } from "./javascript/module.js" ;
1010import { transpileModule } from "./javascript/transpile.js" ;
@@ -54,7 +54,7 @@ export async function build(
5454 { config} : BuildOptions ,
5555 effects : BuildEffects = new FileBuildEffects ( config . output , join ( config . root , ".observablehq" , "cache" ) )
5656) : Promise < void > {
57- const { root, loaders, duckdb} = config ;
57+ const { root, loaders, title , duckdb} = config ;
5858 Telemetry . record ( { event : "build" , step : "start" } ) ;
5959
6060 // Prepare for build (such as by emptying the existing output root).
@@ -75,6 +75,25 @@ export async function build(
7575 let assetCount = 0 ;
7676 let pageCount = 0 ;
7777 const pagePaths = new Set < string > ( ) ;
78+
79+ const buildManifest : BuildManifest = {
80+ ...( title && { title} ) ,
81+ config : { root} ,
82+ pages : [ ] ,
83+ modules : [ ] ,
84+ files : [ ]
85+ } ;
86+
87+ // file is the serving path relative to the base (e.g., /foo)
88+ // path is the source file relative to the source root (e.g., /foo.md)
89+ const addToManifest = ( type : string , file : string , { title, path} : { title ?: string | null ; path : string } ) => {
90+ buildManifest [ type ] . push ( {
91+ path : config . normalizePath ( file ) ,
92+ source : join ( "/" , path ) , // TODO have route return path with leading slash?
93+ ...( title != null && { title} )
94+ } ) ;
95+ } ;
96+
7897 for await ( const path of config . paths ( ) ) {
7998 effects . output . write ( `${ faint ( "load" ) } ${ path } ` ) ;
8099 const start = performance . now ( ) ;
@@ -91,6 +110,7 @@ export async function build(
91110 effects . output . write ( `${ faint ( "in" ) } ${ ( elapsed >= 100 ? yellow : faint ) ( `${ elapsed } ms` ) } \n` ) ;
92111 outputs . set ( path , { type : "module" , resolvers} ) ;
93112 ++ assetCount ;
113+ addToManifest ( "modules" , path , module ) ;
94114 continue ;
95115 }
96116 }
@@ -99,6 +119,7 @@ export async function build(
99119 effects . output . write ( `${ faint ( "copy" ) } ${ join ( root , path ) } ${ faint ( "→" ) } ` ) ;
100120 const sourcePath = join ( root , await file . load ( { useStale : true } , effects ) ) ;
101121 await effects . copyFile ( sourcePath , path ) ;
122+ addToManifest ( "files" , path , file ) ;
102123 ++ assetCount ;
103124 continue ;
104125 }
@@ -209,7 +230,10 @@ export async function build(
209230 // Copy over referenced files, accumulating hashed aliases.
210231 for ( const file of files ) {
211232 effects . output . write ( `${ faint ( "copy" ) } ${ join ( root , file ) } ${ faint ( "→" ) } ` ) ;
212- const sourcePath = join ( root , await loaders . loadFile ( join ( "/" , file ) , { useStale : true } , effects ) ) ;
233+ const path = join ( "/" , file ) ;
234+ const loader = loaders . find ( path ) ;
235+ if ( ! loader ) throw enoent ( path ) ;
236+ const sourcePath = join ( root , await loader . load ( { useStale : true } , effects ) ) ;
213237 const contents = await readFile ( sourcePath ) ;
214238 const hash = createHash ( "sha256" ) . update ( contents ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
215239 const alias = applyHash ( join ( "/_file" , file ) , hash ) ;
@@ -338,15 +362,13 @@ export async function build(
338362 }
339363
340364 // Render pages!
341- const buildManifest : BuildManifest = { pages : [ ] } ;
342- if ( config . title ) buildManifest . title = config . title ;
343365 for ( const [ path , output ] of outputs ) {
344366 effects . output . write ( `${ faint ( "render" ) } ${ path } ${ faint ( "→" ) } ` ) ;
345367 if ( output . type === "page" ) {
346368 const { page, resolvers} = output ;
347369 const html = await renderPage ( page , { ...config , path, resolvers} ) ;
348370 await effects . writeFile ( `${ path } .html` , html ) ;
349- buildManifest . pages . push ( { path : config . normalizePath ( path ) , title : page . title } ) ;
371+ addToManifest ( "pages" , path , page ) ;
350372 } else {
351373 const { resolvers} = output ;
352374 const source = await renderModule ( root , path , resolvers ) ;
@@ -507,5 +529,8 @@ export class FileBuildEffects implements BuildEffects {
507529
508530export interface BuildManifest {
509531 title ?: string ;
510- pages : { path : string ; title : string | null } [ ] ;
532+ config : { root : string } ;
533+ pages : { path : string ; title ?: string | null ; source ?: string } [ ] ;
534+ modules : { path : string ; source ?: string } [ ] ;
535+ files : { path : string ; source ?: string } [ ] ;
511536}
0 commit comments