diff --git a/.gitignore b/.gitignore index 207bc8a7..0837e3f7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ # Qodo /.qodo + +# Gradle +/.gradle +/build diff --git a/.gradle-docs/MIGRATION.md b/.gradle-docs/MIGRATION.md new file mode 100644 index 00000000..50d99164 --- /dev/null +++ b/.gradle-docs/MIGRATION.md @@ -0,0 +1,365 @@ +# Migration Guide: Ant to Gradle + +This document provides guidance for migrating from the legacy Ant build system to the new Gradle build system. + +## Overview + +The Bearsampp Module Node.js project has been fully migrated from Apache Ant to Gradle. The Gradle build system provides better performance, modern tooling, and improved maintainability. + +## What Changed + +### Removed Files + +The following files have been **removed** from the repository: + +| File | Status | Notes | +|-------------------------------|-----------|------------------------------------------| +| `build.xml` | ✗ Removed | Replaced by `build.gradle` | +| `module-nodejs.RELEASE.launch`| ✗ Removed | Eclipse Ant launcher, no longer needed | + +**Note**: These legacy Ant build files have been completely removed. The project now uses pure Gradle for all build operations. + +### New Files + +| File | Purpose | +|-------------------------------|----------------------------------------------| +| `build.gradle` | Main Gradle build script | +| `settings.gradle` | Gradle project settings | +| `gradle.properties` | Gradle configuration properties | +| `.gradle-docs/` | Gradle build documentation directory | +| `.gradle-docs/README.md` | Main build documentation | +| `.gradle-docs/PACKAGING.md` | Packaging and archive structure guide | +| `.gradle-docs/MIGRATION.md` | This migration guide | + +## Command Mapping + +### Basic Commands + +| Ant Command | Gradle Command | Notes | +|--------------------------------------|---------------------------------------------|------------------------------------------| +| `ant release` | `gradle release` | Interactive mode (prompts for version) | +| `ant release -Dinput.bundle=24.6.0` | `gradle release -PbundleVersion=24.6.0` | Non-interactive mode | +| `ant clean` | `gradle clean` | Clean build artifacts | + +### New Commands + +Gradle provides additional commands not available in Ant: + +| Command | Description | +|-------------------------------|----------------------------------------------| +| `gradle info` | Display build configuration information | +| `gradle tasks` | List all available tasks | +| `gradle verify` | Verify build environment and dependencies | +| `gradle listVersions` | List available versions in bin/ | +| `gradle listReleases` | List releases from modules-untouched | +| `gradle releaseAll` | Build all versions in bin/ | +| `gradle validateProperties` | Validate build.properties configuration | +| `gradle checkModulesUntouched`| Check modules-untouched integration | + +## Key Differences + +### 1. Build File Format + +**Ant (build.xml):** +```xml + + + + + + + +``` + +**Gradle (build.gradle):** +```groovy +tasks.register('release') { + group = 'build' + description = 'Build release package' + + doLast { + copy { + from nodeExtractPath + into bundlePrepPath + } + } +} +``` + +### 2. Property Handling + +**Ant:** +```xml + + +``` + +**Gradle:** +```groovy +def buildProps = new Properties() +file('build.properties').withInputStream { buildProps.load(it) } + +ext { + bundleName = buildProps.getProperty('bundle.name', 'nodejs') +} +``` + +### 3. Task Execution + +**Ant:** +- Tasks defined as `` elements +- Dependencies specified with `depends` attribute +- Sequential execution by default + +**Gradle:** +- Tasks defined with `tasks.register()` +- Dependencies specified with `dependsOn` +- Parallel execution possible +- Incremental builds supported + +### 4. Dependency Management + +**Ant:** +- Manual download and extraction +- No built-in dependency management +- Custom scripts required + +**Gradle:** +- Built-in dependency management +- Automatic download and caching +- Repository support (Maven, etc.) + +### 5. IDE Integration + +**Ant:** +- Limited IDE support +- Manual configuration required +- Eclipse `.launch` files needed + +**Gradle:** +- Excellent IDE support (IntelliJ IDEA, Eclipse, VS Code) +- Automatic project import +- No manual configuration needed + +## Migration Steps + +If you're migrating from Ant to Gradle in your own project: + +### Step 1: Install Gradle + +```bash +# Windows (Chocolatey) +choco install gradle + +# macOS (Homebrew) +brew install gradle + +# Linux (SDKMAN!) +sdk install gradle + +# Verify installation +gradle --version +``` + +### Step 2: Create build.gradle + +Create a new `build.gradle` file based on the current implementation. See [build.gradle](../build.gradle) for reference. + +### Step 3: Create settings.gradle + +```groovy +rootProject.name = 'module-nodejs' +``` + +### Step 4: Test the Build + +```bash +# Verify environment +gradle verify + +# List available tasks +gradle tasks + +# Test a build +gradle release -PbundleVersion=24.6.0 +``` + +### Step 5: Update Documentation + +Update your README.md and other documentation to reference Gradle commands instead of Ant. + +### Step 6: Deprecate Ant Files + +Mark `build.xml` and related files as deprecated. You can keep them for reference but note they're no longer used. + +## Troubleshooting Migration Issues + +### Issue: "Cannot find dev project" + +**Ant Behavior:** +```xml + + +``` + +**Gradle Behavior:** +```groovy +ext { + devPath = file("${rootDir}/dev").absolutePath +} + +if (!file(ext.devPath).exists()) { + throw new GradleException("Dev path not found: ${ext.devPath}") +} +``` + +**Solution:** +Ensure the `dev` project exists in the parent directory, or modify the build script if your structure is different. + +### Issue: "Property not found" + +**Ant Behavior:** +Properties loaded from `build.properties` are automatically available. + +**Gradle Behavior:** +Properties must be explicitly loaded: + +```groovy +def buildProps = new Properties() +file('build.properties').withInputStream { buildProps.load(it) } +``` + +**Solution:** +Check that `build.properties` exists and properties are loaded correctly. + +### Issue: "Task not found" + +**Ant Behavior:** +```bash +ant release +``` + +**Gradle Behavior:** +```bash +gradle release +``` + +**Solution:** +Use `gradle tasks` to list all available tasks and verify the task name. + +## Benefits of Gradle + +### 1. Performance + +- **Incremental builds**: Only rebuild what changed +- **Build cache**: Reuse outputs from previous builds +- **Parallel execution**: Run tasks in parallel when possible + +### 2. Maintainability + +- **Groovy DSL**: More readable and maintainable than XML +- **Modular**: Easy to split build logic into multiple files +- **Reusable**: Share build logic across projects + +### 3. Tooling + +- **IDE integration**: Excellent support in IntelliJ IDEA, Eclipse, VS Code +- **Build scans**: Detailed build performance analysis +- **Dependency insight**: Understand dependency trees + +### 4. Ecosystem + +- **Plugins**: Thousands of plugins available +- **Community**: Large and active community +- **Documentation**: Comprehensive official documentation + +## Best Practices + +### 1. Use Gradle Wrapper (Optional) + +While this project doesn't include the Gradle Wrapper, you can add it: + +```bash +gradle wrapper --gradle-version 8.5 +``` + +This creates: +- `gradlew` (Unix) +- `gradlew.bat` (Windows) +- `gradle/wrapper/` directory + +Benefits: +- Ensures consistent Gradle version across team +- No need to install Gradle separately + +### 2. Organize Build Logic + +Keep build logic organized: +- Main build logic in `build.gradle` +- Configuration in `build.properties` +- Documentation in `.gradle-docs/` + +### 3. Use Task Groups + +Organize tasks into logical groups: + +```groovy +tasks.register('myTask') { + group = 'build' // or 'verification', 'help', etc. + description = 'My task description' +} +``` + +### 4. Leverage Gradle Features + +- Use `ext` for shared properties +- Use `doFirst` and `doLast` for task actions +- Use `dependsOn` for task dependencies +- Use `mustRunAfter` for task ordering + +## Additional Resources + +### Official Documentation + +- [Gradle User Manual](https://docs.gradle.org/current/userguide/userguide.html) +- [Gradle DSL Reference](https://docs.gradle.org/current/dsl/) +- [Gradle Build Language Reference](https://docs.gradle.org/current/userguide/writing_build_scripts.html) + +### Tutorials + +- [Getting Started with Gradle](https://docs.gradle.org/current/userguide/getting_started.html) +- [Migrating from Ant to Gradle](https://docs.gradle.org/current/userguide/migrating_from_ant.html) +- [Gradle Best Practices](https://docs.gradle.org/current/userguide/authoring_maintainable_build_scripts.html) + +### Bearsampp Resources + +- [Bearsampp Project](https://github.com/bearsampp/bearsampp) +- [Module PHP (Gradle)](https://github.com/Bearsampp/module-php/tree/gradle-convert) +- [modules-untouched Repository](https://github.com/Bearsampp/modules-untouched) + +## Support + +If you encounter issues during migration: + +1. Check this migration guide +2. Review the [main documentation](README.md) +3. Check [troubleshooting guide](README.md#troubleshooting) +4. Open an issue on [GitHub](https://github.com/bearsampp/module-nodejs/issues) + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.8.21 +**Build System**: Pure Gradle + +## Summary + +The migration from Ant to Gradle provides: + +✅ **Better Performance** - Incremental builds and caching +✅ **Modern Tooling** - Excellent IDE integration +✅ **Easier Maintenance** - Readable Groovy DSL +✅ **More Features** - Rich plugin ecosystem +✅ **Better Documentation** - Comprehensive guides + +The Gradle build system is now the primary and recommended way to build Bearsampp Node.js modules. diff --git a/.gradle-docs/PACKAGING.md b/.gradle-docs/PACKAGING.md new file mode 100644 index 00000000..ebcc99b1 --- /dev/null +++ b/.gradle-docs/PACKAGING.md @@ -0,0 +1,384 @@ +# Packaging and Archive Layout + +This document describes how the Bearsampp Node.js module packages releases and the structure of the resulting archives. + +## Overview + +The Gradle build produces archives that include the top-level version folder, matching the behavior used across all Bearsampp modules (PHP, MySQL, etc.). This ensures consistency and proper extraction behavior. + +## Archive Structure + +### Example for Node.js 24.6.0 + +``` +bearsampp-nodejs-24.6.0-2025.8.21.7z +└── nodejs24.6.0/ ← Version folder at root + ├── node.exe ← Node.js executable + ├── npm ← NPM script + ├── npm.cmd ← NPM Windows command + ├── npx ← NPX script + ├── npx.cmd ← NPX Windows command + ├── node_modules/ ← Node modules directory + │ ├── npm/ + │ └── ... + ├── LICENSE + ├── README.md + └── ... +``` + +### Key Points + +1. **Top-level folder**: The archive contains `nodejs{version}/` as the root directory +2. **Version naming**: Format is `nodejs` + version number (e.g., `nodejs24.6.0`) +3. **Complete structure**: All Node.js files and directories are inside the version folder +4. **Consistency**: Matches the pattern used by other Bearsampp modules + +## Implementation Details + +### Compression Process + +The build system uses two different approaches depending on the archive format: + +#### 7-Zip Compression + +```groovy +// Run from parent directory and include folder name explicitly +def parentDir = prepPath.parentFile +def folderName = prepPath.name // e.g., "nodejs24.6.0" + +ProcessBuilder([ + sevenZipExe, + 'a', // Add to archive + '-t7z', // 7z format + archiveFile.absolutePath, // Output archive + folderName // Folder to include +]) +.directory(parentDir) // Run from parent directory +.start() +``` + +**Why this works:** +- Running from the parent directory ensures the folder name is included in the archive +- Passing the folder name (not path) as the argument includes it at the root level +- Result: Archive contains `nodejs24.6.0/` at the root + +#### ZIP Compression + +```groovy +task("zipArchive_${bundleVersion}", type: Zip) { + from(prepPath.parentFile) { + include "${prepPath.name}/**" + } + destinationDirectory = archiveFile.parentFile + archiveFileName = archiveFile.name +} +``` + +**Why this works:** +- `from(prepPath.parentFile)` starts from the parent directory +- `include "${prepPath.name}/**"` includes the folder and all its contents +- Result: Archive contains `nodejs24.6.0/` at the root + +## Archive Naming Convention + +Archives follow this naming pattern: + +``` +bearsampp-{bundle.name}-{version}-{bundle.release}.{format} +``` + +**Example:** +``` +bearsampp-nodejs-24.6.0-2025.8.21.7z +``` + +**Components:** +- `bearsampp` - Project prefix +- `nodejs` - Bundle name (from build.properties) +- `24.6.0` - Node.js version +- `2025.8.21` - Bundle release date (from build.properties) +- `7z` - Archive format (from build.properties) + +## Hash Files + +Each archive is accompanied by multiple hash files for integrity verification: + +``` +bearsampp-nodejs-24.6.0-2025.8.21.7z +bearsampp-nodejs-24.6.0-2025.8.21.7z.md5 ← MD5 checksum +bearsampp-nodejs-24.6.0-2025.8.21.7z.sha1 ← SHA-1 checksum +bearsampp-nodejs-24.6.0-2025.8.21.7z.sha256 ← SHA-256 checksum +bearsampp-nodejs-24.6.0-2025.8.21.7z.sha512 ← SHA-512 checksum +``` + +### Hash File Format + +Each hash file contains: +``` +{hash_value} {filename} +``` + +**Example (MD5):** +``` +a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 bearsampp-nodejs-24.6.0-2025.8.21.7z +``` + +### Verification + +To verify archive integrity: + +```bash +# Windows (PowerShell) +# MD5 +Get-FileHash bearsampp-nodejs-24.6.0-2025.8.21.7z -Algorithm MD5 + +# SHA256 +Get-FileHash bearsampp-nodejs-24.6.0-2025.8.21.7z -Algorithm SHA256 + +# Linux/macOS +# MD5 +md5sum bearsampp-nodejs-24.6.0-2025.8.21.7z + +# SHA256 +sha256sum bearsampp-nodejs-24.6.0-2025.8.21.7z +``` + +## Output Locations + +### Default Structure + +``` +bearsampp-build/ +├── tmp/ # Temporary build files +│ ├── bundles_prep/bins/nodejs/ # Prepared bundles +│ │ └── nodejs24.6.0/ # Prepared version +│ ├── bundles_build/bins/nodejs/ # Build staging +│ │ └── nodejs24.6.0/ # Built version (non-archived) +│ ├── downloads/nodejs/ # Downloaded archives +│ │ └── nodejs-24.6.0-win-x64.7z +│ └── extract/nodejs/ # Extracted archives +│ └── 24.6.0/ +│ └── node-v24.6.0-win-x64/ +└── bins/nodejs/ # Final packaged archives + └── 2025.8.21/ # Release version + ├── bearsampp-nodejs-24.6.0-2025.8.21.7z + ├── bearsampp-nodejs-24.6.0-2025.8.21.7z.md5 + ├── bearsampp-nodejs-24.6.0-2025.8.21.7z.sha1 + ├── bearsampp-nodejs-24.6.0-2025.8.21.7z.sha256 + └── bearsampp-nodejs-24.6.0-2025.8.21.7z.sha512 +``` + +### Customizing Output Location + +You can override the default output location in two ways: + +#### 1. build.properties + +```properties +build.path = C:/Custom-Build-Path +``` + +#### 2. Environment Variable + +```bash +# Windows (PowerShell) +$env:BEARSAMPP_BUILD_PATH = "C:\Custom-Build-Path" + +# Windows (Command Prompt) +set BEARSAMPP_BUILD_PATH=C:\Custom-Build-Path + +# Linux/macOS +export BEARSAMPP_BUILD_PATH=/path/to/custom/build +``` + +**Priority:** +1. `build.properties` `build.path` property (highest) +2. `BEARSAMPP_BUILD_PATH` environment variable +3. Default: `{repo-parent}/bearsampp-build` (lowest) + +## Verification Guide + +### How to Verify Archive Structure + +After building a release, verify the archive structure: + +#### Method 1: Using 7-Zip Command Line + +```bash +# List archive contents +7z l bearsampp-nodejs-24.6.0-2025.8.21.7z + +# Filter for the version folder +7z l bearsampp-nodejs-24.6.0-2025.8.21.7z | findstr nodejs24.6.0 + +# Expected output should show entries like: +# nodejs24.6.0\node.exe +# nodejs24.6.0\npm +# nodejs24.6.0\node_modules\... +``` + +#### Method 2: Using PowerShell (ZIP) + +```powershell +# Extract to temporary directory +Expand-Archive -Path bearsampp-nodejs-24.6.0-2025.8.21.zip -DestinationPath .\_inspect + +# List contents +Get-ChildItem .\_inspect + +# Expected output: +# Directory: E:\...\\_inspect +# Mode LastWriteTime Length Name +# ---- ------------- ------ ---- +# d----- 1/31/2025 ... nodejs24.6.0 + +# List files inside version folder +Get-ChildItem .\_inspect\nodejs24.6.0 + +# Clean up +Remove-Item .\_inspect -Recurse -Force +``` + +#### Method 3: Using 7-Zip GUI + +1. Open the archive in 7-Zip +2. The first entry should be a folder named `nodejs{version}/` +3. All files should be inside this folder + +### Quick Verification Script + +```bash +# Build a release +gradle release -PbundleVersion=24.6.0 + +# Verify the archive +7z l bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z | findstr /C:"nodejs24.6.0" + +# If successful, you should see multiple lines starting with "nodejs24.6.0\" +``` + +## Build Process Flow + +### Step-by-Step Packaging + +1. **Preparation Phase** + ``` + tmp/bundles_prep/bins/nodejs/nodejs24.6.0/ + ``` + - Copy Node.js files from source (downloaded or local) + - Overlay custom files from bin/nodejs24.6.0/ + - Result: Complete Node.js installation ready for packaging + +2. **Build Phase** + ``` + tmp/bundles_build/bins/nodejs/nodejs24.6.0/ + ``` + - Copy prepared files to build directory + - This is the non-archived version + - Can be used for testing before packaging + +3. **Packaging Phase** + ``` + bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z + ``` + - Compress from parent directory + - Include version folder at root + - Generate hash files + +## Troubleshooting + +### Issue: Archive doesn't contain version folder + +**Symptom:** +``` +Archive contains files directly at root instead of nodejs24.6.0/ folder +``` + +**Solution:** +This should not happen with the current build system. If it does: +1. Check that you're using the latest build.gradle +2. Verify the compression command is running from the parent directory +3. Check the Gradle output for any errors during packaging + +### Issue: Wrong folder name in archive + +**Symptom:** +``` +Archive contains "node-v24.6.0-win-x64" instead of "nodejs24.6.0" +``` + +**Solution:** +This indicates the preparation phase didn't complete correctly: +1. Check that the prep directory was created: `tmp/bundles_prep/bins/nodejs/nodejs24.6.0/` +2. Verify files were copied to the prep directory +3. Run with `--info` flag to see detailed output: `gradle release -PbundleVersion=24.6.0 --info` + +### Issue: Hash files not generated + +**Symptom:** +``` +Archive created but no .md5, .sha1, etc. files +``` + +**Solution:** +1. Check Gradle output for hash generation errors +2. Verify Java has permissions to write to the output directory +3. Try running with `--stacktrace` to see detailed error: `gradle release -PbundleVersion=24.6.0 --stacktrace` + +## Best Practices + +### 1. Always Verify After Build + +After building a release, always verify the archive structure: + +```bash +gradle release -PbundleVersion=24.6.0 +7z l bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z | findstr nodejs24.6.0 +``` + +### 2. Test Extraction + +Before distributing, test that the archive extracts correctly: + +```bash +# Create test directory +mkdir test-extract +cd test-extract + +# Extract archive +7z x ../bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z + +# Verify structure +dir nodejs24.6.0 + +# Test Node.js +nodejs24.6.0\node.exe --version + +# Clean up +cd .. +rmdir /s /q test-extract +``` + +### 3. Verify Hash Files + +Always verify hash files are generated and correct: + +```bash +# Check hash file exists +dir bearsampp-build\bins\nodejs\2025.8.21\*.md5 + +# Verify hash matches +Get-FileHash bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z -Algorithm MD5 +type bearsampp-build\bins\nodejs\2025.8.21\bearsampp-nodejs-24.6.0-2025.8.21.7z.md5 +``` + +## Related Documentation + +- [Main Build Documentation](README.md) - Complete build system documentation +- [build.gradle](../build.gradle) - Build script implementation +- [build.properties](../build.properties) - Build configuration + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.8.21 diff --git a/.gradle-docs/README.md b/.gradle-docs/README.md new file mode 100644 index 00000000..9a8e97dc --- /dev/null +++ b/.gradle-docs/README.md @@ -0,0 +1,498 @@ +# Bearsampp Module Node.js - Gradle Build Documentation + +## Table of Contents + +- [Overview](#overview) +- [Quick Start](#quick-start) +- [Installation](#installation) +- [Build Tasks](#build-tasks) +- [Configuration](#configuration) +- [Architecture](#architecture) +- [Troubleshooting](#troubleshooting) +- [Additional Documentation](#additional-documentation) + +--- + +## Overview + +The Bearsampp Module Node.js project has been converted to a **pure Gradle build system**, replacing the legacy Ant build configuration. This provides: + +- **Modern Build System** - Native Gradle tasks and conventions +- **Better Performance** - Incremental builds and caching +- **Simplified Maintenance** - Pure Groovy/Gradle DSL +- **Enhanced Tooling** - IDE integration and dependency management +- **Cross-Platform Support** - Works on Windows, Linux, and macOS + +### Project Information + +| Property | Value | +|-------------------|------------------------------------------| +| **Project Name** | module-nodejs | +| **Group** | com.bearsampp.modules | +| **Type** | Node.js Module Builder | +| **Build Tool** | Gradle 8.x+ | +| **Language** | Groovy (Gradle DSL) | + +--- + +## Quick Start + +### Prerequisites + +| Requirement | Version | Purpose | +|-------------------|---------------|------------------------------------------| +| **Java** | 8+ | Required for Gradle execution | +| **Gradle** | 8.0+ | Build automation tool | +| **7-Zip** | Latest | Archive extraction and creation | + +### Basic Commands + +```bash +# Display build information +gradle info + +# List all available tasks +gradle tasks + +# Verify build environment +gradle verify + +# Build a release (interactive) +gradle release + +# Build a specific version (non-interactive) +gradle release -PbundleVersion=24.6.0 + +# Clean build artifacts +gradle clean +``` + +--- + +## Installation + +### 1. Clone the Repository + +```bash +git clone https://github.com/bearsampp/module-nodejs.git +cd module-nodejs +``` + +### 2. Verify Environment + +```bash +gradle verify +``` + +This will check: +- Java version (8+) +- Required files (build.properties) +- Directory structure (bin/) +- Build dependencies + +### 3. List Available Versions + +```bash +gradle listVersions +``` + +### 4. Build Your First Release + +```bash +# Interactive mode (prompts for version) +gradle release + +# Or specify version directly +gradle release -PbundleVersion=24.6.0 +``` + +--- + +## Build Tasks + +### Core Build Tasks + +| Task | Description | Example | +|-----------------------|--------------------------------------------------|------------------------------------------| +| `release` | Build and package release (interactive/non-interactive) | `gradle release -PbundleVersion=24.6.0` | +| `releaseAll` | Build all available versions in bin/ | `gradle releaseAll` | +| `clean` | Clean build artifacts and temporary files | `gradle clean` | + +### Verification Tasks + +| Task | Description | Example | +|---------------------------|----------------------------------------------|----------------------------------------------| +| `verify` | Verify build environment and dependencies | `gradle verify` | +| `validateProperties` | Validate build.properties configuration | `gradle validateProperties` | + +### Information Tasks + +| Task | Description | Example | +|---------------------|--------------------------------------------------|----------------------------| +| `info` | Display build configuration information | `gradle info` | +| `listVersions` | List available bundle versions in bin/ | `gradle listVersions` | +| `listReleases` | List all available releases from modules-untouched | `gradle listReleases` | +| `checkModulesUntouched` | Check modules-untouched integration | `gradle checkModulesUntouched` | + +### Task Groups + +| Group | Purpose | +|------------------|--------------------------------------------------| +| **build** | Build and package tasks | +| **verification** | Verification and validation tasks | +| **help** | Help and information tasks | + +--- + +## Configuration + +### build.properties + +The main configuration file for the build: + +```properties +bundle.name = nodejs +bundle.release = 2025.8.21 +bundle.type = bins +bundle.format = 7z +``` + +| Property | Description | Example Value | +|-------------------|--------------------------------------|----------------| +| `bundle.name` | Name of the bundle | `nodejs` | +| `bundle.release` | Release version | `2025.8.21` | +| `bundle.type` | Type of bundle | `bins` | +| `bundle.format` | Archive format | `7z` | + +### gradle.properties + +Gradle-specific configuration: + +```properties +# Gradle daemon configuration +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true + +# JVM settings +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m +``` + +### Directory Structure + +``` +module-nodejs/ +├── .gradle-docs/ # Gradle documentation +│ ├── README.md # Main documentation +│ └── PACKAGING.md # Packaging guide +├── bin/ # Node.js version bundles +│ ├── nodejs24.6.0/ +│ ├── nodejs22.11.0/ +│ ├── archived/ # Archived versions +│ └── ... +├── img/ # Images and assets +│ └── Bearsampp-logo.svg +├── build.gradle # Main Gradle build script +├── settings.gradle # Gradle settings +├── build.properties # Build configuration +├── releases.properties # Available Node.js releases +└── README.md # Main project README + +bearsampp-build/ # External build directory (outside repo) +├── tmp/ # Temporary build files +│ ├── bundles_prep/bins/nodejs/ # Prepared bundles +│ ├── bundles_build/bins/nodejs/ # Build staging +│ ├── downloads/nodejs/ # Downloaded dependencies +│ └── extract/nodejs/ # Extracted archives +└── bins/nodejs/ # Final packaged archives + └── 2025.8.21/ # Release version + ├── bearsampp-nodejs-24.6.0-2025.8.21.7z + ├── bearsampp-nodejs-24.6.0-2025.8.21.7z.md5 + └── ... +``` + +--- + +## Architecture + +### Build Process Flow + +``` +1. User runs: gradle release -PbundleVersion=24.6.0 + ↓ +2. Validate environment and version + ↓ +3. Check if Node.js binaries exist in bin/nodejs24.6.0/ + ↓ +4. If not found, download from modules-untouched + - Check nodejs.properties for version URL + - Fallback to standard URL format + - Extract to tmp/extract/ + ↓ +5. Create preparation directory (tmp/bundles_prep/) + ↓ +6. Copy Node.js files to prep directory + ↓ +7. Overlay any custom files from bin/nodejs24.6.0/ + ↓ +8. Copy to bundles_build directory + ↓ +9. Package prepared folder into archive + - Archive includes top-level folder: nodejs24.6.0/ + - Generate hash files (MD5, SHA1, SHA256, SHA512) + ↓ +10. Output to bearsampp-build/bins/nodejs/{bundle.release}/ +``` + +### Packaging Details + +- **Archive name format**: `bearsampp-nodejs-{version}-{bundle.release}.{7z|zip}` +- **Location**: `bearsampp-build/bins/nodejs/{bundle.release}/` + - Example: `bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z` +- **Content root**: The top-level folder inside the archive is `nodejs{version}/` (e.g., `nodejs24.6.0/`) +- **Structure**: The archive contains the Node.js version folder at the root with all files inside + +**Archive Structure Example**: +``` +bearsampp-nodejs-24.6.0-2025.8.21.7z +└── nodejs24.6.0/ ← Version folder at root + ├── node.exe + ├── npm + ├── npm.cmd + ├── npx + ├── npx.cmd + ├── node_modules/ + └── ... +``` + +**Verification Commands**: + +```bash +# List archive contents with 7z +7z l bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.7z | more + +# You should see entries beginning with: +# nodejs24.6.0/node.exe +# nodejs24.6.0/npm +# nodejs24.6.0/node_modules/ +# nodejs24.6.0/... + +# Extract and inspect with PowerShell (zip example) +Expand-Archive -Path bearsampp-build/bins/nodejs/2025.8.21/bearsampp-nodejs-24.6.0-2025.8.21.zip -DestinationPath .\_inspect +Get-ChildItem .\_inspect\nodejs24.6.0 | Select-Object Name + +# Expected output: +# node.exe +# npm +# npm.cmd +# node_modules/ +# ... +``` + +**Note**: This archive structure matches other Bearsampp modules (PHP, MySQL) where archives contain `{module}{version}/` at the root. This ensures consistency across all Bearsampp modules. + +**Hash Files**: Each archive is accompanied by hash sidecar files: +- `.md5` - MD5 checksum +- `.sha1` - SHA-1 checksum +- `.sha256` - SHA-256 checksum +- `.sha512` - SHA-512 checksum + +Example: +``` +bearsampp-build/bins/nodejs/2025.8.21/ +├── bearsampp-nodejs-24.6.0-2025.8.21.7z +├── bearsampp-nodejs-24.6.0-2025.8.21.7z.md5 +├── bearsampp-nodejs-24.6.0-2025.8.21.7z.sha1 +├── bearsampp-nodejs-24.6.0-2025.8.21.7z.sha256 +└── bearsampp-nodejs-24.6.0-2025.8.21.7z.sha512 +``` + +### Version Resolution Strategy + +The build system uses a two-tier strategy to locate Node.js binaries: + +1. **modules-untouched nodejs.properties** (Primary) + - URL: `https://github.com/Bearsampp/modules-untouched/blob/main/modules/nodejs.properties` + - Contains version-to-URL mappings + - Example: `24.6.0=https://github.com/Bearsampp/modules-untouched/releases/download/nodejs-24.6.0/nodejs-24.6.0-win-x64.7z` + +2. **Standard URL Format** (Fallback) + - Format: `https://github.com/Bearsampp/modules-untouched/releases/download/nodejs-{version}/nodejs-{version}-win-x64.7z` + - Used when version not found in nodejs.properties + - Assumes standard naming convention + +--- + +## Troubleshooting + +### Common Issues + +#### Issue: "Dev path not found" + +**Symptom:** +``` +Dev path not found: E:/Bearsampp-development/dev +``` + +**Solution:** +This is a warning only. The dev path is optional for most tasks. If you need it, ensure the `dev` project exists in the parent directory. + +--- + +#### Issue: "Bundle version not found" + +**Symptom:** +``` +Bundle version not found in bin/ or bin/archived/ +``` + +**Solution:** +1. List available versions: `gradle listVersions` +2. Use an existing version: `gradle release -PbundleVersion=24.6.0` +3. Or the build will automatically download from modules-untouched + +--- + +#### Issue: "Failed to download from modules-untouched" + +**Symptom:** +``` +Failed to download from modules-untouched: Connection refused +``` + +**Solution:** +1. Check internet connectivity +2. Verify version exists: `gradle listReleases` +3. Check modules-untouched repository is accessible +4. Try again later if repository is temporarily unavailable + +--- + +#### Issue: "Java version too old" + +**Symptom:** +``` +Java 8+ required +``` + +**Solution:** +1. Check Java version: `java -version` +2. Install Java 8 or higher +3. Update JAVA_HOME environment variable + +--- + +#### Issue: "7-Zip not found" + +**Symptom:** +``` +7-Zip not found. Please install 7-Zip or set 7Z_HOME environment variable. +``` + +**Solution:** +1. Install 7-Zip from https://www.7-zip.org/ +2. Or set `7Z_HOME` environment variable to 7-Zip installation directory +3. Or change `bundle.format` to `zip` in build.properties + +--- + +### Debug Mode + +Run Gradle with debug output: + +```bash +gradle release -PbundleVersion=24.6.0 --info +gradle release -PbundleVersion=24.6.0 --debug +``` + +### Clean Build + +If you encounter issues, try a clean build: + +```bash +gradle clean +gradle release -PbundleVersion=24.6.0 +``` + +--- + +## Migration Guide + +### From Ant to Gradle + +The project has been fully migrated from Ant to Gradle. Here's what changed: + +#### Removed Files + +| File | Status | Replacement | +|-------------------|-----------|----------------------------| +| `build.xml` | ✗ Removed | `build.gradle` | + +#### Command Mapping + +| Ant Command | Gradle Command | +|--------------------------------------|---------------------------------------------| +| `ant release` | `gradle release` | +| `ant release -Dinput.bundle=24.6.0` | `gradle release -PbundleVersion=24.6.0` | +| `ant clean` | `gradle clean` | + +#### Key Differences + +| Aspect | Ant | Gradle | +|---------------------|------------------------------|----------------------------------| +| **Build File** | XML (build.xml) | Groovy DSL (build.gradle) | +| **Task Definition** | `` | `tasks.register('...')` | +| **Properties** | `` | `ext { ... }` | +| **Dependencies** | Manual downloads | Automatic with repositories | +| **Caching** | None | Built-in incremental builds | +| **IDE Support** | Limited | Excellent (IntelliJ, Eclipse) | + +--- + +## Additional Documentation + +This documentation is part of a comprehensive guide for the Bearsampp Node.js module build system: + +### Documentation Files + +| Document | Description | +|---------------------------|--------------------------------------------------| +| [README.md](README.md) | Main build documentation (this file) | +| [PACKAGING.md](PACKAGING.md) | Packaging and archive structure guide | +| [MIGRATION.md](MIGRATION.md) | Migration guide from Ant to Gradle | + +### Quick Links + +- **Main Project README**: [../README.md](../README.md) +- **Build Script**: [../build.gradle](../build.gradle) +- **Build Configuration**: [../build.properties](../build.properties) +- **Gradle Settings**: [../settings.gradle](../settings.gradle) + +### External Resources + +- [Gradle Documentation](https://docs.gradle.org/) +- [Bearsampp Project](https://github.com/bearsampp/bearsampp) +- [Node.js Downloads](https://nodejs.org/en/download/) +- [modules-untouched Repository](https://github.com/Bearsampp/modules-untouched) + +--- + +## Support + +For issues and questions: + +- **GitHub Issues**: https://github.com/bearsampp/module-nodejs/issues +- **Bearsampp Issues**: https://github.com/bearsampp/bearsampp/issues +- **Documentation**: https://bearsampp.com/module/nodejs + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.8.21 +**Build System**: Pure Gradle (no wrapper, no Ant) + +### Notes + +- This project deliberately does not ship the Gradle Wrapper. Install Gradle 8+ locally and run with `gradle ...`. +- Legacy Ant files have been removed. The project now uses pure Gradle for all build operations. +- See [MIGRATION.md](MIGRATION.md) for details on the Ant to Gradle migration. diff --git a/README.md b/README.md index 3015d9b7..0ec08f1f 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,65 @@ This is a module of [Bearsampp project](https://github.com/bearsampp/bearsampp) involving Node.js. -## Documentation and downloads +## Build System -https://bearsampp.com/module/nodejs +This project uses **Gradle** as its build system. The legacy Ant build has been fully replaced with a modern, pure Gradle implementation. + +### Quick Start + +```bash +# Display build information +gradle info + +# List all available tasks +gradle tasks + +# Verify build environment +gradle verify + +# Build a release (interactive) +gradle release + +# Build a specific version (non-interactive) +gradle release -PbundleVersion=24.6.0 + +# Clean build artifacts +gradle clean +``` + +### Prerequisites + +| Requirement | Version | Purpose | +|-------------------|---------------|------------------------------------------| +| **Java** | 8+ | Required for Gradle execution | +| **Gradle** | 8.0+ | Build automation tool | +| **7-Zip** | Latest | Archive creation (optional for zip) | + +### Available Tasks + +| Task | Description | +|-----------------------|--------------------------------------------------| +| `release` | Build release package (interactive/non-interactive) | +| `releaseAll` | Build all versions in bin/ directory | +| `clean` | Clean build artifacts and temporary files | +| `verify` | Verify build environment and dependencies | +| `info` | Display build configuration information | +| `listVersions` | List available bundle versions in bin/ | +| `listReleases` | List releases from modules-untouched | +| `validateProperties` | Validate build.properties configuration | +| `checkModulesUntouched` | Check modules-untouched integration | + +For complete documentation, see [.gradle-docs/README.md](.gradle-docs/README.md) + +## Documentation + +- **Build Documentation**: [.gradle-docs/README.md](.gradle-docs/README.md) +- **Module Downloads**: https://bearsampp.com/module/nodejs ## Issues Issues must be reported on [Bearsampp repository](https://github.com/bearsampp/bearsampp/issues). + +## Statistics + +![Alt](https://repobeats.axiom.co/api/embed/YOUR_EMBED_ID_HERE.svg "Repobeats analytics image") diff --git a/bin/nodejs22.17.0/bearsampp.conf b/bin/archived/nodejs22.17.0/bearsampp.conf similarity index 100% rename from bin/nodejs22.17.0/bearsampp.conf rename to bin/archived/nodejs22.17.0/bearsampp.conf diff --git a/bin/nodejs22.17.0/etc/npmrc b/bin/archived/nodejs22.17.0/etc/npmrc similarity index 100% rename from bin/nodejs22.17.0/etc/npmrc rename to bin/archived/nodejs22.17.0/etc/npmrc diff --git a/bin/nodejs22.17.0/etc/npmrc.ber b/bin/archived/nodejs22.17.0/etc/npmrc.ber similarity index 100% rename from bin/nodejs22.17.0/etc/npmrc.ber rename to bin/archived/nodejs22.17.0/etc/npmrc.ber diff --git a/bin/nodejs22.17.0/launch.bat b/bin/archived/nodejs22.17.0/launch.bat similarity index 100% rename from bin/nodejs22.17.0/launch.bat rename to bin/archived/nodejs22.17.0/launch.bat diff --git a/bin/nodejs22.17.0/node_modules/npm/npmrc b/bin/archived/nodejs22.17.0/node_modules/npm/npmrc similarity index 100% rename from bin/nodejs22.17.0/node_modules/npm/npmrc rename to bin/archived/nodejs22.17.0/node_modules/npm/npmrc diff --git a/bin/nodejs22.17.0/node_modules/npm/npmrc.ber b/bin/archived/nodejs22.17.0/node_modules/npm/npmrc.ber similarity index 100% rename from bin/nodejs22.17.0/node_modules/npm/npmrc.ber rename to bin/archived/nodejs22.17.0/node_modules/npm/npmrc.ber diff --git a/bin/nodejs22.18.0/bearsampp.conf b/bin/archived/nodejs22.18.0/bearsampp.conf similarity index 100% rename from bin/nodejs22.18.0/bearsampp.conf rename to bin/archived/nodejs22.18.0/bearsampp.conf diff --git a/bin/nodejs22.18.0/etc/npmrc b/bin/archived/nodejs22.18.0/etc/npmrc similarity index 100% rename from bin/nodejs22.18.0/etc/npmrc rename to bin/archived/nodejs22.18.0/etc/npmrc diff --git a/bin/nodejs22.18.0/etc/npmrc.ber b/bin/archived/nodejs22.18.0/etc/npmrc.ber similarity index 100% rename from bin/nodejs22.18.0/etc/npmrc.ber rename to bin/archived/nodejs22.18.0/etc/npmrc.ber diff --git a/bin/nodejs22.18.0/launch.bat b/bin/archived/nodejs22.18.0/launch.bat similarity index 100% rename from bin/nodejs22.18.0/launch.bat rename to bin/archived/nodejs22.18.0/launch.bat diff --git a/bin/nodejs22.18.0/node_modules/npm/npmrc b/bin/archived/nodejs22.18.0/node_modules/npm/npmrc similarity index 100% rename from bin/nodejs22.18.0/node_modules/npm/npmrc rename to bin/archived/nodejs22.18.0/node_modules/npm/npmrc diff --git a/bin/nodejs22.18.0/node_modules/npm/npmrc.ber b/bin/archived/nodejs22.18.0/node_modules/npm/npmrc.ber similarity index 100% rename from bin/nodejs22.18.0/node_modules/npm/npmrc.ber rename to bin/archived/nodejs22.18.0/node_modules/npm/npmrc.ber diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..a9b92e0e --- /dev/null +++ b/build.gradle @@ -0,0 +1,955 @@ +/* + * Bearsampp Module Node.js - Gradle Build (Bruno-style) + * + * This is a 100% Gradle build configuration for the Node.js module. + * It handles downloading, extracting, and packaging Node.js releases. + * + * VERSION RESOLUTION STRATEGY: + * 1. Remote modules-untouched nodejs.properties (primary source) + * URL: https://github.com/Bearsampp/modules-untouched/blob/main/modules/nodejs.properties + * 2. Standard URL format construction (fallback) + * + * DOCUMENTATION: + * All build documentation is located in /.gradle-docs/ + * See /.gradle-docs/README.md for complete documentation index + * + * Usage: + * gradle tasks - List all available tasks + * gradle release -PbundleVersion=24.6.0 - Build release for specific version + * gradle releaseAll - Build all versions + * gradle clean - Clean build artifacts + * gradle info - Display build information + * gradle verify - Verify build environment + * gradle listVersions - List available versions + * gradle listReleases - List releases from modules-untouched + * gradle checkModulesUntouched - Check modules-untouched integration + */ + +plugins { + id 'base' +} + +// ============================================================================ +// PROJECT CONFIGURATION +// ============================================================================ + +// Load build properties +def buildProps = new Properties() +file('build.properties').withInputStream { buildProps.load(it) } + +// Project information +group = 'com.bearsampp.modules' +version = buildProps.getProperty('bundle.release', '1.0.0') +description = "Bearsampp Module - ${buildProps.getProperty('bundle.name', 'nodejs')}" + +// Define project paths +ext { + projectBasedir = projectDir.absolutePath + rootDir = projectDir.parent + devPath = file("${rootDir}/dev").absolutePath + + // Bundle properties from build.properties + bundleName = buildProps.getProperty('bundle.name', 'nodejs') + bundleRelease = buildProps.getProperty('bundle.release', '1.0.0') + bundleType = buildProps.getProperty('bundle.type', 'bins') + bundleFormat = buildProps.getProperty('bundle.format', '7z') + + // Build paths - with configurable base path + def buildPathFromProps = buildProps.getProperty('build.path', '').trim() + def buildPathFromEnv = System.getenv('BEARSAMPP_BUILD_PATH') ?: '' + def defaultBuildPath = "${rootDir}/bearsampp-build" + + buildBasePath = buildPathFromProps ?: (buildPathFromEnv ?: defaultBuildPath) + + // Shared bearsampp-build/tmp structure + buildTmpPath = file("${buildBasePath}/tmp").absolutePath + bundleTmpBuildPath = file("${buildTmpPath}/bundles_build/${bundleType}/${bundleName}").absolutePath + bundleTmpPrepPath = file("${buildTmpPath}/bundles_prep/${bundleType}/${bundleName}").absolutePath + bundleTmpSrcPath = file("${buildTmpPath}/bundles_src").absolutePath + + // Download and extract paths + bundleTmpDownloadPath = file("${buildTmpPath}/downloads/${bundleName}").absolutePath + bundleTmpExtractPath = file("${buildTmpPath}/extract/${bundleName}").absolutePath +} + +// Align with Bruno: place Gradle's own buildDir under the shared bearsampp-build tmp tree +// This prevents creating a local ./build folder in the repository +buildDir = file("${buildBasePath}/tmp/gradle/${bundleName}") + +// Verify dev path exists +if (!file(ext.devPath).exists()) { + throw new GradleException("Dev path not found: ${ext.devPath}. Please ensure the 'dev' project exists in ${ext.rootDir}") +} + +// Repositories +repositories { + mavenCentral() +} + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +// Function to fetch nodejs.properties from modules-untouched repository +def fetchModulesUntouchedProperties() { + def propsUrl = "https://raw.githubusercontent.com/Bearsampp/modules-untouched/main/modules/nodejs.properties" + + println "Fetching nodejs.properties from modules-untouched repository..." + println " URL: ${propsUrl}" + + def tempFile = file("${bundleTmpDownloadPath}/nodejs-untouched.properties") + tempFile.parentFile.mkdirs() + + try { + new URL(propsUrl).withInputStream { input -> + tempFile.withOutputStream { output -> + output << input + } + } + + def props = new Properties() + tempFile.withInputStream { props.load(it) } + + println " ✓ Successfully loaded ${props.size()} versions from modules-untouched" + return props + } catch (Exception e) { + println " ✗ Warning: Could not fetch nodejs.properties from modules-untouched: ${e.message}" + println " Will fall back to standard URL format if needed" + return null + } +} + +// Function to download from modules-untouched repository (or fallback) +def downloadFromModulesUntouched(String version, File destDir) { + println "Checking modules-untouched repository..." + + def untouchedProps = fetchModulesUntouchedProperties() + def untouchedUrl = null + + if (untouchedProps) { + untouchedUrl = untouchedProps.getProperty(version) + if (untouchedUrl) { + println "Found version ${version} in modules-untouched nodejs.properties" + println "Downloading from:" + println " ${untouchedUrl}" + } else { + println "Version ${version} not found in modules-untouched nodejs.properties" + println "Attempting to construct URL based on standard format..." + // Windows x64 standard URL (fallback) — aligns with Bearsampp packaging + untouchedUrl = "https://github.com/Bearsampp/modules-untouched/releases/download/nodejs-${version}/nodejs-${version}-win-x64.7z" + println " ${untouchedUrl}" + } + } else { + println "Could not fetch nodejs.properties, using standard URL format..." + untouchedUrl = "https://github.com/Bearsampp/modules-untouched/releases/download/nodejs-${version}/nodejs-${version}-win-x64.7z" + println " ${untouchedUrl}" + } + + // Determine filename and ensure download dir + def filename = untouchedUrl.substring(untouchedUrl.lastIndexOf('/') + 1) + def downloadDir = file(bundleTmpDownloadPath) + downloadDir.mkdirs() + + def downloadedFile = file("${downloadDir}/${filename}") + + if (!downloadedFile.exists()) { + println " Downloading to: ${downloadedFile}" + try { + new URL(untouchedUrl).withInputStream { input -> + downloadedFile.withOutputStream { output -> + def buffer = new byte[8192] + def bytesRead + while ((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead) + } + } + } + println " Download complete from modules-untouched" + } catch (Exception e) { + throw new GradleException(""" + Failed to download from modules-untouched: ${e.message} + + Tried URL: ${untouchedUrl} + + Please verify: + 1. Version ${version} exists in modules-untouched repository + 2. The URL is correct in nodejs.properties or matches format: nodejs-{version}/nodejs-{version}-win-x64.7z + 3. You have internet connectivity + """.stripIndent()) + } + } else { + println " Using cached file: ${downloadedFile}" + } + + return downloadedFile +} + +// Download and extract Node.js binaries +def downloadAndExtractNode(String version, File destDir) { + def downloadedFile = downloadFromModulesUntouched(version, destDir) + + def extractDir = file(bundleTmpExtractPath) + extractDir.mkdirs() + println " Extracting archive..." + def extractPath = file("${extractDir}/${version}") + if (extractPath.exists()) { delete extractPath } + extractPath.mkdirs() + + def filename = downloadedFile.name + + if (filename.endsWith('.7z')) { + def sevenZipPath = find7ZipExecutable() + if (sevenZipPath) { + def command = [ + sevenZipPath.toString(), + 'x', + downloadedFile.absolutePath.toString(), + "-o${extractPath.absolutePath}".toString(), + '-y' + ] + def process = new ProcessBuilder(command as String[]) + .directory(extractPath) + .redirectErrorStream(true) + .start() + + process.inputStream.eachLine { line -> if (line.trim()) println " ${line}" } + def exitCode = process.waitFor() + if (exitCode != 0) { + throw new GradleException("7zip extraction failed with exit code: ${exitCode}") + } + } else { + throw new GradleException("7zip not found. Please install 7zip or extract manually.") + } + } else if (filename.endsWith('.zip')) { + copy { from zipTree(downloadedFile); into extractPath } + } else { + throw new GradleException("Unsupported archive format: ${filename}") + } + + println " Extraction complete" + + def nodeDir = findNodeDirectory(extractPath) + if (!nodeDir) { + throw new GradleException("Could not find Node.js directory in extracted files") + } + + println " Found Node.js directory: ${nodeDir.name}" + println "" + println "NOTE: Version ${version} was sourced from modules-untouched (or fallback URL)." + + return nodeDir +} + +// Find Node.js directory (looks for node.exe) +def findNodeDirectory(File extractPath) { + def hasNodeExe = { File dir -> + new File(dir, 'node.exe').exists() || new File(dir, 'Node.exe').exists() + } + + if (!extractPath?.exists()) { return null } + if (hasNodeExe(extractPath)) { return extractPath } + + File found = null + def stack = new ArrayDeque() + extractPath.listFiles()?.findAll { it.isDirectory() }?.each { stack.push(it) } + while (!stack.isEmpty() && found == null) { + def dir = stack.pop() + if (hasNodeExe(dir)) { found = dir; break } + dir.listFiles()?.findAll { it.isDirectory() }?.each { stack.push(it) } + } + return found +} + +// ============================================================================ +// GRADLE TASKS +// ============================================================================ + +// Info task +tasks.register('info') { + group = 'help' + description = 'Display build configuration information' + + def projectName = project.name + def projectVersion = project.version + def projectDescription = project.description + def projectBasedirValue = projectBasedir + def rootDirValue = rootDir + def devPathValue = devPath + def bundleNameValue = bundleName + def bundleReleaseValue = bundleRelease + def bundleTypeValue = bundleType + def bundleFormatValue = bundleFormat + def buildBasePathValue = buildBasePath + def buildTmpPathValue = buildTmpPath + def bundleTmpPrepPathValue = bundleTmpPrepPath + def bundleTmpBuildPathValue = bundleTmpBuildPath + def bundleTmpSrcPathValue = bundleTmpSrcPath + def bundleTmpDownloadPathValue = bundleTmpDownloadPath + def bundleTmpExtractPathValue = bundleTmpExtractPath + def javaVersion = JavaVersion.current() + def javaHome = System.getProperty('java.home') + def gradleVersion = gradle.gradleVersion + def gradleHome = gradle.gradleHomeDir + + doLast { + println """ + ================================================================ + Bearsampp Module Node.js - Build Info + ================================================================ + + Project: ${projectName} + Version: ${projectVersion} + Description: ${projectDescription} + + Bundle Properties: + Name: ${bundleNameValue} + Release: ${bundleReleaseValue} + Type: ${bundleTypeValue} + Format: ${bundleFormatValue} + + Paths: + Project Dir: ${projectBasedirValue} + Root Dir: ${rootDirValue} + Dev Path: ${devPathValue} + Build Base: ${buildBasePathValue} + Build Tmp: ${buildTmpPathValue} + Tmp Prep: ${bundleTmpPrepPathValue} + Tmp Build: ${bundleTmpBuildPathValue} + Tmp Src: ${bundleTmpSrcPathValue} + Tmp Download: ${bundleTmpDownloadPathValue} + Tmp Extract: ${bundleTmpExtractPathValue} + + Java: + Version: ${javaVersion} + Home: ${javaHome} + + Gradle: + Version: ${gradleVersion} + Home: ${gradleHome} + + Available Task Groups: + * build - Build and package tasks + * help - Help and information tasks + * verification - Verification tasks + + Quick Start: + gradle tasks - List all available tasks + gradle info - Show this information + gradle release -PbundleVersion=24.6.0 - Build specific version + gradle releaseAll - Build all versions + gradle clean - Clean build artifacts + gradle verify - Verify build environment + """.stripIndent() + } +} + +// Main release task - build a specific version +tasks.register('release') { + group = 'build' + description = 'Build release package for a specific version (use -PbundleVersion=X.X.X or run interactively)' + + def versionProperty = project.findProperty('bundleVersion') + + doLast { + def versionToBuild = versionProperty + + if (!versionToBuild) { + // Interactive prompt + def availableVersions = getAvailableVersions() + if (availableVersions.isEmpty()) { + throw new GradleException("No versions found in bin/ directory") + } + + println "" + println "=".multiply(70) + println "Interactive Release Mode" + println "=".multiply(70) + println "" + println "Available versions:" + + def binDir = file("${projectDir}/bin") + def archivedDir = file("${projectDir}/bin/archived") + + availableVersions.eachWithIndex { version, index -> + def location = "" + if (binDir.exists() && file("${binDir}/${bundleName}${version}").exists()) { + location = "[bin]" + } else if (archivedDir.exists() && file("${archivedDir}/${bundleName}${version}").exists()) { + location = "[bin/archived]" + } + println " ${(index + 1).toString().padLeft(2)}. ${version.padRight(15)} ${location}" + } + println "" + println "Enter version to build (index or version string):" + println "" + + def input = null + try { + def reader = new BufferedReader(new InputStreamReader(System.in)) + input = reader.readLine() + } catch (Exception e) { + throw new GradleException(""" + Failed to read input. Please use non-interactive mode: + gradle release -PbundleVersion=X.X.X + + Available versions: ${availableVersions.join(', ')} + """.stripIndent()) + } + + if (!input || input.trim().isEmpty()) { + throw new GradleException(""" + No version selected. Please use non-interactive mode: + gradle release -PbundleVersion=X.X.X + + Available versions: ${availableVersions.join(', ')} + """.stripIndent()) + } + + def cleaned = input.trim() + if (cleaned.isInteger()) { + def idx = cleaned.toInteger() + if (idx < 1 || idx > availableVersions.size()) { + throw new GradleException(""" + Invalid selection index: ${cleaned} + + Please choose a number between 1 and ${availableVersions.size()} or enter a version string. + """.stripIndent()) + } + versionToBuild = availableVersions[idx - 1] + } else { + versionToBuild = cleaned + if (!availableVersions.contains(versionToBuild)) { + throw new GradleException(""" + Invalid version: ${versionToBuild} + + Please choose from available versions: + ${availableVersions.collect { " - ${it}" }.join('\n')} + """.stripIndent()) + } + } + + println "" + println "Selected version: ${versionToBuild}" + } + + println "" + println "=".multiply(70) + println "Building ${bundleName} ${versionToBuild}" + println "=".multiply(70) + println "" + + // Validate version exists in bin or archived + def bundlePath = file("${projectDir}/bin/${bundleName}${versionToBuild}") + if (!bundlePath.exists()) { + bundlePath = file("${projectDir}/bin/archived/${bundleName}${versionToBuild}") + if (!bundlePath.exists()) { + throw new GradleException("Bundle version not found in bin/ or bin/archived/\n\nAvailable versions:\n${getAvailableVersions().collect { " - ${it}" }.join('\n')}") + } + } + + println "Bundle path: ${bundlePath}" + println "" + + def bundleFolder = bundlePath.name + def bundleVersion = bundleFolder.replace(bundleName, '') + + def bundleSrcDest = bundlePath + def bundleSrcFinal = bundleSrcDest + + def nodeExe = file("${bundleSrcFinal}/node.exe") + if (!nodeExe.exists()) { + // Try cached extract first + def tmpExtractPath = file("${bundleTmpExtractPath}/${bundleVersion}") + def tmpNodeDir = findNodeDirectory(tmpExtractPath) + + if (tmpNodeDir && tmpNodeDir.exists()) { + println "Using cached Node.js binaries from bearsampp-build/tmp" + bundleSrcFinal = tmpNodeDir + } else { + println "" + println "Node.js binaries not found" + println "Downloading Node.js ${bundleVersion}..." + println "" + + try { + bundleSrcFinal = downloadAndExtractNode(bundleVersion, file(bundleTmpExtractPath)) + } catch (Exception e) { + throw new GradleException(""" + Failed to download Node.js binaries: ${e.message} + + You can manually download and extract Node.js binaries to: + ${bundleSrcDest}/ + + Or check that version ${bundleVersion} exists in modules-untouched nodejs.properties + """.stripIndent()) + } + } + } + + nodeExe = file("${bundleSrcFinal}/node.exe") + if (!nodeExe.exists()) { + throw new GradleException("node.exe not found at ${nodeExe}") + } + + println "Source folder: ${bundleSrcFinal}" + println "" + + // Prep directory + def prepPath = file("${bundleTmpPrepPath}/${bundleName}${bundleVersion}") + if (prepPath.exists()) { delete prepPath } + prepPath.mkdirs() + + // Copy Node.js files from extracted/downloaded location + println "Copying Node.js files..." + copy { from bundleSrcFinal; into prepPath } + + // Overlay files from bin bundle directory + println "Overlaying bundle files from bin directory..." + copy { from bundleSrcDest; into prepPath } + + println "" + println "Copying to bundles_build directory..." + def buildPathOut = file("${bundleTmpBuildPath}/${bundleName}${bundleVersion}") + if (buildPathOut.exists()) { delete buildPathOut } + buildPathOut.mkdirs() + copy { from prepPath; into buildPathOut } + println "Non-zip version available at: ${buildPathOut}" + + println "" + println "Preparing archive..." + + // Output path bearsampp-build/{type}/{name}/{release} + def buildPath = file(buildBasePath) + def buildBinsPath = file("${buildPath}/${bundleType}/${bundleName}/${bundleRelease}") + buildBinsPath.mkdirs() + + def destFile = file("${buildBinsPath}/bearsampp-${bundleName}-${bundleVersion}-${bundleRelease}") + + if (bundleFormat == '7z') { + def archiveFile = file("${destFile}.7z") + if (archiveFile.exists()) { delete archiveFile } + + println "Compressing (including top-level folder) ${bundleName}${bundleVersion} to ${archiveFile.name}..." + + def sevenZipExe = find7ZipExecutable() + if (!sevenZipExe) { throw new GradleException("7-Zip not found. Please install 7-Zip or set 7Z_HOME environment variable.") } + println "Using 7-Zip: ${sevenZipExe}" + + // IMPORTANT: run from the parent directory and add the folder name explicitly + def parentDir = prepPath.parentFile + def folderName = prepPath.name + def command = [ sevenZipExe, 'a', '-t7z', archiveFile.absolutePath.toString(), folderName ] + def process = new ProcessBuilder(command as String[]) + .directory(parentDir) + .redirectErrorStream(true) + .start() + process.inputStream.eachLine { line -> if (line.trim()) println " ${line}" } + def exitCode = process.waitFor() + if (exitCode != 0) { throw new GradleException("7zip compression failed with exit code: ${exitCode}") } + + println "Archive created: ${archiveFile}" + println "Generating hash files..." + generateHashFiles(archiveFile) + } else { + def archiveFile = file("${destFile}.zip") + if (archiveFile.exists()) { delete archiveFile } + + println "Compressing (including top-level folder) ${bundleName}${bundleVersion} to ${archiveFile.name}..." + // Use Zip task but include the folder itself so the archive contains nodejs{version}/... + task("zipArchive_${bundleVersion}", type: Zip) { + from(prepPath.parentFile) { + include "${prepPath.name}/**" + } + destinationDirectory = archiveFile.parentFile + archiveFileName = archiveFile.name + }.execute() + + println "Archive created: ${archiveFile}" + println "Generating hash files..." + generateHashFiles(archiveFile) + } + + println "" + println "=".multiply(70) + println "[SUCCESS] Release build completed successfully for version ${versionToBuild}" + println "Output directory: ${buildPathOut}" + println "Archive: ${destFile}.${bundleFormat}" + println "=".multiply(70) + } +} + +// Find 7-Zip executable +def find7ZipExecutable() { + def sevenZipHome = System.getenv('7Z_HOME') + if (sevenZipHome) { + def exe = file("${sevenZipHome}/7z.exe") + if (exe.exists()) { return exe.absolutePath } + } + def commonPaths = [ + 'C:/Program Files/7-Zip/7z.exe', + 'C:/Program Files (x86)/7-Zip/7z.exe', + 'D:/Program Files/7-Zip/7z.exe', + 'D:/Program Files (x86)/7-Zip/7z.exe' + ] + for (path in commonPaths) { + def exe = file(path) + if (exe.exists()) { return exe.absolutePath } + } + try { + def process = ['where', '7z.exe'].execute() + process.waitFor() + if (process.exitValue() == 0) { + def output = process.text.trim() + if (output) { return output.split('\n')[0].trim() } + } + } catch (Exception e) { } + return null +} + +// Generate hash files for an archive +def generateHashFiles(File file) { + if (!file.exists()) { throw new GradleException("File not found for hashing: ${file}") } + + def md5File = new File("${file.absolutePath}.md5") + def md5Hash = calculateHash(file, 'MD5') + md5File.text = "${md5Hash} ${file.name}\n" + println " Created: ${md5File.name}" + + def sha1File = new File("${file.absolutePath}.sha1") + def sha1Hash = calculateHash(file, 'SHA-1') + sha1File.text = "${sha1Hash} ${file.name}\n" + println " Created: ${sha1File.name}" + + def sha256File = new File("${file.absolutePath}.sha256") + def sha256Hash = calculateHash(file, 'SHA-256') + sha256File.text = "${sha256Hash} ${file.name}\n" + println " Created: ${sha256File.name}" + + def sha512File = new File("${file.absolutePath}.sha512") + def sha512Hash = calculateHash(file, 'SHA-512') + sha512File.text = "${sha512Hash} ${file.name}\n" + println " Created: ${sha512File.name}" +} + +// Calculate hash +def calculateHash(File file, String algorithm) { + def digest = java.security.MessageDigest.getInstance(algorithm) + file.withInputStream { stream -> + def buffer = new byte[8192] + def bytesRead + while ((bytesRead = stream.read(buffer)) != -1) { + digest.update(buffer, 0, bytesRead) + } + } + return digest.digest().collect { String.format('%02x', it) }.join('') +} + +// Get available versions from bin and bin/archived +def getAvailableVersions() { + def versions = [] + + def binDir = file("${projectDir}/bin") + if (binDir.exists()) { + def binVersions = binDir.listFiles() + ?.findAll { it.isDirectory() && it.name.startsWith(bundleName) && it.name != 'archived' } + ?.collect { it.name.replace(bundleName, '') } ?: [] + versions.addAll(binVersions) + } + + def archivedDir = file("${projectDir}/bin/archived") + if (archivedDir.exists()) { + def archivedVersions = archivedDir.listFiles() + ?.findAll { it.isDirectory() && it.name.startsWith(bundleName) } + ?.collect { it.name.replace(bundleName, '') } ?: [] + versions.addAll(archivedVersions) + } + + return versions.unique().sort() +} + +// Build all available versions (prep only) +tasks.register('releaseAll') { + group = 'build' + description = 'Build release packages for all available versions in bin/ directory' + + doLast { + def binDir = file("${projectDir}/bin") + if (!binDir.exists()) { throw new GradleException("bin/ directory not found") } + + def versions = getAvailableVersions() + if (versions.isEmpty()) { throw new GradleException("No versions found in bin/ directory") } + + println "" + println "=".multiply(70) + println "Building releases for ${versions.size()} ${bundleName} versions" + println "=".multiply(70) + println "" + + def successCount = 0 + def failedVersions = [] + + versions.each { version -> + println "=".multiply(70) + println "[${successCount + 1}/${versions.size()}] Building ${bundleName} ${version}..." + println "=".multiply(70) + + try { + def bundlePath = file("${projectDir}/bin/${bundleName}${version}") + if (!bundlePath.exists()) { throw new GradleException("Bundle path not found: ${bundlePath}") } + + println "Bundle path: ${bundlePath}" + println "" + + def bundleFolder = bundlePath.name + def bundleVersion = bundleFolder.replace(bundleName, '') + def bundleSrcDest = bundlePath + + def nodeExe = file("${bundleSrcDest}/node.exe") + if (!nodeExe.exists()) { throw new GradleException("node.exe not found at ${nodeExe}") } + + println "Source folder: ${bundleSrcDest}" + println "" + + def prepPath = file("${bundleTmpPrepPath}/${bundleName}${bundleVersion}") + if (prepPath.exists()) { delete prepPath } + prepPath.mkdirs() + + println "Copying Node.js files..." + copy { from bundleSrcDest; into prepPath } + + println "" + println "[SUCCESS] ${bundleName} ${version} completed" + println "Output: ${prepPath}" + successCount++ + + } catch (Exception e) { + println "" + println "[FAILED] ${bundleName} ${version}: ${e.message}" + failedVersions.add(version) + } + + println "" + } + + println "=".multiply(70) + println "Build Summary" + println "=".multiply(70) + println "Total versions: ${versions.size()}" + println "Successful: ${successCount}" + println "Failed: ${failedVersions.size()}" + + if (!failedVersions.isEmpty()) { + println "" + println "Failed versions:" + failedVersions.each { v -> println " - ${v}" } + } + + println "=".multiply(70) + + if (failedVersions.isEmpty()) { + println "[SUCCESS] All versions built successfully!" + } else { + throw new GradleException("${failedVersions.size()} version(s) failed to build") + } + } +} + +// Clean task enhancement (only Gradle artifacts for this script) +tasks.named('clean') { + group = 'build' + description = 'Clean build artifacts and temporary files' + doLast { + def buildDir = file("${projectDir}/build") + if (buildDir.exists()) { delete buildDir } + println "[SUCCESS] Build artifacts cleaned" + } +} + +// Verify environment +tasks.register('verify') { + group = 'verification' + description = 'Verify build environment and dependencies' + doLast { + println "Verifying build environment for module-nodejs..." + def checks = [:] + def javaVersion = JavaVersion.current() + checks['Java 8+'] = javaVersion >= JavaVersion.VERSION_1_8 + checks['build.properties'] = file('build.properties').exists() + checks['dev directory'] = file(devPath).exists() + checks['bin directory'] = file("${projectDir}/bin").exists() + if (bundleFormat == '7z') { checks['7-Zip'] = find7ZipExecutable() != null } + + println "\nEnvironment Check Results:" + println "-".multiply(60) + checks.each { name, passed -> + def status = passed ? "[PASS]" : "[FAIL]" + println " ${status.padRight(10)} ${name}" + } + println "-".multiply(60) + + def allPassed = checks.values().every { it } + if (allPassed) { + println "\n[SUCCESS] All checks passed! Build environment is ready." + println "\nYou can now run:" + println " gradle release -PbundleVersion=24.6.0 - Build release for version" + println " gradle listVersions - List available versions" + } else { + println "\n[WARNING] Some checks failed. Please review the requirements." + throw new GradleException("Build environment verification failed") + } + } +} + +// List releases from modules-untouched +tasks.register('listReleases') { + group = 'help' + description = 'List all available releases from modules-untouched nodejs.properties' + doLast { + def props = fetchModulesUntouchedProperties() + if (!props) { + println "\n[WARNING] Could not fetch modules-untouched nodejs.properties." + println "No release information available." + return + } + println "\nAvailable Node.js Releases (modules-untouched):" + println "-".multiply(80) + props.sort { a, b -> a.key <=> b.key }.each { version, url -> println " ${version.padRight(10)} -> ${url}" } + println "-".multiply(80) + println "Total releases: ${props.size()}" + } +} + +// List versions available locally +tasks.register('listVersions') { + group = 'help' + description = 'List all available bundle versions in bin/ and bin/archived/ directories' + doLast { + def versions = getAvailableVersions() + if (versions.isEmpty()) { println "\nNo versions found in bin/ or bin/archived/ directories"; return } + + println "\nAvailable ${bundleName} versions:" + println "-".multiply(60) + + def binDir = file("${projectDir}/bin") + def archivedDir = file("${projectDir}/bin/archived") + + versions.each { version -> + def location = "" + if (binDir.exists() && file("${binDir}/${bundleName}${version}").exists()) { location = "[bin]" } + else if (archivedDir.exists() && file("${archivedDir}/${bundleName}${version}").exists()) { location = "[bin/archived]" } + println " ${version.padRight(15)} ${location}" + } + println "-".multiply(60) + println "Total versions: ${versions.size()}" + if (!versions.isEmpty()) { + println "\nTo build a specific version:" + println " gradle release -PbundleVersion=${versions.last()}" + } + } +} + +// Validate build.properties +tasks.register('validateProperties') { + group = 'verification' + description = 'Validate build.properties configuration' + doLast { + println "Validating build.properties..." + def required = ['bundle.name', 'bundle.release', 'bundle.type', 'bundle.format'] + def missing = [] + required.each { prop -> if (!buildProps.containsKey(prop) || buildProps.getProperty(prop).trim().isEmpty()) { missing.add(prop) } } + if (missing.isEmpty()) { + println "[SUCCESS] All required properties are present:" + required.each { prop -> println " ${prop} = ${buildProps.getProperty(prop)}" } + } else { + println "[ERROR] Missing required properties:" + missing.each { prop -> println " - ${prop}" } + throw new GradleException("build.properties validation failed") + } + } +} + +// Check modules-untouched integration +tasks.register('checkModulesUntouched') { + group = 'verification' + description = 'Check modules-untouched repository integration and available versions' + doLast { + println "" + println "=".multiply(70) + println "Modules-Untouched Integration Check" + println "=".multiply(70) + println "" + + def propsUrl = "https://raw.githubusercontent.com/Bearsampp/modules-untouched/main/modules/nodejs.properties" + println "Repository URL:" + println " ${propsUrl}" + println "" + + println "Fetching nodejs.properties from modules-untouched..." + def untouchedProps = fetchModulesUntouchedProperties() + + if (untouchedProps) { + println "" + println "=".multiply(70) + println "Available Versions in modules-untouched" + println "=".multiply(70) + + def sortedVersions = untouchedProps.sort { a, b -> + def aParts = a.key.tokenize('.') + def bParts = b.key.tokenize('.') + for (int i = 0; i < Math.min(aParts.size(), bParts.size()); i++) { + def aNum = aParts[i].toInteger() + def bNum = bParts[i].toInteger() + if (aNum != bNum) return aNum <=> bNum + } + return aParts.size() <=> bParts.size() + } + + sortedVersions.each { version, url -> println " ${version.padRight(10)}" } + + println "=".multiply(70) + println "Total versions: ${untouchedProps.size()}" + println "" + println "" + println "=".multiply(70) + println "[SUCCESS] modules-untouched integration is working" + println "=".multiply(70) + println "" + println "Version Resolution Strategy:" + println " 1. Check modules-untouched nodejs.properties (remote)" + println " 2. Construct standard URL format (fallback)" + println "" + println "Documentation: /.gradle-docs/MODULES_UNTOUCHED_INTEGRATION.md" + } else { + println "" + println "=".multiply(70) + println "[WARNING] Could not fetch nodejs.properties from modules-untouched" + println "=".multiply(70) + println "" + println "This may be due to:" + println " - Network connectivity issues" + println " - Repository access problems" + println " - File not available at expected location" + println "" + println "The build system will fall back to:" + println " 1. Standard URL format construction" + } + } +} + +// ============================================================================ +// BUILD LIFECYCLE HOOKS +// ============================================================================ + +gradle.taskGraph.whenReady { graph -> + println """ + ================================================================ + Bearsampp Module Node.js - Gradle Build + ================================================================ + """.stripIndent() +} + +// ============================================================================ +// DEFAULT TASK +// ============================================================================ + +defaultTasks 'info' diff --git a/build.xml b/build.xml deleted file mode 100644 index 11933307..00000000 --- a/build.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..eab38d9d --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Gradle Build Properties for Bearsampp Module Node.js + +# Gradle daemon configuration +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true + +# JVM settings for Gradle +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError + +# Configure console output +org.gradle.console=auto +org.gradle.warning.mode=all + +# Build performance +org.gradle.configureondemand=false + +# Gradle version compatibility +# This project is compatible with Gradle 7.0+ diff --git a/module-nodejs.RELEASE.launch b/module-nodejs.RELEASE.launch deleted file mode 100644 index d6908ad3..00000000 --- a/module-nodejs.RELEASE.launch +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..53e2de93 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,25 @@ +/* + * Bearsampp Module Node.js - Gradle Settings + */ + +rootProject.name = 'module-nodejs' + +// Enable Gradle features for better performance +enableFeaturePreview('STABLE_CONFIGURATION_CACHE') + +// Configure build cache for faster builds +buildCache { + local { + enabled = true + directory = file("${rootDir}/.gradle/build-cache") + } +} + +// Display initialization message +gradle.rootProject { + println """ + ================================================================ + Initializing Bearsampp Module Node.js Build + ================================================================ + """.stripIndent() +}