3131// -------
3232//
3333// ### Standard library
34- use std:: { ffi:: OsStr , fs, io, path:: Path , process:: Command } ;
34+ use std:: {
35+ ffi:: OsStr ,
36+ fs, io,
37+ path:: { Path , PathBuf } ,
38+ process:: Command ,
39+ } ;
3540
3641// ### Third-party
3742use clap:: { Parser , Subcommand } ;
3843use cmd_lib:: run_cmd;
3944use current_platform:: CURRENT_PLATFORM ;
45+ use path_slash:: PathBufExt ;
4046use regex:: Regex ;
4147
4248// ### Local
@@ -68,6 +74,12 @@ enum Commands {
6874 Test ,
6975 /// Build everything.
7076 Build ,
77+ /// Build the Client.
78+ ClientBuild {
79+ /// True to build for distribution, instead of development.
80+ #[ arg( short, long, default_value_t = false ) ]
81+ dist : bool ,
82+ } ,
7183 /// Change the version for the client, server, and extensions.
7284 ChangeVersion {
7385 /// The new version number, such as "0.1.1".
@@ -98,17 +110,18 @@ enum Commands {
98110// These functions are called by the build support functions.
99111/// On Windows, scripts must be run from a shell; on Linux and OS X, scripts are
100112/// directly executable. This function runs a script regardless of OS.
101- fn run_script < T : AsRef < OsStr > , P : AsRef < Path > + std:: fmt:: Display > (
113+ fn run_script < T : AsRef < Path > , A : AsRef < OsStr > , P : AsRef < Path > + std:: fmt:: Display > (
102114 // The script to run.
103115 script : T ,
104116 // Arguments to pass.
105- args : & [ T ] ,
117+ args : & [ A ] ,
106118 // The directory to run the script in.
107119 dir : P ,
108120 // True to report errors based on the process' exit code; false to ignore
109121 // the code.
110122 check_exit_code : bool ,
111123) -> io:: Result < ( ) > {
124+ let script = OsStr :: new ( script. as_ref ( ) ) ;
112125 let mut process;
113126 if cfg ! ( windows) {
114127 process = Command :: new ( "cmd" ) ;
@@ -136,9 +149,11 @@ fn run_script<T: AsRef<OsStr>, P: AsRef<Path> + std::fmt::Display>(
136149/// programs (`robocopy`/`rsync`) to accomplish this. Very important: the `src`
137150/// **must** end with a `/`, otherwise the Windows and Linux copies aren't
138151/// identical.
139- fn quick_copy_dir < P : AsRef < OsStr > > ( src : P , dest : P , files : Option < P > ) -> io:: Result < ( ) > {
152+ fn quick_copy_dir < P : AsRef < Path > > ( src : P , dest : P , files : Option < P > ) -> io:: Result < ( ) > {
140153 assert ! ( src. as_ref( ) . to_string_lossy( ) . ends_with( '/' ) ) ;
141154 let mut copy_process;
155+ let src = OsStr :: new ( src. as_ref ( ) ) ;
156+ let dest = OsStr :: new ( dest. as_ref ( ) ) ;
142157 #[ cfg( windows) ]
143158 {
144159 // From `robocopy /?`:
@@ -165,11 +180,11 @@ fn quick_copy_dir<P: AsRef<OsStr>>(src: P, dest: P, files: Option<P>) -> io::Res
165180 . args ( [
166181 "/MIR" , "/MT" , "/NFL" , "/NDL" , "/NJH" , "/NJS" , "/NP" , "/NS" , "/NC" ,
167182 ] )
168- . arg ( & src)
169- . arg ( & dest) ;
183+ . arg ( src)
184+ . arg ( dest) ;
170185 // Robocopy expects the files to copy after the dest.
171186 if let Some ( files_) = & files {
172- copy_process. arg ( files_) ;
187+ copy_process. arg ( OsStr :: new ( files_. as_ref ( ) ) ) ;
173188 }
174189 }
175190 #[ cfg( not( windows) ) ]
@@ -186,7 +201,7 @@ fn quick_copy_dir<P: AsRef<OsStr>>(src: P, dest: P, files: Option<P>) -> io::Res
186201 let src_combined = match files. as_ref ( ) {
187202 Some ( files_) => {
188203 tmp = src. as_ref ( ) . to_os_string ( ) ;
189- tmp. push ( files_) ;
204+ tmp. push ( OsStr :: new ( files_. as_ref ( ) ) ) ;
190205 tmp. as_os_str ( )
191206 }
192207 None => src. as_ref ( ) ,
@@ -410,7 +425,7 @@ fn run_test() -> io::Result<()> {
410425fn run_build ( ) -> io:: Result < ( ) > {
411426 // Clean out all bundled files before the rebuild.
412427 remove_dir_all_if_exists ( "../client/static/bundled" ) ?;
413- run_script ( "npm" , & [ "run" , "build" ] , "../client" , true ) ?;
428+ run_client_build ( false ) ?;
414429 run_script ( "npm" , & [ "run" , "compile" ] , "../extensions/VSCode" , true ) ?;
415430 run_cmd ! (
416431 cargo build --manifest-path=../builder/Cargo . toml;
@@ -419,6 +434,77 @@ fn run_build() -> io::Result<()> {
419434 Ok ( ( ) )
420435}
421436
437+ // Build the NPM Client.
438+ fn run_client_build (
439+ // True to build for distribution, not development.
440+ dist : bool ,
441+ ) -> io:: Result < ( ) > {
442+ let esbuild = PathBuf :: from_slash ( "node_modules/.bin/esbuild" ) ;
443+ let distflag = if dist { "--minify" } else { "--sourcemap" } ;
444+ // This makes the program work from either the `server/` or `client/` directories.
445+ let rel_path = "../client" ;
446+
447+ // The main build for the Client.
448+ run_script (
449+ & esbuild,
450+ & [
451+ "src/CodeChatEditorFramework.mts" ,
452+ "src/CodeChatEditor.mts" ,
453+ "src/CodeChatEditor-test.mts" ,
454+ "src/css/CodeChatEditorProject.css" ,
455+ "src/css/CodeChatEditor.css" ,
456+ "--bundle" ,
457+ "--outdir=./static/bundled" ,
458+ distflag,
459+ "--format=esm" ,
460+ "--splitting" ,
461+ "--metafile=meta.json" ,
462+ "--entry-names=[dir]/[name]-[hash]" ,
463+ ] ,
464+ rel_path,
465+ true ,
466+ ) ?;
467+ // <a id="#pdf.js></a>The PDF viewer for use with VSCode. Built it separately, since it's loaded apart from the rest of the Client.
468+ run_script (
469+ & esbuild,
470+ & [
471+ "src/pdf.js/viewer.mjs" ,
472+ "node_modules/pdfjs-dist/build/pdf.worker.mjs" ,
473+ "--bundle" ,
474+ "--outdir=./static/bundled" ,
475+ distflag,
476+ "--format=esm" ,
477+ "--loader:.png=dataurl" ,
478+ "--loader:.svg=dataurl" ,
479+ "--loader:.gif=dataurl" ,
480+ ] ,
481+ rel_path,
482+ true ,
483+ ) ?;
484+ // Copy over the cmap (color map?) files, which the bundler doesn't handle.
485+ quick_copy_dir (
486+ format ! ( "{rel_path}/node_modules/pdfjs-dist/cmaps/" ) ,
487+ format ! ( "{rel_path}/static/bundled/node_modules/pdfjs-dist/cmaps/" ) ,
488+ None ,
489+ ) ?;
490+ // The HashReader isn't bundled; instead, it's used to translate the JSON metafile produced by the main esbuild run to the simpler format used by the CodeChat Editor. TODO: rewrite this in Rust.
491+ run_script (
492+ & esbuild,
493+ & [
494+ "src/HashReader.mts" ,
495+ "--outdir=." ,
496+ "--platform=node" ,
497+ "--format=esm" ,
498+ ] ,
499+ rel_path,
500+ true ,
501+ ) ?;
502+ run_script ( "node" , & [ "HashReader.js" ] , rel_path, true ) ?;
503+ // Finally, check the TypeScript with the (slow) TypeScript compiler.
504+ run_script ( "node_modules\\ .bin\\ tsc" , & [ "-noEmit" ] , rel_path, true ) ?;
505+ Ok ( ( ) )
506+ }
507+
422508fn run_change_version ( new_version : & String ) -> io:: Result < ( ) > {
423509 let replacement_string = format ! ( "${{1}}{new_version}${{2}}" ) ;
424510 search_and_replace_file (
@@ -490,6 +576,7 @@ impl Cli {
490576 Commands :: Update => run_update ( ) ,
491577 Commands :: Test => run_test ( ) ,
492578 Commands :: Build => run_build ( ) ,
579+ Commands :: ClientBuild { dist } => run_client_build ( * dist) ,
493580 Commands :: ChangeVersion { new_version } => run_change_version ( new_version) ,
494581 Commands :: Prerelease => run_prerelease ( ) ,
495582 Commands :: Postrelease { target, .. } => run_postrelease ( target) ,
0 commit comments