2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2023-04-19 17:50:56 -04:00
2024-03-31 10:58:19 -04:00
use std ::borrow ::Cow ;
2023-05-10 20:06:59 -04:00
use std ::collections ::BTreeMap ;
2024-07-09 17:33:41 -04:00
use std ::collections ::HashMap ;
2024-07-03 20:54:33 -04:00
use std ::collections ::VecDeque ;
2023-05-10 20:06:59 -04:00
use std ::env ::current_exe ;
2024-03-22 17:03:56 -04:00
use std ::ffi ::OsString ;
2024-02-13 11:22:30 -05:00
use std ::fs ;
2024-08-01 00:15:13 -04:00
use std ::fs ::File ;
2024-03-05 17:14:49 -05:00
use std ::future ::Future ;
2023-04-19 17:50:56 -04:00
use std ::io ::Read ;
use std ::io ::Seek ;
use std ::io ::SeekFrom ;
use std ::io ::Write ;
use std ::path ::Path ;
2023-05-10 20:06:59 -04:00
use std ::path ::PathBuf ;
2024-02-13 11:22:30 -05:00
use std ::process ::Command ;
2023-04-19 17:50:56 -04:00
use deno_ast ::ModuleSpecifier ;
2024-07-03 20:54:33 -04:00
use deno_config ::workspace ::PackageJsonDepResolution ;
2024-08-07 03:43:05 -04:00
use deno_config ::workspace ::ResolverWorkspaceJsrPackage ;
2024-07-03 20:54:33 -04:00
use deno_config ::workspace ::Workspace ;
2024-07-04 20:41:01 -04:00
use deno_config ::workspace ::WorkspaceResolver ;
2023-07-28 11:46:26 -04:00
use deno_core ::anyhow ::bail ;
2023-04-19 17:50:56 -04:00
use deno_core ::anyhow ::Context ;
use deno_core ::error ::AnyError ;
use deno_core ::futures ::io ::AllowStdIo ;
use deno_core ::futures ::AsyncReadExt ;
use deno_core ::futures ::AsyncSeekExt ;
use deno_core ::serde_json ;
use deno_core ::url ::Url ;
2023-05-17 17:38:50 -04:00
use deno_npm ::NpmSystemInfo ;
2024-07-03 20:54:33 -04:00
use deno_runtime ::deno_node ::PackageJson ;
2024-06-13 18:29:27 -04:00
use deno_semver ::npm ::NpmVersionReqParseError ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageReq ;
2024-08-07 03:43:05 -04:00
use deno_semver ::Version ;
2023-08-21 05:53:52 -04:00
use deno_semver ::VersionReqSpecifierParseError ;
2024-07-03 20:54:33 -04:00
use eszip ::EszipRelativeFileBaseUrl ;
use indexmap ::IndexMap ;
2023-04-19 17:50:56 -04:00
use log ::Level ;
use serde ::Deserialize ;
use serde ::Serialize ;
use crate ::args ::CaData ;
use crate ::args ::CliOptions ;
use crate ::args ::CompileFlags ;
2024-07-03 20:54:33 -04:00
use crate ::args ::PackageJsonInstallDepsProvider ;
2024-05-06 19:21:58 -04:00
use crate ::args ::PermissionFlags ;
2024-01-22 12:37:28 -05:00
use crate ::args ::UnstableConfig ;
2023-04-19 17:50:56 -04:00
use crate ::cache ::DenoDir ;
use crate ::file_fetcher ::FileFetcher ;
2024-06-03 17:17:08 -04:00
use crate ::http_util ::HttpClientProvider ;
2023-05-10 20:06:59 -04:00
use crate ::npm ::CliNpmResolver ;
2023-09-29 09:26:25 -04:00
use crate ::npm ::InnerCliNpmResolverRef ;
2024-07-03 20:54:33 -04:00
use crate ::standalone ::virtual_fs ::VfsEntry ;
2024-08-08 03:19:05 -04:00
use crate ::util ::archive ;
2024-07-03 20:54:33 -04:00
use crate ::util ::fs ::canonicalize_path_maybe_not_exists ;
2023-04-19 17:50:56 -04:00
use crate ::util ::progress_bar ::ProgressBar ;
use crate ::util ::progress_bar ::ProgressBarStyle ;
2023-05-10 20:06:59 -04:00
use super ::virtual_fs ::FileBackedVfs ;
use super ::virtual_fs ::VfsBuilder ;
use super ::virtual_fs ::VfsRoot ;
use super ::virtual_fs ::VirtualDirectory ;
2023-04-19 17:50:56 -04:00
const MAGIC_TRAILER : & [ u8 ; 8 ] = b " d3n0l4nd " ;
2023-11-29 09:32:23 -05:00
#[ derive(Deserialize, Serialize) ]
pub enum NodeModules {
Managed {
2024-07-03 20:54:33 -04:00
/// Relative path for the node_modules directory in the vfs.
node_modules_dir : Option < String > ,
2023-11-29 09:32:23 -05:00
} ,
Byonm {
2024-07-10 14:46:25 -04:00
root_node_modules_dir : Option < String > ,
2023-11-29 09:32:23 -05:00
} ,
}
2024-07-03 20:54:33 -04:00
#[ derive(Deserialize, Serialize) ]
pub struct SerializedWorkspaceResolverImportMap {
pub specifier : String ,
pub json : String ,
}
2024-08-07 03:43:05 -04:00
#[ derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize) ]
pub struct SerializedResolverWorkspaceJsrPackage {
pub relative_base : String ,
pub name : String ,
pub version : Option < Version > ,
pub exports : IndexMap < String , String > ,
}
2024-07-03 20:54:33 -04:00
#[ derive(Deserialize, Serialize) ]
pub struct SerializedWorkspaceResolver {
pub import_map : Option < SerializedWorkspaceResolverImportMap > ,
2024-08-07 03:43:05 -04:00
pub jsr_pkgs : Vec < SerializedResolverWorkspaceJsrPackage > ,
2024-07-03 20:54:33 -04:00
pub package_jsons : BTreeMap < String , serde_json ::Value > ,
pub pkg_json_resolution : PackageJsonDepResolution ,
}
2023-04-19 17:50:56 -04:00
#[ derive(Deserialize, Serialize) ]
pub struct Metadata {
pub argv : Vec < String > ,
pub seed : Option < u64 > ,
2024-05-06 19:21:58 -04:00
pub permissions : PermissionFlags ,
2023-04-19 17:50:56 -04:00
pub location : Option < Url > ,
pub v8_flags : Vec < String > ,
pub log_level : Option < Level > ,
pub ca_stores : Option < Vec < String > > ,
pub ca_data : Option < Vec < u8 > > ,
pub unsafely_ignore_certificate_errors : Option < Vec < String > > ,
2024-07-09 17:33:41 -04:00
pub env_vars_from_env_file : HashMap < String , String > ,
2024-07-03 20:54:33 -04:00
pub workspace_resolver : SerializedWorkspaceResolver ,
pub entrypoint_key : String ,
2023-11-29 09:32:23 -05:00
pub node_modules : Option < NodeModules > ,
2024-01-18 18:30:49 -05:00
pub disable_deprecated_api_warning : bool ,
2024-01-22 12:37:28 -05:00
pub unstable_config : UnstableConfig ,
2023-05-10 20:06:59 -04:00
}
pub fn load_npm_vfs ( root_dir_path : PathBuf ) -> Result < FileBackedVfs , AnyError > {
2024-08-01 00:15:13 -04:00
let data = libsui ::find_section ( " d3n0l4nd " ) . unwrap ( ) ;
// We do the first part sync so it can complete quickly
let trailer : [ u8 ; TRAILER_SIZE ] = data [ 0 .. TRAILER_SIZE ] . try_into ( ) . unwrap ( ) ;
let trailer = match Trailer ::parse ( & trailer ) ? {
None = > panic! ( " Could not find trailer " ) ,
Some ( trailer ) = > trailer ,
} ;
let data = & data [ TRAILER_SIZE .. ] ;
let vfs_data =
& data [ trailer . npm_vfs_pos as usize .. trailer . npm_files_pos as usize ] ;
let mut dir : VirtualDirectory = serde_json ::from_slice ( vfs_data ) ? ;
2023-05-10 20:06:59 -04:00
// align the name of the directory with the root dir
dir . name = root_dir_path
. file_name ( )
. unwrap ( )
. to_string_lossy ( )
. to_string ( ) ;
let fs_root = VfsRoot {
dir ,
root_path : root_dir_path ,
start_file_offset : trailer . npm_files_pos ,
} ;
2024-08-01 00:15:13 -04:00
Ok ( FileBackedVfs ::new ( data . to_vec ( ) , fs_root ) )
2023-04-19 17:50:56 -04:00
}
2023-05-10 20:06:59 -04:00
fn write_binary_bytes (
2024-08-01 00:15:13 -04:00
mut file_writer : File ,
2023-04-19 17:50:56 -04:00
original_bin : Vec < u8 > ,
metadata : & Metadata ,
eszip : eszip ::EszipV2 ,
2023-05-10 20:06:59 -04:00
npm_vfs : Option < & VirtualDirectory > ,
npm_files : & Vec < Vec < u8 > > ,
2024-08-01 00:15:13 -04:00
compile_flags : & CompileFlags ,
2023-04-19 17:50:56 -04:00
) -> Result < ( ) , AnyError > {
let metadata = serde_json ::to_string ( metadata ) ? . as_bytes ( ) . to_vec ( ) ;
2023-05-10 20:06:59 -04:00
let npm_vfs = serde_json ::to_string ( & npm_vfs ) ? . as_bytes ( ) . to_vec ( ) ;
2023-04-19 17:50:56 -04:00
let eszip_archive = eszip . into_bytes ( ) ;
2024-08-01 00:15:13 -04:00
let mut writer = Vec ::new ( ) ;
2023-05-10 20:06:59 -04:00
// write the trailer, which includes the positions
// of the data blocks in the file
writer . write_all ( & {
2024-08-01 00:15:13 -04:00
let metadata_pos = eszip_archive . len ( ) as u64 ;
2023-05-10 20:06:59 -04:00
let npm_vfs_pos = metadata_pos + ( metadata . len ( ) as u64 ) ;
let npm_files_pos = npm_vfs_pos + ( npm_vfs . len ( ) as u64 ) ;
Trailer {
2024-08-01 00:15:13 -04:00
eszip_pos : 0 ,
2023-05-10 20:06:59 -04:00
metadata_pos ,
npm_vfs_pos ,
npm_files_pos ,
}
. as_bytes ( )
} ) ? ;
2023-04-19 17:50:56 -04:00
2024-08-01 00:15:13 -04:00
writer . write_all ( & eszip_archive ) ? ;
writer . write_all ( & metadata ) ? ;
writer . write_all ( & npm_vfs ) ? ;
for file in npm_files {
writer . write_all ( file ) ? ;
}
let target = compile_flags . resolve_target ( ) ;
if target . contains ( " linux " ) {
libsui ::Elf ::new ( & original_bin ) . append ( & writer , & mut file_writer ) ? ;
} else if target . contains ( " windows " ) {
libsui ::PortableExecutable ::from ( & original_bin ) ?
. write_resource ( " d3n0l4nd " , writer ) ?
. build ( & mut file_writer ) ? ;
} else if target . contains ( " darwin " ) {
libsui ::Macho ::from ( original_bin ) ?
. write_section ( " d3n0l4nd " , writer ) ?
2024-08-01 05:11:24 -04:00
. build_and_sign ( & mut file_writer ) ? ;
2024-08-01 00:15:13 -04:00
}
2023-04-19 17:50:56 -04:00
Ok ( ( ) )
}
pub fn is_standalone_binary ( exe_path : & Path ) -> bool {
2024-08-01 00:15:13 -04:00
let Ok ( data ) = std ::fs ::read ( exe_path ) else {
2023-04-19 17:50:56 -04:00
return false ;
} ;
2024-08-01 00:15:13 -04:00
libsui ::utils ::is_elf ( & data )
2024-08-01 05:11:24 -04:00
| | libsui ::utils ::is_pe ( & data )
| | libsui ::utils ::is_macho ( & data )
2023-04-19 17:50:56 -04:00
}
/// This function will try to run this binary as a standalone binary
/// produced by `deno compile`. It determines if this is a standalone
2023-05-10 20:06:59 -04:00
/// binary by skipping over the trailer width at the end of the file,
/// then checking for the magic trailer string `d3n0l4nd`. If found,
/// the bundle is executed. If not, this function exits with `Ok(None)`.
2024-03-05 17:14:49 -05:00
pub fn extract_standalone (
2024-03-31 10:58:19 -04:00
cli_args : Cow < Vec < OsString > > ,
2024-03-05 17:14:49 -05:00
) -> Result <
Option < impl Future < Output = Result < ( Metadata , eszip ::EszipV2 ) , AnyError > > > ,
AnyError ,
> {
2024-08-01 00:15:13 -04:00
let Some ( data ) = libsui ::find_section ( " d3n0l4nd " ) else {
return Ok ( None ) ;
} ;
2024-03-05 17:14:49 -05:00
// We do the first part sync so it can complete quickly
2024-08-01 00:15:13 -04:00
let trailer = match Trailer ::parse ( & data [ 0 .. TRAILER_SIZE ] ) ? {
2023-05-10 20:06:59 -04:00
None = > return Ok ( None ) ,
Some ( trailer ) = > trailer ,
} ;
2023-04-19 17:50:56 -04:00
2024-03-31 10:58:19 -04:00
let cli_args = cli_args . into_owned ( ) ;
2024-03-05 17:14:49 -05:00
// If we have an eszip, read it out
Ok ( Some ( async move {
let bufreader =
2024-08-01 00:15:13 -04:00
deno_core ::futures ::io ::BufReader ::new ( & data [ TRAILER_SIZE .. ] ) ;
2023-04-19 17:50:56 -04:00
2024-03-05 17:14:49 -05:00
let ( eszip , loader ) = eszip ::EszipV2 ::parse ( bufreader )
. await
. context ( " Failed to parse eszip header " ) ? ;
2023-04-19 17:50:56 -04:00
2024-08-01 00:15:13 -04:00
let bufreader = loader . await . context ( " Failed to parse eszip archive " ) ? ;
2023-04-19 17:50:56 -04:00
2024-03-05 17:14:49 -05:00
let mut metadata = String ::new ( ) ;
2023-04-19 17:50:56 -04:00
2024-03-05 17:14:49 -05:00
bufreader
. take ( trailer . metadata_len ( ) )
. read_to_string ( & mut metadata )
. await
. context ( " Failed to read metadata from the current executable " ) ? ;
2023-04-19 17:50:56 -04:00
2024-03-05 17:14:49 -05:00
let mut metadata : Metadata = serde_json ::from_str ( & metadata ) . unwrap ( ) ;
2024-03-22 17:03:56 -04:00
metadata . argv . reserve ( cli_args . len ( ) - 1 ) ;
for arg in cli_args . into_iter ( ) . skip ( 1 ) {
metadata . argv . push ( arg . into_string ( ) . unwrap ( ) ) ;
}
2023-04-19 17:50:56 -04:00
2024-03-05 17:14:49 -05:00
Ok ( ( metadata , eszip ) )
} ) )
2023-04-19 17:50:56 -04:00
}
2023-05-10 20:06:59 -04:00
const TRAILER_SIZE : usize = std ::mem ::size_of ::< Trailer > ( ) + 8 ; // 8 bytes for the magic trailer string
struct Trailer {
eszip_pos : u64 ,
metadata_pos : u64 ,
npm_vfs_pos : u64 ,
npm_files_pos : u64 ,
}
impl Trailer {
pub fn parse ( trailer : & [ u8 ] ) -> Result < Option < Trailer > , AnyError > {
let ( magic_trailer , rest ) = trailer . split_at ( 8 ) ;
if magic_trailer ! = MAGIC_TRAILER {
return Ok ( None ) ;
}
let ( eszip_archive_pos , rest ) = rest . split_at ( 8 ) ;
let ( metadata_pos , rest ) = rest . split_at ( 8 ) ;
let ( npm_vfs_pos , npm_files_pos ) = rest . split_at ( 8 ) ;
let eszip_archive_pos = u64_from_bytes ( eszip_archive_pos ) ? ;
let metadata_pos = u64_from_bytes ( metadata_pos ) ? ;
let npm_vfs_pos = u64_from_bytes ( npm_vfs_pos ) ? ;
let npm_files_pos = u64_from_bytes ( npm_files_pos ) ? ;
Ok ( Some ( Trailer {
eszip_pos : eszip_archive_pos ,
metadata_pos ,
npm_vfs_pos ,
npm_files_pos ,
} ) )
}
pub fn metadata_len ( & self ) -> u64 {
self . npm_vfs_pos - self . metadata_pos
}
pub fn npm_vfs_len ( & self ) -> u64 {
self . npm_files_pos - self . npm_vfs_pos
}
pub fn as_bytes ( & self ) -> Vec < u8 > {
let mut trailer = MAGIC_TRAILER . to_vec ( ) ;
trailer . write_all ( & self . eszip_pos . to_be_bytes ( ) ) . unwrap ( ) ;
trailer . write_all ( & self . metadata_pos . to_be_bytes ( ) ) . unwrap ( ) ;
trailer . write_all ( & self . npm_vfs_pos . to_be_bytes ( ) ) . unwrap ( ) ;
trailer
. write_all ( & self . npm_files_pos . to_be_bytes ( ) )
. unwrap ( ) ;
trailer
}
}
2023-04-19 17:50:56 -04:00
fn u64_from_bytes ( arr : & [ u8 ] ) -> Result < u64 , AnyError > {
let fixed_arr : & [ u8 ; 8 ] = arr
. try_into ( )
. context ( " Failed to convert the buffer into a fixed-size array " ) ? ;
Ok ( u64 ::from_be_bytes ( * fixed_arr ) )
}
2023-05-01 14:35:23 -04:00
pub struct DenoCompileBinaryWriter < ' a > {
deno_dir : & ' a DenoDir ,
2024-06-03 17:17:08 -04:00
file_fetcher : & ' a FileFetcher ,
http_client_provider : & ' a HttpClientProvider ,
2023-09-29 09:26:25 -04:00
npm_resolver : & ' a dyn CliNpmResolver ,
2024-07-04 20:41:01 -04:00
workspace_resolver : & ' a WorkspaceResolver ,
2023-05-17 17:38:50 -04:00
npm_system_info : NpmSystemInfo ,
2023-04-19 17:50:56 -04:00
}
2023-05-01 14:35:23 -04:00
impl < ' a > DenoCompileBinaryWriter < ' a > {
2023-05-10 20:06:59 -04:00
#[ allow(clippy::too_many_arguments) ]
2023-04-19 17:50:56 -04:00
pub fn new (
2023-05-01 14:35:23 -04:00
deno_dir : & ' a DenoDir ,
2024-06-03 17:17:08 -04:00
file_fetcher : & ' a FileFetcher ,
http_client_provider : & ' a HttpClientProvider ,
2023-09-29 09:26:25 -04:00
npm_resolver : & ' a dyn CliNpmResolver ,
2024-07-04 20:41:01 -04:00
workspace_resolver : & ' a WorkspaceResolver ,
2023-05-17 17:38:50 -04:00
npm_system_info : NpmSystemInfo ,
2023-04-19 17:50:56 -04:00
) -> Self {
Self {
deno_dir ,
2024-06-03 17:17:08 -04:00
file_fetcher ,
http_client_provider ,
2023-05-10 20:06:59 -04:00
npm_resolver ,
2024-07-04 20:41:01 -04:00
workspace_resolver ,
2023-05-17 17:38:50 -04:00
npm_system_info ,
2023-04-19 17:50:56 -04:00
}
}
pub async fn write_bin (
& self ,
2024-08-01 00:15:13 -04:00
writer : File ,
2023-04-19 17:50:56 -04:00
eszip : eszip ::EszipV2 ,
2024-07-03 20:54:33 -04:00
root_dir_url : EszipRelativeFileBaseUrl < '_ > ,
entrypoint : & ModuleSpecifier ,
2023-04-19 17:50:56 -04:00
compile_flags : & CompileFlags ,
cli_options : & CliOptions ,
) -> Result < ( ) , AnyError > {
// Select base binary based on target
2023-07-29 08:06:47 -04:00
let mut original_binary = self . get_base_binary ( compile_flags ) . await ? ;
2023-07-28 11:46:26 -04:00
if compile_flags . no_terminal {
2023-07-29 08:06:47 -04:00
let target = compile_flags . resolve_target ( ) ;
if ! target . contains ( " windows " ) {
2023-07-28 11:46:26 -04:00
bail! (
2023-07-29 08:06:47 -04:00
" The `--no-terminal` flag is only available when targeting Windows (current: {}) " ,
target ,
2023-07-28 11:46:26 -04:00
)
}
set_windows_binary_to_gui ( & mut original_binary ) ? ;
}
2024-07-04 20:41:01 -04:00
self . write_standalone_binary (
writer ,
original_binary ,
eszip ,
root_dir_url ,
entrypoint ,
cli_options ,
compile_flags ,
)
2023-04-19 17:50:56 -04:00
}
async fn get_base_binary (
& self ,
2023-07-29 08:06:47 -04:00
compile_flags : & CompileFlags ,
2023-04-19 17:50:56 -04:00
) -> Result < Vec < u8 > , AnyError > {
2024-02-13 11:22:30 -05:00
// Used for testing.
//
// Phase 2 of the 'min sized' deno compile RFC talks
// about adding this as a flag.
if let Some ( path ) = std ::env ::var_os ( " DENORT_BIN " ) {
2024-04-27 17:11:57 -04:00
return std ::fs ::read ( & path ) . with_context ( | | {
format! ( " Could not find denort at ' {} ' " , path . to_string_lossy ( ) )
} ) ;
2023-04-19 17:50:56 -04:00
}
2023-07-29 08:06:47 -04:00
let target = compile_flags . resolve_target ( ) ;
2024-02-13 11:22:30 -05:00
let binary_name = format! ( " denort- {target} .zip " ) ;
2023-04-19 17:50:56 -04:00
let binary_path_suffix = if crate ::version ::is_canary ( ) {
format! ( " canary/ {} / {} " , crate ::version ::GIT_COMMIT_HASH , binary_name )
} else {
format! ( " release/v {} / {} " , env! ( " CARGO_PKG_VERSION " ) , binary_name )
} ;
let download_directory = self . deno_dir . dl_folder_path ( ) ;
let binary_path = download_directory . join ( & binary_path_suffix ) ;
if ! binary_path . exists ( ) {
self
. download_base_binary ( & download_directory , & binary_path_suffix )
. await ? ;
}
let archive_data = std ::fs ::read ( binary_path ) ? ;
let temp_dir = tempfile ::TempDir ::new ( ) ? ;
2024-08-08 03:19:05 -04:00
let base_binary_path = archive ::unpack_into_dir ( archive ::UnpackArgs {
exe_name : " denort " ,
archive_name : & binary_name ,
archive_data : & archive_data ,
is_windows : target . contains ( " windows " ) ,
dest_path : temp_dir . path ( ) ,
} ) ? ;
2023-04-19 17:50:56 -04:00
let base_binary = std ::fs ::read ( base_binary_path ) ? ;
drop ( temp_dir ) ; // delete the temp dir
Ok ( base_binary )
}
async fn download_base_binary (
& self ,
output_directory : & Path ,
binary_path_suffix : & str ,
) -> Result < ( ) , AnyError > {
let download_url = format! ( " https://dl.deno.land/ {binary_path_suffix} " ) ;
let maybe_bytes = {
let progress_bars = ProgressBar ::new ( ProgressBarStyle ::DownloadBars ) ;
let progress = progress_bars . update ( & download_url ) ;
self
2024-06-03 17:17:08 -04:00
. http_client_provider
. get_or_create ( ) ?
2024-07-17 19:37:31 -04:00
. download_with_progress ( download_url . parse ( ) ? , None , & progress )
2023-04-19 17:50:56 -04:00
. await ?
} ;
let bytes = match maybe_bytes {
Some ( bytes ) = > bytes ,
None = > {
log ::info! ( " Download could not be found, aborting " ) ;
std ::process ::exit ( 1 )
}
} ;
std ::fs ::create_dir_all ( output_directory ) ? ;
let output_path = output_directory . join ( binary_path_suffix ) ;
std ::fs ::create_dir_all ( output_path . parent ( ) . unwrap ( ) ) ? ;
tokio ::fs ::write ( output_path , bytes ) . await ? ;
Ok ( ( ) )
}
/// This functions creates a standalone deno binary by appending a bundle
/// and magic trailer to the currently executing binary.
2024-07-03 20:54:33 -04:00
#[ allow(clippy::too_many_arguments) ]
2024-07-04 20:41:01 -04:00
fn write_standalone_binary (
2023-04-19 17:50:56 -04:00
& self ,
2024-08-01 00:15:13 -04:00
writer : File ,
2023-04-19 17:50:56 -04:00
original_bin : Vec < u8 > ,
2023-06-08 11:48:29 -04:00
mut eszip : eszip ::EszipV2 ,
2024-07-03 20:54:33 -04:00
root_dir_url : EszipRelativeFileBaseUrl < '_ > ,
2023-04-19 17:50:56 -04:00
entrypoint : & ModuleSpecifier ,
cli_options : & CliOptions ,
compile_flags : & CompileFlags ,
) -> Result < ( ) , AnyError > {
let ca_data = match cli_options . ca_data ( ) {
Some ( CaData ::File ( ca_file ) ) = > Some (
std ::fs ::read ( ca_file )
. with_context ( | | format! ( " Reading: {ca_file} " ) ) ? ,
) ,
Some ( CaData ::Bytes ( bytes ) ) = > Some ( bytes . clone ( ) ) ,
None = > None ,
} ;
2024-07-03 20:54:33 -04:00
let root_path = root_dir_url . inner ( ) . to_file_path ( ) . unwrap ( ) ;
let ( npm_vfs , npm_files , node_modules ) = match self . npm_resolver . as_inner ( )
{
InnerCliNpmResolverRef ::Managed ( managed ) = > {
let snapshot =
managed . serialized_valid_snapshot_for_system ( & self . npm_system_info ) ;
if ! snapshot . as_serialized ( ) . packages . is_empty ( ) {
let ( root_dir , files ) = self
. build_vfs ( & root_path , cli_options ) ?
. into_dir_and_files ( ) ;
eszip . add_npm_snapshot ( snapshot ) ;
2023-11-29 09:32:23 -05:00
(
Some ( root_dir ) ,
files ,
2024-07-03 20:54:33 -04:00
Some ( NodeModules ::Managed {
node_modules_dir : self . npm_resolver . root_node_modules_path ( ) . map (
| path | {
root_dir_url
. specifier_key (
& ModuleSpecifier ::from_directory_path ( path ) . unwrap ( ) ,
)
. into_owned ( )
} ,
2023-11-29 09:32:23 -05:00
) ,
} ) ,
)
2024-07-03 20:54:33 -04:00
} else {
( None , Vec ::new ( ) , None )
2023-10-02 17:53:55 -04:00
}
2024-07-03 20:54:33 -04:00
}
InnerCliNpmResolverRef ::Byonm ( resolver ) = > {
let ( root_dir , files ) = self
. build_vfs ( & root_path , cli_options ) ?
. into_dir_and_files ( ) ;
(
Some ( root_dir ) ,
files ,
Some ( NodeModules ::Byonm {
2024-07-10 14:46:25 -04:00
root_node_modules_dir : resolver . root_node_modules_path ( ) . map (
| node_modules_dir | {
root_dir_url
. specifier_key (
& ModuleSpecifier ::from_directory_path ( node_modules_dir )
. unwrap ( ) ,
)
. into_owned ( )
} ,
) ,
2024-07-03 20:54:33 -04:00
} ) ,
)
}
} ;
2023-05-10 20:06:59 -04:00
2024-07-09 17:33:41 -04:00
let env_vars_from_env_file = match cli_options . env_file_name ( ) {
Some ( env_filename ) = > {
log ::info! ( " {} Environment variables from the file \" {} \" were embedded in the generated executable file " , crate ::colors ::yellow ( " Warning " ) , env_filename ) ;
get_file_env_vars ( env_filename . to_string ( ) ) ?
}
None = > Default ::default ( ) ,
} ;
2023-04-19 17:50:56 -04:00
let metadata = Metadata {
argv : compile_flags . args . clone ( ) ,
seed : cli_options . seed ( ) ,
location : cli_options . location_flag ( ) . clone ( ) ,
2024-05-06 19:21:58 -04:00
permissions : cli_options . permission_flags ( ) . clone ( ) ,
2023-04-19 17:50:56 -04:00
v8_flags : cli_options . v8_flags ( ) . clone ( ) ,
unsafely_ignore_certificate_errors : cli_options
. unsafely_ignore_certificate_errors ( )
. clone ( ) ,
log_level : cli_options . log_level ( ) ,
ca_stores : cli_options . ca_stores ( ) . clone ( ) ,
ca_data ,
2024-07-09 17:33:41 -04:00
env_vars_from_env_file ,
2024-07-03 20:54:33 -04:00
entrypoint_key : root_dir_url . specifier_key ( entrypoint ) . into_owned ( ) ,
workspace_resolver : SerializedWorkspaceResolver {
2024-07-04 20:41:01 -04:00
import_map : self . workspace_resolver . maybe_import_map ( ) . map ( | i | {
2024-07-03 20:54:33 -04:00
SerializedWorkspaceResolverImportMap {
specifier : if i . base_url ( ) . scheme ( ) = = " file " {
root_dir_url . specifier_key ( i . base_url ( ) ) . into_owned ( )
} else {
// just make a remote url local
" deno.json " . to_string ( )
} ,
json : i . to_json ( ) ,
}
} ) ,
2024-08-07 03:43:05 -04:00
jsr_pkgs : self
. workspace_resolver
. jsr_packages ( )
. map ( | pkg | SerializedResolverWorkspaceJsrPackage {
relative_base : root_dir_url . specifier_key ( & pkg . base ) . into_owned ( ) ,
name : pkg . name . clone ( ) ,
version : pkg . version . clone ( ) ,
exports : pkg . exports . clone ( ) ,
} )
. collect ( ) ,
2024-07-04 20:41:01 -04:00
package_jsons : self
. workspace_resolver
2024-07-03 20:54:33 -04:00
. package_jsons ( )
. map ( | pkg_json | {
(
root_dir_url
. specifier_key ( & pkg_json . specifier ( ) )
. into_owned ( ) ,
serde_json ::to_value ( pkg_json ) . unwrap ( ) ,
)
} )
. collect ( ) ,
2024-07-04 20:41:01 -04:00
pkg_json_resolution : self . workspace_resolver . pkg_json_dep_resolution ( ) ,
2024-07-03 20:54:33 -04:00
} ,
2023-11-29 09:32:23 -05:00
node_modules ,
2024-01-18 18:30:49 -05:00
disable_deprecated_api_warning : cli_options
. disable_deprecated_api_warning ,
2024-01-22 12:37:28 -05:00
unstable_config : UnstableConfig {
legacy_flag_enabled : cli_options . legacy_unstable_flag ( ) ,
bare_node_builtins : cli_options . unstable_bare_node_builtins ( ) ,
2024-04-05 10:34:51 -04:00
byonm : cli_options . use_byonm ( ) ,
2024-01-22 12:37:28 -05:00
sloppy_imports : cli_options . unstable_sloppy_imports ( ) ,
features : cli_options . unstable_features ( ) ,
} ,
2023-04-19 17:50:56 -04:00
} ;
2023-05-10 20:06:59 -04:00
write_binary_bytes (
writer ,
original_bin ,
& metadata ,
eszip ,
npm_vfs . as_ref ( ) ,
& npm_files ,
2024-08-01 00:15:13 -04:00
compile_flags ,
2023-05-10 20:06:59 -04:00
)
}
2024-07-03 20:54:33 -04:00
fn build_vfs (
& self ,
root_path : & Path ,
cli_options : & CliOptions ,
) -> Result < VfsBuilder , AnyError > {
2023-12-06 16:25:24 -05:00
fn maybe_warn_different_system ( system_info : & NpmSystemInfo ) {
if system_info ! = & NpmSystemInfo ::default ( ) {
log ::warn! ( " {} The node_modules directory may be incompatible with the target system. " , crate ::colors ::yellow ( " Warning " ) ) ;
}
}
2023-09-29 09:26:25 -04:00
match self . npm_resolver . as_inner ( ) {
InnerCliNpmResolverRef ::Managed ( npm_resolver ) = > {
2023-10-03 19:05:06 -04:00
if let Some ( node_modules_path ) = npm_resolver . root_node_modules_path ( ) {
2023-12-06 16:25:24 -05:00
maybe_warn_different_system ( & self . npm_system_info ) ;
2024-07-03 20:54:33 -04:00
let mut builder = VfsBuilder ::new ( root_path . to_path_buf ( ) ) ? ;
2023-11-29 09:32:23 -05:00
builder . add_dir_recursive ( node_modules_path ) ? ;
2023-09-29 09:26:25 -04:00
Ok ( builder )
} else {
// DO NOT include the user's registry url as it may contain credentials,
// but also don't make this dependent on the registry url
2024-05-23 17:26:23 -04:00
let root_path = npm_resolver . global_cache_root_folder ( ) ;
2023-09-29 09:26:25 -04:00
let mut builder = VfsBuilder ::new ( root_path ) ? ;
2023-09-30 12:06:38 -04:00
for package in npm_resolver . all_system_packages ( & self . npm_system_info )
2023-09-29 09:26:25 -04:00
{
let folder =
npm_resolver . resolve_pkg_folder_from_pkg_id ( & package . id ) ? ;
builder . add_dir_recursive ( & folder ) ? ;
}
2024-07-03 20:54:33 -04:00
// Flatten all the registries folders into a single "node_modules/localhost" folder
// that will be used by denort when loading the npm cache. This avoids us exposing
// the user's private registry information and means we don't have to bother
// serializing all the different registry config into the binary.
builder . with_root_dir ( | root_dir | {
root_dir . name = " node_modules " . to_string ( ) ;
let mut new_entries = Vec ::with_capacity ( root_dir . entries . len ( ) ) ;
let mut localhost_entries = IndexMap ::new ( ) ;
for entry in std ::mem ::take ( & mut root_dir . entries ) {
match entry {
VfsEntry ::Dir ( dir ) = > {
for entry in dir . entries {
log ::debug! (
" Flattening {} into node_modules " ,
entry . name ( )
) ;
if let Some ( existing ) =
localhost_entries . insert ( entry . name ( ) . to_string ( ) , entry )
{
panic! (
" Unhandled scenario where a duplicate entry was found: {:?} " ,
existing
) ;
}
}
}
VfsEntry ::File ( _ ) | VfsEntry ::Symlink ( _ ) = > {
new_entries . push ( entry ) ;
}
}
}
new_entries . push ( VfsEntry ::Dir ( VirtualDirectory {
name : " localhost " . to_string ( ) ,
entries : localhost_entries . into_iter ( ) . map ( | ( _ , v ) | v ) . collect ( ) ,
} ) ) ;
// needs to be sorted by name
new_entries . sort_by ( | a , b | a . name ( ) . cmp ( b . name ( ) ) ) ;
root_dir . entries = new_entries ;
} ) ;
2023-09-29 09:26:25 -04:00
Ok ( builder )
}
}
2024-07-03 20:54:33 -04:00
InnerCliNpmResolverRef ::Byonm ( _ ) = > {
2023-12-06 16:25:24 -05:00
maybe_warn_different_system ( & self . npm_system_info ) ;
2024-07-03 20:54:33 -04:00
let mut builder = VfsBuilder ::new ( root_path . to_path_buf ( ) ) ? ;
2024-07-19 15:56:07 -04:00
for pkg_json in cli_options . workspace ( ) . package_jsons ( ) {
2024-07-03 20:54:33 -04:00
builder . add_file_at_path ( & pkg_json . path ) ? ;
2023-11-29 09:32:23 -05:00
}
2024-07-03 20:54:33 -04:00
// traverse and add all the node_modules directories in the workspace
let mut pending_dirs = VecDeque ::new ( ) ;
pending_dirs . push_back (
2024-07-19 15:56:07 -04:00
cli_options . workspace ( ) . root_dir ( ) . to_file_path ( ) . unwrap ( ) ,
2024-07-03 20:54:33 -04:00
) ;
while let Some ( pending_dir ) = pending_dirs . pop_front ( ) {
let entries = fs ::read_dir ( & pending_dir ) . with_context ( | | {
format! ( " Failed reading: {} " , pending_dir . display ( ) )
} ) ? ;
for entry in entries {
let entry = entry ? ;
let path = entry . path ( ) ;
if ! path . is_dir ( ) {
continue ;
}
if path . ends_with ( " node_modules " ) {
builder . add_dir_recursive ( & path ) ? ;
} else {
pending_dirs . push_back ( path ) ;
}
}
2023-11-29 09:32:23 -05:00
}
Ok ( builder )
2023-05-10 20:06:59 -04:00
}
}
2023-04-19 17:50:56 -04:00
}
}
2023-07-28 11:46:26 -04:00
2024-07-09 17:33:41 -04:00
/// This function returns the environment variables specified
/// in the passed environment file.
fn get_file_env_vars (
filename : String ,
) -> Result < HashMap < String , String > , dotenvy ::Error > {
let mut file_env_vars = HashMap ::new ( ) ;
for item in dotenvy ::from_filename_iter ( filename ) ? {
let Ok ( ( key , val ) ) = item else {
continue ; // this failure will be warned about on load
} ;
file_env_vars . insert ( key , val ) ;
}
Ok ( file_env_vars )
}
2023-07-28 11:46:26 -04:00
/// This function sets the subsystem field in the PE header to 2 (GUI subsystem)
/// For more information about the PE header: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
fn set_windows_binary_to_gui ( bin : & mut [ u8 ] ) -> Result < ( ) , AnyError > {
// Get the PE header offset located in an i32 found at offset 60
// See: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only
let start_pe = u32 ::from_le_bytes ( ( bin [ 60 .. 64 ] ) . try_into ( ) ? ) ;
// Get image type (PE32 or PE32+) indicates whether the binary is 32 or 64 bit
// The used offset and size values can be found here:
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only
let start_32 = start_pe as usize + 28 ;
let magic_32 =
u16 ::from_le_bytes ( bin [ ( start_32 ) .. ( start_32 + 2 ) ] . try_into ( ) ? ) ;
let start_64 = start_pe as usize + 24 ;
let magic_64 =
u16 ::from_le_bytes ( bin [ ( start_64 ) .. ( start_64 + 2 ) ] . try_into ( ) ? ) ;
// Take the standard fields size for the current architecture (32 or 64 bit)
// This is the ofset for the Windows-Specific fields
let standard_fields_size = if magic_32 = = 0x10b {
28
} else if magic_64 = = 0x20b {
24
} else {
bail! ( " Could not find a matching magic field in the PE header " )
} ;
// Set the subsystem field (offset 68) to 2 (GUI subsystem)
// For all possible options, see: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-windows-specific-fields-image-only
let subsystem_offset = 68 ;
let subsystem_start =
start_pe as usize + standard_fields_size + subsystem_offset ;
let subsystem : u16 = 2 ;
bin [ ( subsystem_start ) .. ( subsystem_start + 2 ) ]
. copy_from_slice ( & subsystem . to_le_bytes ( ) ) ;
Ok ( ( ) )
}