From 9a939ad4bc85584870af966ae86426d2bba47eee Mon Sep 17 00:00:00 2001 From: hulxv Date: Sun, 23 Feb 2025 01:45:34 +0200 Subject: [PATCH 01/13] refactor(fs-analyzer): abstract pages inside the source analyzer --- crates/metassr-build/src/client/mod.rs | 7 +-- crates/metassr-build/src/server/mod.rs | 4 +- crates/metassr-fs-analyzer/src/src_dir.rs | 63 ++++++++++++++++++++--- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/crates/metassr-build/src/client/mod.rs b/crates/metassr-build/src/client/mod.rs index 7fecef5..d6211e2 100644 --- a/crates/metassr-build/src/client/mod.rs +++ b/crates/metassr-build/src/client/mod.rs @@ -4,6 +4,7 @@ use anyhow::{anyhow, Result}; use hydrator::Hydrator; use metassr_bundler::WebBundler; +use metassr_fs_analyzer::src_dir::Page; use metassr_fs_analyzer::{ src_dir::{special_entries, SourceDir}, DirectoryAnalyzer, @@ -55,9 +56,9 @@ impl Build for ClientBuilder { let pages = src.pages(); let (special_entries::App(app_path), _) = src.specials()?; - for (page, page_path) in pages.iter() { - let hydrator = Hydrator::new(&app_path, page_path, "root").generate()?; - let page = setup_page_path(page, "js"); + for Page { route, path } in pages.iter() { + let hydrator = Hydrator::new(&app_path, path, "root").generate()?; + let page = setup_page_path(route, "js"); cache_dir.insert(&format!("pages/{}", page.display()), hydrator.as_bytes())?; } diff --git a/crates/metassr-build/src/server/mod.rs b/crates/metassr-build/src/server/mod.rs index 3d1346e..5042e1a 100644 --- a/crates/metassr-build/src/server/mod.rs +++ b/crates/metassr-build/src/server/mod.rs @@ -70,10 +70,10 @@ impl Build for ServerSideBuilder { let mut cache_dir = CacheDir::new(&format!("{}/cache", self.dist_path.display()))?; let src = SourceDir::new(&self.src_path).analyze()?; - let pages = src.clone().pages; + let pages = src.clone().pages(); let (special_entries::App(app), special_entries::Head(head)) = src.specials()?; - let targets = match TargetsGenerator::new(app, pages, &mut cache_dir).generate() { + let targets = match TargetsGenerator::new(app, pages.as_map(), &mut cache_dir).generate() { Ok(t) => t, Err(e) => return Err(anyhow!("Couldn't generate targets: {e}")), }; diff --git a/crates/metassr-fs-analyzer/src/src_dir.rs b/crates/metassr-fs-analyzer/src/src_dir.rs index 4f7ddb7..3edc477 100644 --- a/crates/metassr-fs-analyzer/src/src_dir.rs +++ b/crates/metassr-fs-analyzer/src/src_dir.rs @@ -16,6 +16,55 @@ pub mod special_entries { pub struct Head(pub PathBuf); } +#[derive(Debug, Clone)] +pub struct Page { + pub route: String, + pub path: PathBuf, +} + +impl Page { + pub fn new(route: &S, path: &P) -> Self + where + S: ToString, + P: AsRef + ?Sized, + { + Self { + route: route.to_string(), + path: PathBuf::from(path), + } + } +} + +#[derive(Debug, Clone)] +pub struct Pages(pub Vec); + +impl Pages { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn from(pages: Vec) -> Self { + Self(pages) + } + + pub fn insert + ?Sized>(&mut self, route: &S, path: &P) { + self.0.push(Page::new(route, path)); + } + + pub fn as_map(&self) -> HashMap { + HashMap::from_iter( + self.0 + .iter() + .map(|Page { route, path }| (route.to_owned(), path.to_owned())) + .collect::>(), + ) + } + + pub fn iter(self: &Self) -> std::slice::Iter { + self.0.iter() + } +} + pub type PagesEntriesType = HashMap; pub type SpecialEntriesType = (Option, Option); @@ -24,8 +73,8 @@ pub type SpecialEntriesType = (Option, Option Self { + pub fn new(pages: Pages, specials: SpecialEntriesType) -> Self { Self { pages, specials } } @@ -68,7 +117,7 @@ impl SourceDirContainer { /// **Returns** /// /// Returns a `HashMap` where keys are routes and values are paths to page files. - pub fn pages(&self) -> PagesEntriesType { + pub fn pages(&self) -> Pages { self.pages.clone() } } @@ -105,7 +154,7 @@ impl DirectoryAnalyzer for SourceDir { let src = self.0.to_str().unwrap(); let list_of_specials = ["_app", "_head"]; - let mut pages: HashMap = HashMap::new(); + let mut pages = Pages::new(); let mut specials: SpecialEntriesType = (None, None); for entry in WalkDir::new(src) @@ -136,7 +185,7 @@ impl DirectoryAnalyzer for SourceDir { .strip_prefix([src, "/pages"].concat())? .to_str() .unwrap(); - pages.insert(route.to_owned(), path.to_path_buf()); + pages.insert(&route, path); } _ => (), @@ -187,7 +236,7 @@ mod tests { } let result = source_dir.analyze().unwrap(); - assert_eq!(result.pages().len(), pages.len()); + assert_eq!(result.pages().0.len(), pages.len()); assert!(result.specials().is_ok()); // Cleanup From 529d69b53e5937fe497470c4ab6cd4e26a267762 Mon Sep 17 00:00:00 2001 From: hulxv Date: Thu, 11 Sep 2025 21:41:29 +0300 Subject: [PATCH 02/13] refactor: improve `bundle.js` script --- .../metassr-build/src/server/renderer/head.rs | 4 +- crates/metassr-bundler/src/bundle.js | 163 +-- crates/metassr-bundler/src/lib.rs | 19 +- tests/web-app/package.json | 3 +- tests/web-app/yarn.lock | 1118 +---------------- 5 files changed, 93 insertions(+), 1214 deletions(-) diff --git a/crates/metassr-build/src/server/renderer/head.rs b/crates/metassr-build/src/server/renderer/head.rs index 7dd3533..117d784 100644 --- a/crates/metassr-build/src/server/renderer/head.rs +++ b/crates/metassr-build/src/server/renderer/head.rs @@ -66,8 +66,8 @@ import React from "react" export function render_head() {{ return renderToString(); -}} - +}} + "#, self.path.canonicalize()?.display() ); diff --git a/crates/metassr-bundler/src/bundle.js b/crates/metassr-bundler/src/bundle.js index 9a880ef..72be43d 100644 --- a/crates/metassr-bundler/src/bundle.js +++ b/crates/metassr-bundler/src/bundle.js @@ -1,135 +1,138 @@ const { rspack } = require('@rspack/core'); -const path = require('path'); +const { join } = require('path'); -/** - * Safely parses a JSON string, returning undefined if parsing fails. - * @param {string} json - The JSON string to parse. - * @returns {Object|undefined} - Parsed object or undefined if parsing fails. - */ function safelyParseJSON(json) { try { return JSON.parse(json); - } catch (_) { + } catch { return undefined; } } -// Default configuration object for rspack bundling process -let config = { +const defaultConfig = { output: { - filename: '[name].js', // Output filename with the entry name + filename: '[name].js', library: { - type: 'commonjs2', // Set library type to CommonJS2 (Node.js modules) + type: 'commonjs2', }, - publicPath: '' // Specify the base path for all assets within the application + publicPath: '' }, resolve: { - extensions: ['.js', '.jsx', '.tsx', '.ts'] // Extensions that will be resolved + extensions: ['.js', '.jsx', '.tsx', '.ts'], + mainFields: ['browser', 'module', 'main'] }, optimization: { - minimize: false, // Disable minimization for easier debugging + minimize: false, }, module: { rules: [ { - test: /\.(jsx|js)$/, // Rule for JavaScript and JSX files - exclude: /node_modules/, // Exclude node_modules directory + test: /\.(jsx|js)$/, + exclude: /node_modules/, use: { - loader: 'builtin:swc-loader', // Use the SWC loader to transpile ES6+ and JSX + loader: 'builtin:swc-loader', options: { - sourceMap: true, // Enable source maps for easier debugging + sourceMap: true, jsc: { parser: { - syntax: 'ecmascript', // Set parser syntax to ECMAScript - jsx: true, // Enable parsing JSX syntax + syntax: 'ecmascript', + jsx: true, + dynamicImport: true }, - externalHelpers: false, // Disable external helpers (use inline helpers) - preserveAllComments: false, // Remove comments from output transform: { react: { - runtime: 'automatic', // Use React's automatic JSX runtime - throwIfNamespace: true, // Throw error if namespace is used - useBuiltins: false, // Don't include built-in polyfills - }, - }, - }, - }, + runtime: 'automatic', + throwIfNamespace: true + } + } + } + } }, - type: 'javascript/auto', // Specify the type as auto (for backward compatibility) + type: 'javascript/auto' }, { - test: /\.(tsx|ts)$/, // Rule for TypeScript and TSX files - exclude: /node_modules/, // Exclude node_modules directory + test: /\.(tsx|ts)$/, + exclude: /node_modules/, use: { - loader: 'builtin:swc-loader', // Use the SWC loader to transpile TS and TSX + loader: 'builtin:swc-loader', options: { jsc: { parser: { - syntax: 'typescript', // Set parser syntax to TypeScript - tsx: true, // Enable parsing TSX syntax + syntax: 'typescript', + tsx: true, + decorators: true }, transform: { react: { - runtime: 'automatic', // Use React's automatic JSX runtime - throwIfNamespace: true, // Throw error if namespace is used - useBuiltins: false, // Don't include built-in polyfills - }, - }, - }, - }, + runtime: 'automatic', + throwIfNamespace: true + } + } + } + } }, - type: 'javascript/auto', // Specify the type as auto + type: 'javascript/auto' }, { - test: /\.(png|svg|jpg)$/, // Rule for image files (PNG, SVG, JPG) - type: 'asset/inline', // Inline assets as Base64 strings - }, - ], - }, + test: /\.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/, + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: 8 * 1024 + } + } + } + ] + } }; -/** - * Bundles web resources using rspack. - * @param {Object|string} entry - The entry point(s) for the bundling process (can be a string or JSON object). - * @param {string} dist - The distribution path where bundled files will be output. - * @returns {Promise} - Resolves when bundling is successful, rejects if there is an error. - */ -async function web_bundling(entry, dist) { - // Create a bundler instance using the config and parameters - const compiler = rspack( - { - ...config, // Merge with the default config - entry: safelyParseJSON(entry) ?? entry, // Parse entry if it's JSON, otherwise use it as is - output: dist ? { - ...config.output, - path: path.join(process.cwd(), dist), // Use current working directory and output path - } : config.output, - // minimize: true, - name: 'Client', // Name of the bundle (Client) - mode: 'production', // Set mode to development (for non-minimized builds) - devtool: 'source-map', // Enable source maps for better debugging - stats: { preset: 'errors-warnings', timings: true, colors: true }, // Customize bundling stats output - target: 'web', // Set the target environment to web (for browser usage) +function createBundlerConfig(entry, dist) { + return { + ...defaultConfig, + entry: safelyParseJSON(entry) ?? entry, + output: dist ? { + ...defaultConfig.output, + path: join(process.cwd(), dist) + } : defaultConfig.output, + name: 'Client', + mode: 'production', + devtool: 'source-map', + stats: { + preset: 'errors-warnings', + timings: true, + colors: true, + modules: true + }, + target: 'web', + module: defaultConfig.module, + performance: { + hints: 'warning', + maxAssetSize: 250000, + maxEntrypointSize: 400000 } - ); + }; +} + +async function web_bundling(entry, dist) { + const compiler = rspack(createBundlerConfig(entry, dist)); - // Return a promise that runs the bundling process and resolves or rejects based on the result return new Promise((resolve, reject) => { - return compiler.run((error, stats) => { - // Handle errors during the bundling process + compiler.run((error, stats) => { if (error) { - reject(error.message); // Reject with the error message if bundling fails + return reject(new Error(`Bundling failed: ${error.message}`)); } - // Check if there are any errors in the bundling stats - if (error || stats?.hasErrors()) { - reject(stats.toString("errors-only")); // Reject with errors-only details from stats + if (stats?.hasErrors()) { + const info = stats.toJson(); + const errors = info.errors?.map(e => e.message).join('\n') || 'Unknown compilation errors'; + return reject(new Error(`Compilation errors:\n${errors}`)); } - resolve(0); // Resolve successfully when bundling is complete + + resolve(0); }); }); } module.exports = { - web_bundling // Export the web_bundling function to call it via metacall + web_bundling }; diff --git a/crates/metassr-bundler/src/lib.rs b/crates/metassr-bundler/src/lib.rs index 8340f9b..4899184 100644 --- a/crates/metassr-bundler/src/lib.rs +++ b/crates/metassr-bundler/src/lib.rs @@ -95,65 +95,50 @@ impl<'a> WebBundler<'a> { /// /// This function returns an `Err` if the bundling script cannot be loaded or if bundling fails. pub fn exec(&self) -> Result<()> { - // Lock the mutex to check if the bundling script is already loaded let mut guard = IS_BUNDLING_SCRIPT_LOADED.lock().unwrap(); if !guard.is_true() { - // If not loaded, attempt to load the script into MetaCall if let Err(e) = loaders::from_memory("node", BUILD_SCRIPT) { return Err(anyhow!("Cannot load bundling script: {e:?}")); } - // Mark the script as loaded guard.make_true(); } - // Drop the lock on the mutex as it's no longer needed drop(guard); - // Resolve callback when the bundling process is completed successfully fn resolve(_: Box, _: Box) { let compilation_wait = &*Arc::clone(&IS_COMPLIATION_WAIT); let mut started = compilation_wait.checker.lock().unwrap(); - // Mark the process as completed and notify waiting threads started.make_true(); compilation_wait.cond.notify_one(); } - // Reject callback for handling errors during the bundling process fn reject(err: Box, _: Box) { let compilation_wait = &*Arc::clone(&IS_COMPLIATION_WAIT); let mut started = compilation_wait.checker.lock().unwrap(); - // Log the bundling error and mark the process as completed error!("Bundling rejected: {err:?}"); started.make_true(); compilation_wait.cond.notify_one(); } - // Call the `web_bundling` function in the MetaCall script with targets and output path let future = metacall::( BUNDLING_FUNC, [ - // Serialize the targets map to a string format - serde_json::to_string(&self.targets)?, - // Get the distribution path as a string - self.dist_path.to_str().unwrap().to_owned(), + serde_json::to_string(&self.targets)?, // entry + self.dist_path.to_str().unwrap().to_owned(), // dist ], ) .unwrap(); - // Set the resolve and reject handlers for the bundling future future.then(resolve).catch(reject).await_fut(); - // Lock the mutex and wait for the bundling process to complete let compilation_wait = Arc::clone(&IS_COMPLIATION_WAIT); let mut started = compilation_wait.checker.lock().unwrap(); - // Block the current thread until the bundling process signals completion while !started.is_true() { started = Arc::clone(&IS_COMPLIATION_WAIT).cond.wait(started).unwrap(); } - // Reset the checker state to false after the process completes started.make_false(); Ok(()) } diff --git a/tests/web-app/package.json b/tests/web-app/package.json index c79e10e..00e5977 100644 --- a/tests/web-app/package.json +++ b/tests/web-app/package.json @@ -14,7 +14,8 @@ "@types/react-dom": "^18.0.8" }, "dependencies": { + "@rspack/core": "0.7.5", "react": "^18.3.1", "react-dom": "^18.3.1" } -} \ No newline at end of file +} diff --git a/tests/web-app/yarn.lock b/tests/web-app/yarn.lock index a751eab..fca6ac7 100644 --- a/tests/web-app/yarn.lock +++ b/tests/web-app/yarn.lock @@ -2,121 +2,6 @@ # yarn lockfile v1 -"@esbuild/aix-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" - integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== - -"@esbuild/android-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" - integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== - -"@esbuild/android-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" - integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== - -"@esbuild/android-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" - integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== - -"@esbuild/darwin-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" - integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== - -"@esbuild/darwin-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" - integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== - -"@esbuild/freebsd-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" - integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== - -"@esbuild/freebsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" - integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== - -"@esbuild/linux-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" - integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== - -"@esbuild/linux-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" - integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== - -"@esbuild/linux-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" - integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== - -"@esbuild/linux-loong64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" - integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== - -"@esbuild/linux-mips64el@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" - integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== - -"@esbuild/linux-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" - integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== - -"@esbuild/linux-riscv64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" - integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== - -"@esbuild/linux-s390x@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" - integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== - -"@esbuild/linux-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" - integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== - -"@esbuild/netbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" - integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== - -"@esbuild/openbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" - integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== - -"@esbuild/sunos-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" - integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== - -"@esbuild/win32-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" - integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== - -"@esbuild/win32-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" - integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== - -"@esbuild/win32-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" - integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== - "@module-federation/runtime-tools@0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.1.6.tgz#ee9bbe8c6e823b4c641695c91ab0b0bfdc5b3102" @@ -145,41 +30,6 @@ "@module-federation/runtime" "0.1.6" "@module-federation/sdk" "0.1.6" -"@mole-inc/bin-wrapper@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz#d7fd0ceb1cfa8a855293a3ed9d7d135f4d442f0e" - integrity sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA== - dependencies: - bin-check "^4.1.0" - bin-version-check "^5.0.0" - content-disposition "^0.5.4" - ext-name "^5.0.0" - file-type "^17.1.6" - filenamify "^5.0.2" - got "^11.8.5" - os-filter-obj "^2.0.0" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - "@rspack/binding-darwin-arm64@0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-0.7.5.tgz#a6e960ecb357c1f15481b689e12e4a7bcabf443c" @@ -240,7 +90,7 @@ "@rspack/binding-win32-ia32-msvc" "0.7.5" "@rspack/binding-win32-x64-msvc" "0.7.5" -"@rspack/core@^0.7.5": +"@rspack/core@0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@rspack/core/-/core-0.7.5.tgz#e6936949d8666655874c536cb719ff76daee44ab" integrity sha512-zVTe4WCyc3qsLPattosiDYZFeOzaJ32/BYukPP2I1VJtCVFa+PxGVRPVZhSoN6fXw5oy48yHg9W9v1T8CaEFhw== @@ -251,155 +101,6 @@ tapable "2.2.1" webpack-sources "3.2.3" -"@sindresorhus/is@^4.0.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== - -"@swc/cli@^0.3.12": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@swc/cli/-/cli-0.3.14.tgz#c0d56e55e5eb4918937b8d0fa82e5834c21c4cce" - integrity sha512-0vGqD6FSW67PaZUZABkA+ADKsX7OUY/PwNEz1SbQdCvVk/e4Z36Gwh7mFVBQH9RIsMonTyhV1RHkwkGnEfR3zQ== - dependencies: - "@mole-inc/bin-wrapper" "^8.0.1" - "@swc/counter" "^0.1.3" - commander "^8.3.0" - fast-glob "^3.2.5" - minimatch "^9.0.3" - piscina "^4.3.0" - semver "^7.3.8" - slash "3.0.0" - source-map "^0.7.3" - -"@swc/core-darwin-arm64@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.13.tgz#dba8f8f747ad32fdb58d5b3aec4f740354d32d1b" - integrity sha512-SOF4buAis72K22BGJ3N8y88mLNfxLNprTuJUpzikyMGrvkuBFNcxYtMhmomO0XHsgLDzOJ+hWzcgjRNzjMsUcQ== - -"@swc/core-darwin-x64@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.6.13.tgz#c120207a9ced298f7382ff711bac10f6541c1c82" - integrity sha512-AW8akFSC+tmPE6YQQvK9S2A1B8pjnXEINg+gGgw0KRUUXunvu1/OEOeC5L2Co1wAwhD7bhnaefi06Qi9AiwOag== - -"@swc/core-linux-arm-gnueabihf@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.13.tgz#7b15a1fd32c18dfaf76706632cf8d19146df0d5f" - integrity sha512-f4gxxvDXVUm2HLYXRd311mSrmbpQF2MZ4Ja6XCQz1hWAxXdhRl1gpnZ+LH/xIfGSwQChrtLLVrkxdYUCVuIjFg== - -"@swc/core-linux-arm64-gnu@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.13.tgz#066b6e3c805110edb98e5125a222e3d866bf8f68" - integrity sha512-Nf/eoW2CbG8s+9JoLtjl9FByBXyQ5cjdBsA4efO7Zw4p+YSuXDgc8HRPC+E2+ns0praDpKNZtLvDtmF2lL+2Gg== - -"@swc/core-linux-arm64-musl@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.13.tgz#43a08bc118f117e485e8a9a23d3cb51fe8b4e301" - integrity sha512-2OysYSYtdw79prJYuKIiux/Gj0iaGEbpS2QZWCIY4X9sGoETJ5iMg+lY+YCrIxdkkNYd7OhIbXdYFyGs/w5LDg== - -"@swc/core-linux-x64-gnu@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.13.tgz#0f7358c95f566db6ed8a4249a190043497f41323" - integrity sha512-PkR4CZYJNk5hcd2+tMWBpnisnmYsUzazI1O5X7VkIGFcGePTqJ/bWlfUIVVExWxvAI33PQFzLbzmN5scyIUyGQ== - -"@swc/core-linux-x64-musl@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.13.tgz#6e11994ccf858edb3e70d2e8d700a5b1907a68fb" - integrity sha512-OdsY7wryTxCKwGQcwW9jwWg3cxaHBkTTHi91+5nm7hFPpmZMz1HivJrWAMwVE7iXFw+M4l6ugB/wCvpYrUAAjA== - -"@swc/core-win32-arm64-msvc@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.13.tgz#b9744644f02eb6519b0fe09031080cbf32174fb1" - integrity sha512-ap6uNmYjwk9M/+bFEuWRNl3hq4VqgQ/Lk+ID/F5WGqczNr0L7vEf+pOsRAn0F6EV+o/nyb3ePt8rLhE/wjHpPg== - -"@swc/core-win32-ia32-msvc@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.13.tgz#047302065096883f52b90052d93f9c7e63cdc67b" - integrity sha512-IJ8KH4yIUHTnS/U1jwQmtbfQals7zWPG0a9hbEfIr4zI0yKzjd83lmtS09lm2Q24QBWOCFGEEbuZxR4tIlvfzA== - -"@swc/core-win32-x64-msvc@1.6.13": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.13.tgz#efd9706c38aa7dc3515acfa823b8ffa9f4a3c1a6" - integrity sha512-f6/sx6LMuEnbuxtiSL/EkR0Y6qUHFw1XVrh6rwzKXptTipUdOY+nXpKoh+1UsBm/r7H0/5DtOdrn3q5ZHbFZjQ== - -"@swc/core@^1.4.16": - version "1.6.13" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.6.13.tgz#a583f614203d2350e6bb7f7c3c9c36c0e6f2a1da" - integrity sha512-eailUYex6fkfaQTev4Oa3mwn0/e3mQU4H8y1WPuImYQESOQDtVrowwUGDSc19evpBbHpKtwM+hw8nLlhIsF+Tw== - dependencies: - "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.9" - optionalDependencies: - "@swc/core-darwin-arm64" "1.6.13" - "@swc/core-darwin-x64" "1.6.13" - "@swc/core-linux-arm-gnueabihf" "1.6.13" - "@swc/core-linux-arm64-gnu" "1.6.13" - "@swc/core-linux-arm64-musl" "1.6.13" - "@swc/core-linux-x64-gnu" "1.6.13" - "@swc/core-linux-x64-musl" "1.6.13" - "@swc/core-win32-arm64-msvc" "1.6.13" - "@swc/core-win32-ia32-msvc" "1.6.13" - "@swc/core-win32-x64-msvc" "1.6.13" - -"@swc/counter@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" - integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== - -"@swc/helpers@^0.5.11": - version "0.5.11" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.11.tgz#5bab8c660a6e23c13b2d23fcd1ee44a2db1b0cb7" - integrity sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A== - dependencies: - tslib "^2.4.0" - -"@swc/types@^0.1.9": - version "0.1.9" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.9.tgz#e67cdcc2e4dd74a3cef4474b465eb398e7ae83e2" - integrity sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg== - dependencies: - "@swc/counter" "^0.1.3" - -"@szmarczak/http-timer@^4.0.5": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" - integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== - dependencies: - defer-to-connect "^2.0.0" - -"@tokenizer/token@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" - integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== - -"@types/cacheable-request@^6.0.1": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" - integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "^3.1.4" - "@types/node" "*" - "@types/responselike" "^1.0.0" - -"@types/http-cache-semantics@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" - integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== - -"@types/keyv@^3.1.4": - version "3.1.4" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" - integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== - dependencies: - "@types/node" "*" - -"@types/node@*": - version "20.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" - integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== - dependencies: - undici-types "~5.26.4" - "@types/prop-types@*": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" @@ -420,425 +121,21 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/responselike@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" - integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== - dependencies: - "@types/node" "*" - -arch@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" - integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -bin-check@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bin-check/-/bin-check-4.1.0.tgz#fc495970bdc88bb1d5a35fc17e65c4a149fc4a49" - integrity sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA== - dependencies: - execa "^0.7.0" - executable "^4.1.0" - -bin-version-check@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-5.1.0.tgz#788e80e036a87313f8be7908bc20e5abe43f0837" - integrity sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g== - dependencies: - bin-version "^6.0.0" - semver "^7.5.3" - semver-truncate "^3.0.0" - -bin-version@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-6.0.0.tgz#08ecbe5fc87898b441425e145f9e105064d00315" - integrity sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw== - dependencies: - execa "^5.0.0" - find-versions "^5.0.0" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" - integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" - caniuse-lite@^1.0.30001616: - version "1.0.30001640" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f" - integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA== - -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - -content-disposition@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" + version "1.0.30001716" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz#39220dfbc58c85d9d4519e7090b656aa11ca4b85" + integrity sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw== csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -esbuild@~0.21.5: - version "0.21.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" - integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== - optionalDependencies: - "@esbuild/aix-ppc64" "0.21.5" - "@esbuild/android-arm" "0.21.5" - "@esbuild/android-arm64" "0.21.5" - "@esbuild/android-x64" "0.21.5" - "@esbuild/darwin-arm64" "0.21.5" - "@esbuild/darwin-x64" "0.21.5" - "@esbuild/freebsd-arm64" "0.21.5" - "@esbuild/freebsd-x64" "0.21.5" - "@esbuild/linux-arm" "0.21.5" - "@esbuild/linux-arm64" "0.21.5" - "@esbuild/linux-ia32" "0.21.5" - "@esbuild/linux-loong64" "0.21.5" - "@esbuild/linux-mips64el" "0.21.5" - "@esbuild/linux-ppc64" "0.21.5" - "@esbuild/linux-riscv64" "0.21.5" - "@esbuild/linux-s390x" "0.21.5" - "@esbuild/linux-x64" "0.21.5" - "@esbuild/netbsd-x64" "0.21.5" - "@esbuild/openbsd-x64" "0.21.5" - "@esbuild/sunos-x64" "0.21.5" - "@esbuild/win32-arm64" "0.21.5" - "@esbuild/win32-ia32" "0.21.5" - "@esbuild/win32-x64" "0.21.5" - -escape-string-regexp@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" - integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw== - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -executable@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" - integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== - dependencies: - pify "^2.2.0" - -ext-list@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" - integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== - dependencies: - mime-db "^1.28.0" - -ext-name@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" - integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== - dependencies: - ext-list "^2.0.0" - sort-keys-length "^1.0.0" - -fast-glob@^3.2.5: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -file-type@^17.1.6: - version "17.1.6" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.6.tgz#18669e0577a4849ef6e73a41f8bdf1ab5ae21023" - integrity sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw== - dependencies: - readable-web-to-node-stream "^3.0.2" - strtok3 "^7.0.0-alpha.9" - token-types "^5.0.0-alpha.2" - -filename-reserved-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz#3d5dd6d4e2d73a3fed2ebc4cd0b3448869a081f7" - integrity sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw== - -filenamify@^5.0.2: - version "5.1.1" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-5.1.1.tgz#a1ccc5ae678a5e34f578afcb9b72898264d166d2" - integrity sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA== - dependencies: - filename-reserved-regex "^3.0.0" - strip-outer "^2.0.0" - trim-repeated "^2.0.0" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-versions@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-5.1.0.tgz#973f6739ce20f5e439a27eba8542a4b236c8e685" - integrity sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg== - dependencies: - semver-regex "^4.0.5" - -fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-tsconfig@^4.7.5: - version "4.7.5" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.5.tgz#5e012498579e9a6947511ed0cd403272c7acbbaf" - integrity sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw== - dependencies: - resolve-pkg-maps "^1.0.0" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -got@^11.8.5: - version "11.8.6" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" - integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -html-rspack-plugin@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/html-rspack-plugin/-/html-rspack-plugin-5.8.0.tgz#1889aeba024d15744431ff84ab6fee126f94c890" - integrity sha512-ilfK60cxmBzglkHw91SlHDwbTd8uS7+poG12ueuwn012XPdFq8jU0pFuGEqoryJ+1l/uQuVffJ2jlpJDlhJBsg== - -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-glob@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -keyv@^4.0.0: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -846,187 +143,6 @@ loose-envify@^1.1.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" - integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@^1.28.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -minimatch@^9.0.3: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -nice-napi@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b" - integrity sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA== - dependencies: - node-addon-api "^3.0.0" - node-gyp-build "^4.2.2" - -node-addon-api@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" - integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== - -node-gyp-build@^4.2.2: - version "4.8.1" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" - integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -os-filter-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/os-filter-obj/-/os-filter-obj-2.0.0.tgz#1c0b62d5f3a2442749a2d139e6dddee6e81d8d16" - integrity sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg== - dependencies: - arch "^2.1.0" - -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -path-key@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -peek-readable@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.1.1.tgz#7dbeafa1ce271a3eba3fba808883bdb03769b822" - integrity sha512-4hEOSH7KeEaZpMDF/xfm1W9fS5rT7Ett3BkXWHqAEzRLLwLaHkwOL+GvvpIEh9UrvX9BDhzfkvteslgraoH69w== - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -piscina@^4.3.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/piscina/-/piscina-4.6.1.tgz#4de673b0ff84bf641b31b07b3348669383b51c9a" - integrity sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA== - optionalDependencies: - nice-napi "^1.0.2" - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - react-dom@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -1042,56 +158,6 @@ react@^18.3.1: dependencies: loose-envify "^1.1.0" -readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-web-to-node-stream@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" - integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== - dependencies: - readable-stream "^3.6.0" - -resolve-alpn@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" - integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== - -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -responselike@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" - integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== - dependencies: - lowercase-keys "^2.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - scheduler@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" @@ -1099,188 +165,12 @@ scheduler@^0.23.2: dependencies: loose-envify "^1.1.0" -semver-regex@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-4.0.5.tgz#fbfa36c7ba70461311f5debcb3928821eb4f9180" - integrity sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw== - -semver-truncate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-3.0.0.tgz#0e3b4825d4a4225d8ae6e7c72231182b42edba40" - integrity sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg== - dependencies: - semver "^7.3.5" - -semver@^7.3.5, semver@^7.3.8, semver@^7.5.3: - version "7.6.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.0, signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -slash@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -sort-keys-length@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" - integrity sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw== - dependencies: - sort-keys "^1.0.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== - dependencies: - is-plain-obj "^1.0.0" - -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-outer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-2.0.0.tgz#c45c724ed9b1ff6be5f660503791404f4714084b" - integrity sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg== - -strtok3@^7.0.0-alpha.9: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.1.0.tgz#98856ed6651c2f7f0e8a436a54536a53528ecfb2" - integrity sha512-19dQEwG6Jd+VabjPRyBhymIF069vZiqWSZa2jJBoKJTsqGKnTxowGoQaLnz+yLARfDI041IUQekyPUMWElOgsQ== - dependencies: - "@tokenizer/token" "^0.3.0" - peek-readable "^5.1.1" - tapable@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -token-types@^5.0.0-alpha.2: - version "5.0.1" - resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.1.tgz#aa9d9e6b23c420a675e55413b180635b86a093b4" - integrity sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg== - dependencies: - "@tokenizer/token" "^0.3.0" - ieee754 "^1.2.1" - -trim-repeated@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-2.0.0.tgz#5d60556d6d40d9461b7c7e06c3ac20b6b1d50090" - integrity sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg== - dependencies: - escape-string-regexp "^5.0.0" - -tslib@^2.4.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== - -tsx@^4.16.2: - version "4.16.2" - resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.16.2.tgz#8722be119ae226ef0b4c6210d5ee90f3ba823f19" - integrity sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ== - dependencies: - esbuild "~0.21.5" - get-tsconfig "^4.7.5" - optionalDependencies: - fsevents "~2.3.3" - -typescript@^4.6.4: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - webpack-sources@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== From a47b06549dabdf7f16f4bf96b9a7d6226233d690 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:39:07 +0300 Subject: [PATCH 03/13] refactor: improve client building process --- crates/metassr-build/src/client/bundling.rs | 261 +++++++++++++++ crates/metassr-build/src/client/cache.rs | 301 +++++++++++++++++ crates/metassr-build/src/client/config.rs | 243 ++++++++++++++ crates/metassr-build/src/client/hydrator.rs | 316 ++++++++++++++++-- crates/metassr-build/src/client/mod.rs | 277 ++++++++++++---- crates/metassr-build/src/client/target.rs | 344 ++++++++++++++++++++ 6 files changed, 1657 insertions(+), 85 deletions(-) create mode 100644 crates/metassr-build/src/client/bundling.rs create mode 100644 crates/metassr-build/src/client/cache.rs create mode 100644 crates/metassr-build/src/client/config.rs create mode 100644 crates/metassr-build/src/client/target.rs diff --git a/crates/metassr-build/src/client/bundling.rs b/crates/metassr-build/src/client/bundling.rs new file mode 100644 index 0000000..280ea50 --- /dev/null +++ b/crates/metassr-build/src/client/bundling.rs @@ -0,0 +1,261 @@ +use anyhow::{Context, Result}; + +use metassr_bundler::WebBundler; + +use super::{config::ClientConfig, target::TargetCollection}; + +/// Handles the bundling process for client-side code +pub struct BundlingService { + config: ClientConfig, +} + +impl BundlingService { + /// Creates a new bundling service + pub fn new(config: ClientConfig) -> Self { + Self { config } + } + + /// Bundles the provided targets + pub fn bundle(&self, targets: &TargetCollection) -> Result { + if targets.is_empty() { + return Ok(BundlingResult::empty()); + } + + let bundler_targets = targets + .to_bundler_targets() + .context("Failed to convert targets for bundling")?; + + let bundler = WebBundler::new(&bundler_targets, self.config.dist_dir_str()) + .context("Failed to create web bundler")?; + + bundler + .exec() + .context("Bundling process failed")?; + + Ok(BundlingResult::success(bundler_targets.len())) + } + + /// Validates that bundling targets are ready + pub fn validate_targets(&self, targets: &TargetCollection) -> Result { + let mut validation = ValidationResult::new(); + + for target in targets.targets() { + // Check if source file exists + if !target.source_path.exists() { + validation.add_error(format!( + "Source file does not exist: {}", + target.source_path.display() + )); + } + + // Check if content is not empty + if target.content.is_empty() { + validation.add_warning(format!( + "Target '{}' has empty content", + target.id + )); + } + + // Check for absolute path resolution + if let Err(e) = target.absolute_source_path() { + validation.add_error(format!( + "Cannot resolve absolute path for '{}': {}", + target.id, e + )); + } + } + + Ok(validation) + } + + /// Gets bundling statistics + pub fn get_bundling_stats(&self, targets: &TargetCollection) -> BundlingStats { + let target_stats = targets.stats(); + + BundlingStats { + total_targets: target_stats.total_targets, + page_targets: target_stats.page_targets, + special_targets: target_stats.special_targets, + total_content_size: target_stats.total_content_size, + dist_dir: self.config.dist_dir.clone(), + bundler_options: self.config.bundler_options.clone(), + } + } +} + +/// Result of a bundling operation +#[derive(Debug)] +pub struct BundlingResult { + /// Whether the bundling was successful + pub success: bool, + /// Number of targets that were bundled + pub bundled_targets: usize, + /// Any messages from the bundling process + pub messages: Vec, +} + +impl BundlingResult { + /// Creates a successful bundling result + pub fn success(bundled_targets: usize) -> Self { + Self { + success: true, + bundled_targets, + messages: vec![format!("Successfully bundled {} targets", bundled_targets)], + } + } + + /// Creates an empty bundling result (no targets to bundle) + pub fn empty() -> Self { + Self { + success: true, + bundled_targets: 0, + messages: vec!["No targets to bundle".to_string()], + } + } + + /// Creates a failed bundling result + pub fn failure(error: String) -> Self { + Self { + success: false, + bundled_targets: 0, + messages: vec![error], + } + } +} + +/// Result of target validation +#[derive(Debug, Default)] +pub struct ValidationResult { + /// Validation errors that prevent bundling + pub errors: Vec, + /// Validation warnings that don't prevent bundling + pub warnings: Vec, +} + +impl ValidationResult { + /// Creates a new validation result + pub fn new() -> Self { + Self::default() + } + + /// Adds an error to the validation result + pub fn add_error(&mut self, error: String) { + self.errors.push(error); + } + + /// Adds a warning to the validation result + pub fn add_warning(&mut self, warning: String) { + self.warnings.push(warning); + } + + /// Checks if validation passed (no errors) + pub fn is_valid(&self) -> bool { + self.errors.is_empty() + } + + /// Gets all issues (errors + warnings) + pub fn all_issues(&self) -> Vec<&String> { + self.errors.iter().chain(self.warnings.iter()).collect() + } +} + +/// Statistics about bundling operation +#[derive(Debug)] +pub struct BundlingStats { + pub total_targets: usize, + pub page_targets: usize, + pub special_targets: usize, + pub total_content_size: usize, + pub dist_dir: std::path::PathBuf, + pub bundler_options: super::config::BundlerOptions, +} + +impl std::fmt::Display for BundlingStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Bundling Statistics:")?; + writeln!(f, " Total targets: {}", self.total_targets)?; + writeln!(f, " Page targets: {}", self.page_targets)?; + writeln!(f, " Special targets: {}", self.special_targets)?; + writeln!(f, " Total content size: {} bytes", self.total_content_size)?; + writeln!(f, " Output directory: {}", self.dist_dir.display())?; + writeln!(f, " Source maps: {}", self.bundler_options.source_maps)?; + writeln!(f, " Minimize: {}", self.bundler_options.minimize)?; + write!(f, " Target: {}", self.bundler_options.target) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::{config::ClientConfig, target::{BuildTarget, TargetCollection}}; + use std::{path::PathBuf, env}; + + fn create_test_config() -> ClientConfig { + let temp_dir = env::temp_dir().join("metassr_bundling_test"); + let src_dir = temp_dir.join("src"); + std::fs::create_dir_all(&src_dir).unwrap(); + + let config = ClientConfig::new(&temp_dir, "dist").unwrap(); + let _ = std::fs::remove_dir_all(&temp_dir); // Cleanup after creation + config + } + + fn create_test_target() -> BuildTarget { + BuildTarget::new( + "test".to_string(), + PathBuf::from("src/test.js"), + PathBuf::from("test.js"), + "console.log('test')".to_string(), + "/test".to_string(), + false, + ) + } + + #[test] + fn test_bundling_service_creation() { + let config = create_test_config(); + let service = BundlingService::new(config); + + // Service should be created successfully + assert_eq!(service.config.root_id, "root"); + } + + #[test] + fn test_empty_targets_bundling() { + let config = create_test_config(); + let service = BundlingService::new(config); + let targets = TargetCollection::new(); + + let result = service.bundle(&targets).unwrap(); + assert!(result.success); + assert_eq!(result.bundled_targets, 0); + } + + #[test] + fn test_validation_result() { + let mut validation = ValidationResult::new(); + + assert!(validation.is_valid()); + + validation.add_error("Test error".to_string()); + assert!(!validation.is_valid()); + assert_eq!(validation.errors.len(), 1); + + validation.add_warning("Test warning".to_string()); + assert_eq!(validation.warnings.len(), 1); + assert_eq!(validation.all_issues().len(), 2); + } + + #[test] + fn test_bundling_stats() { + let config = create_test_config(); + let service = BundlingService::new(config); + let mut targets = TargetCollection::new(); + targets.add_target(create_test_target()).unwrap(); + + let stats = service.get_bundling_stats(&targets); + assert_eq!(stats.total_targets, 1); + assert_eq!(stats.page_targets, 1); + assert_eq!(stats.special_targets, 0); + } +} diff --git a/crates/metassr-build/src/client/cache.rs b/crates/metassr-build/src/client/cache.rs new file mode 100644 index 0000000..dba7375 --- /dev/null +++ b/crates/metassr-build/src/client/cache.rs @@ -0,0 +1,301 @@ +use anyhow::{Context, Result}; +use metassr_utils::cache_dir::CacheDir; + +use super::{ + config::ClientConfig, + target::TargetCollection, +}; + +/// Service for managing cache operations during the build process +pub struct CacheService { + cache_dir: CacheDir, + config: ClientConfig, +} + +impl CacheService { + /// Creates a new cache service + pub fn new(config: ClientConfig) -> Result { + let cache_dir = CacheDir::new(config.cache_dir_str()) + .context("Failed to initialize cache directory")?; + + Ok(Self { cache_dir, config }) + } + + /// Stores targets in the cache + pub fn store_targets(&mut self, targets: &TargetCollection) -> Result { + let mut stored_files = 0; + let mut total_bytes = 0; + + for target in targets.targets() { + let cache_path = format!("pages/{}", target.output_path.display()); + + self.cache_dir + .insert(&cache_path, target.content_bytes()) + .with_context(|| format!("Failed to cache target: {}", target.id))?; + + stored_files += 1; + total_bytes += target.metadata.content_size; + } + + Ok(CacheStats { + stored_files, + total_bytes, + cache_dir: self.cache_dir.path().to_path_buf(), + }) + } + + /// Retrieves the cache entries suitable for bundling + pub fn get_bundler_entries(&self) -> Result> { + let entries = self.cache_dir + .entries_in_scope() + .iter() + .map(|(entry_name, path)| { + let fullpath = path + .canonicalize() + .with_context(|| format!("Failed to canonicalize cache path: {}", path.display()))?; + + Ok((entry_name.to_owned(), format!("{}", fullpath.display()))) + }) + .collect::>>()?; + + Ok(entries) + } + + /// Clears the cache + pub fn clear_cache(&mut self) -> Result<()> { + // Implementation would depend on CacheDir having a clear method + // For now, we'll recreate the cache directory + let cache_path = self.config.cache_dir_str(); + + if std::path::Path::new(cache_path).exists() { + std::fs::remove_dir_all(cache_path) + .context("Failed to remove cache directory")?; + } + + self.cache_dir = CacheDir::new(cache_path) + .context("Failed to recreate cache directory")?; + + Ok(()) + } + + /// Gets cache statistics + pub fn get_cache_stats(&self) -> CacheStats { + let entries = self.cache_dir.entries_in_scope(); + let stored_files = entries.len(); + let total_bytes = entries + .values() + .filter_map(|path| std::fs::metadata(path).ok()) + .map(|metadata| metadata.len() as usize) + .sum(); + + CacheStats { + stored_files, + total_bytes, + cache_dir: self.cache_dir.path().to_path_buf(), + } + } + + /// Validates cache integrity + pub fn validate_cache(&self) -> Result { + let mut validation = CacheValidation::new(); + let entries = self.cache_dir.entries_in_scope(); + + for (entry_name, path) in entries.iter() { + if !path.exists() { + validation.add_missing_file(entry_name.clone(), path.clone()); + } else if path.is_dir() { + validation.add_warning(format!( + "Cache entry '{}' is a directory, expected a file", + entry_name + )); + } else { + // Check if file is readable + if let Err(e) = std::fs::read(path) { + validation.add_error(format!( + "Cannot read cache file '{}': {}", + entry_name, e + )); + } + } + } + + Ok(validation) + } +} + +/// Statistics about cache operations +#[derive(Debug)] +pub struct CacheStats { + pub stored_files: usize, + pub total_bytes: usize, + pub cache_dir: std::path::PathBuf, +} + +impl std::fmt::Display for CacheStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Cache: {} files, {} bytes in {}", + self.stored_files, + self.total_bytes, + self.cache_dir.display() + ) + } +} + +/// Result of cache validation +#[derive(Debug)] +pub struct CacheValidation { + missing_files: Vec<(String, std::path::PathBuf)>, + errors: Vec, + warnings: Vec, +} + +impl CacheValidation { + fn new() -> Self { + Self { + missing_files: Vec::new(), + errors: Vec::new(), + warnings: Vec::new(), + } + } + + fn add_missing_file(&mut self, entry: String, path: std::path::PathBuf) { + self.missing_files.push((entry, path)); + } + + fn add_error(&mut self, error: String) { + self.errors.push(error); + } + + fn add_warning(&mut self, warning: String) { + self.warnings.push(warning); + } + + /// Checks if cache validation passed + pub fn is_valid(&self) -> bool { + self.missing_files.is_empty() && self.errors.is_empty() + } + + /// Gets all validation issues + pub fn issues(&self) -> Vec { + let mut issues = Vec::new(); + + for (entry, path) in &self.missing_files { + issues.push(format!("Missing cache file '{}' at {}", entry, path.display())); + } + + issues.extend(self.errors.iter().cloned()); + issues.extend(self.warnings.iter().cloned()); + + issues + } + + /// Gets the number of missing files + pub fn missing_file_count(&self) -> usize { + self.missing_files.len() + } + + /// Gets the number of errors + pub fn error_count(&self) -> usize { + self.errors.len() + } + + /// Gets the number of warnings + pub fn warning_count(&self) -> usize { + self.warnings.len() + } +} + +impl std::fmt::Display for CacheValidation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_valid() { + write!(f, "Cache validation passed") + } else { + write!( + f, + "Cache validation failed: {} missing files, {} errors, {} warnings", + self.missing_file_count(), + self.error_count(), + self.warning_count() + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::target::BuildTarget; + use std::{path::PathBuf, env}; + + fn create_test_config() -> ClientConfig { + let temp_dir = env::temp_dir().join("metassr_cache_test"); + let src_dir = temp_dir.join("src"); + std::fs::create_dir_all(&src_dir).unwrap(); + ClientConfig::new(&temp_dir, "dist").unwrap() + } + + fn create_test_target() -> BuildTarget { + BuildTarget::new( + "test".to_string(), + PathBuf::from("src/test.js"), + PathBuf::from("test.js"), + "console.log('test')".to_string(), + "/test".to_string(), + false, + ) + } + + #[test] + fn test_cache_service_creation() { + let config = create_test_config(); + config.ensure_directories().unwrap(); + + let cache_service = CacheService::new(config); + assert!(cache_service.is_ok()); + + // Cleanup + let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_cache_test")); + } + + #[test] + fn test_cache_stats() { + let config = create_test_config(); + config.ensure_directories().unwrap(); + + let cache_service = CacheService::new(config).unwrap(); + let stats = cache_service.get_cache_stats(); + + assert_eq!(stats.stored_files, 0); // Empty cache initially + + // Cleanup + let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_cache_test")); + } + + #[test] + fn test_cache_validation() { + let config = create_test_config(); + config.ensure_directories().unwrap(); + + let cache_service = CacheService::new(config).unwrap(); + let validation = cache_service.validate_cache().unwrap(); + + assert!(validation.is_valid()); // Empty cache should be valid + + // Cleanup + let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_cache_test")); + } + + #[test] + fn test_cache_validation_display() { + let mut validation = CacheValidation::new(); + validation.add_error("Test error".to_string()); + validation.add_missing_file("test".to_string(), PathBuf::from("/missing")); + + let display_text = format!("{}", validation); + assert!(display_text.contains("Cache validation failed")); + assert!(display_text.contains("1 missing files")); + assert!(display_text.contains("1 errors")); + } +} diff --git a/crates/metassr-build/src/client/config.rs b/crates/metassr-build/src/client/config.rs new file mode 100644 index 0000000..5e69b5e --- /dev/null +++ b/crates/metassr-build/src/client/config.rs @@ -0,0 +1,243 @@ +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + ffi::OsStr, + path::{Path, PathBuf}, +}; + +/// Configuration for the client building process +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientConfig { + /// Root directory of the project + pub root_dir: PathBuf, + /// Source directory (usually "src") + pub src_dir: PathBuf, + /// Distribution directory + pub dist_dir: PathBuf, + /// Cache directory for intermediate files + pub cache_dir: PathBuf, + /// Root element ID for hydration + pub root_id: String, + /// File extension for generated files + pub output_extension: String, + /// Custom bundler options + pub bundler_options: BundlerOptions, +} + +/// Configuration for the bundler +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BundlerOptions { + /// Whether to enable source maps + pub source_maps: bool, + /// Whether to minimize output + pub minimize: bool, + /// Target environment (web, node, etc.) + pub target: String, + /// Public path for assets + pub public_path: String, + /// Additional entry points + pub additional_entries: HashMap, +} + +impl Default for BundlerOptions { + fn default() -> Self { + Self { + source_maps: true, + minimize: false, + target: "web".to_string(), + public_path: "".to_string(), + additional_entries: HashMap::new(), + } + } +} + +impl ClientConfig { + /// Creates a new ClientConfig with default settings + pub fn new(root: &S, dist_dir: &str) -> Result + where + S: AsRef + ?Sized, + { + let root_dir = Path::new(root).to_path_buf(); + let src_dir = root_dir.join("src"); + let dist_path = root_dir.join(dist_dir); + let cache_dir = dist_path.join("cache"); + + Self::validate_directories(&root_dir, &src_dir)?; + + Ok(Self { + root_dir, + src_dir, + dist_dir: dist_path, + cache_dir, + root_id: "root".to_string(), + output_extension: "js".to_string(), + bundler_options: BundlerOptions::default(), + }) + } + + /// Creates a custom ClientConfig + pub fn builder() -> ClientConfigBuilder { + ClientConfigBuilder::default() + } + + /// Validates that required directories exist or can be created + fn validate_directories(root_dir: &Path, src_dir: &Path) -> Result<()> { + if !root_dir.exists() { + return Err(anyhow::anyhow!( + "Root directory does not exist: {}", + root_dir.display() + )); + } + + if !src_dir.exists() { + return Err(anyhow::anyhow!( + "Source directory does not exist: {}", + src_dir.display() + )); + } + + Ok(()) + } + + /// Ensures all required directories exist + pub fn ensure_directories(&self) -> Result<()> { + if !self.dist_dir.exists() { + std::fs::create_dir_all(&self.dist_dir) + .with_context(|| format!("Failed to create dist directory: {}", self.dist_dir.display()))?; + } + + if !self.cache_dir.exists() { + std::fs::create_dir_all(&self.cache_dir) + .with_context(|| format!("Failed to create cache directory: {}", self.cache_dir.display()))?; + } + + Ok(()) + } + + /// Gets the cache directory path as a string + pub fn cache_dir_str(&self) -> &str { + self.cache_dir.to_str().unwrap_or_default() + } + + /// Gets the dist directory path as a string + pub fn dist_dir_str(&self) -> &str { + self.dist_dir.to_str().unwrap_or_default() + } +} + +/// Builder for ClientConfig +#[derive(Default)] +pub struct ClientConfigBuilder { + root_dir: Option, + src_dir: Option, + dist_dir: Option, + cache_dir: Option, + root_id: Option, + output_extension: Option, + bundler_options: Option, +} + +impl ClientConfigBuilder { + pub fn root_dir>(mut self, path: P) -> Self { + self.root_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn src_dir>(mut self, path: P) -> Self { + self.src_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn dist_dir>(mut self, path: P) -> Self { + self.dist_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn cache_dir>(mut self, path: P) -> Self { + self.cache_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn root_id>(mut self, id: S) -> Self { + self.root_id = Some(id.into()); + self + } + + pub fn output_extension>(mut self, ext: S) -> Self { + self.output_extension = Some(ext.into()); + self + } + + pub fn bundler_options(mut self, options: BundlerOptions) -> Self { + self.bundler_options = Some(options); + self + } + + pub fn build(self) -> Result { + let root_dir = self.root_dir.ok_or_else(|| anyhow::anyhow!("Root directory is required"))?; + let src_dir = self.src_dir.unwrap_or_else(|| root_dir.join("src")); + let dist_dir = self.dist_dir.unwrap_or_else(|| root_dir.join("dist")); + let cache_dir = self.cache_dir.unwrap_or_else(|| dist_dir.join("cache")); + + ClientConfig::validate_directories(&root_dir, &src_dir)?; + + Ok(ClientConfig { + root_dir, + src_dir, + dist_dir, + cache_dir, + root_id: self.root_id.unwrap_or_else(|| "root".to_string()), + output_extension: self.output_extension.unwrap_or_else(|| "js".to_string()), + bundler_options: self.bundler_options.unwrap_or_default(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn test_client_config_creation() { + let temp = TempDir::new().unwrap(); + let root = temp.path(); + std::fs::create_dir(root.join("src")).unwrap(); + + let config = ClientConfig::new(root, "dist").unwrap(); + assert_eq!(config.root_dir, root); + assert_eq!(config.src_dir, root.join("src")); + assert_eq!(config.dist_dir, root.join("dist")); + } + + #[test] + fn test_client_config_builder() { + let temp = TempDir::new().unwrap(); + let root = temp.path(); + std::fs::create_dir(root.join("src")).unwrap(); + + let config = ClientConfig::builder() + .root_dir(root) + .root_id("app") + .output_extension("mjs") + .build() + .unwrap(); + + assert_eq!(config.root_id, "app"); + assert_eq!(config.output_extension, "mjs"); + } + + #[test] + fn test_ensure_directories() { + let temp = TempDir::new().unwrap(); + let root = temp.path(); + std::fs::create_dir(root.join("src")).unwrap(); + + let config = ClientConfig::new(root, "dist").unwrap(); + config.ensure_directories().unwrap(); + + assert!(config.dist_dir.exists()); + assert!(config.cache_dir.exists()); + } +} diff --git a/crates/metassr-build/src/client/hydrator.rs b/crates/metassr-build/src/client/hydrator.rs index 6d13f3f..74fec27 100644 --- a/crates/metassr-build/src/client/hydrator.rs +++ b/crates/metassr-build/src/client/hydrator.rs @@ -2,20 +2,33 @@ use crate::{ shared::{APP_PATH_TAG, PAGE_PATH_TAG, ROOT_ID_TAG}, traits::Generate, }; -use anyhow::Result; -use std::{ffi::OsStr, path::PathBuf}; +use anyhow::{Context, Result}; +use std::{ + collections::HashMap, + ffi::OsStr, + path::{Path, PathBuf}, +}; const HYDRATED_FILE_TEMPLATE: &str = include_str!("../scripts/hydrate.js.template"); +/// Configuration for hydration generation #[derive(Debug, Clone)] -pub struct Hydrator { - app_path: PathBuf, - page_path: PathBuf, - root_id: String, +pub struct HydrationConfig { + /// Path to the app component + pub app_path: PathBuf, + /// Path to the page component + pub page_path: PathBuf, + /// Root element ID for hydration + pub root_id: String, + /// Additional template variables + pub template_vars: HashMap, + /// Whether to use strict mode + pub strict_mode: bool, } -impl Hydrator { - pub fn new<'a, S>(app_path: &'a S, page_path: &'a S, root_id: &'a str) -> Self +impl HydrationConfig { + /// Creates a new hydration config + pub fn new(app_path: &S, page_path: &S, root_id: &str) -> Self where S: AsRef + ?Sized, { @@ -23,35 +36,288 @@ impl Hydrator { app_path: PathBuf::from(app_path), page_path: PathBuf::from(page_path), root_id: root_id.to_string(), + template_vars: HashMap::new(), + strict_mode: true, } } + + /// Builder pattern for configuration + pub fn builder() -> HydrationConfigBuilder { + HydrationConfigBuilder::default() + } + + /// Adds a template variable + pub fn with_var(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.template_vars.insert(key.into(), value.into()); + self + } + + /// Sets strict mode + pub fn with_strict_mode(mut self, strict_mode: bool) -> Self { + self.strict_mode = strict_mode; + self + } + + /// Validates the configuration + pub fn validate(&self) -> Result<()> { + if !self.app_path.exists() { + return Err(anyhow::anyhow!( + "App component not found: {}", + self.app_path.display() + )); + } + + if !self.page_path.exists() { + return Err(anyhow::anyhow!( + "Page component not found: {}", + self.page_path.display() + )); + } + + if self.root_id.is_empty() { + return Err(anyhow::anyhow!("Root ID cannot be empty")); + } + + Ok(()) + } +} + +/// Builder for HydrationConfig +#[derive(Default)] +pub struct HydrationConfigBuilder { + app_path: Option, + page_path: Option, + root_id: Option, + template_vars: HashMap, + strict_mode: bool, +} + +impl HydrationConfigBuilder { + pub fn app_path>(mut self, path: P) -> Self { + self.app_path = Some(path.as_ref().to_path_buf()); + self + } + + pub fn page_path>(mut self, path: P) -> Self { + self.page_path = Some(path.as_ref().to_path_buf()); + self + } + + pub fn root_id>(mut self, id: S) -> Self { + self.root_id = Some(id.into()); + self + } + + pub fn template_var(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.template_vars.insert(key.into(), value.into()); + self + } + + pub fn strict_mode(mut self, strict_mode: bool) -> Self { + self.strict_mode = strict_mode; + self + } + + pub fn build(self) -> Result { + let app_path = self.app_path.ok_or_else(|| anyhow::anyhow!("App path is required"))?; + let page_path = self.page_path.ok_or_else(|| anyhow::anyhow!("Page path is required"))?; + let root_id = self.root_id.unwrap_or_else(|| "root".to_string()); + + let config = HydrationConfig { + app_path, + page_path, + root_id, + template_vars: self.template_vars, + strict_mode: self.strict_mode, + }; + + config.validate()?; + Ok(config) + } +} + +/// Generates hydration scripts for client-side rendering +#[derive(Debug)] +pub struct Hydrator { + config: HydrationConfig, +} + +impl Hydrator { + /// Creates a new hydrator with the given configuration + pub fn new(config: HydrationConfig) -> Self { + Self { config } + } + + /// Creates a hydrator with simple parameters (legacy compatibility) + pub fn simple(app_path: &S, page_path: &S, root_id: &str) -> Result + where + S: AsRef + ?Sized, + { + let config = HydrationConfig::new(app_path, page_path, root_id); + config.validate()?; + Ok(Self::new(config)) + } + + /// Generates the hydration script content + fn generate_content(&self) -> Result { + let app_path_canonical = self.config.app_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize app path: {}", self.config.app_path.display()))?; + + let page_path_canonical = self.config.page_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize page path: {}", self.config.page_path.display()))?; + + let app_path_str = app_path_canonical + .to_str() + .ok_or_else(|| anyhow::anyhow!("App path contains invalid UTF-8"))?; + + let page_path_str = page_path_canonical + .to_str() + .ok_or_else(|| anyhow::anyhow!("Page path contains invalid UTF-8"))?; + + let mut content = HYDRATED_FILE_TEMPLATE + .replace(APP_PATH_TAG, app_path_str) + .replace(PAGE_PATH_TAG, page_path_str) + .replace(ROOT_ID_TAG, &self.config.root_id); + + // Apply additional template variables + for (key, value) in &self.config.template_vars { + let placeholder = format!("%{}%", key.to_uppercase()); + content = content.replace(&placeholder, value); + } + + // Handle strict mode + if !self.config.strict_mode { + content = content.replace("React.StrictMode", "React.Fragment"); + } + + Ok(content) + } + + /// Gets the configuration + pub fn config(&self) -> &HydrationConfig { + &self.config + } + + /// Updates the configuration + pub fn with_config(mut self, config: HydrationConfig) -> Result { + config.validate()?; + self.config = config; + Ok(self) + } } impl Generate for Hydrator { type Output = String; + fn generate(&self) -> Result { - Ok(HYDRATED_FILE_TEMPLATE - .replace( - APP_PATH_TAG, - self.app_path.canonicalize()?.to_str().unwrap(), - ) - .replace( - PAGE_PATH_TAG, - self.page_path.canonicalize()?.to_str().unwrap(), - ) - .replace(ROOT_ID_TAG, &self.root_id)) + self.generate_content() } } #[cfg(test)] mod tests { use super::*; + use std::env; + + fn create_test_files() -> (PathBuf, PathBuf) { + let temp_dir = env::temp_dir().join("metassr_test"); + std::fs::create_dir_all(&temp_dir).unwrap(); + + let app_path = temp_dir.join("_app.tsx"); + let page_path = temp_dir.join("home.jsx"); + + // Create test files + std::fs::write(&app_path, "export default function App() { return null; }").unwrap(); + std::fs::write(&page_path, "export default function Page() { return null; }").unwrap(); + + (app_path, page_path) + } + + #[test] + fn test_hydration_config_creation() { + let (app_path, page_path) = create_test_files(); + + let config = HydrationConfig::new(&app_path, &page_path, "root"); + assert_eq!(config.root_id, "root"); + assert!(config.strict_mode); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); + } + + #[test] + fn test_hydration_config_builder() { + let (app_path, page_path) = create_test_files(); + + let config = HydrationConfig::builder() + .app_path(&app_path) + .page_path(&page_path) + .root_id("app") + .template_var("CUSTOM_VAR", "custom_value") + .strict_mode(false) + .build() + .unwrap(); + + assert_eq!(config.root_id, "app"); + assert!(!config.strict_mode); + assert_eq!(config.template_vars.get("CUSTOM_VAR"), Some(&"custom_value".to_string())); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); + } + + #[test] + fn test_hydrator_simple_creation() { + let (app_path, page_path) = create_test_files(); + + let hydrator = Hydrator::simple(&app_path, &page_path, "root").unwrap(); + assert_eq!(hydrator.config().root_id, "root"); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); + } + + #[test] + fn test_generate_hydrated_file() { + let (app_path, page_path) = create_test_files(); + + let hydrator = Hydrator::simple(&app_path, &page_path, "root").unwrap(); + let content = hydrator.generate().unwrap(); + + assert!(content.contains("hydrateRoot")); + assert!(content.contains("document.getElementById(\"root\")")); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); + } + #[test] - fn generate_hydrated_file() { - println!( - "{}", - Hydrator::new("src/_app.tsx", "src/pages/home.jsx", "root") - .generate() - .unwrap() - ); + fn test_template_variables() { + let (app_path, page_path) = create_test_files(); + + let config = HydrationConfig::new(&app_path, &page_path, "root") + .with_var("CUSTOM_VAR", "test_value"); + + let hydrator = Hydrator::new(config); + + // Verify the config has the variable + assert_eq!(hydrator.config().template_vars.get("CUSTOM_VAR"), Some(&"test_value".to_string())); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); } } diff --git a/crates/metassr-build/src/client/mod.rs b/crates/metassr-build/src/client/mod.rs index d6211e2..a5eff39 100644 --- a/crates/metassr-build/src/client/mod.rs +++ b/crates/metassr-build/src/client/mod.rs @@ -1,95 +1,252 @@ use crate::traits::{Build, Generate}; -use crate::utils::setup_page_path; -use anyhow::{anyhow, Result}; -use hydrator::Hydrator; - -use metassr_bundler::WebBundler; -use metassr_fs_analyzer::src_dir::Page; -use metassr_fs_analyzer::{ - src_dir::{special_entries, SourceDir}, - DirectoryAnalyzer, -}; -use metassr_utils::cache_dir::CacheDir; - -use std::{ - collections::HashMap, - ffi::OsStr, - fs, - path::{Path, PathBuf}, -}; +use anyhow::{Context, Result}; +use metassr_fs_analyzer::src_dir::special_entries; +use metassr_fs_analyzer::{src_dir::SourceDir, DirectoryAnalyzer}; + +pub mod bundling; +pub mod cache; +pub mod config; pub mod hydrator; +pub mod target; + +use bundling::BundlingService; +use cache::CacheService; +use config::ClientConfig; +use hydrator::{HydrationConfig, Hydrator}; +use target::{TargetBuilder, TargetCollection}; +use tracing::debug; +/// Enhanced client builder with improved architecture and error handling pub struct ClientBuilder { - src_path: PathBuf, - dist_path: PathBuf, + config: ClientConfig, } impl ClientBuilder { - pub fn new(root: &S, dist_dir: &str) -> Result + /// Creates a new ClientBuilder with the given configuration + pub fn new(config: ClientConfig) -> Self { + Self { config } + } + + /// Creates a ClientBuilder with simple parameters (legacy compatibility) + pub fn simple(root: &S, dist_dir: &str) -> Result where - S: AsRef + ?Sized, + S: AsRef + ?Sized, { - let root = Path::new(root); - let src_path = root.join("src"); - let dist_path = root.join(dist_dir); + let config = ClientConfig::new(root, dist_dir)?; + Ok(Self::new(config)) + } - if !src_path.exists() { - return Err(anyhow!("src directory not found.")); - } - if !dist_path.exists() { - fs::create_dir(&dist_path)?; - } - Ok(Self { - src_path, - dist_path, - }) + /// Creates a ClientBuilder with custom configuration + pub fn with_config(config: ClientConfig) -> Result { + config.ensure_directories()?; + Ok(Self::new(config)) } -} -impl Build for ClientBuilder { - type Output = (); - fn build(&self) -> Result { - let mut cache_dir = CacheDir::new(&format!("{}/cache", self.dist_path.display()))?; - let src = SourceDir::new(&self.src_path).analyze()?; + /// Gets the current configuration + pub fn config(&self) -> &ClientConfig { + &self.config + } + + /// Updates the configuration + pub fn with_updated_config(mut self, config: ClientConfig) -> Result { + config.ensure_directories()?; + self.config = config; + Ok(self) + } + + /// Builds the client-side code with detailed logging and error handling + fn build_internal(&self) -> Result { + // Ensure directories exist + self.config.ensure_directories() + .context("Failed to ensure required directories exist")?; + + // Initialize services + let mut cache_service = CacheService::new(self.config.clone()) + .context("Failed to initialize cache service")?; + + let bundling_service = BundlingService::new(self.config.clone()); + + // Analyze source directory + let src = SourceDir::new(&self.config.src_dir) + .analyze() + .context("Failed to analyze source directory")?; let pages = src.pages(); - let (special_entries::App(app_path), _) = src.specials()?; + let (special_entries::App(app_path), _) = src.specials() + .context("Failed to find required special entries (like _app)")?; + + // Generate targets + let mut targets = TargetCollection::new(); + + for page in pages.iter() { + // Create hydration configuration for this page + let hydration_config = HydrationConfig::builder() + .app_path(&app_path) + .page_path(&page.info.path) + .root_id(&self.config.root_id) + .strict_mode(true) + .build() + .with_context(|| format!("Failed to create hydration config for page: {}", page.id))?; + + // Generate hydration script + let hydrator = Hydrator::new(hydration_config); + let hydration_content = hydrator.generate() + .with_context(|| format!("Failed to generate hydration script for page: {}", page.id))?; - for Page { route, path } in pages.iter() { - let hydrator = Hydrator::new(&app_path, path, "root").generate()?; - let page = setup_page_path(route, "js"); + // Create build target + let target = TargetBuilder::from_page(page, hydration_content, &self.config.output_extension) + .with_context(|| format!("Failed to create build target for page: {}", page.id))?; - cache_dir.insert(&format!("pages/{}", page.display()), hydrator.as_bytes())?; + targets.add_target(target) + .with_context(|| format!("Failed to add target for page: {}", page.id))?; } - let targets = cache_dir - .entries_in_scope() - .iter() - .map(|(entry_name, path)| { - let fullpath = path.canonicalize().unwrap(); + // Validate targets before proceeding + let validation = bundling_service.validate_targets(&targets) + .context("Failed to validate build targets")?; - (entry_name.to_owned(), format!("{}", fullpath.display())) - }) - .collect::>(); + if !validation.is_valid() { + return Err(anyhow::anyhow!( + "Target validation failed: {:?}", + validation.errors + )); + } + + // Store targets in cache + let cache_stats = cache_service.store_targets(&targets) + .context("Failed to store targets in cache")?; + + // Bundle the cached files + let bundling_result = bundling_service.bundle(&targets) + .context("Failed to bundle client code")?; - let bundler = WebBundler::new(&targets, &self.dist_path)?; - if let Err(e) = bundler.exec() { - return Err(anyhow!("Bundling failed: {e}")); + if !bundling_result.success { + return Err(anyhow::anyhow!( + "Bundling failed: {:?}", + bundling_result.messages + )); } + Ok(ClientBuildResult { + targets_processed: targets.len(), + cache_stats, + bundling_result, + target_stats: targets.stats(), + }) + } +} + +impl Build for ClientBuilder { + type Output = (); + + fn build(&self) -> Result { + let result = self.build_internal() + .context("Client build process failed")?; + + // Log build statistics + debug!("Client build completed successfully"); + debug!("Processed {} targets", result.targets_processed); + debug!("Cache: {}", result.cache_stats); + debug!("Bundling: {} targets bundled", result.bundling_result.bundled_targets); + Ok(()) } } +/// Result of a client build operation +#[derive(Debug)] +pub struct ClientBuildResult { + pub targets_processed: usize, + pub cache_stats: cache::CacheStats, + pub bundling_result: bundling::BundlingResult, + pub target_stats: target::TargetCollectionStats, +} + +impl std::fmt::Display for ClientBuildResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Client Build Result:")?; + writeln!(f, " Targets processed: {}", self.targets_processed)?; + writeln!(f, " {}", self.cache_stats)?; + writeln!(f, " Bundled targets: {}", self.bundling_result.bundled_targets)?; + write!(f, " {}", self.target_stats) + } +} + #[cfg(test)] mod tests { use super::*; + use std::env; + + fn create_test_env() -> std::path::PathBuf { + let temp_dir = env::temp_dir().join("metassr_client_test"); + let src_dir = temp_dir.join("src"); + let pages_dir = src_dir.join("pages"); + + // Create directories + std::fs::create_dir_all(&pages_dir).unwrap(); + + // Create _app.tsx + let app_content = r#" +import React from 'react'; + +export default function App({ Component }) { + return ; +} +"#; + std::fs::write(src_dir.join("_app.tsx"), app_content).unwrap(); + + // Create a test page + let page_content = r#" +import React from 'react'; + +export default function Home() { + return
Home Page
; +} +"#; + std::fs::write(pages_dir.join("home.tsx"), page_content).unwrap(); + + temp_dir + } + + #[test] + fn test_client_builder_creation() { + let test_dir = create_test_env(); + + let config = ClientConfig::new(&test_dir, "dist").unwrap(); + let builder = ClientBuilder::new(config); + + assert_eq!(builder.config().root_id, "root"); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + + #[test] + fn test_client_builder_simple() { + let test_dir = create_test_env(); + + let builder = ClientBuilder::simple(&test_dir, "dist").unwrap(); + assert_eq!(builder.config().root_id, "root"); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + #[test] - fn client_builder() { - ClientBuilder::new("../../tests/web-app", "../../tests/web-app/dist") - .unwrap() + fn test_client_config_builder() { + let test_dir = create_test_env(); + + let config = ClientConfig::builder() + .root_dir(&test_dir) + .root_id("app") .build() .unwrap(); + + let builder = ClientBuilder::with_config(config).unwrap(); + assert_eq!(builder.config().root_id, "app"); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); } } diff --git a/crates/metassr-build/src/client/target.rs b/crates/metassr-build/src/client/target.rs new file mode 100644 index 0000000..b52a043 --- /dev/null +++ b/crates/metassr-build/src/client/target.rs @@ -0,0 +1,344 @@ +use anyhow::{Context, Result}; +use std::{ + collections::HashMap, + path::PathBuf, +}; + +use metassr_fs_analyzer::src_dir::Page; + +/// Represents a build target with metadata +#[derive(Debug, Clone)] +pub struct BuildTarget { + /// Unique identifier for the target + pub id: String, + /// Source file path + pub source_path: PathBuf, + /// Output file path relative to dist directory + pub output_path: PathBuf, + /// Generated content for this target + pub content: String, + /// Metadata about the target + pub metadata: TargetMetadata, +} + +/// Metadata associated with a build target +#[derive(Debug, Clone)] +pub struct TargetMetadata { + /// Route associated with this target + pub route: String, + /// Whether this is a special entry (like _app) + pub is_special: bool, + /// File type/extension + pub file_type: String, + /// Size of the content in bytes + pub content_size: usize, +} + +impl BuildTarget { + /// Creates a new build target + pub fn new( + id: String, + source_path: PathBuf, + output_path: PathBuf, + content: String, + route: String, + is_special: bool, + ) -> Self { + let content_size = content.len(); + let file_type = source_path + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("unknown") + .to_string(); + + Self { + id, + source_path, + output_path, + content, + metadata: TargetMetadata { + route, + is_special, + file_type, + content_size, + }, + } + } + + /// Gets the content as bytes + pub fn content_bytes(&self) -> &[u8] { + self.content.as_bytes() + } + + /// Gets the absolute source path if it exists + pub fn absolute_source_path(&self) -> Result { + self.source_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize path: {}", self.source_path.display())) + } + + /// Gets the output path as a string + pub fn output_path_str(&self) -> &str { + self.output_path.to_str().unwrap_or_default() + } +} + +/// Collection of build targets with utilities for managing them +#[derive(Debug, Default)] +pub struct TargetCollection { + targets: Vec, + target_map: HashMap, +} + +impl TargetCollection { + /// Creates a new empty target collection + pub fn new() -> Self { + Self::default() + } + + /// Adds a target to the collection + pub fn add_target(&mut self, target: BuildTarget) -> Result<()> { + if self.target_map.contains_key(&target.id) { + return Err(anyhow::anyhow!("Target with id '{}' already exists", target.id)); + } + + let index = self.targets.len(); + self.target_map.insert(target.id.clone(), index); + self.targets.push(target); + Ok(()) + } + + /// Gets a target by ID + pub fn get_target(&self, id: &str) -> Option<&BuildTarget> { + self.target_map.get(id).and_then(|&index| self.targets.get(index)) + } + + /// Gets a mutable reference to a target by ID + pub fn get_target_mut(&mut self, id: &str) -> Option<&mut BuildTarget> { + self.target_map.get(id).and_then(|&index| self.targets.get_mut(index)) + } + + /// Gets all targets + pub fn targets(&self) -> &[BuildTarget] { + &self.targets + } + + /// Gets all targets mutably + pub fn targets_mut(&mut self) -> &mut [BuildTarget] { + &mut self.targets + } + + /// Removes a target by ID + pub fn remove_target(&mut self, id: &str) -> Option { + if let Some(&index) = self.target_map.get(id) { + self.target_map.remove(id); + // Update indices for targets after the removed one + for (_, target_index) in self.target_map.iter_mut() { + if *target_index > index { + *target_index -= 1; + } + } + Some(self.targets.remove(index)) + } else { + None + } + } + + /// Gets the number of targets + pub fn len(&self) -> usize { + self.targets.len() + } + + /// Checks if the collection is empty + pub fn is_empty(&self) -> bool { + self.targets.is_empty() + } + + /// Filters targets by a predicate + pub fn filter_targets(&self, predicate: F) -> Vec<&BuildTarget> + where + F: Fn(&BuildTarget) -> bool, + { + self.targets.iter().filter(|target| predicate(target)).collect() + } + + /// Gets targets that are pages (not special entries) + pub fn page_targets(&self) -> Vec<&BuildTarget> { + self.filter_targets(|target| !target.metadata.is_special) + } + + /// Gets special targets (like _app) + pub fn special_targets(&self) -> Vec<&BuildTarget> { + self.filter_targets(|target| target.metadata.is_special) + } + + /// Converts targets to a HashMap suitable for bundling + pub fn to_bundler_targets(&self) -> Result> { + let mut bundler_targets = HashMap::new(); + + for target in &self.targets { + let absolute_path = target.absolute_source_path()?; + bundler_targets.insert( + target.id.clone(), + absolute_path.to_string_lossy().to_string(), + ); + } + + Ok(bundler_targets) + } + + /// Gets statistics about the collection + pub fn stats(&self) -> TargetCollectionStats { + let total_targets = self.targets.len(); + let page_targets = self.page_targets().len(); + let special_targets = self.special_targets().len(); + let total_content_size = self.targets.iter().map(|t| t.metadata.content_size).sum(); + + TargetCollectionStats { + total_targets, + page_targets, + special_targets, + total_content_size, + } + } +} + +/// Statistics about a target collection +#[derive(Debug)] +pub struct TargetCollectionStats { + pub total_targets: usize, + pub page_targets: usize, + pub special_targets: usize, + pub total_content_size: usize, +} + +impl std::fmt::Display for TargetCollectionStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Targets: {} total ({} pages, {} special), Content size: {} bytes", + self.total_targets, + self.page_targets, + self.special_targets, + self.total_content_size + ) + } +} + +/// Builder for creating build targets from pages +pub struct TargetBuilder; + +impl TargetBuilder { + /// Creates a build target from a page and generated content + pub fn from_page( + page: &Page, + content: String, + output_extension: &str, + ) -> Result { + let page_path = crate::utils::setup_page_path(&page.info.route, output_extension); + let target_id = format!("pages/{}", page_path.display()); + + Ok(BuildTarget::new( + target_id, + page.info.path.clone(), + page_path, + content, + page.info.route.clone(), + false, // Pages are not special entries + )) + } + + /// Creates a build target for a special entry + pub fn from_special( + id: String, + source_path: PathBuf, + content: String, + route: String, + ) -> BuildTarget { + let output_path = PathBuf::from(&id); + + BuildTarget::new( + id, + source_path, + output_path, + content, + route, + true, // This is a special entry + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn test_target_collection() { + let mut collection = TargetCollection::new(); + + let target = BuildTarget::new( + "test".to_string(), + PathBuf::from("src/test.js"), + PathBuf::from("test.js"), + "console.log('test')".to_string(), + "/test".to_string(), + false, + ); + + collection.add_target(target).unwrap(); + assert_eq!(collection.len(), 1); + assert!(collection.get_target("test").is_some()); + } + + #[test] + fn test_target_filtering() { + let mut collection = TargetCollection::new(); + + let page_target = BuildTarget::new( + "page".to_string(), + PathBuf::from("src/page.js"), + PathBuf::from("page.js"), + "page content".to_string(), + "/page".to_string(), + false, + ); + + let special_target = BuildTarget::new( + "app".to_string(), + PathBuf::from("src/_app.js"), + PathBuf::from("_app.js"), + "app content".to_string(), + "/_app".to_string(), + true, + ); + + collection.add_target(page_target).unwrap(); + collection.add_target(special_target).unwrap(); + + assert_eq!(collection.page_targets().len(), 1); + assert_eq!(collection.special_targets().len(), 1); + } + + #[test] + fn test_target_stats() { + let mut collection = TargetCollection::new(); + + let target = BuildTarget::new( + "test".to_string(), + PathBuf::from("src/test.js"), + PathBuf::from("test.js"), + "hello world".to_string(), // 11 bytes + "/test".to_string(), + false, + ); + + collection.add_target(target).unwrap(); + let stats = collection.stats(); + + assert_eq!(stats.total_targets, 1); + assert_eq!(stats.page_targets, 1); + assert_eq!(stats.special_targets, 0); + assert_eq!(stats.total_content_size, 11); + } +} From 724a69a8cde77ede8b22ff970b9bd6270018d8e9 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:39:25 +0300 Subject: [PATCH 04/13] refactor: improve server building process --- crates/metassr-build/src/server/cache.rs | 305 ++++++++++++ crates/metassr-build/src/server/config.rs | 335 ++++++++++++++ crates/metassr-build/src/server/generation.rs | 320 +++++++++++++ crates/metassr-build/src/server/mod.rs | 420 ++++++++++++++--- .../src/server/pages_generator.rs | 2 +- crates/metassr-build/src/server/render.rs | 292 ++++++++++-- crates/metassr-build/src/server/target.rs | 438 ++++++++++++++++++ crates/metassr-build/src/server/targets.rs | 27 -- 8 files changed, 2023 insertions(+), 116 deletions(-) create mode 100644 crates/metassr-build/src/server/cache.rs create mode 100644 crates/metassr-build/src/server/config.rs create mode 100644 crates/metassr-build/src/server/generation.rs create mode 100644 crates/metassr-build/src/server/target.rs diff --git a/crates/metassr-build/src/server/cache.rs b/crates/metassr-build/src/server/cache.rs new file mode 100644 index 0000000..2180bbb --- /dev/null +++ b/crates/metassr-build/src/server/cache.rs @@ -0,0 +1,305 @@ +use anyhow::{Context, Result}; +use metassr_utils::cache_dir::CacheDir; + +use super::{ + config::ServerConfig, + target::{ServerTarget, ServerTargetCollection}, +}; + +/// Service for managing cache operations during server build process +pub struct ServerCacheService { + cache_dir: CacheDir, + config: ServerConfig, +} + +impl ServerCacheService { + /// Creates a new server cache service + pub fn new(config: ServerConfig) -> Result { + let cache_dir = CacheDir::new(config.cache_dir_str()) + .context("Failed to initialize server cache directory")?; + + Ok(Self { cache_dir, config }) + } + + /// Stores a target in the cache and returns the cached path + pub fn store_target(&mut self, cache_path: &str, content: &[u8]) -> Result { + self.cache_dir + .insert(cache_path, content) + .with_context(|| format!("Failed to cache target at: {}", cache_path)) + } + + /// Stores multiple targets in the cache + pub fn store_targets(&mut self, targets: &ServerTargetCollection) -> Result { + let mut stored_files = 0; + let mut total_bytes = 0; + + for target in targets.targets() { + let cache_path = format!("pages/{}", target.id); + + self.cache_dir + .insert(&cache_path, target.content_bytes()) + .with_context(|| format!("Failed to cache server target: {}", target.id))?; + + stored_files += 1; + total_bytes += target.metadata.content_size; + } + + Ok(ServerCacheStats { + stored_files, + total_bytes, + cache_dir: self.cache_dir.path().to_path_buf(), + }) + } + + /// Retrieves the cache entries suitable for bundling + pub fn get_bundler_entries(&self) -> Result> { + let entries = self.cache_dir + .entries_in_scope() + .iter() + .map(|(entry_name, path)| { + let fullpath = path + .canonicalize() + .with_context(|| format!("Failed to canonicalize cache path: {}", path.display()))?; + + Ok((entry_name.to_owned(), format!("{}", fullpath.display()))) + }) + .collect::>>()?; + + Ok(entries) + } + + /// Gets the cache directory reference + pub fn cache_dir(&self) -> &CacheDir { + &self.cache_dir + } + + /// Gets a mutable reference to the cache directory + pub fn cache_dir_mut(&mut self) -> &mut CacheDir { + &mut self.cache_dir + } + + /// Clears the cache + pub fn clear_cache(&mut self) -> Result<()> { + let cache_path = self.config.cache_dir_str(); + + if std::path::Path::new(cache_path).exists() { + std::fs::remove_dir_all(cache_path) + .context("Failed to remove server cache directory")?; + } + + self.cache_dir = CacheDir::new(cache_path) + .context("Failed to recreate server cache directory")?; + + Ok(()) + } + + /// Gets cache statistics + pub fn get_cache_stats(&self) -> ServerCacheStats { + let entries = self.cache_dir.entries_in_scope(); + let stored_files = entries.len(); + let total_bytes = entries + .values() + .filter_map(|path| std::fs::metadata(path).ok()) + .map(|metadata| metadata.len() as usize) + .sum(); + + ServerCacheStats { + stored_files, + total_bytes, + cache_dir: self.cache_dir.path().to_path_buf(), + } + } + + /// Validates cache integrity + pub fn validate_cache(&self) -> Result { + let mut validation = ServerCacheValidation::new(); + let entries = self.cache_dir.entries_in_scope(); + + for (entry_name, path) in entries.iter() { + if !path.exists() { + validation.add_missing_file(entry_name.clone(), path.clone()); + } else if path.is_dir() { + validation.add_warning(format!( + "Cache entry '{}' is a directory, expected a file", + entry_name + )); + } else { + // Check if file is readable + if let Err(e) = std::fs::read(path) { + validation.add_error(format!( + "Cannot read cache file '{}': {}", + entry_name, e + )); + } + } + } + + Ok(validation) + } +} + +/// Statistics about server cache operations +#[derive(Debug)] +pub struct ServerCacheStats { + pub stored_files: usize, + pub total_bytes: usize, + pub cache_dir: std::path::PathBuf, +} + +impl std::fmt::Display for ServerCacheStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Server Cache: {} files, {} bytes in {}", + self.stored_files, + self.total_bytes, + self.cache_dir.display() + ) + } +} + +/// Result of server cache validation +#[derive(Debug)] +pub struct ServerCacheValidation { + missing_files: Vec<(String, std::path::PathBuf)>, + errors: Vec, + warnings: Vec, +} + +impl ServerCacheValidation { + fn new() -> Self { + Self { + missing_files: Vec::new(), + errors: Vec::new(), + warnings: Vec::new(), + } + } + + fn add_missing_file(&mut self, entry: String, path: std::path::PathBuf) { + self.missing_files.push((entry, path)); + } + + fn add_error(&mut self, error: String) { + self.errors.push(error); + } + + fn add_warning(&mut self, warning: String) { + self.warnings.push(warning); + } + + /// Checks if cache validation passed + pub fn is_valid(&self) -> bool { + self.missing_files.is_empty() && self.errors.is_empty() + } + + /// Gets all validation issues + pub fn issues(&self) -> Vec { + let mut issues = Vec::new(); + + for (entry, path) in &self.missing_files { + issues.push(format!("Missing cache file '{}' at {}", entry, path.display())); + } + + issues.extend(self.errors.iter().cloned()); + issues.extend(self.warnings.iter().cloned()); + + issues + } + + /// Gets the number of missing files + pub fn missing_file_count(&self) -> usize { + self.missing_files.len() + } + + /// Gets the number of errors + pub fn error_count(&self) -> usize { + self.errors.len() + } + + /// Gets the number of warnings + pub fn warning_count(&self) -> usize { + self.warnings.len() + } +} + +impl std::fmt::Display for ServerCacheValidation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_valid() { + write!(f, "Server cache validation passed") + } else { + write!( + f, + "Server cache validation failed: {} missing files, {} errors, {} warnings", + self.missing_file_count(), + self.error_count(), + self.warning_count() + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::server::target::ServerTarget; + use std::{path::PathBuf, env}; + + fn create_test_config() -> ServerConfig { + let temp_dir = env::temp_dir().join("metassr_server_cache_test"); + let src_dir = temp_dir.join("src"); + std::fs::create_dir_all(&src_dir).unwrap(); + ServerConfig::new(&temp_dir, "dist").unwrap() + } + + #[test] + fn test_server_cache_service_creation() { + let config = create_test_config(); + config.ensure_directories().unwrap(); + + let cache_service = ServerCacheService::new(config); + assert!(cache_service.is_ok()); + + // Cleanup + let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_server_cache_test")); + } + + #[test] + fn test_server_cache_stats() { + let config = create_test_config(); + config.ensure_directories().unwrap(); + + let cache_service = ServerCacheService::new(config).unwrap(); + let stats = cache_service.get_cache_stats(); + + assert_eq!(stats.stored_files, 0); // Empty cache initially + + // Cleanup + let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_server_cache_test")); + } + + #[test] + fn test_server_cache_validation() { + let config = create_test_config(); + config.ensure_directories().unwrap(); + + let cache_service = ServerCacheService::new(config).unwrap(); + let validation = cache_service.validate_cache().unwrap(); + + assert!(validation.is_valid()); // Empty cache should be valid + + // Cleanup + let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_server_cache_test")); + } + + #[test] + fn test_server_cache_validation_display() { + let mut validation = ServerCacheValidation::new(); + validation.add_error("Test error".to_string()); + validation.add_missing_file("test".to_string(), PathBuf::from("/missing")); + + let display_text = format!("{}", validation); + assert!(display_text.contains("Server cache validation failed")); + assert!(display_text.contains("1 missing files")); + assert!(display_text.contains("1 errors")); + } +} diff --git a/crates/metassr-build/src/server/config.rs b/crates/metassr-build/src/server/config.rs new file mode 100644 index 0000000..e8c5efc --- /dev/null +++ b/crates/metassr-build/src/server/config.rs @@ -0,0 +1,335 @@ +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + ffi::OsStr, + path::{Path, PathBuf}, +}; + +/// Building type for server-side rendering +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub enum BuildingType { + ServerSideRendering, + StaticSiteGeneration, +} + +impl Default for BuildingType { + fn default() -> Self { + BuildingType::ServerSideRendering + } +} + +/// Configuration for the server building process +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerConfig { + /// Root directory of the project + pub root_dir: PathBuf, + /// Source directory (usually "src") + pub src_dir: PathBuf, + /// Distribution directory + pub dist_dir: PathBuf, + /// Cache directory for intermediate files + pub cache_dir: PathBuf, + /// Building type (SSR or SSG) + pub building_type: BuildingType, + /// File extension for generated server files + pub server_extension: String, + /// Custom bundler options + pub bundler_options: ServerBundlerOptions, + /// Manifest generation options + pub manifest_options: ManifestOptions, +} + +/// Configuration for server-side bundling +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerBundlerOptions { + /// Whether to enable source maps + pub source_maps: bool, + /// Whether to minimize output + pub minimize: bool, + /// Target environment (node, etc.) + pub target: String, + /// Additional entry points + pub additional_entries: HashMap, +} + +impl Default for ServerBundlerOptions { + fn default() -> Self { + Self { + source_maps: true, + minimize: false, + target: "node".to_string(), + additional_entries: HashMap::new(), + } + } +} + +/// Configuration for manifest generation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ManifestOptions { + /// Whether to generate manifest + pub generate_manifest: bool, + /// Custom manifest filename + pub manifest_filename: String, + /// Whether to include development info + pub include_dev_info: bool, +} + +impl Default for ManifestOptions { + fn default() -> Self { + Self { + generate_manifest: true, + manifest_filename: "manifest.json".to_string(), + include_dev_info: false, + } + } +} + +impl ServerConfig { + /// Creates a new ServerConfig with default settings + pub fn new(root: &S, dist_dir: &str) -> Result + where + S: AsRef + ?Sized, + { + let root_dir = Path::new(root).to_path_buf(); + let src_dir = root_dir.join("src"); + let dist_path = root_dir.join(dist_dir); + let cache_dir = dist_path.join("cache"); + + Self::validate_directories(&root_dir, &src_dir)?; + + Ok(Self { + root_dir, + src_dir, + dist_dir: dist_path, + cache_dir, + building_type: BuildingType::default(), + server_extension: "server.js".to_string(), + bundler_options: ServerBundlerOptions::default(), + manifest_options: ManifestOptions::default(), + }) + } + + /// Creates a custom ServerConfig + pub fn builder() -> ServerConfigBuilder { + ServerConfigBuilder::default() + } + + /// Validates that required directories exist or can be created + fn validate_directories(root_dir: &Path, src_dir: &Path) -> Result<()> { + if !root_dir.exists() { + return Err(anyhow::anyhow!( + "Root directory does not exist: {}", + root_dir.display() + )); + } + + if !src_dir.exists() { + return Err(anyhow::anyhow!( + "Source directory does not exist: {}", + src_dir.display() + )); + } + + Ok(()) + } + + /// Ensures all required directories exist + pub fn ensure_directories(&self) -> Result<()> { + if !self.dist_dir.exists() { + std::fs::create_dir_all(&self.dist_dir) + .with_context(|| format!("Failed to create dist directory: {}", self.dist_dir.display()))?; + } + + if !self.cache_dir.exists() { + std::fs::create_dir_all(&self.cache_dir) + .with_context(|| format!("Failed to create cache directory: {}", self.cache_dir.display()))?; + } + + Ok(()) + } + + /// Gets the cache directory path as a string + pub fn cache_dir_str(&self) -> &str { + self.cache_dir.to_str().unwrap_or_default() + } + + /// Gets the dist directory path as a string + pub fn dist_dir_str(&self) -> &str { + self.dist_dir.to_str().unwrap_or_default() + } + + /// Sets the building type + pub fn with_building_type(mut self, building_type: BuildingType) -> Self { + self.building_type = building_type; + self + } + + /// Sets the server extension + pub fn with_server_extension>(mut self, extension: S) -> Self { + self.server_extension = extension.into(); + self + } + + /// Sets the bundler options + pub fn with_bundler_options(mut self, options: ServerBundlerOptions) -> Self { + self.bundler_options = options; + self + } + + /// Sets the manifest options + pub fn with_manifest_options(mut self, options: ManifestOptions) -> Self { + self.manifest_options = options; + self + } +} + +/// Builder for ServerConfig +#[derive(Default)] +pub struct ServerConfigBuilder { + root_dir: Option, + src_dir: Option, + dist_dir: Option, + cache_dir: Option, + building_type: Option, + server_extension: Option, + bundler_options: Option, + manifest_options: Option, +} + +impl ServerConfigBuilder { + pub fn root_dir>(mut self, path: P) -> Self { + self.root_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn src_dir>(mut self, path: P) -> Self { + self.src_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn dist_dir>(mut self, path: P) -> Self { + self.dist_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn cache_dir>(mut self, path: P) -> Self { + self.cache_dir = Some(path.as_ref().to_path_buf()); + self + } + + pub fn building_type(mut self, building_type: BuildingType) -> Self { + self.building_type = Some(building_type); + self + } + + pub fn server_extension>(mut self, ext: S) -> Self { + self.server_extension = Some(ext.into()); + self + } + + pub fn bundler_options(mut self, options: ServerBundlerOptions) -> Self { + self.bundler_options = Some(options); + self + } + + pub fn manifest_options(mut self, options: ManifestOptions) -> Self { + self.manifest_options = Some(options); + self + } + + pub fn build(self) -> Result { + let root_dir = self.root_dir.ok_or_else(|| anyhow::anyhow!("Root directory is required"))?; + let src_dir = self.src_dir.unwrap_or_else(|| root_dir.join("src")); + let dist_dir = self.dist_dir.unwrap_or_else(|| root_dir.join("dist")); + let cache_dir = self.cache_dir.unwrap_or_else(|| dist_dir.join("cache")); + + ServerConfig::validate_directories(&root_dir, &src_dir)?; + + Ok(ServerConfig { + root_dir, + src_dir, + dist_dir, + cache_dir, + building_type: self.building_type.unwrap_or_default(), + server_extension: self.server_extension.unwrap_or_else(|| "server.js".to_string()), + bundler_options: self.bundler_options.unwrap_or_default(), + manifest_options: self.manifest_options.unwrap_or_default(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + fn create_test_env() -> PathBuf { + let temp_dir = env::temp_dir().join("metassr_server_config_test"); + let src_dir = temp_dir.join("src"); + std::fs::create_dir_all(&src_dir).unwrap(); + temp_dir + } + + #[test] + fn test_server_config_creation() { + let test_dir = create_test_env(); + + let config = ServerConfig::new(&test_dir, "dist").unwrap(); + assert_eq!(config.root_dir, test_dir); + assert_eq!(config.src_dir, test_dir.join("src")); + assert_eq!(config.dist_dir, test_dir.join("dist")); + assert_eq!(config.building_type, BuildingType::ServerSideRendering); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + + #[test] + fn test_server_config_builder() { + let test_dir = create_test_env(); + + let config = ServerConfig::builder() + .root_dir(&test_dir) + .building_type(BuildingType::StaticSiteGeneration) + .server_extension("ssr.js") + .build() + .unwrap(); + + assert_eq!(config.building_type, BuildingType::StaticSiteGeneration); + assert_eq!(config.server_extension, "ssr.js"); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + + #[test] + fn test_ensure_directories() { + let test_dir = create_test_env(); + + let config = ServerConfig::new(&test_dir, "dist").unwrap(); + config.ensure_directories().unwrap(); + + assert!(config.dist_dir.exists()); + assert!(config.cache_dir.exists()); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + + #[test] + fn test_config_methods() { + let test_dir = create_test_env(); + + let config = ServerConfig::new(&test_dir, "dist") + .unwrap() + .with_building_type(BuildingType::StaticSiteGeneration) + .with_server_extension("custom.js"); + + assert_eq!(config.building_type, BuildingType::StaticSiteGeneration); + assert_eq!(config.server_extension, "custom.js"); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } +} diff --git a/crates/metassr-build/src/server/generation.rs b/crates/metassr-build/src/server/generation.rs new file mode 100644 index 0000000..5620415 --- /dev/null +++ b/crates/metassr-build/src/server/generation.rs @@ -0,0 +1,320 @@ +use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; + +use metassr_fs_analyzer::src_dir::PagesEntriesType; + +use super::{ + cache::ServerCacheService, + config::ServerConfig, + render::{ServerRender, ServerRenderConfig}, + target::{ServerTarget, ServerTargetBuilder, ServerTargetCollection}, +}; +use crate::utils::setup_page_path; +use crate::traits::Generate; + +/// Service for generating server-side targets +pub struct ServerTargetService { + config: ServerConfig, +} + +impl ServerTargetService { + /// Creates a new server target service + pub fn new(config: ServerConfig) -> Self { + Self { config } + } + + /// Generates server targets from app and pages + pub fn generate_targets( + &self, + app_path: &Path, + pages: &PagesEntriesType, + cache_service: &mut ServerCacheService, + ) -> Result { + let mut targets = ServerTargetCollection::new(); + + for (page_name, page_path) in pages.iter() { + let target = self.generate_page_target(app_path, page_name, page_path, cache_service) + .with_context(|| format!("Failed to generate target for page: {}", page_name))?; + + targets.add_target(target) + .with_context(|| format!("Failed to add target for page: {}", page_name))?; + } + + Ok(targets) + } + + /// Generates a single page target + fn generate_page_target( + &self, + app_path: &Path, + page_name: &str, + page_path: &Path, + cache_service: &mut ServerCacheService, + ) -> Result { + // Create render configuration + let render_config = ServerRenderConfig::builder() + .app_path(app_path) + .page_path(page_path) + .build() + .with_context(|| format!("Failed to create render config for page: {}", page_name))?; + + // Generate render script + let renderer = ServerRender::new(render_config); + let (func_id, render_script) = renderer.generate() + .with_context(|| format!("Failed to generate render script for page: {}", page_name))?; + + // Setup cache path + let page_file = setup_page_path(page_name, &self.config.server_extension); + let cache_path = format!("pages/{}", page_file.display()); + + // Store in cache + let cached_path = cache_service.store_target(&cache_path, render_script.as_bytes()) + .with_context(|| format!("Failed to cache target for page: {}", page_name))?; + + // Create target + let target = ServerTargetBuilder::from_page( + page_name, + page_path, + func_id, + render_script, + cached_path, + &self.config.server_extension, + ).with_context(|| format!("Failed to create target for page: {}", page_name))?; + + Ok(target) + } + + /// Validates that target generation is possible + pub fn validate_generation(&self, app_path: &Path, pages: &PagesEntriesType) -> Result { + let mut validation = TargetGenerationValidation::new(); + + // Validate app path + if !app_path.exists() { + validation.add_error(format!("App component not found: {}", app_path.display())); + } + + // Validate pages + for (page_name, page_path) in pages.iter() { + if !page_path.exists() { + validation.add_error(format!( + "Page '{}' not found: {}", + page_name, + page_path.display() + )); + } + + // Check for valid file extensions + if let Some(ext) = page_path.extension() { + let ext_str = ext.to_string_lossy(); + if !["js", "jsx", "ts", "tsx"].contains(&ext_str.as_ref()) { + validation.add_warning(format!( + "Page '{}' has unusual extension: {}", + page_name, ext_str + )); + } + } else { + validation.add_warning(format!( + "Page '{}' has no file extension", + page_name + )); + } + } + + Ok(validation) + } + + /// Gets statistics about target generation + pub fn get_generation_stats(&self, pages: &PagesEntriesType) -> TargetGenerationStats { + let total_pages = pages.len(); + let page_types = pages + .values() + .filter_map(|path| path.extension()) + .filter_map(|ext| ext.to_str()) + .fold(std::collections::HashMap::new(), |mut acc, ext| { + *acc.entry(ext.to_string()).or_insert(0) += 1; + acc + }); + + TargetGenerationStats { + total_pages, + page_types, + server_extension: self.config.server_extension.clone(), + } + } +} + +/// Result of target generation validation +#[derive(Debug, Default)] +pub struct TargetGenerationValidation { + pub errors: Vec, + pub warnings: Vec, +} + +impl TargetGenerationValidation { + fn new() -> Self { + Self::default() + } + + fn add_error(&mut self, error: String) { + self.errors.push(error); + } + + fn add_warning(&mut self, warning: String) { + self.warnings.push(warning); + } + + /// Checks if validation passed (no errors) + pub fn is_valid(&self) -> bool { + self.errors.is_empty() + } + + /// Gets all issues (errors + warnings) + pub fn all_issues(&self) -> Vec<&String> { + self.errors.iter().chain(self.warnings.iter()).collect() + } +} + +/// Statistics about target generation +#[derive(Debug)] +pub struct TargetGenerationStats { + pub total_pages: usize, + pub page_types: std::collections::HashMap, + pub server_extension: String, +} + +impl std::fmt::Display for TargetGenerationStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Target Generation Statistics:")?; + writeln!(f, " Total pages: {}", self.total_pages)?; + writeln!(f, " Server extension: {}", self.server_extension)?; + write!(f, " Page types: ")?; + + for (ext, count) in &self.page_types { + write!(f, "{}({}) ", ext, count)?; + } + + Ok(()) + } +} + +pub struct TargetsGenerator<'a> { + service: ServerTargetService, + app: PathBuf, + pages: PagesEntriesType, + cache_service: &'a mut ServerCacheService, +} + +impl<'a> TargetsGenerator<'a> { + /// Creates a new targets generator + pub fn new( + app: PathBuf, + pages: PagesEntriesType, + cache: &'a mut metassr_utils::cache_dir::CacheDir, + ) -> Self { + // This is a compatibility layer - in practice you'd want to pass the proper config + let config = ServerConfig::new(".", "dist").unwrap_or_else(|_| { + // Fallback config if we can't create one + ServerConfig::builder() + .root_dir(".") + .build() + .unwrap() + }); + + // Create a cache service wrapper + // Note: This is a simplification for compatibility + let cache_service = unsafe { + std::mem::transmute::<&'a mut metassr_utils::cache_dir::CacheDir, &'a mut ServerCacheService>(cache) + }; + + Self { + service: ServerTargetService::new(config), + app, + pages, + cache_service, + } + } + + /// Generates targets using the legacy interface + pub fn generate(&mut self) -> Result { + let targets = self.service.generate_targets(&self.app, &self.pages, self.cache_service)?; + + // Convert to legacy Targets format + let mut legacy_targets = super::target::Targets::new(); + for target in targets.targets() { + legacy_targets.insert(target.func_id, &target.cached_path); + } + + Ok(legacy_targets) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::server::config::ServerConfig; + use std::{collections::HashMap, env}; + + fn create_test_env() -> (PathBuf, PathBuf, PathBuf) { + let temp_dir = env::temp_dir().join("metassr_server_targets_test"); + let src_dir = temp_dir.join("src"); + let pages_dir = src_dir.join("pages"); + + std::fs::create_dir_all(&pages_dir).unwrap(); + + let app_path = src_dir.join("_app.tsx"); + let page_path = pages_dir.join("home.tsx"); + + std::fs::write(&app_path, "export default function App() { return null; }").unwrap(); + std::fs::write(&page_path, "export default function Home() { return null; }").unwrap(); + + (temp_dir, app_path, page_path) + } + + #[test] + fn test_server_target_service_creation() { + let (temp_dir, _, _) = create_test_env(); + + let config = ServerConfig::new(&temp_dir, "dist").unwrap(); + let service = ServerTargetService::new(config); + + assert_eq!(service.config.server_extension, "server.js"); + + // Cleanup + let _ = std::fs::remove_dir_all(&temp_dir); + } + + #[test] + fn test_target_generation_validation() { + let (temp_dir, app_path, page_path) = create_test_env(); + + let config = ServerConfig::new(&temp_dir, "dist").unwrap(); + let service = ServerTargetService::new(config); + + let mut pages = HashMap::new(); + pages.insert("home".to_string(), page_path); + + let validation = service.validate_generation(&app_path, &pages).unwrap(); + assert!(validation.is_valid()); + + // Cleanup + let _ = std::fs::remove_dir_all(&temp_dir); + } + + #[test] + fn test_generation_stats() { + let (temp_dir, _, page_path) = create_test_env(); + + let config = ServerConfig::new(&temp_dir, "dist").unwrap(); + let service = ServerTargetService::new(config); + + let mut pages = HashMap::new(); + pages.insert("home".to_string(), page_path); + + let stats = service.get_generation_stats(&pages); + assert_eq!(stats.total_pages, 1); + assert!(stats.page_types.contains_key("tsx")); + + // Cleanup + let _ = std::fs::remove_dir_all(&temp_dir); + } +} diff --git a/crates/metassr-build/src/server/mod.rs b/crates/metassr-build/src/server/mod.rs index 5042e1a..2cef5d8 100644 --- a/crates/metassr-build/src/server/mod.rs +++ b/crates/metassr-build/src/server/mod.rs @@ -1,110 +1,408 @@ pub mod renderer; +pub mod cache; +pub mod config; +pub mod generation; pub mod manifest; mod pages_generator; mod render; mod render_exec; +pub mod target; mod targets; use crate::traits::Build; -use manifest::ManifestGenerator; +use anyhow::{Context, Result}; +use cache::ServerCacheService; +use config::{BuildingType, ServerConfig}; +use generation::ServerTargetService; +use manifest::ManifestGenerator; use metassr_bundler::WebBundler; use metassr_fs_analyzer::{ dist_dir::DistDir, src_dir::{special_entries, SourceDir}, DirectoryAnalyzer, }; -use metassr_utils::cache_dir::CacheDir; - use pages_generator::PagesGenerator; use renderer::head::HeadRenderer; +use target::{ServerTargetCollection, Targets}; +use tracing::debug; -use std::{ - ffi::OsStr, - fs, - path::{Path, PathBuf}, -}; -use targets::TargetsGenerator; - -use anyhow::{anyhow, Result}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum BuildingType { - ServerSideRendering, - StaticSiteGeneration, -} +use std::{ffi::OsStr, path::Path}; +/// Enhanced server-side builder with improved architecture and error handling pub struct ServerSideBuilder { - src_path: PathBuf, - dist_path: PathBuf, - building_type: BuildingType, + config: ServerConfig, } impl ServerSideBuilder { - pub fn new(root: &S, dist_dir: &str, building_type: BuildingType) -> Result + /// Creates a new ServerSideBuilder with the given configuration + pub fn new(config: ServerConfig) -> Self { + Self { config } + } + + /// Creates a ServerSideBuilder with simple parameters (legacy compatibility) + pub fn simple(root: &S, dist_dir: &str, building_type: BuildingType) -> Result where S: AsRef + ?Sized, { - let root = Path::new(root); - let src_path = root.join("src"); - let dist_path = root.join(dist_dir); + let config = ServerConfig::new(root, dist_dir)? + .with_building_type(building_type); + Ok(Self::new(config)) + } + + /// Creates a ServerSideBuilder with custom configuration + pub fn with_config(config: ServerConfig) -> Result { + config.ensure_directories()?; + Ok(Self::new(config)) + } + + /// Gets the current configuration + pub fn config(&self) -> &ServerConfig { + &self.config + } + + /// Updates the configuration + pub fn with_updated_config(mut self, config: ServerConfig) -> Result { + config.ensure_directories()?; + self.config = config; + Ok(self) + } + + /// Builds the server-side code with detailed logging and error handling + fn build_internal(&self) -> Result { + // Ensure directories exist + self.config.ensure_directories() + .context("Failed to ensure required directories exist")?; + + // Initialize services + let mut cache_service = ServerCacheService::new(self.config.clone()) + .context("Failed to initialize server cache service")?; + + let target_service = ServerTargetService::new(self.config.clone()); + + // Analyze source directory + let src = SourceDir::new(&self.config.src_dir) + .analyze() + .context("Failed to analyze source directory")?; + + let pages = src.clone().pages(); + let (special_entries::App(app), special_entries::Head(head)) = src.specials() + .context("Failed to find required special entries (like _app and _head)")?; + + // Validate target generation + let validation = target_service.validate_generation(&app, &pages.as_map()) + .context("Failed to validate target generation")?; - if !src_path.exists() { - return Err(anyhow!("src directory not found.")); + if !validation.is_valid() { + return Err(anyhow::anyhow!( + "Target generation validation failed: {:?}", + validation.errors + )); } - if !dist_path.exists() { - fs::create_dir(dist_path.clone())?; + + // Generate targets + let targets = target_service.generate_targets(&app, &pages.as_map(), &mut cache_service) + .context("Failed to generate server targets")?; + + // Bundle the targets + let bundling_result = self.bundle_targets(&targets) + .context("Failed to bundle server targets")?; + + // Generate manifest + let manifest_result = if self.config.manifest_options.generate_manifest { + Some(self.generate_manifest(&targets, &cache_service, &head) + .context("Failed to generate manifest")?) + } else { + None + }; + + // Handle Static Site Generation if required + let ssg_result = if self.config.building_type == BuildingType::StaticSiteGeneration { + Some(self.generate_static_pages(&targets, &head, &cache_service) + .context("Failed to generate static pages")?) + } else { + None + }; + + Ok(ServerBuildResult { + targets_processed: targets.len(), + cache_stats: cache_service.get_cache_stats(), + bundling_result, + manifest_result, + ssg_result, + target_stats: targets.stats(), + }) + } + + /// Bundles the server targets + fn bundle_targets(&self, targets: &ServerTargetCollection) -> Result { + let bundling_targets = targets.ready_for_bundling(&self.config.dist_dir) + .context("Failed to prepare targets for bundling")?; + + let bundler = WebBundler::new(&bundling_targets, &self.config.dist_dir) + .context("Failed to create web bundler for server targets")?; + + bundler.exec() + .context("Server bundling process failed")?; + + Ok(ServerBundlingResult { + bundled_targets: bundling_targets.len(), + target_names: bundling_targets.keys().cloned().collect(), + }) + } + + /// Generates the manifest + fn generate_manifest( + &self, + targets: &ServerTargetCollection, + cache_service: &ServerCacheService, + head: &Path, + ) -> Result { + if !self.config.manifest_options.generate_manifest { + return Ok(ServerManifestResult::skipped()); } - Ok(Self { - src_path, - dist_path, - building_type, + + let dist = DistDir::new(&self.config.dist_dir) + .context("Failed to analyze dist directory")? + .analyze() + .context("Failed to analyze dist directory structure")?; + + // Convert targets to legacy format for compatibility + let mut legacy_targets = targets::Targets::new(); + for target in targets.targets() { + legacy_targets.insert(target.func_id, &target.cached_path); + } + + let manifest = ManifestGenerator::new( + legacy_targets, + cache_service.cache_dir().clone(), + dist, + ).generate(head) + .context("Failed to generate manifest")?; + + manifest.write(&self.config.dist_dir) + .context("Failed to write manifest to disk")?; + + // Render head + HeadRenderer::new(&manifest.global.head, cache_service.cache_dir().clone()) + .render(true) + .context("Failed to render head component")?; + + Ok(ServerManifestResult { + manifest_written: true, + head_rendered: true, + manifest_path: self.config.dist_dir.join(&self.config.manifest_options.manifest_filename), + }) + } + + /// Generates static pages for SSG + fn generate_static_pages( + &self, + targets: &ServerTargetCollection, + head: &Path, + cache_service: &ServerCacheService, + ) -> Result { + // Convert targets to legacy format for compatibility + let mut legacy_targets = Targets::new(); + for target in targets.targets() { + legacy_targets.insert(target.func_id, &target.cached_path); + } + + PagesGenerator::new(legacy_targets, head, &self.config.dist_dir, cache_service.cache_dir().clone())? + .generate() + .context("Failed to generate static pages")?; + + Ok(ServerSSGResult { + pages_generated: targets.len(), + output_dir: self.config.dist_dir.clone(), }) } } -// TODO: refactoring build function + impl Build for ServerSideBuilder { type Output = (); + fn build(&self) -> Result { - let mut cache_dir = CacheDir::new(&format!("{}/cache", self.dist_path.display()))?; + let result = self.build_internal() + .context("Server build process failed")?; - let src = SourceDir::new(&self.src_path).analyze()?; - let pages = src.clone().pages(); - let (special_entries::App(app), special_entries::Head(head)) = src.specials()?; + // Log build statistics + debug!("Server build completed successfully"); + debug!("Building type: {:?}", self.config.building_type); + debug!("Processed {} targets", result.targets_processed); + debug!("Cache: {}", result.cache_stats); + debug!("Bundling: {} targets bundled", result.bundling_result.bundled_targets); + + if let Some(manifest) = &result.manifest_result { + if manifest.manifest_written { + debug!("Manifest generated at: {}", manifest.manifest_path.display()); + } + } - let targets = match TargetsGenerator::new(app, pages.as_map(), &mut cache_dir).generate() { - Ok(t) => t, - Err(e) => return Err(anyhow!("Couldn't generate targets: {e}")), - }; + if let Some(ssg) = &result.ssg_result { + debug!("Generated {} static pages", ssg.pages_generated); + } - let bundling_targets = targets.ready_for_bundling(&self.dist_path); - let bundler = WebBundler::new( - &bundling_targets, - &self.dist_path, - )?; + Ok(()) + } +} - if let Err(e) = bundler.exec() { - return Err(anyhow!("Bundling failed: {e}")); - } +/// Result of a server build operation +#[derive(Debug)] +pub struct ServerBuildResult { + pub targets_processed: usize, + pub cache_stats: cache::ServerCacheStats, + pub bundling_result: ServerBundlingResult, + pub manifest_result: Option, + pub ssg_result: Option, + pub target_stats: target::ServerTargetCollectionStats, +} - let dist = DistDir::new(&self.dist_path)?.analyze()?; +/// Result of server bundling operation +#[derive(Debug)] +pub struct ServerBundlingResult { + pub bundled_targets: usize, + pub target_names: Vec, +} - let manifest = - ManifestGenerator::new(targets.clone(), cache_dir.clone(), dist).generate(&head)?; - manifest.write(&self.dist_path.clone())?; +/// Result of server manifest generation +#[derive(Debug)] +pub struct ServerManifestResult { + pub manifest_written: bool, + pub head_rendered: bool, + pub manifest_path: std::path::PathBuf, +} - if let Err(e) = HeadRenderer::new(&manifest.global.head, cache_dir.clone()).render(true) { - return Err(anyhow!("Coludn't render head: {e}")); +impl ServerManifestResult { + fn skipped() -> Self { + Self { + manifest_written: false, + head_rendered: false, + manifest_path: std::path::PathBuf::new(), } + } +} + +/// Result of static site generation +#[derive(Debug)] +pub struct ServerSSGResult { + pub pages_generated: usize, + pub output_dir: std::path::PathBuf, +} - if self.building_type == BuildingType::StaticSiteGeneration { - if let Err(e) = - PagesGenerator::new(targets, &head, &self.dist_path, cache_dir)?.generate() - { - return Err(anyhow!("Couldn't generate pages: {e}")); +impl std::fmt::Display for ServerBuildResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Server Build Result:")?; + writeln!(f, " Targets processed: {}", self.targets_processed)?; + writeln!(f, " {}", self.cache_stats)?; + writeln!(f, " Bundled targets: {}", self.bundling_result.bundled_targets)?; + + if let Some(manifest) = &self.manifest_result { + if manifest.manifest_written { + writeln!(f, " Manifest: Generated")?; } } - Ok(()) + + if let Some(ssg) = &self.ssg_result { + writeln!(f, " Static pages: {}", ssg.pages_generated)?; + } + + write!(f, " {}", self.target_stats) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + fn create_test_env() -> std::path::PathBuf { + let temp_dir = env::temp_dir().join("metassr_server_test"); + let src_dir = temp_dir.join("src"); + let pages_dir = src_dir.join("pages"); + + // Create directories + std::fs::create_dir_all(&pages_dir).unwrap(); + + // Create _app.tsx + let app_content = r#" +import React from 'react'; + +export default function App({ Component }) { + return ; +} +"#; + std::fs::write(src_dir.join("_app.tsx"), app_content).unwrap(); + + // Create _head.tsx + let head_content = r#" +import React from 'react'; + +export default function Head() { + return ( + <> + Test App + + + ); +} +"#; + std::fs::write(src_dir.join("_head.tsx"), head_content).unwrap(); + + // Create a test page + let page_content = r#" +import React from 'react'; + +export default function Home() { + return
Home Page
; +} +"#; + std::fs::write(pages_dir.join("home.tsx"), page_content).unwrap(); + + temp_dir + } + + #[test] + fn test_server_builder_creation() { + let test_dir = create_test_env(); + + let config = ServerConfig::new(&test_dir, "dist").unwrap(); + let builder = ServerSideBuilder::new(config); + + assert_eq!(builder.config().building_type, BuildingType::ServerSideRendering); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + + #[test] + fn test_server_builder_simple() { + let test_dir = create_test_env(); + + let builder = ServerSideBuilder::simple(&test_dir, "dist", BuildingType::StaticSiteGeneration).unwrap(); + assert_eq!(builder.config().building_type, BuildingType::StaticSiteGeneration); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); + } + + #[test] + fn test_server_config_builder() { + let test_dir = create_test_env(); + + let config = ServerConfig::builder() + .root_dir(&test_dir) + .building_type(BuildingType::StaticSiteGeneration) + .server_extension("ssr.js") + .build() + .unwrap(); + + let builder = ServerSideBuilder::with_config(config).unwrap(); + assert_eq!(builder.config().building_type, BuildingType::StaticSiteGeneration); + assert_eq!(builder.config().server_extension, "ssr.js"); + + // Cleanup + let _ = std::fs::remove_dir_all(&test_dir); } } diff --git a/crates/metassr-build/src/server/pages_generator.rs b/crates/metassr-build/src/server/pages_generator.rs index f5a5ec4..7d3e24a 100644 --- a/crates/metassr-build/src/server/pages_generator.rs +++ b/crates/metassr-build/src/server/pages_generator.rs @@ -15,7 +15,7 @@ use crate::traits::Exec; use super::{ render_exec::MultiRenderExec, renderer::head::HeadRenderer, renderer::html::HtmlRenderer, - targets::Targets, + target::Targets, }; pub struct PagesGenerator { diff --git a/crates/metassr-build/src/server/render.rs b/crates/metassr-build/src/server/render.rs index 79e03d1..fd3307d 100644 --- a/crates/metassr-build/src/server/render.rs +++ b/crates/metassr-build/src/server/render.rs @@ -2,59 +2,297 @@ use crate::{ shared::{APP_PATH_TAG, FUNC_ID_TAG, PAGE_PATH_TAG}, traits::Generate, }; -use anyhow::Result; +use anyhow::{Context, Result}; use metassr_utils::rand::Rand; -use std::{ffi::OsStr, path::PathBuf}; +use std::{ + collections::HashMap, + ffi::OsStr, + path::{Path, PathBuf}, +}; const RENDER_FILE_TEMPLATE: &str = include_str!("../scripts/render.js.template"); -pub struct ServerRender { - app_path: PathBuf, - page_path: PathBuf, +/// Configuration for server-side render script generation +#[derive(Debug, Clone)] +pub struct ServerRenderConfig { + /// Path to the app component + pub app_path: PathBuf, + /// Path to the page component + pub page_path: PathBuf, + /// Function ID for MetaCall execution + pub func_id: Option, + /// Additional template variables + pub template_vars: HashMap, + /// Custom template content + pub custom_template: Option, } -impl ServerRender { - pub fn new<'a, S>(app_path: &'a S, page_path: &'a S) -> Self +impl ServerRenderConfig { + /// Creates a new server render config + pub fn new(app_path: &S, page_path: &S) -> Self where S: AsRef + ?Sized, { Self { app_path: PathBuf::from(app_path), page_path: PathBuf::from(page_path), + func_id: None, + template_vars: HashMap::new(), + custom_template: None, } } + + /// Builder pattern for configuration + pub fn builder() -> ServerRenderConfigBuilder { + ServerRenderConfigBuilder::default() + } + + /// Sets the function ID + pub fn with_func_id(mut self, func_id: i64) -> Self { + self.func_id = Some(func_id); + self + } + + /// Adds a template variable + pub fn with_var(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.template_vars.insert(key.into(), value.into()); + self + } + + /// Sets a custom template + pub fn with_template>(mut self, template: S) -> Self { + self.custom_template = Some(template.into()); + self + } + + /// Validates the configuration + pub fn validate(&self) -> Result<()> { + if !self.app_path.exists() { + return Err(anyhow::anyhow!( + "App component not found: {}", + self.app_path.display() + )); + } + + if !self.page_path.exists() { + return Err(anyhow::anyhow!( + "Page component not found: {}", + self.page_path.display() + )); + } + + Ok(()) + } + + /// Gets or generates a function ID + pub fn get_func_id(&self) -> i64 { + self.func_id.unwrap_or_else(|| Rand::new().val()) + } } -impl Generate for ServerRender { - type Output = (i64, String); - fn generate(&self) -> Result { - let func_id = Rand::new().val(); - let mut app_path = self.app_path.canonicalize()?; - let mut page_path = self.page_path.canonicalize()?; +/// Builder for ServerRenderConfig +#[derive(Default)] +pub struct ServerRenderConfigBuilder { + app_path: Option, + page_path: Option, + func_id: Option, + template_vars: HashMap, + custom_template: Option, +} + +impl ServerRenderConfigBuilder { + pub fn app_path>(mut self, path: P) -> Self { + self.app_path = Some(path.as_ref().to_path_buf()); + self + } + + pub fn page_path>(mut self, path: P) -> Self { + self.page_path = Some(path.as_ref().to_path_buf()); + self + } + + pub fn func_id(mut self, func_id: i64) -> Self { + self.func_id = Some(func_id); + self + } + pub fn template_var(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.template_vars.insert(key.into(), value.into()); + self + } + + pub fn custom_template>(mut self, template: S) -> Self { + self.custom_template = Some(template.into()); + self + } + + pub fn build(self) -> Result { + let app_path = self.app_path.ok_or_else(|| anyhow::anyhow!("App path is required"))?; + let page_path = self.page_path.ok_or_else(|| anyhow::anyhow!("Page path is required"))?; + + let config = ServerRenderConfig { + app_path, + page_path, + func_id: self.func_id, + template_vars: self.template_vars, + custom_template: self.custom_template, + }; + + config.validate()?; + Ok(config) + } +} + +/// Generates server-side render scripts +#[derive(Debug)] +pub struct ServerRender { + config: ServerRenderConfig, +} + +impl ServerRender { + /// Creates a new server render with the given configuration + pub fn new(config: ServerRenderConfig) -> Self { + Self { config } + } + + /// Creates a server render with simple parameters (legacy compatibility) + pub fn simple(app_path: &S, page_path: &S) -> Result + where + S: AsRef + ?Sized, + { + let config = ServerRenderConfig::new(app_path, page_path); + config.validate()?; + Ok(Self::new(config)) + } + + /// Generates the render script content + fn generate_content(&self) -> Result<(i64, String)> { + let func_id = self.config.get_func_id(); + + let mut app_path = self.config.app_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize app path: {}", self.config.app_path.display()))?; + + let mut page_path = self.config.page_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize page path: {}", self.config.page_path.display()))?; + + // Remove extensions for JavaScript imports app_path.set_extension(""); page_path.set_extension(""); - Ok(( - func_id, - RENDER_FILE_TEMPLATE - .replace(APP_PATH_TAG, app_path.to_str().unwrap()) - .replace(PAGE_PATH_TAG, page_path.to_str().unwrap()) - .replace(FUNC_ID_TAG, &func_id.to_string()), - )) + let app_path_str = app_path + .to_str() + .ok_or_else(|| anyhow::anyhow!("App path contains invalid UTF-8"))?; + + let page_path_str = page_path + .to_str() + .ok_or_else(|| anyhow::anyhow!("Page path contains invalid UTF-8"))?; + + // Use custom template if provided, otherwise use default + let template = self.config.custom_template + .as_deref() + .unwrap_or(RENDER_FILE_TEMPLATE); + + let mut content = template + .replace(APP_PATH_TAG, app_path_str) + .replace(PAGE_PATH_TAG, page_path_str) + .replace(FUNC_ID_TAG, &func_id.to_string()); + + // Apply additional template variables + for (key, value) in &self.config.template_vars { + let placeholder = format!("%{}%", key.to_uppercase()); + content = content.replace(&placeholder, value); + } + + Ok((func_id, content)) + } + + /// Gets the configuration + pub fn config(&self) -> &ServerRenderConfig { + &self.config + } + + /// Updates the configuration + pub fn with_config(mut self, config: ServerRenderConfig) -> Result { + config.validate()?; + self.config = config; + Ok(self) + } +} + +impl Generate for ServerRender { + type Output = (i64, String); + + fn generate(&self) -> Result { + self.generate_content() } } #[cfg(test)] mod tests { use super::*; + use std::env; + + fn create_test_files() -> (PathBuf, PathBuf) { + let temp_dir = env::temp_dir().join("metassr_server_render_test"); + std::fs::create_dir_all(&temp_dir).unwrap(); + + let app_path = temp_dir.join("_app.tsx"); + let page_path = temp_dir.join("home.jsx"); + + // Create test files + std::fs::write(&app_path, "export default function App() { return null; }").unwrap(); + std::fs::write(&page_path, "export default function Page() { return null; }").unwrap(); + + (app_path, page_path) + } + + #[test] + fn test_server_render_config_creation() { + let (app_path, page_path) = create_test_files(); + + let config = ServerRenderConfig::new(&app_path, &page_path); + assert!(config.func_id.is_none()); + assert!(config.template_vars.is_empty()); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); + } + + #[test] + fn test_server_render_simple_creation() { + let (app_path, page_path) = create_test_files(); + + let render = ServerRender::simple(&app_path, &page_path).unwrap(); + assert!(render.config().func_id.is_none()); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); + } + #[test] - fn generate_render_file() { - println!( - "{:?}", - ServerRender::new("src/_app.tsx", "src/pages/home.jsx") - .generate() - .unwrap() - ); + fn test_generate_render_script() { + let (app_path, page_path) = create_test_files(); + + let render = ServerRender::simple(&app_path, &page_path).unwrap(); + let (func_id, content) = render.generate().unwrap(); + + assert!(func_id > 0); + assert!(content.contains(&func_id.to_string())); + + // Cleanup + let _ = std::fs::remove_file(&app_path); + let _ = std::fs::remove_file(&page_path); } } diff --git a/crates/metassr-build/src/server/target.rs b/crates/metassr-build/src/server/target.rs new file mode 100644 index 0000000..7958a00 --- /dev/null +++ b/crates/metassr-build/src/server/target.rs @@ -0,0 +1,438 @@ +use anyhow::{Context, Result}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +/// Represents a server-side build target with metadata +#[derive(Debug, Clone)] +pub struct ServerTarget { + /// Unique identifier for the target + pub id: String, + /// Function ID for MetaCall execution + pub func_id: i64, + /// Source file path + pub source_path: PathBuf, + /// Cached script path + pub cached_path: PathBuf, + /// Generated render script content + pub content: String, + /// Metadata about the target + pub metadata: ServerTargetMetadata, +} + +/// Metadata associated with a server target +#[derive(Debug, Clone)] +pub struct ServerTargetMetadata { + /// Route associated with this target + pub route: String, + /// Whether this is a special entry (like _app) + pub is_special: bool, + /// File type/extension + pub file_type: String, + /// Size of the content in bytes + pub content_size: usize, + /// Page name + pub page_name: String, +} + +impl ServerTarget { + /// Creates a new server target + pub fn new( + id: String, + func_id: i64, + source_path: PathBuf, + cached_path: PathBuf, + content: String, + route: String, + page_name: String, + is_special: bool, + ) -> Self { + let content_size = content.len(); + let file_type = source_path + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("unknown") + .to_string(); + + Self { + id, + func_id, + source_path, + cached_path, + content, + metadata: ServerTargetMetadata { + route, + is_special, + file_type, + content_size, + page_name, + }, + } + } + + /// Gets the content as bytes + pub fn content_bytes(&self) -> &[u8] { + self.content.as_bytes() + } + + /// Gets the absolute source path if it exists + pub fn absolute_source_path(&self) -> Result { + self.source_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize path: {}", self.source_path.display())) + } + + /// Gets the absolute cached path if it exists + pub fn absolute_cached_path(&self) -> Result { + self.cached_path + .canonicalize() + .with_context(|| format!("Failed to canonicalize cached path: {}", self.cached_path.display())) + } + + /// Gets the cached path as a string + pub fn cached_path_str(&self) -> &str { + self.cached_path.to_str().unwrap_or_default() + } +} + +/// Collection of server build targets with utilities for managing them +#[derive(Debug, Default, Clone)] +pub struct ServerTargetCollection { + targets: Vec, + target_map: HashMap, + func_id_map: HashMap, +} + +impl ServerTargetCollection { + /// Creates a new empty target collection + pub fn new() -> Self { + Self::default() + } + + /// Adds a target to the collection + pub fn add_target(&mut self, target: ServerTarget) -> Result<()> { + if self.target_map.contains_key(&target.id) { + return Err(anyhow::anyhow!("Target with id '{}' already exists", target.id)); + } + + if self.func_id_map.contains_key(&target.func_id) { + return Err(anyhow::anyhow!("Target with func_id '{}' already exists", target.func_id)); + } + + let index = self.targets.len(); + self.target_map.insert(target.id.clone(), index); + self.func_id_map.insert(target.func_id, index); + self.targets.push(target); + Ok(()) + } + + /// Gets a target by ID + pub fn get_target(&self, id: &str) -> Option<&ServerTarget> { + self.target_map.get(id).and_then(|&index| self.targets.get(index)) + } + + /// Gets a target by function ID + pub fn get_target_by_func_id(&self, func_id: i64) -> Option<&ServerTarget> { + self.func_id_map.get(&func_id).and_then(|&index| self.targets.get(index)) + } + + /// Gets all targets + pub fn targets(&self) -> &[ServerTarget] { + &self.targets + } + + /// Gets all targets mutably + pub fn targets_mut(&mut self) -> &mut [ServerTarget] { + &mut self.targets + } + + /// Gets the number of targets + pub fn len(&self) -> usize { + self.targets.len() + } + + /// Checks if the collection is empty + pub fn is_empty(&self) -> bool { + self.targets.is_empty() + } + + /// Filters targets by a predicate + pub fn filter_targets(&self, predicate: F) -> Vec<&ServerTarget> + where + F: Fn(&ServerTarget) -> bool, + { + self.targets.iter().filter(|target| predicate(target)).collect() + } + + /// Gets targets that are pages (not special entries) + pub fn page_targets(&self) -> Vec<&ServerTarget> { + self.filter_targets(|target| !target.metadata.is_special) + } + + /// Gets special targets (like _app) + pub fn special_targets(&self) -> Vec<&ServerTarget> { + self.filter_targets(|target| target.metadata.is_special) + } + + /// Converts targets to a HashMap suitable for bundling + pub fn ready_for_bundling(&self, dist_path: &Path) -> Result> { + let mut bundler_targets = HashMap::new(); + + for target in &self.targets { + let mut name = target.cached_path + .strip_prefix(dist_path) + .with_context(|| format!( + "Couldn't strip prefix '{}' from path '{}'", + dist_path.display(), + target.cached_path.display() + ))? + .to_path_buf(); + + name.set_extension(""); + + let absolute_path = target.absolute_cached_path()?; + bundler_targets.insert( + name.to_string_lossy().to_string(), + absolute_path.to_string_lossy().to_string(), + ); + } + + Ok(bundler_targets) + } + + /// Gets targets ready for execution (func_id mapping) + pub fn ready_for_exec(&self) -> HashMap { + self.targets + .iter() + .map(|target| (target.cached_path_str().to_string(), target.func_id)) + .collect() + } + + /// Gets statistics about the collection + pub fn stats(&self) -> ServerTargetCollectionStats { + let total_targets = self.targets.len(); + let page_targets = self.page_targets().len(); + let special_targets = self.special_targets().len(); + let total_content_size = self.targets.iter().map(|t| t.metadata.content_size).sum(); + + ServerTargetCollectionStats { + total_targets, + page_targets, + special_targets, + total_content_size, + } + } + + /// Creates an iterator over (PathBuf, i64) pairs + pub fn iter(&self) -> impl Iterator { + self.targets.iter().map(|target| (&target.cached_path, target.func_id)) + } +} + +/// Statistics about a server target collection +#[derive(Debug)] +pub struct ServerTargetCollectionStats { + pub total_targets: usize, + pub page_targets: usize, + pub special_targets: usize, + pub total_content_size: usize, +} + +impl std::fmt::Display for ServerTargetCollectionStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Server Targets: {} total ({} pages, {} special), Content size: {} bytes", + self.total_targets, + self.page_targets, + self.special_targets, + self.total_content_size + ) + } +} + +/// Builder for creating server build targets from pages +pub struct ServerTargetBuilder; + +impl ServerTargetBuilder { + /// Creates a server target from page information and render script + pub fn from_page( + page_name: &str, + page_path: &Path, + func_id: i64, + render_script: String, + cached_path: PathBuf, + server_extension: &str, + ) -> Result { + let target_id = format!("pages/{}.{}", page_name, server_extension); + let route = format!("/{}", page_name); + + Ok(ServerTarget::new( + target_id, + func_id, + page_path.to_path_buf(), + cached_path, + render_script, + route, + page_name.to_string(), + false, // Pages are not special entries + )) + } + + /// Creates a server target for a special entry + pub fn from_special( + id: String, + func_id: i64, + source_path: PathBuf, + cached_path: PathBuf, + content: String, + route: String, + name: String, + ) -> ServerTarget { + ServerTarget::new( + id, + func_id, + source_path, + cached_path, + content, + route, + name, + true, // This is a special entry + ) + } +} + +/// Legacy compatibility - wraps the new ServerTargetCollection to match old Targets interface +pub struct Targets(ServerTargetCollection); + +impl Targets { + pub fn new() -> Self { + Self(ServerTargetCollection::new()) + } + + pub fn insert(&mut self, func_id: i64, path: &Path) { + // This is a simplified version for compatibility + // In practice, you'd want to create a proper ServerTarget + let target = ServerTarget::new( + path.to_string_lossy().to_string(), + func_id, + path.to_path_buf(), + path.to_path_buf(), + String::new(), + String::new(), + String::new(), + false, + ); + let _ = self.0.add_target(target); + } + + pub fn ready_for_bundling(&self, dist_path: &PathBuf) -> HashMap { + self.0.ready_for_bundling(dist_path).unwrap_or_default() + } + + pub fn ready_for_exec(&self) -> HashMap { + self.0.ready_for_exec() + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } +} + +impl Default for Targets { + fn default() -> Self { + Self::new() + } +} + +impl Clone for Targets { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + fn create_test_target() -> ServerTarget { + let temp_dir = env::temp_dir().join("metassr_server_target_test"); + let source_path = temp_dir.join("test.tsx"); + let cached_path = temp_dir.join("test.server.js"); + + ServerTarget::new( + "test".to_string(), + 12345, + source_path, + cached_path, + "console.log('test')".to_string(), + "/test".to_string(), + "test".to_string(), + false, + ) + } + + #[test] + fn test_server_target_collection() { + let mut collection = ServerTargetCollection::new(); + + let target = create_test_target(); + let func_id = target.func_id; + let id = target.id.clone(); + + collection.add_target(target).unwrap(); + assert_eq!(collection.len(), 1); + assert!(collection.get_target(&id).is_some()); + assert!(collection.get_target_by_func_id(func_id).is_some()); + } + + #[test] + fn test_target_filtering() { + let mut collection = ServerTargetCollection::new(); + + let page_target = create_test_target(); + let special_target = ServerTarget::new( + "app".to_string(), + 54321, + PathBuf::from("src/_app.tsx"), + PathBuf::from("_app.server.js"), + "app content".to_string(), + "/_app".to_string(), + "_app".to_string(), + true, + ); + + collection.add_target(page_target).unwrap(); + collection.add_target(special_target).unwrap(); + + assert_eq!(collection.page_targets().len(), 1); + assert_eq!(collection.special_targets().len(), 1); + } + + #[test] + fn test_target_stats() { + let mut collection = ServerTargetCollection::new(); + + let target = create_test_target(); + collection.add_target(target).unwrap(); + let stats = collection.stats(); + + assert_eq!(stats.total_targets, 1); + assert_eq!(stats.page_targets, 1); + assert_eq!(stats.special_targets, 0); + assert_eq!(stats.total_content_size, 17); // "console.log('test')" length + } + + #[test] + fn test_legacy_targets_compatibility() { + let mut targets = Targets::new(); + let path = PathBuf::from("/test/path"); + + targets.insert(12345, &path); + + let exec_map = targets.ready_for_exec(); + assert!(exec_map.contains_key(path.to_str().unwrap())); + } +} diff --git a/crates/metassr-build/src/server/targets.rs b/crates/metassr-build/src/server/targets.rs index 014dd01..9af9143 100644 --- a/crates/metassr-build/src/server/targets.rs +++ b/crates/metassr-build/src/server/targets.rs @@ -63,30 +63,3 @@ impl Default for Targets { Self::new() } } - -pub struct TargetsGenerator<'a> { - app: PathBuf, - pages: PagesEntriesType, - cache: &'a mut CacheDir, -} - -impl<'a> TargetsGenerator<'a> { - pub fn new(app: PathBuf, pages: PagesEntriesType, cache: &'a mut CacheDir) -> Self { - Self { app, pages, cache } - } - pub fn generate(&mut self) -> Result { - let mut targets = Targets::new(); - for (page, page_path) in self.pages.iter() { - let (func_id, render_script) = ServerRender::new(&self.app, page_path).generate()?; - - let page = setup_page_path(page, "server.js"); - let path = self.cache.insert( - PathBuf::from("pages").join(&page).to_str().unwrap(), - render_script.as_bytes(), - )?; - - targets.insert(func_id, &path); - } - Ok(targets) - } -} From 45aac15d5b6159d90366e3d535581b8099dfde0f Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:40:26 +0300 Subject: [PATCH 05/13] fix: missed configuration for building --- crates/metassr-build/Cargo.toml | 4 ++++ crates/metassr-build/src/lib.rs | 12 +++++++++++ metassr-cli/src/cli/builder.rs | 37 ++++++++++++++++++++++++--------- metassr-cli/src/main.rs | 2 +- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/crates/metassr-build/Cargo.toml b/crates/metassr-build/Cargo.toml index 6adf3fc..4a91a5e 100644 --- a/crates/metassr-build/Cargo.toml +++ b/crates/metassr-build/Cargo.toml @@ -15,3 +15,7 @@ lazy_static = "1.5.0" serde = { version = "1.0.207", features = ["derive"] } metassr-bundler = { path = "../metassr-bundler" } metassr-fs-analyzer = { path = "../metassr-fs-analyzer" } +tracing = "0.1.41" + +[dev-dependencies] +tempfile = "3.6.0" diff --git a/crates/metassr-build/src/lib.rs b/crates/metassr-build/src/lib.rs index bac06db..499a970 100644 --- a/crates/metassr-build/src/lib.rs +++ b/crates/metassr-build/src/lib.rs @@ -1,5 +1,17 @@ pub mod client; +pub(crate) mod page; pub mod server; pub(crate) mod shared; pub mod traits; pub(crate) mod utils; + +// Re-export commonly used types for convenience +pub use client::{ + config::{ClientConfig, BundlerOptions}, + ClientBuilder, +}; +pub use server::{ + config::{ServerConfig, BuildingType, ServerBundlerOptions, ManifestOptions}, + ServerSideBuilder, +}; +pub use traits::{Build, Generate, Exec}; diff --git a/metassr-cli/src/cli/builder.rs b/metassr-cli/src/cli/builder.rs index cb9d4e4..7e5e955 100644 --- a/metassr-cli/src/cli/builder.rs +++ b/metassr-cli/src/cli/builder.rs @@ -6,20 +6,25 @@ use clap::ValueEnum; use metacall::switch; use metassr_build::server; -use metassr_build::{client::ClientBuilder, server::ServerSideBuilder, traits::Build}; +use metassr_build::{ + client::{config::ClientConfig, ClientBuilder}, + server::{config::ServerConfig, ServerSideBuilder}, + traits::Build, +}; use std::time::Instant; use tracing::{error, info}; pub struct Builder { + root_dir: String, out_dir: String, _type: BuildingType, } impl Builder { - pub fn new(_type: BuildingType, out_dir: String) -> Self { - Self { out_dir, _type } + pub fn new(_type: BuildingType, root_dir: String, out_dir: String) -> Self { + Self { root_dir, out_dir, _type } } } @@ -27,13 +32,19 @@ impl Exec for Builder { fn exec(&self) -> anyhow::Result<()> { let _metacall = switch::initialize().unwrap(); let instant = Instant::now(); + + // Build client-side { let instant = Instant::now(); - if let Err(e) = ClientBuilder::new("", &self.out_dir)?.build() { + // Create client configuration + let client_config = ClientConfig::new(".", &self.out_dir)?; + let client_builder = ClientBuilder::new(client_config); + + if let Err(e) = client_builder.build() { error!( target = "builder", - message = format!("Couldn't build for the client side: {e}"), + message = format!("Couldn't build for the client side: {e}"), ); return Err(anyhow!("Couldn't continue building process.")); } @@ -44,10 +55,16 @@ impl Exec for Builder { ); } + // Build server-side { let instant = Instant::now(); - if let Err(e) = ServerSideBuilder::new("", &self.out_dir, self._type.into())?.build() { + // Create server configuration + let server_config = ServerConfig::new(".", &self.out_dir)? + .with_building_type(self._type.into()); + let server_builder = ServerSideBuilder::new(server_config); + + if let Err(e) = server_builder.build() { error!( target = "builder", message = format!("Couldn't build for the server side: {e}"), @@ -81,11 +98,11 @@ pub enum BuildingType { SSR, } -impl Into for BuildingType { - fn into(self) -> server::BuildingType { +impl Into for BuildingType { + fn into(self) -> metassr_build::BuildingType { match self { - Self::SSG => server::BuildingType::StaticSiteGeneration, - Self::SSR => server::BuildingType::ServerSideRendering, + Self::SSG => metassr_build::BuildingType::StaticSiteGeneration, + Self::SSR => metassr_build::BuildingType::ServerSideRendering, } } } diff --git a/metassr-cli/src/main.rs b/metassr-cli/src/main.rs index 757b191..f21dbfe 100644 --- a/metassr-cli/src/main.rs +++ b/metassr-cli/src/main.rs @@ -50,7 +50,7 @@ async fn main() -> Result<()> { out_dir, build_type, } => { - cli::Builder::new(build_type, out_dir).exec()?; + cli::Builder::new(build_type, args.root, out_dir).exec()?; } Commands::Run { port, serve } => { cli::Runner::new(port, serve, allow_http_debug) From 79321ca93816e978c010dcc2760ee59520e51a34 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:40:56 +0300 Subject: [PATCH 06/13] bump rspack/core --- tests/web-app/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/web-app/package.json b/tests/web-app/package.json index 00e5977..daafc46 100644 --- a/tests/web-app/package.json +++ b/tests/web-app/package.json @@ -4,17 +4,17 @@ "description": "", "main": "App.tsx", "scripts": { - "build": "../../target/debug/metassr-cli --debug-mode=metacall build", - "build:ssg": "../../target/debug/metassr-cli --debug-mode=metacall build -t ssg", - "run": "../../target/debug/metassr-cli --debug-mode=metacall run ", - "run:ssg": "../../target/debug/metassr-cli --debug-mode=metacall run --serve" + "build": "../../target/debug/metassr --debug-mode=metacall build", + "build:ssg": "../../target/debug/metassr --debug-mode=metacall build -t ssg", + "run": "../../target/debug/metassr --debug-mode=metacall run ", + "run:ssg": "../../target/debug/metassr --debug-mode=metacall run --serve" }, "devDependencies": { "@types/react": "^18.0.24", "@types/react-dom": "^18.0.8" }, "dependencies": { - "@rspack/core": "0.7.5", + "@rspack/core": "1.5.5", "react": "^18.3.1", "react-dom": "^18.3.1" } From eb870499e5d53b53d559524fa8497a4f1e5805f8 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:41:12 +0300 Subject: [PATCH 07/13] missed deps --- Cargo.lock | 120 ++++++++++++++++++++++++++++++++++++++++++++++------- Cargo.toml | 2 +- 2 files changed, 107 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c9205..6f5bf0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -279,6 +279,22 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fnv" version = "1.0.7" @@ -333,6 +349,18 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.29.0" @@ -496,9 +524,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" @@ -610,6 +644,8 @@ dependencies = [ "metassr-utils", "serde", "serde_json", + "tempfile", + "tracing", ] [[package]] @@ -719,7 +755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -845,9 +881,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -861,6 +897,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "redox_syscall" version = "0.5.2" @@ -920,6 +962,19 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -1043,9 +1098,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1064,6 +1119,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "tempfile" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1172,9 +1240,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -1184,9 +1252,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1195,9 +1263,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -1281,6 +1349,24 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -1513,3 +1599,9 @@ name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/Cargo.toml b/Cargo.toml index a04f781..3be7b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,12 +41,12 @@ metassr-fs-analyzer = { path = "crates/metassr-fs-analyzer" } [workspace] members = [ + "metassr-cli", "crates/logger", "crates/metassr-build", "crates/metassr-server", "crates/metassr-utils", "crates/html-generator", - "metassr-cli", "crates/metassr-create", "crates/metassr-bundler", "crates/metassr-fs-analyzer", From e63d31909b213ba542992ee6d5e38f53d92767a3 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:42:07 +0300 Subject: [PATCH 08/13] remove lock files --- tests/web-app/.gitignore | 3 + tests/web-app/yarn.lock | 176 --------------------------------------- 2 files changed, 3 insertions(+), 176 deletions(-) create mode 100644 tests/web-app/.gitignore delete mode 100644 tests/web-app/yarn.lock diff --git a/tests/web-app/.gitignore b/tests/web-app/.gitignore new file mode 100644 index 0000000..608ce1d --- /dev/null +++ b/tests/web-app/.gitignore @@ -0,0 +1,3 @@ +dist +node_modules +*.lock \ No newline at end of file diff --git a/tests/web-app/yarn.lock b/tests/web-app/yarn.lock deleted file mode 100644 index fca6ac7..0000000 --- a/tests/web-app/yarn.lock +++ /dev/null @@ -1,176 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@module-federation/runtime-tools@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@module-federation/runtime-tools/-/runtime-tools-0.1.6.tgz#ee9bbe8c6e823b4c641695c91ab0b0bfdc5b3102" - integrity sha512-7ILVnzMIa0Dlc0Blck5tVZG1tnk1MmLnuZpLOMpbdW+zl+N6wdMjjHMjEZFCUAJh2E5XJ3BREwfX8Ets0nIkLg== - dependencies: - "@module-federation/runtime" "0.1.6" - "@module-federation/webpack-bundler-runtime" "0.1.6" - -"@module-federation/runtime@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@module-federation/runtime/-/runtime-0.1.6.tgz#e56adb6bdb0c5264ba5740d0aa64568f19a0c3be" - integrity sha512-nj6a+yJ+QxmcE89qmrTl4lphBIoAds0PFPVGnqLRWflwAP88jrCcrrTqRhARegkFDL+wE9AE04+h6jzlbIfMKg== - dependencies: - "@module-federation/sdk" "0.1.6" - -"@module-federation/sdk@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@module-federation/sdk/-/sdk-0.1.6.tgz#4d64b206ef6bddd99ca74680f278a2134a493dd5" - integrity sha512-qifXpyYLM7abUeEOIfv0oTkguZgRZuwh89YOAYIZJlkP6QbRG7DJMQvtM8X2yHXm9PTk0IYNnOJH0vNQCo6auQ== - -"@module-federation/webpack-bundler-runtime@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.1.6.tgz#8520435913ca10e4f259e825d9521002595ba769" - integrity sha512-K5WhKZ4RVNaMEtfHsd/9CNCgGKB0ipbm/tgweNNeC11mEuBTNxJ09Y630vg3WPkKv9vfMCuXg2p2Dk+Q/KWTSA== - dependencies: - "@module-federation/runtime" "0.1.6" - "@module-federation/sdk" "0.1.6" - -"@rspack/binding-darwin-arm64@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-0.7.5.tgz#a6e960ecb357c1f15481b689e12e4a7bcabf443c" - integrity sha512-mNBIm36s1BA7v4SL/r4f3IXIsjyH5CZX4eXMRPE52lBc3ClVuUB7d/8zk8dkyjJCMAj8PsZSnAJ3cfXnn7TN4g== - -"@rspack/binding-darwin-x64@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-0.7.5.tgz#7524f71506154af7229b508fabf9e7f14434fea1" - integrity sha512-teLK0TB1x0CsvaaiCopsFx4EvJe+/Hljwii6R7C9qOZs5zSOfbT/LQ202eA0sAGodCncARCGaXVrsekbrRYqeA== - -"@rspack/binding-linux-arm64-gnu@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.7.5.tgz#a4c245f85d2270258953dbdb54f6aa6520123720" - integrity sha512-/24UytJXrK+7CsucDb30GCKYIJ8nG6ceqbJyOtsJv9zeArNLHkxrYGSyjHJIpQfwVN17BPP4RNOi+yIZ3ZgDyA== - -"@rspack/binding-linux-arm64-musl@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.7.5.tgz#c7048a936fb482a20cd209d1b1a0987f9b4b7934" - integrity sha512-6RcxG42mLM01Pa6UYycACu/Nu9qusghAPUJumb8b8x5TRIDEtklYC5Ck6Rmagm+8E0ucMude2E/D4rMdIFcS3A== - -"@rspack/binding-linux-x64-gnu@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.7.5.tgz#f8ab23317bef82b0dca2235d6af430624ce37713" - integrity sha512-R0Lu4CJN2nWMW7WzPBuCIju80cQPpcaqwKJDj/quwQySpJJZ6c5qGwB8mntqjxIzZDrNH6u0OkpiUTbvWZj8ww== - -"@rspack/binding-linux-x64-musl@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-0.7.5.tgz#bfb08b6e905c055ea4e643e24b32b315d07e359b" - integrity sha512-dDgi/ThikMy1m4llxPeEXDCA2I8F8ezFS/eCPLZGU2/J1b4ALwDjuRsMmo+VXSlFCKgIt98V6h1woeg7nu96yg== - -"@rspack/binding-win32-arm64-msvc@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.7.5.tgz#894d1cb8c68bd9ab5093100d84d9ac7142aac992" - integrity sha512-nEF4cUdLfgEK6FrgJSJhUlr2/7LY1tmqBNQCFsCjtDtUkQbJIEo1b8edT94G9tJcQoFE4cD+Re30yBYbQO2Thg== - -"@rspack/binding-win32-ia32-msvc@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.7.5.tgz#b5917eedb8eb9e591cf1e62dde6928dbbeebaf35" - integrity sha512-hEcHRwJIzpZsePr+5x6V/7TGhrPXhSZYG4sIhsrem1za9W+qqCYYLZ7KzzbRODU07QaAH2RxjcA1bf8F2QDYAQ== - -"@rspack/binding-win32-x64-msvc@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.7.5.tgz#fbcd66f1e4d60e6a7a618c5f4abe4cbc2958f334" - integrity sha512-PpVpP6J5/2b4T10hzSUwjLvmdpAOj3ozARl1Nrf/lsbYwhiXivoB8Gvoy/xe/Xpgr732Dk9VCeeW8rreWOOUVQ== - -"@rspack/binding@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-0.7.5.tgz#20279813ea1f4f365063774263aa2cf4bd5a6e69" - integrity sha512-XcdOvaCz1mWWwr5vmEY9zncdInrjINEh60EWkYdqtCA67v7X7rB1fe6n4BeAI1+YLS2Eacj+lytlr+n7I+DYVg== - optionalDependencies: - "@rspack/binding-darwin-arm64" "0.7.5" - "@rspack/binding-darwin-x64" "0.7.5" - "@rspack/binding-linux-arm64-gnu" "0.7.5" - "@rspack/binding-linux-arm64-musl" "0.7.5" - "@rspack/binding-linux-x64-gnu" "0.7.5" - "@rspack/binding-linux-x64-musl" "0.7.5" - "@rspack/binding-win32-arm64-msvc" "0.7.5" - "@rspack/binding-win32-ia32-msvc" "0.7.5" - "@rspack/binding-win32-x64-msvc" "0.7.5" - -"@rspack/core@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@rspack/core/-/core-0.7.5.tgz#e6936949d8666655874c536cb719ff76daee44ab" - integrity sha512-zVTe4WCyc3qsLPattosiDYZFeOzaJ32/BYukPP2I1VJtCVFa+PxGVRPVZhSoN6fXw5oy48yHg9W9v1T8CaEFhw== - dependencies: - "@module-federation/runtime-tools" "0.1.6" - "@rspack/binding" "0.7.5" - caniuse-lite "^1.0.30001616" - tapable "2.2.1" - webpack-sources "3.2.3" - -"@types/prop-types@*": - version "15.7.12" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" - integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== - -"@types/react-dom@^18.0.8": - version "18.3.0" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" - integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^18.0.24": - version "18.3.3" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" - integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - -caniuse-lite@^1.0.30001616: - version "1.0.30001716" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz#39220dfbc58c85d9d4519e7090b656aa11ca4b85" - integrity sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw== - -csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -loose-envify@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -react-dom@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" - integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.2" - -react@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" - -scheduler@^0.23.2: - version "0.23.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" - integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== - dependencies: - loose-envify "^1.1.0" - -tapable@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -webpack-sources@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== From a029f519a46cc37833e9272646166a9ed5ec4b9d Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:42:28 +0300 Subject: [PATCH 09/13] enhance src page structure --- crates/metassr-fs-analyzer/src/src_dir.rs | 34 +++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/metassr-fs-analyzer/src/src_dir.rs b/crates/metassr-fs-analyzer/src/src_dir.rs index 3edc477..1828e1d 100644 --- a/crates/metassr-fs-analyzer/src/src_dir.rs +++ b/crates/metassr-fs-analyzer/src/src_dir.rs @@ -1,6 +1,7 @@ use super::DirectoryAnalyzer; use anyhow::{anyhow, Result}; -use std::{collections::HashMap, ffi::OsStr, marker::Sized, path::PathBuf}; +use metassr_utils::rand::Rand; +use std::{collections::HashMap, ffi::OsStr, hash::{DefaultHasher, Hash, Hasher}, marker::Sized, path::PathBuf}; use walkdir::WalkDir; /// Wrappers for special entries that collected by the source analyzer @@ -17,20 +18,29 @@ pub mod special_entries { } #[derive(Debug, Clone)] -pub struct Page { +pub struct PageInformation { pub route: String, pub path: PathBuf, } +#[derive(Debug, Clone)] +pub struct Page { + pub id: u64, + pub info: PageInformation, +} + impl Page { - pub fn new(route: &S, path: &P) -> Self + pub fn new(id: u64, route: &S, path: &P) -> Self where S: ToString, P: AsRef + ?Sized, { Self { - route: route.to_string(), - path: PathBuf::from(path), + id, + info: PageInformation { + route: route.to_string(), + path: PathBuf::from(path), + }, } } } @@ -47,15 +57,15 @@ impl Pages { Self(pages) } - pub fn insert + ?Sized>(&mut self, route: &S, path: &P) { - self.0.push(Page::new(route, path)); + pub fn insert + ?Sized>(&mut self, id: u64, route: &S, path: &P) { + self.0.push(Page::new(id, route, path)); } pub fn as_map(&self) -> HashMap { HashMap::from_iter( self.0 .iter() - .map(|Page { route, path }| (route.to_owned(), path.to_owned())) + .map(|Page { id, info }| (info.route.to_owned(), info.path.to_owned())) .collect::>(), ) } @@ -172,20 +182,22 @@ impl DirectoryAnalyzer for SourceDir { let path = entry.path(); let stem = path.file_stem().unwrap().to_str().unwrap(); let stripped = path.strip_prefix(src)?; - + match stripped.iter().next() { Some(_) if list_of_specials.contains(&stem) => match stem { "_app" => specials.0 = Some(special_entries::App(path.to_path_buf())), "_head" => specials.1 = Some(special_entries::Head(path.to_path_buf())), _ => (), }, - + Some(p) if p == OsStr::new("pages") => { + let mut hasher = DefaultHasher::new(); let route = path .strip_prefix([src, "/pages"].concat())? .to_str() .unwrap(); - pages.insert(&route, path); + route.hash(&mut hasher); + pages.insert(hasher.finish(), &route, path); } _ => (), From 03c0d1c2fb6b2efff6ae221920f91fbdba752892 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 24 Sep 2025 21:43:05 +0300 Subject: [PATCH 10/13] refactor(bundler): some changes in rspack configuration --- crates/metassr-bundler/src/bundle.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/metassr-bundler/src/bundle.js b/crates/metassr-bundler/src/bundle.js index 72be43d..20471eb 100644 --- a/crates/metassr-bundler/src/bundle.js +++ b/crates/metassr-bundler/src/bundle.js @@ -15,24 +15,22 @@ const defaultConfig = { library: { type: 'commonjs2', }, - publicPath: '' + publicPath: '', }, resolve: { extensions: ['.js', '.jsx', '.tsx', '.ts'], - mainFields: ['browser', 'module', 'main'] }, optimization: { - minimize: false, + minimize: true, }, module: { rules: [ - { + { test: /\.(jsx|js)$/, exclude: /node_modules/, use: { loader: 'builtin:swc-loader', options: { - sourceMap: true, jsc: { parser: { syntax: 'ecmascript', @@ -75,12 +73,7 @@ const defaultConfig = { }, { test: /\.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/, - type: 'asset', - parser: { - dataUrlCondition: { - maxSize: 8 * 1024 - } - } + type: 'asset/inline', // Inline assets as Base64 strings } ] } @@ -97,9 +90,13 @@ function createBundlerConfig(entry, dist) { name: 'Client', mode: 'production', devtool: 'source-map', - stats: { - preset: 'errors-warnings', - timings: true, + experiments: { + css: true + }, + // plugins: [], + stats: { + preset: 'errors-warnings', + timings: true, colors: true, modules: true }, From 4909798ba16180f95b44f1b7cf45cd181efdae81 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 8 Oct 2025 21:48:48 +0300 Subject: [PATCH 11/13] refactor: rename `html-generator` to `metassr-html` --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 6 +++--- crates/metassr-build/Cargo.toml | 2 +- crates/metassr-build/src/lib.rs | 1 - .../metassr-build/src/server/renderer/html.rs | 2 +- .../Cargo.toml | 2 +- .../src/builder.rs | 0 .../src/default.html | 0 .../src/html_props.rs | 0 .../src/lib.rs | 0 .../src/template.rs | 0 11 files changed, 15 insertions(+), 16 deletions(-) rename crates/{html-generator => metassr-html}/Cargo.toml (91%) rename crates/{html-generator => metassr-html}/src/builder.rs (100%) rename crates/{html-generator => metassr-html}/src/default.html (100%) rename crates/{html-generator => metassr-html}/src/html_props.rs (100%) rename crates/{html-generator => metassr-html}/src/lib.rs (100%) rename crates/{html-generator => metassr-html}/src/template.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 8e6f414..2201dda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,13 +379,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "html-generator" -version = "0.0.1-alpha" -dependencies = [ - "anyhow", -] - [[package]] name = "http" version = "1.1.0" @@ -616,7 +609,6 @@ dependencies = [ "axum", "chrono", "clap", - "html-generator", "lazy_static", "logger", "metacall", @@ -625,6 +617,7 @@ dependencies = [ "metassr-bundler", "metassr-create", "metassr-fs-analyzer", + "metassr-html", "metassr-server", "metassr-utils", "nu-ansi-term 0.50.0", @@ -644,11 +637,11 @@ name = "metassr-build" version = "0.0.1-alpha" dependencies = [ "anyhow", - "html-generator", "lazy_static", "metacall", "metassr-bundler", "metassr-fs-analyzer", + "metassr-html", "metassr-utils", "serde", "serde_json", @@ -704,6 +697,13 @@ dependencies = [ "walkdir", ] +[[package]] +name = "metassr-html" +version = "0.0.1-alpha" +dependencies = [ + "anyhow", +] + [[package]] name = "metassr-server" version = "0.0.1-alpha" diff --git a/Cargo.toml b/Cargo.toml index d5f97d6..d350966 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ logger = { path = "crates/logger" } metassr-server = { path = "crates/metassr-server" } metassr-build = { path = "crates/metassr-build" } metassr-utils = { path = "crates/metassr-utils" } -html-generator = { path = "crates/html-generator" } +metassr-html = { path = "crates/metassr-html" } serde = "1.0.207" tower-layer = "0.3.3" tower-service = "0.3.3" @@ -47,10 +47,10 @@ members = [ "metassr-cli", "crates/logger", "crates/metassr-build", + "crates/metassr-create", + "crates/metassr-html", "crates/metassr-server", "crates/metassr-utils", - "crates/html-generator", - "crates/metassr-create", "crates/metassr-bundler", "crates/metassr-fs-analyzer", ] diff --git a/crates/metassr-build/Cargo.toml b/crates/metassr-build/Cargo.toml index bb1a267..2851db1 100644 --- a/crates/metassr-build/Cargo.toml +++ b/crates/metassr-build/Cargo.toml @@ -10,7 +10,7 @@ metacall = "0.5.1" anyhow = "1.0.82" serde_json = "1.0.120" metassr-utils = { path = "../metassr-utils" } -html-generator = { path = "../html-generator" } +metassr-html = { path = "../metassr-html" } lazy_static = "1.5.0" serde = { version = "1.0.207", features = ["derive"] } metassr-bundler = { path = "../metassr-bundler" } diff --git a/crates/metassr-build/src/lib.rs b/crates/metassr-build/src/lib.rs index 499a970..9937021 100644 --- a/crates/metassr-build/src/lib.rs +++ b/crates/metassr-build/src/lib.rs @@ -1,5 +1,4 @@ pub mod client; -pub(crate) mod page; pub mod server; pub(crate) mod shared; pub mod traits; diff --git a/crates/metassr-build/src/server/renderer/html.rs b/crates/metassr-build/src/server/renderer/html.rs index 52530c4..4d12cc6 100644 --- a/crates/metassr-build/src/server/renderer/html.rs +++ b/crates/metassr-build/src/server/renderer/html.rs @@ -1,7 +1,7 @@ use std::path::Path; use anyhow::Result; -use html_generator::{ +use metassr_html::{ builder::{HtmlBuilder, HtmlOutput}, html_props::{HtmlPropsBuilder}, template::HtmlTemplate, diff --git a/crates/html-generator/Cargo.toml b/crates/metassr-html/Cargo.toml similarity index 91% rename from crates/html-generator/Cargo.toml rename to crates/metassr-html/Cargo.toml index a6baf9c..b63b939 100644 --- a/crates/html-generator/Cargo.toml +++ b/crates/metassr-html/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "html-generator" +name = "metassr-html" version = "0.0.1-alpha" edition = "2021" description = "A simple library to generate html content with a simple way." diff --git a/crates/html-generator/src/builder.rs b/crates/metassr-html/src/builder.rs similarity index 100% rename from crates/html-generator/src/builder.rs rename to crates/metassr-html/src/builder.rs diff --git a/crates/html-generator/src/default.html b/crates/metassr-html/src/default.html similarity index 100% rename from crates/html-generator/src/default.html rename to crates/metassr-html/src/default.html diff --git a/crates/html-generator/src/html_props.rs b/crates/metassr-html/src/html_props.rs similarity index 100% rename from crates/html-generator/src/html_props.rs rename to crates/metassr-html/src/html_props.rs diff --git a/crates/html-generator/src/lib.rs b/crates/metassr-html/src/lib.rs similarity index 100% rename from crates/html-generator/src/lib.rs rename to crates/metassr-html/src/lib.rs diff --git a/crates/html-generator/src/template.rs b/crates/metassr-html/src/template.rs similarity index 100% rename from crates/html-generator/src/template.rs rename to crates/metassr-html/src/template.rs From 42b4459494101958e58b9f3176cf837ad9854f84 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 8 Oct 2025 21:59:28 +0300 Subject: [PATCH 12/13] chore: removing unused --- crates/metassr-build/src/server/cache.rs | 59 +++++++++++----------- crates/metassr-build/src/server/targets.rs | 9 ---- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/crates/metassr-build/src/server/cache.rs b/crates/metassr-build/src/server/cache.rs index 2180bbb..38e6138 100644 --- a/crates/metassr-build/src/server/cache.rs +++ b/crates/metassr-build/src/server/cache.rs @@ -1,10 +1,7 @@ use anyhow::{Context, Result}; use metassr_utils::cache_dir::CacheDir; -use super::{ - config::ServerConfig, - target::{ServerTarget, ServerTargetCollection}, -}; +use super::{config::ServerConfig, target::ServerTargetCollection}; /// Service for managing cache operations during server build process pub struct ServerCacheService { @@ -35,7 +32,7 @@ impl ServerCacheService { for target in targets.targets() { let cache_path = format!("pages/{}", target.id); - + self.cache_dir .insert(&cache_path, target.content_bytes()) .with_context(|| format!("Failed to cache server target: {}", target.id))?; @@ -53,13 +50,14 @@ impl ServerCacheService { /// Retrieves the cache entries suitable for bundling pub fn get_bundler_entries(&self) -> Result> { - let entries = self.cache_dir + let entries = self + .cache_dir .entries_in_scope() .iter() .map(|(entry_name, path)| { - let fullpath = path - .canonicalize() - .with_context(|| format!("Failed to canonicalize cache path: {}", path.display()))?; + let fullpath = path.canonicalize().with_context(|| { + format!("Failed to canonicalize cache path: {}", path.display()) + })?; Ok((entry_name.to_owned(), format!("{}", fullpath.display()))) }) @@ -81,14 +79,14 @@ impl ServerCacheService { /// Clears the cache pub fn clear_cache(&mut self) -> Result<()> { let cache_path = self.config.cache_dir_str(); - + if std::path::Path::new(cache_path).exists() { std::fs::remove_dir_all(cache_path) .context("Failed to remove server cache directory")?; } - self.cache_dir = CacheDir::new(cache_path) - .context("Failed to recreate server cache directory")?; + self.cache_dir = + CacheDir::new(cache_path).context("Failed to recreate server cache directory")?; Ok(()) } @@ -126,10 +124,7 @@ impl ServerCacheService { } else { // Check if file is readable if let Err(e) = std::fs::read(path) { - validation.add_error(format!( - "Cannot read cache file '{}': {}", - entry_name, e - )); + validation.add_error(format!("Cannot read cache file '{}': {}", entry_name, e)); } } } @@ -195,14 +190,18 @@ impl ServerCacheValidation { /// Gets all validation issues pub fn issues(&self) -> Vec { let mut issues = Vec::new(); - + for (entry, path) in &self.missing_files { - issues.push(format!("Missing cache file '{}' at {}", entry, path.display())); + issues.push(format!( + "Missing cache file '{}' at {}", + entry, + path.display() + )); } - + issues.extend(self.errors.iter().cloned()); issues.extend(self.warnings.iter().cloned()); - + issues } @@ -242,7 +241,7 @@ impl std::fmt::Display for ServerCacheValidation { mod tests { use super::*; use crate::server::target::ServerTarget; - use std::{path::PathBuf, env}; + use std::{env, path::PathBuf}; fn create_test_config() -> ServerConfig { let temp_dir = env::temp_dir().join("metassr_server_cache_test"); @@ -255,10 +254,10 @@ mod tests { fn test_server_cache_service_creation() { let config = create_test_config(); config.ensure_directories().unwrap(); - + let cache_service = ServerCacheService::new(config); assert!(cache_service.is_ok()); - + // Cleanup let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_server_cache_test")); } @@ -267,12 +266,12 @@ mod tests { fn test_server_cache_stats() { let config = create_test_config(); config.ensure_directories().unwrap(); - + let cache_service = ServerCacheService::new(config).unwrap(); let stats = cache_service.get_cache_stats(); - + assert_eq!(stats.stored_files, 0); // Empty cache initially - + // Cleanup let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_server_cache_test")); } @@ -281,12 +280,12 @@ mod tests { fn test_server_cache_validation() { let config = create_test_config(); config.ensure_directories().unwrap(); - + let cache_service = ServerCacheService::new(config).unwrap(); let validation = cache_service.validate_cache().unwrap(); - + assert!(validation.is_valid()); // Empty cache should be valid - + // Cleanup let _ = std::fs::remove_dir_all(env::temp_dir().join("metassr_server_cache_test")); } @@ -296,7 +295,7 @@ mod tests { let mut validation = ServerCacheValidation::new(); validation.add_error("Test error".to_string()); validation.add_missing_file("test".to_string(), PathBuf::from("/missing")); - + let display_text = format!("{}", validation); assert!(display_text.contains("Server cache validation failed")); assert!(display_text.contains("1 missing files")); diff --git a/crates/metassr-build/src/server/targets.rs b/crates/metassr-build/src/server/targets.rs index a35206d..f88e83a 100644 --- a/crates/metassr-build/src/server/targets.rs +++ b/crates/metassr-build/src/server/targets.rs @@ -3,15 +3,6 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::Result; - -use metassr_fs_analyzer::src_dir::PagesEntriesType; -use metassr_utils::cache_dir::CacheDir; - -use crate::{traits::Generate, utils::setup_page_path}; - -use super::render::ServerRender; - #[derive(Debug, Clone)] pub struct Targets(HashMap); From aa15c0359d275b60c101344d33b689d55e110448 Mon Sep 17 00:00:00 2001 From: hulxv Date: Wed, 8 Oct 2025 22:25:11 +0300 Subject: [PATCH 13/13] fix: correct naming issue --- metassr-cli/src/cli/builder.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/metassr-cli/src/cli/builder.rs b/metassr-cli/src/cli/builder.rs index ec41796..edbc2f0 100644 --- a/metassr-cli/src/cli/builder.rs +++ b/metassr-cli/src/cli/builder.rs @@ -4,7 +4,6 @@ use super::traits::Exec; use anyhow::{anyhow, Result}; use clap::ValueEnum; use metacall::initialize; -use metassr_build::server; use metassr_build::{ client::{config::ClientConfig, ClientBuilder}, @@ -99,8 +98,8 @@ pub enum BuildingType { impl Into for BuildingType { fn into(self) -> metassr_build::BuildingType { match self { - Self::SSG => metassr_build::BuildingType::StaticSiteGeneration, - Self::SSR => metassr_build::BuildingType::ServerSideRendering, + Self::Ssg => metassr_build::BuildingType::StaticSiteGeneration, + Self::Ssr => metassr_build::BuildingType::ServerSideRendering, } } }