diff --git a/.github/workflows/bundler-tests.yml b/.github/workflows/bundler-tests.yml
new file mode 100644
index 0000000..8ccdc45
--- /dev/null
+++ b/.github/workflows/bundler-tests.yml
@@ -0,0 +1,45 @@
+name: Bundler Tests
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ bundler-tests:
+ name: Bundler Integration Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Build Docker image
+ run: docker build -f Dockerfile.dev -t metassr-test .
+
+ - name: Run bundler tests in Docker
+ run: |
+ docker run --rm -v $(pwd)/tests/web-app/dist:/root/tests/web-app/dist metassr-test bash -c "
+ # Run the build
+ cd /root/tests/web-app
+ npm run build
+
+ # Copy the test script into the container and run it
+ cp /root/tests/test-bundle.sh ./
+ chmod +x test-bundle.sh
+ ./test-bundle.sh
+ "
+
+ - name: Upload dist artifacts
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: bundler-dist-output
+ path: tests/web-app/dist/
+ retention-days: 7
diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml
new file mode 100644
index 0000000..b5f5e0f
--- /dev/null
+++ b/.github/workflows/clippy.yml
@@ -0,0 +1,40 @@
+name: Rust Clippy
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ clippy:
+ name: Run Clippy
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy
+
+ - name: Cache Rust dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Run clippy
+ run: cargo clippy --all-targets --all-features -- -D warnings
diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml
new file mode 100644
index 0000000..284bd37
--- /dev/null
+++ b/.github/workflows/fmt.yml
@@ -0,0 +1,29 @@
+name: Rust Formatting
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ fmt:
+ name: Check Formatting
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt
+
+ - name: Check formatting
+ run: cargo fmt --check
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 3a2bcba..0000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: Test
-
-on:
- push:
- branches: [ "master" ]
- pull_request:
- branches: [ "master" ]
- workflow_dispatch:
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
- cancel-in-progress: true
-
-jobs:
- test:
- name: Test
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest] # TODO: , windows-latest]
- steps:
- - name: Check out the repo
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Install MetaCall Linux (debug)
- if: matrix.os == 'ubuntu-latest'
- run: curl -sL https://raw.githubusercontent.com/metacall/install/master/install.sh | sh -s -- --debug
-
- - name: Install MetaCall MacOS
- if: matrix.os == 'macos-latest'
- run: curl -sL https://raw.githubusercontent.com/metacall/install/master/install.sh | sh
-
- - name: Install MetaCall Windows
- if: matrix.os == 'windows-latest'
- run: powershell -NoProfile -ExecutionPolicy Unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/metacall/install/master/install.ps1')))"
-
- - name: Install Rust
- uses: actions-rs/toolchain@v1
- with:
- toolchain: stable
- components: rustfmt, clippy
- override: true
-
- - name: Clippy
- run: cargo clippy -- -D warnings
-
- - name: Build
- run: cargo build
-
- - name: Unit Testing
- run: cargo test
-
- - name: Integration Testing
- working-directory: ./tests/web-app
- run: |
- npm install
- npm run build
- npm start &
- sleep 10
- response=$(curl --write-out "%{http_code}" --silent --output /dev/null http://localhost:8080)
- if [ "$response" -ne 200 ]; then
- echo "Server did not respond with status 200"
- exit 1
- fi
-
- # TODO: Improve testing by checking if resulting HTML is correct
-
- # - name: Install Playwright
- # run: npm install -g playwright
-
- # - name: Run Playwright test
- # run: |
- # node -e "
- # const { chromium } = require('playwright');
- # (async () => {
- # const browser = await chromium.launch();
- # const page = await browser.newPage();
- # await page.goto('http://localhost:3000');
- # const html = await page.content();
- # if (!html.includes('
My Node App')) {
- # console.error('HTML content did not match expected value');
- # process.exit(1);
- # }
- # await browser.close();
- # })();
- # "
diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml
new file mode 100644
index 0000000..b9d7fd5
--- /dev/null
+++ b/.github/workflows/typos.yml
@@ -0,0 +1,15 @@
+name: Spell Checker action
+on: [pull_request, push]
+
+jobs:
+ run:
+ name: Start Spell Checking with Typos
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Actions Repository
+ uses: actions/checkout@v2
+
+ - name: Check spelling of * Files
+ uses: crate-ci/typos@master
+ with:
+ files: ./*
\ No newline at end of file
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
new file mode 100644
index 0000000..04dd3bb
--- /dev/null
+++ b/.github/workflows/unit-tests.yml
@@ -0,0 +1,31 @@
+name: Rust Tests
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ name: Run Rust Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Build Docker image
+ run: docker build -f Dockerfile.dev -t metassr-test .
+
+ - name: Run Rust tests in Docker
+ run: |
+ docker run --rm metassr-test bash -c "
+ cd /root
+ cargo test --verbose
+ "
diff --git a/TODO.md b/TODO.md
index 6d922d7..eb7a0ec 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,7 +1,7 @@
## TODO
#### Main features
-- [x] Serving staic files are located in ``./static/**``
+- [x] Serving static files are located in ``./static/**``
- [x] the HTML builder
diff --git a/crates/metassr-build/src/server/manifest.rs b/crates/metassr-build/src/server/manifest.rs
index c8effd0..4d0f3e4 100644
--- a/crates/metassr-build/src/server/manifest.rs
+++ b/crates/metassr-build/src/server/manifest.rs
@@ -119,7 +119,7 @@ impl ManifestGenerator {
}
}
pub fn generate + ?Sized>(&self, head: &H) -> Result {
- let cache_path = self.cache.path();
+ let cache_path = self.cache.path();
let global = GlobalEntry::new(head, cache_path)?;
let mut manifest = Manifest::new(global);
diff --git a/crates/metassr-build/src/server/mod.rs b/crates/metassr-build/src/server/mod.rs
index 3d1346e..89a5bf2 100644
--- a/crates/metassr-build/src/server/mod.rs
+++ b/crates/metassr-build/src/server/mod.rs
@@ -79,10 +79,7 @@ impl Build for ServerSideBuilder {
};
let bundling_targets = targets.ready_for_bundling(&self.dist_path);
- let bundler = WebBundler::new(
- &bundling_targets,
- &self.dist_path,
- )?;
+ let bundler = WebBundler::new(&bundling_targets, &self.dist_path)?;
if let Err(e) = bundler.exec() {
return Err(anyhow!("Bundling failed: {e}"));
diff --git a/crates/metassr-build/src/server/renderer/head.rs b/crates/metassr-build/src/server/renderer/head.rs
index a22451b..bc33d10 100644
--- a/crates/metassr-build/src/server/renderer/head.rs
+++ b/crates/metassr-build/src/server/renderer/head.rs
@@ -51,8 +51,7 @@ impl HeadRenderer {
let bundling_targets = self.bundling_target()?;
let bundler = WebBundler::new(&bundling_targets, self.cache_dir.path())?;
- if let Err(e) = bundler.exec()
- {
+ if let Err(e) = bundler.exec() {
return Err(anyhow!("Cannot bundling head: {e}"));
}
Ok(())
diff --git a/crates/metassr-build/src/server/renderer/html.rs b/crates/metassr-build/src/server/renderer/html.rs
index 52530c4..14d8a1e 100644
--- a/crates/metassr-build/src/server/renderer/html.rs
+++ b/crates/metassr-build/src/server/renderer/html.rs
@@ -3,7 +3,7 @@ use std::path::Path;
use anyhow::Result;
use html_generator::{
builder::{HtmlBuilder, HtmlOutput},
- html_props::{HtmlPropsBuilder},
+ html_props::HtmlPropsBuilder,
template::HtmlTemplate,
};
use metassr_fs_analyzer::dist_dir::PageEntry;
diff --git a/crates/metassr-build/src/server/renderer/mod.rs b/crates/metassr-build/src/server/renderer/mod.rs
index b8a1121..c0deeb8 100644
--- a/crates/metassr-build/src/server/renderer/mod.rs
+++ b/crates/metassr-build/src/server/renderer/mod.rs
@@ -1,3 +1,3 @@
pub mod head;
pub mod html;
-pub mod page;
\ No newline at end of file
+pub mod page;
diff --git a/crates/metassr-build/src/utils.rs b/crates/metassr-build/src/utils.rs
index eb32fdf..131641b 100644
--- a/crates/metassr-build/src/utils.rs
+++ b/crates/metassr-build/src/utils.rs
@@ -5,9 +5,10 @@ use std::{
pub fn setup_page_path(page: &str, ext: &str) -> PathBuf {
match Path::new(page) {
- path if path.file_stem() != Some(OsStr::new("index")) => {
- path.to_path_buf().with_extension("").join(format!("index.{ext}"))
- }
+ path if path.file_stem() != Some(OsStr::new("index")) => path
+ .to_path_buf()
+ .with_extension("")
+ .join(format!("index.{ext}")),
path => path.to_path_buf().with_extension(ext),
}
diff --git a/crates/metassr-bundler/src/lib.rs b/crates/metassr-bundler/src/lib.rs
index dcb4414..e7ad259 100644
--- a/crates/metassr-bundler/src/lib.rs
+++ b/crates/metassr-bundler/src/lib.rs
@@ -17,7 +17,7 @@ lazy_static! {
static ref IS_BUNDLING_SCRIPT_LOADED: Mutex = Mutex::new(CheckerState::default());
/// A simple checker to check if the bundling function is done or not. It is used to block the program until bundling done.
- static ref IS_COMPLIATION_WAIT: Arc = Arc::new(CompilationWait::default());
+ static ref IS_COMPILATION_WAIT: Arc = Arc::new(CompilationWait::default());
}
static BUILD_SCRIPT: &str = include_str!("./bundle.js");
const BUNDLING_FUNC: &str = "web_bundling";
@@ -111,7 +111,7 @@ impl<'a> WebBundler<'a> {
// Resolve callback when the bundling process is completed successfully
fn resolve(result: Box, _: Box) -> Box {
- let compilation_wait = &*Arc::clone(&IS_COMPLIATION_WAIT);
+ let compilation_wait = &*Arc::clone(&IS_COMPILATION_WAIT);
let mut started = compilation_wait.checker.lock().unwrap();
// Mark the process as completed and notify waiting threads
@@ -123,7 +123,7 @@ impl<'a> WebBundler<'a> {
// Reject callback for handling errors during the bundling process
fn reject(err: Box, _: Box) -> Box {
- let compilation_wait = &*Arc::clone(&IS_COMPLIATION_WAIT);
+ let compilation_wait = &*Arc::clone(&IS_COMPILATION_WAIT);
let mut started = compilation_wait.checker.lock().unwrap();
// Log the bundling error and mark the process as completed
@@ -151,12 +151,12 @@ impl<'a> WebBundler<'a> {
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 compilation_wait = Arc::clone(&IS_COMPILATION_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();
+ started = Arc::clone(&IS_COMPILATION_WAIT).cond.wait(started).unwrap();
}
// Reset the checker state to false after the process completes
diff --git a/crates/metassr-fs-analyzer/src/dist_dir.rs b/crates/metassr-fs-analyzer/src/dist_dir.rs
index cfee74a..9641ba6 100644
--- a/crates/metassr-fs-analyzer/src/dist_dir.rs
+++ b/crates/metassr-fs-analyzer/src/dist_dir.rs
@@ -63,7 +63,7 @@ impl PageEntry {
/// ```no_run
/// use metassr_fs_analyzer::dist_dir::PageEntry;
/// use std::path::PathBuf;
- ///
+ ///
/// let page_entry = PageEntry::new(PathBuf::from("/dist/pages/home"));
/// println!("{:?}", page_entry.path);
/// ```
@@ -164,7 +164,7 @@ impl DistDir {
///
/// ```no_run
/// use metassr_fs_analyzer::{dist_dir::DistDir, DirectoryAnalyzer};
-///
+///
/// let dist_dir = DistDir::new("/path/to/dist").unwrap();
/// let result = dist_dir.analyze().unwrap();
/// for (page, entry) in result.pages {
diff --git a/crates/metassr-server/src/handler.rs b/crates/metassr-server/src/handler.rs
index 865cc21..db27087 100644
--- a/crates/metassr-server/src/handler.rs
+++ b/crates/metassr-server/src/handler.rs
@@ -38,7 +38,9 @@ impl<'a, S: Clone + Send + Sync + 'static> PagesHandler<'a, S> {
pub fn build(&mut self) -> Result<()> {
for (route, entries) in self.pages.iter() {
let html = match self.running_type {
- RunningType::StaticSiteGeneration => Box::new(read_to_string(entries.path.join("index.html"))?),
+ RunningType::StaticSiteGeneration => {
+ Box::new(read_to_string(entries.path.join("index.html"))?)
+ }
RunningType::ServerSideRendering => {
Box::new(PageRenderer::from_manifest(&self.dist_dir, route)?.render()?)
}
diff --git a/crates/metassr-server/src/lib.rs b/crates/metassr-server/src/lib.rs
index 5c9a09e..7e36f1f 100644
--- a/crates/metassr-server/src/lib.rs
+++ b/crates/metassr-server/src/lib.rs
@@ -67,7 +67,9 @@ impl Server {
};
app.fallback(fallback)
}
- RunningType::ServerSideRendering => app.fallback(|| async { Redirect::to("/_notfound") }),
+ RunningType::ServerSideRendering => {
+ app.fallback(|| async { Redirect::to("/_notfound") })
+ }
}
PagesHandler::new(&mut app, &dist_dir, self.configs.running_type)?.build()?;
diff --git a/crates/metassr-utils/src/cache_dir.rs b/crates/metassr-utils/src/cache_dir.rs
index 0159efa..7ad20db 100644
--- a/crates/metassr-utils/src/cache_dir.rs
+++ b/crates/metassr-utils/src/cache_dir.rs
@@ -88,7 +88,6 @@ impl CacheDir {
/// cache.insert("data.txt", "Some data".as_bytes()).unwrap();
/// ```
pub fn insert(&mut self, pathname: &str, buf: &[u8]) -> Result {
-
// Set the path
let path = format!("{}/{}", self.path.to_str().unwrap(), pathname);
let path = Path::new(&path);
diff --git a/docs/getting-started/cli.md b/docs/getting-started/cli.md
index bc1ae98..79d6011 100644
--- a/docs/getting-started/cli.md
+++ b/docs/getting-started/cli.md
@@ -91,7 +91,7 @@ Starts the HTTP server to serve your SSR application. You can also use this comm
The port number on which the HTTP server will run.
- **`--serve`**
- Enables serving of the generated static site directly, it's used if you build your porject with `ssg` building type.
+ Enables serving of the generated static site directly, it's used if you build your project with `ssg` building type.
**Usage:**
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..9af3708
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,140 @@
+# MetaSSR Bundler Tests
+
+This directory contains tests for the MetaSSR bundler.
+
+## Web App Test
+
+The `web-app` example tests the bundler with a React application including:
+- TypeScript/TSX files
+- CSS imports (should be embedded in the bundle)
+- Image assets (should be inlined as base64)
+- Multiple pages and components
+
+### Expected Output
+
+After running `npm run build` in the `web-app` directory, the `dist` folder should contain:
+
+```
+dist/
+├── cache/
+│ ├── head.js # Head bundle
+│ ├── head.js.map # Head source map
+│ └── pages/
+│ ├── blog/
+│ │ ├── $article/
+│ │ │ ├── index.js # Client bundle
+│ │ │ ├── index.server.js # Server bundle
+│ │ │ ├── index.server.js.map # Server source map
+│ │ │ ├── index.server.css # Server CSS (separate)
+│ │ │ └── index.server.css.map # CSS source map
+│ │ ├── index.js
+│ │ ├── index.server.js
+│ │ ├── index.server.js.map
+│ │ ├── index.server.css
+│ │ └── index.server.css.map
+│ ├── home/ # Similar structure for home page
+│ ├── index.js # Root page client bundle
+│ ├── index.server.js # Root page server bundle
+│ ├── index.server.js.map
+│ ├── index.server.css
+│ ├── index.server.css.map
+│ └── _notfound/ # Similar structure for 404 page
+├── pages/
+│ ├── blog/
+│ │ ├── $article/
+│ │ │ ├── index.js.js # Client bundle
+│ │ │ ├── index.js.js.map # Client source map
+│ │ │ ├── index.js.css # Client CSS (separate)
+│ │ │ └── index.js.css.map # CSS source map
+│ │ └── (similar structure for blog index)
+│ ├── home/ # Similar structure for home page
+│ ├── _notfound/ # Similar structure for 404 page
+│ └── (root page files)
+└── manifest.json # Build manifest
+```
+
+**Note:** The build creates both client and server bundles. CSS files are currently generated separately for both client and server builds. This may be expected behavior for SSR (Server-Side Rendering).
+
+### Running Tests Locally
+
+1. Build the project:
+```bash
+cd tests/web-app
+npm install
+npm run build
+```
+
+2. Run the test script from anywhere in the project:
+```bash
+# Make the script executable
+chmod +x tests/test-bundle.sh
+
+# Run from project root
+./tests/test-bundle.sh
+
+# Or run from anywhere
+/path/to/metassr/tests/test-bundle.sh
+```
+
+### Running Tests in Docker
+
+```bash
+# Build the Docker image
+docker build -f Dockerfile.dev -t metassr-test .
+
+# Run tests inside Docker
+docker run --rm metassr-test bash -c "cd /root/tests/web-app && npm run build && ls -la dist/"
+```
+
+### CI/CD
+
+The GitHub Actions workflow (`.github/workflows/test.yml`) automatically runs these tests on every push and pull request.
+
+## Test Configuration
+
+The test script is designed to be easily configurable. Key configuration sections:
+
+### Expected Directories
+```bash
+EXPECTED_DIRECTORIES=(
+ "cache"
+ "cache/pages"
+ "cache/pages/blog"
+ "cache/pages/home"
+ "cache/pages/_notfound"
+ "pages"
+ "pages/blog"
+ "pages/home"
+ "pages/_notfound"
+)
+```
+
+### Expected Files
+```bash
+EXPECTED_FILES=(
+ "manifest.json"
+ "cache/head.js"
+ "cache/head.js.map"
+)
+```
+
+### Expected Patterns
+```bash
+EXPECTED_PATTERNS=(
+ "cache/pages/*/index.js"
+ "cache/pages/*/index.server.js"
+ "cache/pages/*/index.server.js.map"
+ "pages/*/index.js.js"
+ "pages/*/index.js.js.map"
+)
+```
+
+### CSS Embedding Check
+```bash
+CSS_SEPARATE_FILES_PATTERNS=(
+ "pages/*/index.js.css"
+ "cache/pages/*/index.server.css"
+)
+```
+
+To modify the test expectations, simply update these arrays in the test script.
diff --git a/tests/test-bundle.sh b/tests/test-bundle.sh
new file mode 100755
index 0000000..88de053
--- /dev/null
+++ b/tests/test-bundle.sh
@@ -0,0 +1,225 @@
+#!/bin/bash
+
+# Test script for MetaSSR bundler
+# This script verifies the bundler output
+
+set -e
+
+# Get the script directory and project root
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+WEB_APP_DIR="$PROJECT_ROOT/tests/web-app"
+DIST_DIR="$WEB_APP_DIR/dist"
+
+# Configuration: Expected files and directories
+EXPECTED_DIRECTORIES=(
+ "cache"
+ "cache/pages"
+ "cache/pages/blog"
+ "cache/pages/home"
+ "cache/pages/_notfound"
+ "pages"
+ "pages/blog"
+ "pages/home"
+ "pages/_notfound"
+)
+
+EXPECTED_FILES=(
+ "manifest.json"
+ "cache/head.js"
+ "cache/head.js.map"
+)
+
+# Expected patterns for generated files
+EXPECTED_PATTERNS=(
+ "cache/pages/*/index.js"
+ "cache/pages/*/index.server.js"
+ "cache/pages/*/index.server.js.map"
+ "pages/*/index.js.js"
+ "pages/*/index.js.js.map"
+)
+
+# CSS embedding test configuration
+CSS_SEPARATE_FILES_PATTERNS=(
+ "pages/*/index.js.css"
+ "cache/pages/*/index.server.css"
+)
+
+# Minimum file sizes (in bytes)
+MIN_BUNDLE_SIZE=500
+MIN_CSS_SIZE=100
+
+echo "Running MetaSSR bundler tests..."
+echo "[INFO] Project root: $PROJECT_ROOT"
+echo "[INFO] Web app directory: $WEB_APP_DIR"
+echo "[INFO] Dist directory: $DIST_DIR"
+
+# Function to check if a file/directory exists
+check_exists() {
+ local path="$1"
+ local type="$2" # "file" or "directory"
+
+ if [ "$type" = "directory" ] && [ -d "$path" ]; then
+ echo "[PASS] Found directory: $(basename "$path")"
+ return 0
+ elif [ "$type" = "file" ] && [ -f "$path" ]; then
+ echo "[PASS] Found file: $(basename "$path")"
+ return 0
+ else
+ echo "[ERROR] Missing $type: $path"
+ return 1
+ fi
+}
+
+# Function to check file patterns using find
+check_pattern() {
+ local pattern="$1"
+ local description="$2"
+
+ # Convert glob pattern to find pattern
+ local find_pattern="${pattern//\*/*}"
+ local found_files
+ found_files=$(find "$DIST_DIR" -path "$DIST_DIR/$find_pattern" 2>/dev/null | wc -l)
+
+ if [ "$found_files" -gt 0 ]; then
+ echo "[PASS] Found $found_files files matching pattern: $description"
+ return 0
+ else
+ echo "[ERROR] No files found matching pattern: $pattern"
+ return 1
+ fi
+}
+
+# Function to check file size
+check_file_size() {
+ local file="$1"
+ local min_size="$2"
+ local description="$3"
+
+ if [ ! -f "$file" ]; then
+ echo "[ERROR] File not found for size check: $file"
+ return 1
+ fi
+
+ local size
+ size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
+
+ if [ "$size" -ge "$min_size" ]; then
+ echo "[PASS] $description size: $size bytes (>= $min_size)"
+ return 0
+ else
+ echo "[ERROR] $description size too small: $size bytes (< $min_size)"
+ return 1
+ fi
+}
+
+# Function to validate JavaScript syntax
+validate_js() {
+ local file="$1"
+
+ if [ ! -f "$file" ]; then
+ echo "[SKIP] JavaScript validation (file not found): $file"
+ return 0
+ fi
+
+ if node -c "$file" 2>/dev/null; then
+ echo "[PASS] Valid JavaScript: $(basename "$file")"
+ return 0
+ else
+ echo "[WARN] Invalid JavaScript syntax: $file"
+ return 1
+ fi
+}
+
+# Main test execution
+FAILED_TESTS=0
+
+# Check if web-app directory exists
+if ! check_exists "$WEB_APP_DIR" "directory"; then
+ exit 1
+fi
+
+# Check if dist directory exists
+if ! check_exists "$DIST_DIR" "directory"; then
+ echo "[INFO] Available directories in web-app:"
+ ls -la "$WEB_APP_DIR"
+ exit 1
+fi
+
+echo ""
+echo "=== Checking expected directories ==="
+for dir in "${EXPECTED_DIRECTORIES[@]}"; do
+ if ! check_exists "$DIST_DIR/$dir" "directory"; then
+ ((FAILED_TESTS++))
+ fi
+done
+
+echo ""
+echo "=== Checking expected files ==="
+for file in "${EXPECTED_FILES[@]}"; do
+ if ! check_exists "$DIST_DIR/$file" "file"; then
+ ((FAILED_TESTS++))
+ fi
+done
+
+echo ""
+echo "=== Checking file patterns ==="
+for pattern in "${EXPECTED_PATTERNS[@]}"; do
+ if ! check_pattern "$pattern" "$pattern"; then
+ ((FAILED_TESTS++))
+ fi
+done
+
+echo ""
+echo "=== Checking CSS embedding ==="
+CSS_SEPARATE_COUNT=0
+for pattern in "${CSS_SEPARATE_FILES_PATTERNS[@]}"; do
+ find_pattern="${pattern//\*/*}"
+ found_files=$(find "$DIST_DIR" -path "$DIST_DIR/$find_pattern" 2>/dev/null | wc -l)
+ CSS_SEPARATE_COUNT=$((CSS_SEPARATE_COUNT + found_files))
+done
+
+if [ "$CSS_SEPARATE_COUNT" -gt 0 ]; then
+ echo "[WARN] Found $CSS_SEPARATE_COUNT separate CSS files. CSS should be embedded in JS bundles."
+ echo "[INFO] This might be expected behavior for server-side rendering."
+else
+ echo "[PASS] No separate CSS files found - CSS is embedded"
+fi
+
+echo ""
+echo "=== Checking file sizes ==="
+# Check sizes of key files
+if [ -f "$DIST_DIR/cache/head.js" ]; then
+ check_file_size "$DIST_DIR/cache/head.js" "$MIN_BUNDLE_SIZE" "Head bundle"
+fi
+
+# Check first page bundle
+FIRST_PAGE_JS=$(find "$DIST_DIR/pages" -name "index.js.js" | head -1)
+if [ -n "$FIRST_PAGE_JS" ]; then
+ check_file_size "$FIRST_PAGE_JS" "$MIN_BUNDLE_SIZE" "Page bundle"
+fi
+
+echo ""
+echo "=== Validating JavaScript syntax ==="
+# Validate key JavaScript files
+find "$DIST_DIR" -name "*.js" -not -name "*.js.map" | head -5 | while read -r js_file; do
+ validate_js "$js_file"
+done
+
+echo ""
+echo "=== Dist directory structure ==="
+echo "Contents of dist directory:"
+find "$DIST_DIR" -type f | sort | while read -r file; do
+ rel_path="${file#$DIST_DIR/}"
+ size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
+ printf " %-50s %8s bytes\n" "$rel_path" "$size"
+done
+
+echo ""
+if [ "$FAILED_TESTS" -eq 0 ]; then
+ echo "All tests passed!"
+ exit 0
+else
+ echo "Failed tests: $FAILED_TESTS"
+ exit 1
+fi