Skip to content

Feat(jssg): better validation #1620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
include bundler
  • Loading branch information
AugustinMauroy committed Jul 18, 2025
commit 92a02bce4a48ce0c5266fa5597a52e31d6d2ef63
1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ chrono = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }
regex = { workspace = true }
rspack = "0.1"
walkdir = { workspace = true }
tempfile = { workspace = true }
num_cpus = "1.16"
Expand Down
153 changes: 146 additions & 7 deletions crates/cli/src/commands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use butterflow_core::utils;
use clap::Args;
use log::{debug, info, warn};
use reqwest;
use rspack::{Compiler, Config, Entry, ModuleOptions, OutputOptions, ResolveOptions};
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
Expand Down Expand Up @@ -158,13 +160,15 @@ pub async fn handler(args: &Command) -> Result<()> {
manifest.workflow.display()
))?;
let js_ast_grep_entries = workflow.get_js_ast_grep_entry_points();
let contain_js_entries = !js_ast_grep_entries.is_empty();

// Create package tarball
let tarball_path = create_package_tarball(&package_path, &manifest, args.dry_run)?;
// Create package tarball with JS bundling
let tarball_path = create_package_tarball(&package_path, &manifest, &js_ast_grep_entries, args.dry_run)?;

if args.dry_run {
println!("✓ Package validation successful");
if !js_ast_grep_entries.is_empty() {
println!("✓ JavaScript files bundled: {}", js_ast_grep_entries.len());
}
println!(
"✓ Tarball created: {} ({} bytes)",
tarball_path.display(),
Expand Down Expand Up @@ -196,7 +200,7 @@ pub async fn handler(args: &Command) -> Result<()> {
// Upload package
let response = upload_package(
&registry_url,
&bundle_path,
&tarball_path,
&manifest,
&auth.tokens.access_token,
)
Expand All @@ -213,13 +217,117 @@ pub async fn handler(args: &Command) -> Result<()> {
println!("🔗 Download: {}", response.package.download_url);

// Clean up temporary bundle
if let Err(e) = fs::remove_file(&bundle_path) {
if let Err(e) = fs::remove_file(&tarball_path) {
warn!("Failed to clean up temporary bundle: {e}");
}

Ok(())
}

async fn bundle_js_files(
package_path: &Path,
js_entries: &[String],
temp_dir: &Path,
) -> Result<Vec<(String, String)>> {
let mut bundled_files = Vec::new();

for entry in js_entries {
let entry_path = package_path.join(entry);
if !entry_path.exists() {
warn!("JavaScript entry file not found: {}", entry_path.display());
continue;
}

info!("Bundling JavaScript file: {}", entry);

// Create output directory for this entry
let output_dir = temp_dir.join("bundled");
fs::create_dir_all(&output_dir)?;

// Generate output filename
let entry_name = Path::new(entry)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("index");
let output_filename = format!("{}.bundle.js", entry_name);
let output_path = output_dir.join(&output_filename);

// Configure Rspack
let config = Config {
entry: Entry::from([(
"main".to_string(),
entry_path.to_string_lossy().to_string(),
)]),
output: OutputOptions {
path: output_dir.to_string_lossy().to_string(),
filename: output_filename.clone(),
..Default::default()
},
module: ModuleOptions {
rules: vec![
// Add rules for JavaScript/TypeScript files
rspack::ModuleRule {
test: Some(rspack::RuleTest::Regexp(r"\.m?js$".to_string())),
r#type: Some("javascript/auto".to_string()),
..Default::default()
},
rspack::ModuleRule {
test: Some(rspack::RuleTest::Regexp(r"\.ts$".to_string())),
r#type: Some("javascript/auto".to_string()),
r#use: vec![rspack::RuleUse {
loader: "builtin:swc-loader".to_string(),
options: Some(serde_json::json!({
"jsc": {
"parser": {
"syntax": "typescript"
},
"transform": {}
}
})),
}],
..Default::default()
},
],
..Default::default()
},
resolve: ResolveOptions {
extensions: vec![".js".to_string(), ".ts".to_string(), ".mjs".to_string()],
..Default::default()
},
mode: rspack::Mode::Production,
target: vec!["node".to_string()],
..Default::default()
};

// Create compiler and run bundling
let compiler = Compiler::new(config)?;
let stats = compiler.run().await?;

if stats.has_errors() {
let errors = stats.compilation.get_errors();
for error in errors {
warn!("Bundling error: {}", error);
}
return Err(anyhow!("Failed to bundle JavaScript file: {}", entry));
}

if output_path.exists() {
// Read the bundled content
let bundled_content = fs::read_to_string(&output_path)?;

// Store the relative path and content
let relative_entry = format!("bundled/{}", output_filename);
bundled_files.push((relative_entry, bundled_content));

info!("Successfully bundled: {} -> {}", entry, output_filename);
} else {
warn!("Bundle output file not found: {}", output_path.display());
}
}

Ok(bundled_files)
}

fn load_manifest(package_path: &Path) -> Result<CodemodManifest> {
let manifest_path = package_path.join("codemod.yaml");

Expand All @@ -244,33 +352,64 @@ fn load_manifest(package_path: &Path) -> Result<CodemodManifest> {
fn create_package_tarball(
package_path: &Path,
manifest: &CodemodManifest,
js_entries: &[String],
dry_run: bool,
) -> Result<PathBuf> {
let temp_dir = TempDir::new()?;
let bundle_name = format!("{}-{}.tar.gz", manifest.name, manifest.version);
let temp_bundle_path = temp_dir.path().join(&bundle_name);

// Bundle JavaScript files if any exist
let bundled_files = if !js_entries.is_empty() {
info!("Bundling {} JavaScript entry files", js_entries.len());
tokio::runtime::Runtime::new()?.block_on(async {
bundle_js_files(package_path, js_entries, temp_dir.path()).await
})?
} else {
Vec::new()
};

// Create tar.gz archive
let tar_gz = fs::File::create(&temp_bundle_path)?;
let enc = flate2::write::GzEncoder::new(tar_gz, flate2::Compression::default());
let mut tar = tar::Builder::new(enc);

// Add files to archive
// Add original files to archive (excluding JS entries that will be bundled)
let mut file_count = 0;
for entry in WalkDir::new(package_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path() != package_path) // Skip the root directory itself
.filter(|e| should_include_file(e.path(), package_path))
.filter(|e| should_include_file(e.path(), package_root))
{
if entry.file_type().is_file() {
let relative_path = entry.path().strip_prefix(package_path)?;
let relative_path_str = relative_path.to_string_lossy();

// Skip original JS entry files since we'll include bundled versions
if js_entries.iter().any(|js_entry| relative_path_str == *js_entry) {
debug!("Skipping original JS entry file: {}", relative_path_str);
continue;
}

debug!("Adding file to bundle: {}", relative_path.display());
tar.append_path_with_name(entry.path(), relative_path)?;
file_count += 1;
}
}

// Add bundled JavaScript files
for (bundled_path, bundled_content) in bundled_files {
debug!("Adding bundled file: {}", bundled_path);
let mut header = tar::Header::new_gnu();
header.set_path(&bundled_path)?;
header.set_size(bundled_content.len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append(&header, bundled_content.as_bytes())?;
file_count += 1;
}

info!("Added {file_count} files to bundle");

// Finish the tar archive and flush the gzip encoder
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy