Skip to content

Commit c931f7a

Browse files
committed
feat: Include eszip extractor, refactor references for payload, fs crate
1 parent c659974 commit c931f7a

File tree

9 files changed

+192
-28
lines changed

9 files changed

+192
-28
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ deno_core = { version = "0.222.0" }
2727
deno_console = { version = "0.121.0" }
2828
deno_crypto = { version = "0.135.0" }
2929
deno_fetch = { version = "0.145.0" }
30-
deno_fs = "0.31.0"
30+
deno_fs = { version = "0.31.0", features = ["sync_fs"] }
3131
deno_config = "=0.3.1"
3232
deno_io = "0.31.0"
3333
deno_graph = "=0.55.0"

crates/base/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ anyhow = { workspace = true }
1717
bytes = { version = "1.2.1" }
1818
cityhash = { version = "0.1.1" }
1919
deno_ast = { workspace = true }
20-
deno_fs = { workspace = true, features = ["sync_fs"] }
20+
deno_fs.workspace = true
2121
deno_io = { workspace = true }
2222
deno_core = { workspace = true }
2323
deno_console = { workspace = true }
@@ -69,7 +69,7 @@ sb_node = { version = "0.1.0", path = "../node" }
6969
anyhow = { workspace = true }
7070
bytes = { version = "1.2.1" }
7171
deno_ast = { workspace = true }
72-
deno_fs = { workspace = true, features = ["sync_fs"] }
72+
deno_fs.workspace = true
7373
deno_io = { workspace = true }
7474
deno_core = { workspace = true }
7575
deno_console = { workspace = true }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const numbers = {
2+
"Uno": 1,
3+
"Dos": 2
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const hello = "";
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import isEven from "npm:is-even";
22
import { serve } from "https://deno.land/[email protected]/http/server.ts"
3+
import { hello } from "./hello.js";
4+
import { numbers } from "./folder1/folder2/numbers.ts"
35

46
serve(async (req: Request) => {
57
return new Response(
6-
JSON.stringify({ is_even: isEven(10) }),
8+
JSON.stringify({ is_even: isEven(10), hello, numbers }),
79
{ status: 200, headers: { "Content-Type": "application/json" } },
810
)
911
})

crates/cli/src/main.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use clap::builder::FalseyValueParser;
77
use clap::{arg, crate_version, value_parser, ArgAction, Command};
88
use deno_core::url::Url;
99
use sb_graph::emitter::EmitterFactory;
10-
use sb_graph::generate_binary_eszip;
1110
use sb_graph::import_map::load_import_map;
11+
use sb_graph::{extract_from_file, generate_binary_eszip};
1212
use std::fs::File;
1313
use std::io::Write;
1414
use std::path::PathBuf;
@@ -58,7 +58,12 @@ fn cli() -> Command {
5858
.arg(arg!(--"output" <DIR> "Path to output eszip file").default_value("bin.eszip"))
5959
.arg(arg!(--"entrypoint" <Path> "Path to entrypoint to bundle as an eszip").required(true))
6060
.arg(arg!(--"import-map" <Path> "Path to import map file"))
61-
)
61+
).subcommand(
62+
Command::new("unbundle")
63+
.about("Unbundles an .eszip file into the specified directory")
64+
.arg(arg!(--"output" <DIR> "Path to extract the ESZIP content").default_value("./"))
65+
.arg(arg!(--"eszip" <DIR> "Path of eszip to extract").required(true))
66+
)
6267
}
6368

6469
//async fn exit_with_code(result: Result<(), Error>) {
@@ -162,6 +167,20 @@ fn main() -> Result<(), anyhow::Error> {
162167
let mut file = File::create(output_path.as_str()).unwrap();
163168
file.write_all(&bin).unwrap();
164169
}
170+
Some(("unbundle", sub_matches)) => {
171+
let output_path = sub_matches.get_one::<String>("output").cloned().unwrap();
172+
let eszip_path = sub_matches.get_one::<String>("eszip").cloned().unwrap();
173+
174+
let output_path = PathBuf::from(output_path.as_str());
175+
let eszip_path = PathBuf::from(eszip_path.as_str());
176+
177+
extract_from_file(eszip_path, output_path.clone()).await;
178+
179+
println!(
180+
"Eszip extracted successfully inside path {}",
181+
output_path.to_str().unwrap()
182+
);
183+
}
165184
_ => {
166185
// unrecognized command
167186
}

crates/sb_core/util/path.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use deno_ast::ModuleSpecifier;
2-
use std::path::PathBuf;
2+
use std::path::{Path, PathBuf};
33

44
/// Gets if the provided character is not supported on all
55
/// kinds of file systems.
@@ -41,3 +41,22 @@ pub fn root_url_to_safe_local_dirname(root: &ModuleSpecifier) -> PathBuf {
4141

4242
result
4343
}
44+
45+
pub fn find_lowest_path(paths: &Vec<String>) -> Option<String> {
46+
let mut lowest_path: Option<(&str, usize)> = None;
47+
48+
for path_str in paths {
49+
// Extract the path part from the URL
50+
let path = Path::new(path_str);
51+
52+
// Count the components
53+
let component_count = path.components().count();
54+
55+
// Update the lowest path if this one has fewer components
56+
if lowest_path.is_none() || component_count < lowest_path.unwrap().1 {
57+
lowest_path = Some((path_str, component_count));
58+
}
59+
}
60+
61+
lowest_path.map(|(path, _)| path.to_string())
62+
}

crates/sb_graph/lib.rs

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ use crate::emitter::EmitterFactory;
22
use crate::graph_util::{create_eszip_from_graph_raw, create_graph};
33
use deno_ast::MediaType;
44
use deno_core::error::AnyError;
5+
use deno_core::futures::io::{AllowStdIo, BufReader};
56
use deno_core::{serde_json, FastString, JsBuffer, ModuleSpecifier};
67
use deno_fs::{FileSystem, RealFs};
78
use deno_npm::NpmSystemInfo;
89
use eszip::{EszipV2, ModuleKind};
910
use sb_fs::{build_vfs, VfsOpts};
10-
use std::path::PathBuf;
11+
use std::fs;
12+
use std::fs::{create_dir_all, File};
13+
use std::io::Write;
14+
use std::path::{Path, PathBuf};
1115
use std::sync::Arc;
1216

1317
pub mod emitter;
@@ -25,6 +29,26 @@ pub enum EszipPayloadKind {
2529
Eszip(EszipV2),
2630
}
2731

32+
pub async fn payload_to_eszip(eszip_payload_kind: EszipPayloadKind) -> EszipV2 {
33+
match eszip_payload_kind {
34+
EszipPayloadKind::Eszip(data) => data,
35+
_ => {
36+
let bytes = match eszip_payload_kind {
37+
EszipPayloadKind::JsBufferKind(js_buffer) => Vec::from(&*js_buffer),
38+
EszipPayloadKind::VecKind(vec) => vec,
39+
_ => panic!("It should not get here"),
40+
};
41+
42+
let bufreader = BufReader::new(AllowStdIo::new(bytes.as_slice()));
43+
let (eszip, loader) = eszip::EszipV2::parse(bufreader).await.unwrap();
44+
45+
loader.await.unwrap();
46+
47+
eszip
48+
}
49+
}
50+
}
51+
2852
pub async fn generate_binary_eszip(
2953
file: PathBuf,
3054
emitter_factory: Arc<EmitterFactory>,
@@ -95,3 +119,116 @@ pub async fn generate_binary_eszip(
95119
eszip
96120
}
97121
}
122+
123+
fn extract_file_specifiers(eszip: &EszipV2) -> Vec<String> {
124+
eszip
125+
.specifiers()
126+
.iter()
127+
.filter(|specifier| specifier.starts_with("file:"))
128+
.cloned()
129+
.collect()
130+
}
131+
132+
pub struct ExtractEszipPayload {
133+
pub data: EszipPayloadKind,
134+
pub folder: PathBuf,
135+
}
136+
137+
fn create_module_path(
138+
global_specifier: &str,
139+
entry_path: &Path,
140+
output_folder: &Path,
141+
) -> PathBuf {
142+
let cleaned_specifier = global_specifier.replace(entry_path.to_str().unwrap(), "");
143+
let module_path = PathBuf::from(cleaned_specifier);
144+
145+
if let Some(parent) = module_path.parent() {
146+
if parent.parent().is_some() {
147+
let output_folder_and_mod_folder =
148+
output_folder.join(parent.strip_prefix("/").unwrap());
149+
if !output_folder_and_mod_folder.exists() {
150+
create_dir_all(&output_folder_and_mod_folder).unwrap();
151+
}
152+
}
153+
}
154+
155+
output_folder.join(module_path.strip_prefix("/").unwrap())
156+
}
157+
158+
async fn extract_modules(
159+
eszip: &EszipV2,
160+
specifiers: &[String],
161+
lowest_path: &str,
162+
output_folder: &Path,
163+
) {
164+
let main_path = PathBuf::from(lowest_path);
165+
let entry_path = main_path.parent().unwrap();
166+
for global_specifier in specifiers {
167+
let module_path = create_module_path(global_specifier, entry_path, output_folder);
168+
let module_content = eszip
169+
.get_module(global_specifier)
170+
.unwrap()
171+
.take_source()
172+
.await
173+
.unwrap();
174+
175+
let mut file = File::create(&module_path).unwrap();
176+
file.write_all(module_content.as_ref()).unwrap();
177+
}
178+
}
179+
180+
pub async fn extract_eszip(payload: ExtractEszipPayload) {
181+
let eszip = payload_to_eszip(payload.data).await;
182+
let output_folder = payload.folder;
183+
184+
if !output_folder.exists() {
185+
create_dir_all(&output_folder).unwrap();
186+
}
187+
188+
let file_specifiers = extract_file_specifiers(&eszip);
189+
if let Some(lowest_path) = sb_core::util::path::find_lowest_path(&file_specifiers) {
190+
extract_modules(&eszip, &file_specifiers, &lowest_path, &output_folder).await;
191+
} else {
192+
panic!("Path seems to be invalid");
193+
}
194+
}
195+
196+
pub async fn extract_from_file(eszip_file: PathBuf, output_path: PathBuf) {
197+
let eszip_content = fs::read(eszip_file).expect("File does not exist");
198+
extract_eszip(ExtractEszipPayload {
199+
data: EszipPayloadKind::VecKind(eszip_content),
200+
folder: output_path,
201+
})
202+
.await;
203+
}
204+
205+
#[cfg(test)]
206+
mod test {
207+
use crate::{
208+
extract_eszip, generate_binary_eszip, EmitterFactory, EszipPayloadKind, ExtractEszipPayload,
209+
};
210+
use std::fs::remove_dir_all;
211+
use std::path::PathBuf;
212+
use std::sync::Arc;
213+
214+
#[tokio::test]
215+
#[allow(clippy::arc_with_non_send_sync)]
216+
async fn test_module_code_no_eszip() {
217+
let eszip = generate_binary_eszip(
218+
PathBuf::from("../base/test_cases/npm/index.ts"),
219+
Arc::new(EmitterFactory::new()),
220+
None,
221+
None,
222+
)
223+
.await;
224+
let eszip = eszip.unwrap();
225+
extract_eszip(ExtractEszipPayload {
226+
data: EszipPayloadKind::Eszip(eszip),
227+
folder: PathBuf::from("../base/test_cases/extracted-npm/"),
228+
})
229+
.await;
230+
231+
assert!(PathBuf::from("../base/test_cases/extracted-npm/hello.js").exists());
232+
remove_dir_all(PathBuf::from("../base/test_cases/extracted-npm/")).unwrap();
233+
}
234+
}

crates/sb_module_loader/standalone/mod.rs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use sb_core::util::http_util::HttpClient;
2020
use sb_fs::file_system::DenoCompileFileSystem;
2121
use sb_fs::load_npm_vfs;
2222
use sb_graph::graph_resolver::MappedSpecifierResolver;
23-
use sb_graph::{EszipPayloadKind, SOURCE_CODE_ESZIP_KEY, VFS_ESZIP_KEY};
23+
use sb_graph::{payload_to_eszip, EszipPayloadKind, SOURCE_CODE_ESZIP_KEY, VFS_ESZIP_KEY};
2424
use sb_node::analyze::NodeCodeTranslator;
2525
use sb_node::NodeResolver;
2626
use sb_npm::package_json::PackageJsonDepsProvider;
@@ -198,25 +198,7 @@ pub async fn create_module_loader_for_standalone_from_eszip_kind(
198198
maybe_import_map_arc: Option<Arc<ImportMap>>,
199199
maybe_import_map_path: Option<String>,
200200
) -> Result<RuntimeProviders, AnyError> {
201-
use deno_core::futures::io::{AllowStdIo, BufReader};
202-
203-
let eszip = match eszip_payload_kind {
204-
EszipPayloadKind::Eszip(data) => data,
205-
_ => {
206-
let bytes = match eszip_payload_kind {
207-
EszipPayloadKind::JsBufferKind(js_buffer) => Vec::from(&*js_buffer),
208-
EszipPayloadKind::VecKind(vec) => vec,
209-
_ => panic!("It should not get here"),
210-
};
211-
212-
let bufreader = BufReader::new(AllowStdIo::new(bytes.as_slice()));
213-
let (eszip, loader) = eszip::EszipV2::parse(bufreader).await.unwrap();
214-
215-
loader.await.unwrap();
216-
217-
eszip
218-
}
219-
};
201+
let eszip = payload_to_eszip(eszip_payload_kind).await;
220202

221203
let mut maybe_import_map: Option<ImportMap> = None;
222204

0 commit comments

Comments
 (0)