Fix version display issues, container shutdown problems, and modernize build scripts
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
# Development Guide
|
||||
|
||||
## Quick Start Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development server (with hot reload)
|
||||
npm start
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Serve production build locally
|
||||
npm run serve
|
||||
```
|
||||
|
||||
## Docker Commands
|
||||
|
||||
```bash
|
||||
# Build Docker container
|
||||
docker build -t jmespath-playground .
|
||||
|
||||
# Run Docker container
|
||||
docker run -p 3000:3000 jmespath-playground
|
||||
|
||||
# Development with Docker Compose
|
||||
docker-compose --profile dev up jmespath-playground-dev
|
||||
|
||||
# Production with Docker Compose
|
||||
docker-compose up jmespath-playground
|
||||
```
|
||||
|
||||
## Project Structure Overview
|
||||
|
||||
```
|
||||
src/
|
||||
├── App.js # Main component with JMESPath logic
|
||||
├── App.css # App-specific styles
|
||||
├── App.test.js # Basic tests
|
||||
├── index.js # React entry point
|
||||
├── index.css # Global styles and Bootstrap overrides
|
||||
└── setupTests.js # Test configuration
|
||||
```
|
||||
|
||||
## Key Features Implemented
|
||||
|
||||
1. **Real-time JMESPath Evaluation**: Uses the `jmespath` library to evaluate expressions as user types
|
||||
2. **JSON Validation**: Parses and validates JSON input with error reporting
|
||||
3. **Bootstrap UI**: Responsive layout with cards, buttons, and form controls
|
||||
4. **Error Handling**: Clear error messages for both JSON and JMESPath syntax issues
|
||||
5. **Sample Data**: Pre-loaded examples with "Load Sample" button
|
||||
6. **JSON Formatting**: "Format JSON" button to prettify JSON input
|
||||
7. **Clear Function**: "Clear All" button to reset all inputs
|
||||
|
||||
## Component Architecture
|
||||
|
||||
The main `App.js` component manages:
|
||||
- State for JMESPath expression, JSON data, results, and errors
|
||||
- Auto-evaluation using `useEffect` when inputs change
|
||||
- Error handling for both JSON parsing and JMESPath evaluation
|
||||
- UI event handlers for buttons and input changes
|
||||
|
||||
## Styling
|
||||
|
||||
- Bootstrap 5.3.2 for responsive grid and components
|
||||
- Custom CSS for enhanced UX (color coding, hover effects)
|
||||
- Gradient header with professional appearance
|
||||
- Color-coded input areas (yellow for JMESPath, green for JSON, blue for results)
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Built with React 18 and targets:
|
||||
- Modern evergreen browsers
|
||||
- Node.js 24 LTS compatibility
|
||||
- Mobile-responsive design
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
@@ -56,6 +56,10 @@ RUN npm ci --only=production && npm cache clean --force
|
||||
COPY --from=builder /app/build ./build
|
||||
COPY --from=builder /app/server.js ./server.js
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY entrypoint.sh ./entrypoint.sh
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Expose port 3000
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -64,4 +68,4 @@ ENV LISTEN_ADDR=0.0.0.0
|
||||
ENV LISTEN_PORT=3000
|
||||
|
||||
# Start the integrated server
|
||||
CMD ["node", "server.js"]
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
152
README.md
152
README.md
@@ -8,62 +8,53 @@ A React-based web application for testing and validating JMESPath expressions ag
|
||||
|
||||
## Features
|
||||
|
||||
- 🎯 **Real-time Evaluation**: JMESPath expressions are evaluated instantly as you type
|
||||
- 📝 **JSON Validation**: Built-in JSON syntax validation and error reporting
|
||||
- 📁 **File Upload**: Load JSON data directly from local files (supports JSON Lines format for .log files)
|
||||
- 🎨 **Bootstrap UI**: Clean, responsive interface with Bootstrap styling
|
||||
- 🔄 **Sample Data**: Pre-loaded examples to get started quickly
|
||||
- 📱 **Responsive Design**: Works on desktop, tablet, and mobile devices
|
||||
- 🐳 **Docker Ready**: Containerized for easy deployment
|
||||
- ✅ **Error Handling**: Clear error messages for both JSON and JMESPath syntax issues
|
||||
|
||||
## Application Layout
|
||||
|
||||
The application is divided into three main sections:
|
||||
|
||||
1. **Top Section**: Title and description of the tool
|
||||
2. **Middle Section**: Input area for JMESPath expressions
|
||||
3. **Bottom Sections**:
|
||||
- **Left**: JSON data input area
|
||||
- **Right**: Query results output area
|
||||
- **Real-time Evaluation**: JMESPath expressions are evaluated instantly as you type
|
||||
- **File Upload**: Load JSON data directly from local files (supports JSON Lines format for .log files)
|
||||
- **Remote API**: Upload sample data remotely via REST API with encrypted sessions
|
||||
- **Container Ready**: Containerized for easy deployment
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 24 LTS or higher
|
||||
- npm or yarn package manager
|
||||
- npm package manager
|
||||
|
||||
### Local Development
|
||||
|
||||
1. **Clone the repository**:
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd jmespath-playground
|
||||
```
|
||||
|
||||
2. **Install dependencies**:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Start the development server**:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
4. **Open your browser** and navigate to `http://localhost:3000`
|
||||
|
||||
### Container Deployment (Optional)
|
||||
### Container Deployment
|
||||
|
||||
You can optionally run the application in a Docker container:
|
||||
You can optionally run the application in a container:
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker build -t jmespath-playground .
|
||||
# Build the container image
|
||||
npm run build-image
|
||||
|
||||
# Run the container
|
||||
# Run the container (Docker or Apple Container Tools)
|
||||
docker run -p 3000:3000 jmespath-playground
|
||||
# or
|
||||
container run -p 3000:3000 jmespath-playground
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -82,93 +73,56 @@ docker run -p 3000:3000 jmespath-playground
|
||||
- Format JSON for better readability
|
||||
- Clear all inputs
|
||||
|
||||
### Example JMESPath Expressions
|
||||
## Remote API Usage
|
||||
|
||||
Try these examples with the sample data:
|
||||
The application includes a REST API for uploading sample data remotely:
|
||||
|
||||
- `people[*].name` - Get all names
|
||||
- `people[0]` - Get the first person
|
||||
- `people[?age > 30]` - Filter people older than 30
|
||||
- `people[*].skills[0]` - Get the first skill of each person
|
||||
- `length(people)` - Count the number of people
|
||||
1. **Access API Key**: Click the key-lock button (🔒) to view your unique API key
|
||||
2. **Upload Data**: Use curl or any HTTP client to upload JSON data:
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: YOUR_API_KEY" \
|
||||
--data @sample-data.json \
|
||||
"http://your-domain.com/api/v1/upload"
|
||||
```
|
||||
3. **Auto-reload**: The running app will detect new data and show a reload button
|
||||
|
||||
## Available Scripts
|
||||
**API Endpoints:**
|
||||
- `POST /api/v1/upload` - Upload sample data
|
||||
- `GET /api/v1/sample` - Retrieve current sample data
|
||||
- `GET /api/v1/state` - Get current state ID
|
||||
- `GET /api/v1/health` - Simple health check (returns "OK")
|
||||
- `GET /api/v1/status` - Detailed status information (JSON)
|
||||
|
||||
In the project directory, you can run:
|
||||
## Server Configuration
|
||||
|
||||
### `npm start`
|
||||
The server can be configured using environment variables:
|
||||
|
||||
Runs the app in development mode. The page will reload when you make edits.
|
||||
**Network Settings:**
|
||||
- `LISTEN_ADDR` - Server bind address (default: `127.0.0.1`)
|
||||
- `LISTEN_PORT` - Server port (default: `3000`)
|
||||
|
||||
### `npm test`
|
||||
**Session Management:**
|
||||
- `MAX_SESSIONS` - Maximum number of concurrent sessions (default: `100`)
|
||||
- `MAX_SAMPLE_SIZE` - Maximum size of uploaded sample data in bytes (default: `1048576` - 1MB)
|
||||
- `MAX_SESSION_TTL` - Session time-to-live in milliseconds (default: `3600000` - 1 hour)
|
||||
|
||||
Launches the test runner in interactive watch mode.
|
||||
Example usage:
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder. It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
### `npm run serve`
|
||||
|
||||
Serves the production build locally on port 3000.
|
||||
|
||||
### Docker Scripts
|
||||
|
||||
### `npm run docker:build`
|
||||
|
||||
Builds a Docker container.
|
||||
|
||||
### `npm run docker:run`
|
||||
|
||||
Runs the Docker container.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
jmespath-playground/
|
||||
├── .github/
|
||||
│ ├── workflows/
|
||||
│ │ └── build-container.yml # CI/CD pipeline
|
||||
│ └── copilot-instructions.md # AI agent instructions
|
||||
├── public/
|
||||
│ ├── index.html
|
||||
│ ├── manifest.json
|
||||
│ └── favicon.ico
|
||||
├── src/
|
||||
│ ├── App.js # Main application component
|
||||
│ ├── App.css # App-specific styles
|
||||
│ ├── App.test.js # App tests
|
||||
│ ├── index.js # React entry point
|
||||
│ ├── index.css # Global styles
|
||||
│ ├── setupTests.js # Test configuration
|
||||
│ └── reportWebVitals.js
|
||||
├── scripts/
|
||||
│ ├── build.sh # Build script
|
||||
│ └── dev.sh # Development script
|
||||
├── Dockerfile # Docker container
|
||||
├── Dockerfile.dev # Development container
|
||||
├── docker-compose.yml # Container orchestration
|
||||
├── package.json # Dependencies and scripts
|
||||
├── README.md # Comprehensive documentation
|
||||
├── DEVELOPMENT.md # Developer guide
|
||||
└── demo.sh # Demo script
|
||||
```bash
|
||||
MAX_SESSIONS=200 MAX_SAMPLE_SIZE=2097152 LISTEN_PORT=8080 node server.js
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **React 18.2.0**: Frontend framework
|
||||
- **Bootstrap 5.3.2**: CSS framework for styling
|
||||
- **JMESPath 0.16.0**: JMESPath expression evaluation
|
||||
- **React 18.2.0**: Frontend framework with modern hooks and components
|
||||
- **Bootstrap 5.3.2**: CSS framework with dark/light theme support
|
||||
- **JMESPath 0.16.0**: JMESPath expression evaluation library
|
||||
- **Express.js 4.19.2**: Backend API server with session management
|
||||
- **Node.js 24 LTS**: Runtime environment
|
||||
- **Docker**: Optional containerization
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature/new-feature`
|
||||
3. Make your changes and commit them: `git commit -m 'Add new feature'`
|
||||
4. Push to the branch: `git push origin feature/new-feature`
|
||||
5. Submit a pull request
|
||||
- **UUID 9.0.0**: Cryptographically secure session IDs
|
||||
- **Container**: Containerization for easy deployment
|
||||
|
||||
## License
|
||||
|
||||
@@ -177,7 +131,3 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
||||
## About JMESPath
|
||||
|
||||
JMESPath is a query language for JSON. It allows you to declaratively specify how to extract elements from a JSON document. For more information about JMESPath syntax and capabilities, visit the [official JMESPath website](https://jmespath.org/).
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter any issues or have questions, please [open an issue](../../issues) on GitHub.
|
||||
|
||||
79
demo.sh
79
demo.sh
@@ -1,79 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# JMESPath Testing Tool - Demo Script
|
||||
|
||||
echo "🚀 JMESPath Testing Tool Demo"
|
||||
echo "==============================="
|
||||
echo ""
|
||||
|
||||
# Check if Node.js is installed
|
||||
if command -v node &> /dev/null; then
|
||||
echo "✅ Node.js version: $(node --version)"
|
||||
else
|
||||
echo "❌ Node.js not found. Please install Node.js 24 LTS or higher."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if npm is installed
|
||||
if command -v npm &> /dev/null; then
|
||||
echo "✅ npm version: $(npm --version)"
|
||||
else
|
||||
echo "❌ npm not found. Please install npm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Docker
|
||||
if command -v docker &> /dev/null; then
|
||||
echo "✅ Docker available: $(docker --version | cut -d' ' -f3 | cut -d',' -f1)"
|
||||
DOCKER_AVAILABLE=true
|
||||
else
|
||||
echo "⚠️ Docker not found"
|
||||
DOCKER_AVAILABLE=false
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Installing dependencies..."
|
||||
npm install
|
||||
|
||||
echo ""
|
||||
echo "🧪 Running tests..."
|
||||
npm test -- --watchAll=false
|
||||
|
||||
echo ""
|
||||
echo "🔨 Building React application..."
|
||||
echo " (Version will be automatically tagged as -dev since not building from git tag)"
|
||||
npm run build
|
||||
|
||||
echo ""
|
||||
echo "🎉 Demo completed successfully!"
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo "==================="
|
||||
echo ""
|
||||
echo "Development:"
|
||||
echo " npm start - Start React development server (port 3000)"
|
||||
echo " npm run server - Start Express API server only (port 3000)"
|
||||
echo " npm test - Run test suite"
|
||||
echo ""
|
||||
echo "Production:"
|
||||
echo " npm run build - Build React app for production"
|
||||
echo " node server/server.js - Start integrated server with built app"
|
||||
echo ""
|
||||
if [ "$DOCKER_AVAILABLE" = true ]; then
|
||||
echo "Docker:"
|
||||
echo " docker build -t jmespath-playground ."
|
||||
echo " docker run -p 3000:3000 jmespath-playground"
|
||||
echo ""
|
||||
echo "Docker Compose:"
|
||||
echo " docker compose up --build"
|
||||
echo " docker compose down"
|
||||
else
|
||||
echo "Docker (install Docker first):"
|
||||
echo " docker build -t jmespath-playground ."
|
||||
echo " docker run -p 3000:3000 jmespath-playground"
|
||||
echo " docker compose up --build"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🌐 The application will be available at:"
|
||||
echo " http://localhost:3000"
|
||||
3
entrypoint.sh
Normal file
3
entrypoint.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
exec node server.js
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jmespath-playground",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "A React-based web application for testing JMESPath expressions against JSON data",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,8 @@
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --watchAll=false",
|
||||
"test:watch": "react-scripts test",
|
||||
"server": "node server.js"
|
||||
"server": "node server.js",
|
||||
"build-image": "node scripts/build-image.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
|
||||
89
scripts/build-image.js
Normal file
89
scripts/build-image.js
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
|
||||
function execCommand(command, description) {
|
||||
try {
|
||||
console.log(`${description}...`);
|
||||
execSync(command, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error(`Error: ${description} failed`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getContainerTool() {
|
||||
// Check for Docker first (primary tool)
|
||||
try {
|
||||
execSync('docker --version', { stdio: 'ignore' });
|
||||
return 'docker';
|
||||
} catch (error) {
|
||||
// Fall back to Apple's container command
|
||||
try {
|
||||
execSync('container --version', { stdio: 'ignore' });
|
||||
return 'container';
|
||||
} catch (error) {
|
||||
console.error('Error: No container tool found. Please install Docker or Apple Container Tools to build container images.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getVersion() {
|
||||
try {
|
||||
// Try to get version from git tag
|
||||
const gitTag = execSync('git tag --points-at HEAD', { encoding: 'utf8' }).trim();
|
||||
if (gitTag) {
|
||||
return { version: gitTag.replace(/^v/, ''), isRelease: true };
|
||||
}
|
||||
} catch (error) {
|
||||
// Git command failed, ignore
|
||||
}
|
||||
|
||||
// Development build - use package.json version with -dev suffix
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
return { version: `${packageJson.version}-dev`, isRelease: false };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const containerTool = getContainerTool();
|
||||
const { version, isRelease } = getVersion();
|
||||
|
||||
console.log(`Building ${isRelease ? 'release' : 'development'} version: ${version}`);
|
||||
|
||||
// Build container image
|
||||
const tags = isRelease
|
||||
? [
|
||||
`-t skoszewski/jmespath-playground:${version}`,
|
||||
`-t skoszewski/jmespath-playground:latest`
|
||||
].join(' ')
|
||||
: [
|
||||
`-t skoszewski/jmespath-playground:dev`,
|
||||
`-t skoszewski/jmespath-playground:latest`
|
||||
].join(' ');
|
||||
|
||||
const buildCommand = `${containerTool} build --build-arg VERSION="${version}" --build-arg IS_RELEASE="${isRelease}" ${tags} .`;
|
||||
|
||||
execCommand(buildCommand, 'Building container image');
|
||||
|
||||
console.log('Container image build completed successfully!');
|
||||
|
||||
// Show usage instructions
|
||||
if (isRelease) {
|
||||
console.log(`\nTo run the container:`);
|
||||
console.log(` ${containerTool} run -p 3000:3000 skoszewski/jmespath-playground:${version}`);
|
||||
if (containerTool === 'docker') {
|
||||
console.log(`\nTo push to Docker Hub:`);
|
||||
console.log(` docker push skoszewski/jmespath-playground:${version}`);
|
||||
console.log(` docker push skoszewski/jmespath-playground:latest`);
|
||||
}
|
||||
} else {
|
||||
console.log(`\nTo run the container:`);
|
||||
console.log(` ${containerTool} run -p 3000:3000 skoszewski/jmespath-playground:dev`);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# JMESPath Testing Tool - Docker Image Build Script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🐳 JMESPath Testing Tool - Docker Image Build"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Check if Docker is available
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "❌ Docker not found. Please install Docker to build container images."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine version information for Docker build
|
||||
VERSION=$(git tag --points-at HEAD 2>/dev/null | sed 's/^v//' | head -n 1)
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
# We're at a tagged commit - release build
|
||||
echo "📦 Building release version: $VERSION"
|
||||
docker build \
|
||||
--build-arg VERSION="$VERSION" \
|
||||
--build-arg IS_RELEASE="true" \
|
||||
-t skoszewski/jmespath-playground:$VERSION \
|
||||
-t skoszewski/jmespath-playground:latest .
|
||||
echo "✅ Built Docker images: skoszewski/jmespath-playground:$VERSION, skoszewski/jmespath-playground:latest"
|
||||
|
||||
echo ""
|
||||
echo "To run the release container:"
|
||||
echo " docker run -p 3000:3000 skoszewski/jmespath-playground:$VERSION"
|
||||
echo " docker run -p 3000:3000 skoszewski/jmespath-playground:latest"
|
||||
|
||||
echo ""
|
||||
echo "To push to Docker Hub:"
|
||||
echo " docker push skoszewski/jmespath-playground:$VERSION"
|
||||
echo " docker push skoszewski/jmespath-playground:latest"
|
||||
else
|
||||
# Development build
|
||||
PACKAGE_VERSION=$(grep '"version"' package.json | cut -d'"' -f4)
|
||||
DEV_VERSION="${PACKAGE_VERSION}-dev"
|
||||
echo "📦 Building development version: $DEV_VERSION"
|
||||
docker build \
|
||||
--build-arg VERSION="$DEV_VERSION" \
|
||||
--build-arg IS_RELEASE="false" \
|
||||
-t skoszewski/jmespath-playground:dev \
|
||||
-t skoszewski/jmespath-playground:latest .
|
||||
echo "✅ Built Docker images: skoszewski/jmespath-playground:dev, skoszewski/jmespath-playground:latest"
|
||||
|
||||
echo ""
|
||||
echo "To run the development container:"
|
||||
echo " docker run -p 3000:3000 skoszewski/jmespath-playground:dev"
|
||||
echo " docker run -p 3000:3000 skoszewski/jmespath-playground:latest"
|
||||
|
||||
echo ""
|
||||
echo "To push to Docker Hub:"
|
||||
echo " docker push skoszewski/jmespath-playground:dev"
|
||||
echo " docker push skoszewski/jmespath-playground:latest"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 Docker image build completed successfully!"
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# JMESPath Testing Tool - Build Script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 JMESPath Testing Tool - Build Script"
|
||||
echo "======================================="
|
||||
echo ""
|
||||
|
||||
# Check Node.js version
|
||||
if command -v node &> /dev/null; then
|
||||
NODE_VERSION=$(node --version | sed 's/v//')
|
||||
MAJOR_VERSION=$(echo $NODE_VERSION | cut -d. -f1)
|
||||
if [ "$MAJOR_VERSION" -ge 24 ]; then
|
||||
echo "✅ Node.js $NODE_VERSION (compatible with v24+ requirement)"
|
||||
else
|
||||
echo "❌ Node.js $NODE_VERSION found, but v24+ is required"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Node.js not found. Please install Node.js 24 LTS or higher."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the React application
|
||||
echo "📦 Installing dependencies..."
|
||||
npm install
|
||||
|
||||
echo "🔨 Building production bundle..."
|
||||
npm run build
|
||||
|
||||
echo "✅ Build completed successfully!"
|
||||
echo ""
|
||||
echo "To run the application:"
|
||||
echo " npm run server # Run integrated server locally"
|
||||
echo ""
|
||||
echo "To build Docker image:"
|
||||
echo " scripts/build-image.sh # Build Docker container image"
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# JMESPath Testing Tool - Development Script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 JMESPath Testing Tool - Development"
|
||||
echo "====================================="
|
||||
echo ""
|
||||
|
||||
# Check Node.js
|
||||
if command -v node &> /dev/null; then
|
||||
echo "✅ Node.js version: $(node --version)"
|
||||
else
|
||||
echo "❌ Node.js not found. Please install Node.js 24 LTS."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install dependencies if needed
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "📦 Installing dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Start development server
|
||||
echo "🚀 Starting development server..."
|
||||
echo " The app will open at http://localhost:3000"
|
||||
echo " Press Ctrl+C to stop the server"
|
||||
echo ""
|
||||
npm start
|
||||
@@ -2,63 +2,57 @@
|
||||
|
||||
/**
|
||||
* JMESPath Playground Upload Script (JavaScript)
|
||||
* Usage: node upload.js [-u URL] "json_file.json"
|
||||
* Usage: node upload.js [-u URL] [-k API_KEY] "json_file.json"
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { URL } = require('url');
|
||||
const { parseArgs } = require('util');
|
||||
|
||||
function showUsage() {
|
||||
const scriptName = path.basename(process.argv[1]);
|
||||
console.log(`Usage: node ${scriptName} [-u|--url URL] <json_file>`);
|
||||
console.log(`Usage: node ${scriptName} [-u|--url URL] [-k|--key API_KEY] <json_file>`);
|
||||
console.log('');
|
||||
console.log('Options:');
|
||||
console.log(' -u, --url URL API URL (default: http://localhost:3000)');
|
||||
console.log(' -h, --help Show this help message');
|
||||
console.log(' -u, --url URL API URL (default: http://localhost:3000)');
|
||||
console.log(' -k, --key API_KEY API key (not required for localhost)');
|
||||
console.log(' -h, --help Show this help message');
|
||||
console.log('');
|
||||
console.log('Example:');
|
||||
console.log('Examples:');
|
||||
console.log(` node ${scriptName} data.json`);
|
||||
console.log(` node ${scriptName} -u http://example.com:3000 data.json`);
|
||||
console.log(` node ${scriptName} -u http://example.com:3000 -k your-api-key data.json`);
|
||||
}
|
||||
|
||||
function parseArguments() {
|
||||
const args = process.argv.slice(2);
|
||||
let apiUrl = 'http://localhost:3000';
|
||||
let jsonFile = '';
|
||||
function getArguments() {
|
||||
const { values, positionals } = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
url: { type: 'string', short: 'u', default: 'http://localhost:3000' },
|
||||
key: { type: 'string', short: 'k' },
|
||||
help: { type: 'boolean', short: 'h' }
|
||||
},
|
||||
allowPositionals: true
|
||||
});
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
|
||||
if (arg === '-u' || arg === '--url') {
|
||||
if (i + 1 >= args.length) {
|
||||
console.error('Error: URL argument required for -u/--url option');
|
||||
process.exit(1);
|
||||
}
|
||||
apiUrl = args[i + 1];
|
||||
i++; // Skip next argument
|
||||
} else if (arg === '-h' || arg === '--help') {
|
||||
showUsage();
|
||||
process.exit(0);
|
||||
} else if (arg.startsWith('-')) {
|
||||
console.error(`Error: Unknown option ${arg}`);
|
||||
showUsage();
|
||||
process.exit(1);
|
||||
} else {
|
||||
if (jsonFile) {
|
||||
console.error('Error: Multiple JSON files specified');
|
||||
process.exit(1);
|
||||
}
|
||||
jsonFile = arg;
|
||||
}
|
||||
if (values.help) {
|
||||
showUsage();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!jsonFile) {
|
||||
if (positionals.length !== 1) {
|
||||
console.error('Error: JSON file required');
|
||||
showUsage();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return { apiUrl, jsonFile };
|
||||
return {
|
||||
apiUrl: values.url,
|
||||
apiKey: values.key || '',
|
||||
jsonFile: positionals[0]
|
||||
};
|
||||
}
|
||||
|
||||
async function validateJsonFile(jsonFile) {
|
||||
@@ -80,28 +74,84 @@ async function validateJsonFile(jsonFile) {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadData(apiUrl, jsonFile, jsonData) {
|
||||
console.log('Uploading sample data to JMESPath Playground...');
|
||||
console.log(`JSON file: ${jsonFile}`);
|
||||
console.log(`API URL: ${apiUrl}`);
|
||||
console.log('');
|
||||
|
||||
function isLocalhost(url) {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/v1/upload`, {
|
||||
const parsed = new URL(url);
|
||||
const hostname = parsed.hostname;
|
||||
return hostname === 'localhost' ||
|
||||
hostname === '127.0.0.1' ||
|
||||
hostname.startsWith('127.') ||
|
||||
hostname === '::1';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(url, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parsedUrl = new URL(url);
|
||||
const isHttps = parsedUrl.protocol === 'https:';
|
||||
const client = isHttps ? https : http;
|
||||
|
||||
const requestOptions = {
|
||||
hostname: parsedUrl.hostname,
|
||||
port: parsedUrl.port,
|
||||
path: parsedUrl.pathname,
|
||||
method: options.method || 'GET',
|
||||
headers: options.headers || {}
|
||||
};
|
||||
|
||||
const req = client.request(requestOptions, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
ok: res.statusCode >= 200 && res.statusCode < 300,
|
||||
status: res.statusCode,
|
||||
json: () => Promise.resolve(JSON.parse(data))
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadData(apiUrl, apiKey, jsonFile, jsonData) {
|
||||
try {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
// Only send API key for non-localhost requests
|
||||
const isLocal = isLocalhost(apiUrl);
|
||||
if (!isLocal && apiKey) {
|
||||
headers['X-API-Key'] = apiKey;
|
||||
} else if (!isLocal && !apiKey) {
|
||||
console.error('Error: API key required for non-localhost URLs');
|
||||
console.error('Use -k/--key option to specify API key');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const response = await makeRequest(`${apiUrl}/api/v1/upload`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: headers,
|
||||
body: jsonData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(`HTTP ${response.status}: ${errorData.error || 'Upload failed'}`);
|
||||
}
|
||||
|
||||
console.log('Sample data uploaded successfully!');
|
||||
console.log(`Open ${apiUrl} in your browser to see the reload button.`);
|
||||
console.log('You can then enter your JMESPath expression in the web interface.');
|
||||
const result = await response.json();
|
||||
console.log(JSON.stringify(result));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error uploading data:', error.message);
|
||||
@@ -110,9 +160,9 @@ async function uploadData(apiUrl, jsonFile, jsonData) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { apiUrl, jsonFile } = parseArguments();
|
||||
const { apiUrl, apiKey, jsonFile } = getArguments();
|
||||
const jsonData = await validateJsonFile(jsonFile);
|
||||
await uploadData(apiUrl, jsonFile, jsonData);
|
||||
await uploadData(apiUrl, apiKey, jsonFile, jsonData);
|
||||
}
|
||||
|
||||
// Run the script
|
||||
|
||||
@@ -22,15 +22,15 @@ try {
|
||||
console.log(`✅ Building release version ${version} (tagged: ${gitTag})`);
|
||||
isRelease = true;
|
||||
} else {
|
||||
// We're not at a tagged commit - add -dev suffix
|
||||
version = `${version}-dev`;
|
||||
console.log(`📦 Building development version ${version}`);
|
||||
// We're not at a tagged commit - use unknown version
|
||||
version = 'unknown';
|
||||
console.log(`📦 Building development version with unknown version`);
|
||||
isRelease = false;
|
||||
}
|
||||
} catch (error) {
|
||||
// Git command failed (maybe not in a git repo)
|
||||
version = `${version}-dev`;
|
||||
console.log(`⚠️ Cannot determine git status, using development version ${version}`);
|
||||
version = 'unknown';
|
||||
console.log(`⚠️ Cannot determine git status, using unknown version`);
|
||||
isRelease = false;
|
||||
}
|
||||
|
||||
|
||||
152
server.js
152
server.js
@@ -1,7 +1,9 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const os = require('os');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { parseArgs } = require('util');
|
||||
|
||||
// Environment configuration
|
||||
const MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS) || 100;
|
||||
@@ -16,21 +18,21 @@ function generateSalt() {
|
||||
function isLocalhostRequest(req) {
|
||||
// Get client IP with fallback options
|
||||
const forwarded = req.get('X-Forwarded-For');
|
||||
const ip = forwarded ? forwarded.split(',')[0].trim() :
|
||||
req.ip ||
|
||||
req.connection.remoteAddress ||
|
||||
req.socket.remoteAddress ||
|
||||
const ip = forwarded ? forwarded.split(',')[0].trim() :
|
||||
req.ip ||
|
||||
req.connection.remoteAddress ||
|
||||
req.socket.remoteAddress ||
|
||||
'127.0.0.1';
|
||||
|
||||
|
||||
const host = req.get('host') || '';
|
||||
|
||||
|
||||
// Check for localhost IP addresses (IPv4 and IPv6)
|
||||
const localhostIPs = ['127.0.0.1', '::1', '::ffff:127.0.0.1', 'localhost'];
|
||||
const isLocalIP = localhostIPs.includes(ip) || ip.startsWith('127.') || ip === '::1';
|
||||
|
||||
|
||||
// Check for localhost hostnames
|
||||
const isLocalHost = host.startsWith('localhost:') || host.startsWith('127.0.0.1:') || host === 'localhost' || host === '127.0.0.1';
|
||||
|
||||
|
||||
return isLocalIP || isLocalHost;
|
||||
}
|
||||
|
||||
@@ -40,12 +42,12 @@ function encrypt(data, key) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
cipher.setAAD(Buffer.from('session-data'));
|
||||
|
||||
|
||||
let encrypted = cipher.update(JSON.stringify(data), 'utf8');
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
|
||||
return {
|
||||
iv: iv.toString('hex'),
|
||||
data: encrypted.toString('hex'),
|
||||
@@ -69,10 +71,10 @@ function decrypt(encryptedObj, key) {
|
||||
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
||||
decipher.setAAD(Buffer.from('session-data'));
|
||||
decipher.setAuthTag(Buffer.from(encryptedObj.tag, 'hex'));
|
||||
|
||||
|
||||
let decrypted = decipher.update(Buffer.from(encryptedObj.data, 'hex'), null, 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
|
||||
return JSON.parse(decrypted);
|
||||
} catch (error) {
|
||||
console.error('⚠️ Decryption exception:', {
|
||||
@@ -141,7 +143,7 @@ function createApp() {
|
||||
// Check if request is from localhost - if so, skip API key validation
|
||||
const isFromLocalhost = isLocalhostRequest(req);
|
||||
let apiKey = req.headers['x-api-key'];
|
||||
|
||||
|
||||
if (!isFromLocalhost) {
|
||||
// Validate API key header for remote clients
|
||||
if (!apiKey || !isValidApiKey(apiKey)) {
|
||||
@@ -159,7 +161,7 @@ function createApp() {
|
||||
|
||||
// Check session limits
|
||||
if (sessions.size >= MAX_SESSIONS) {
|
||||
return res.status(429).json({
|
||||
return res.status(429).json({
|
||||
error: 'Maximum number of sessions reached. Please try again later.',
|
||||
maxSessions: MAX_SESSIONS,
|
||||
currentSessions: sessions.size
|
||||
@@ -176,7 +178,7 @@ function createApp() {
|
||||
// Check data size
|
||||
const dataSize = Buffer.byteLength(JSON.stringify(uploadedData), 'utf8');
|
||||
if (dataSize > MAX_SAMPLE_SIZE) {
|
||||
return res.status(413).json({
|
||||
return res.status(413).json({
|
||||
error: 'Sample data too large',
|
||||
maxSize: MAX_SAMPLE_SIZE,
|
||||
receivedSize: dataSize
|
||||
@@ -190,7 +192,7 @@ function createApp() {
|
||||
|
||||
// Encrypt and store session data
|
||||
const encryptedData = encrypt(uploadedData, key);
|
||||
|
||||
|
||||
sessions.set(sessionId, {
|
||||
salt: salt.toString('hex'),
|
||||
encryptedData,
|
||||
@@ -201,8 +203,8 @@ function createApp() {
|
||||
|
||||
console.log(`📁 Session created: ${sessionId.substring(0, 8)}... (${sessions.size}/${MAX_SESSIONS})`);
|
||||
|
||||
res.json({
|
||||
message: 'Sample data uploaded successfully',
|
||||
res.json({
|
||||
message: 'Sample data uploaded successfully',
|
||||
state: stateGuid,
|
||||
sessionId: sessionId.substring(0, 8) + '...'
|
||||
});
|
||||
@@ -213,25 +215,25 @@ function createApp() {
|
||||
sessionCount: sessions.size,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
|
||||
// Provide more specific error messages based on error type
|
||||
if (error.name === 'SyntaxError') {
|
||||
return res.status(400).json({
|
||||
return res.status(400).json({
|
||||
error: 'Invalid JSON data format',
|
||||
details: 'The uploaded data could not be parsed as valid JSON'
|
||||
});
|
||||
} else if (error.message.includes('encrypt')) {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Encryption failed',
|
||||
details: 'Failed to encrypt session data. Please try again with a new API key.'
|
||||
});
|
||||
} else if (error.message.includes('PBKDF2')) {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Key derivation failed',
|
||||
details: 'Failed to derive encryption key from API key'
|
||||
});
|
||||
} else {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Upload processing failed',
|
||||
details: 'An unexpected error occurred while processing your upload. Please try again.'
|
||||
});
|
||||
@@ -244,7 +246,7 @@ function createApp() {
|
||||
// Check if request is from localhost - if so, skip API key validation
|
||||
const isFromLocalhost = isLocalhostRequest(req);
|
||||
let apiKey = req.headers['x-api-key'];
|
||||
|
||||
|
||||
if (!isFromLocalhost) {
|
||||
// Validate API key header for remote clients
|
||||
if (!apiKey || !isValidApiKey(apiKey)) {
|
||||
@@ -281,25 +283,25 @@ function createApp() {
|
||||
sessionCount: sessions.size,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
|
||||
// Provide more specific error messages based on error type
|
||||
if (error.message.includes('decrypt')) {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Decryption failed',
|
||||
details: 'Failed to decrypt session data. The session may be corrupted or the API key may be incorrect.'
|
||||
});
|
||||
} else if (error.message.includes('JSON')) {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Data corruption detected',
|
||||
details: 'The stored session data appears to be corrupted and cannot be parsed.'
|
||||
});
|
||||
} else if (error.name === 'TypeError') {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Session data format error',
|
||||
details: 'The session data format is invalid or corrupted.'
|
||||
});
|
||||
} else {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'Sample retrieval failed',
|
||||
details: 'An unexpected error occurred while retrieving sample data. The session may have been corrupted.'
|
||||
});
|
||||
@@ -312,7 +314,7 @@ function createApp() {
|
||||
// Check if request is from localhost - if so, skip API key validation
|
||||
const isFromLocalhost = isLocalhostRequest(req);
|
||||
let apiKey = req.headers['x-api-key'];
|
||||
|
||||
|
||||
if (!isFromLocalhost) {
|
||||
// Validate API key header for remote clients
|
||||
if (!apiKey || !isValidApiKey(apiKey)) {
|
||||
@@ -341,15 +343,15 @@ function createApp() {
|
||||
sessionCount: sessions.size,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
|
||||
// Provide more specific error messages
|
||||
if (error.message.includes('API key')) {
|
||||
return res.status(403).json({
|
||||
return res.status(403).json({
|
||||
error: 'API key processing failed',
|
||||
details: 'Failed to process the provided API key'
|
||||
});
|
||||
} else {
|
||||
return res.status(500).json({
|
||||
return res.status(500).json({
|
||||
error: 'State retrieval failed',
|
||||
details: 'An unexpected error occurred while retrieving session state. Please try again.'
|
||||
});
|
||||
@@ -357,9 +359,9 @@ function createApp() {
|
||||
}
|
||||
});
|
||||
|
||||
// Health endpoint (no auth required)
|
||||
app.get('/api/v1/health', (req, res) => {
|
||||
cleanupExpiredSessions(); // Cleanup on health check
|
||||
// Status endpoint (no auth required) - detailed information
|
||||
app.get('/api/v1/status', (req, res) => {
|
||||
cleanupExpiredSessions(); // Cleanup on status check
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
sessions: {
|
||||
@@ -376,6 +378,11 @@ function createApp() {
|
||||
});
|
||||
});
|
||||
|
||||
// Health endpoint (no auth required) - simple OK response
|
||||
app.get('/api/v1/health', (req, res) => {
|
||||
res.type('text/plain').send('OK');
|
||||
});
|
||||
|
||||
// Serve React app for all other routes
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'build', 'index.html'));
|
||||
@@ -386,37 +393,62 @@ function createApp() {
|
||||
|
||||
// Start server if this file is run directly
|
||||
if (require.main === module) {
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
let listenAddr = process.env.LISTEN_ADDR || '127.0.0.1';
|
||||
let listenPort = process.env.LISTEN_PORT || 3000;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '-h' || args[i] === '--listen-addr') {
|
||||
listenAddr = args[i + 1];
|
||||
i++;
|
||||
} else if (args[i] === '-p' || args[i] === '--port') {
|
||||
listenPort = args[i + 1];
|
||||
i++;
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
'listen-addr': { type: 'string', short: 'h', default: process.env.LISTEN_ADDR || '127.0.0.1' },
|
||||
'port': { type: 'string', short: 'p', default: process.env.LISTEN_PORT || '3000' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const app = createApp();
|
||||
const PORT = parseInt(listenPort);
|
||||
const HOST = listenAddr;
|
||||
const PORT = parseInt(values.port);
|
||||
const HOST = values['listen-addr'];
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
console.log(`🚀 JMESPath Playground Server running on http://${HOST}:${PORT}`);
|
||||
console.log(`📊 Configuration:`);
|
||||
console.log(`JMESPath Playground Server running`);
|
||||
|
||||
// Show actual accessible URLs
|
||||
if (HOST === '0.0.0.0') {
|
||||
console.log(` Listening on all interfaces:`);
|
||||
const interfaces = os.networkInterfaces();
|
||||
for (const [name, addrs] of Object.entries(interfaces)) {
|
||||
for (const addr of addrs) {
|
||||
if (addr.family === 'IPv4' && !addr.internal) {
|
||||
console.log(` http://${addr.address}:${PORT}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also show localhost for local access
|
||||
console.log(` http://127.0.0.1:${PORT}`);
|
||||
} else {
|
||||
console.log(` http://${HOST}:${PORT}`);
|
||||
}
|
||||
|
||||
console.log(`Configuration:`);
|
||||
console.log(` Max Sessions: ${MAX_SESSIONS}`);
|
||||
console.log(` Max Sample Size: ${(MAX_SAMPLE_SIZE / 1024 / 1024).toFixed(1)}MB`);
|
||||
console.log(` Session TTL: ${(MAX_SESSION_TTL / 1000 / 60).toFixed(0)} minutes`);
|
||||
console.log(`🔗 API endpoints:`);
|
||||
console.log(` POST http://${HOST}:${PORT}/api/v1/upload (requires X-API-Key)`);
|
||||
console.log(` GET http://${HOST}:${PORT}/api/v1/sample (requires X-API-Key)`);
|
||||
console.log(` GET http://${HOST}:${PORT}/api/v1/state (requires X-API-Key)`);
|
||||
console.log(` GET http://${HOST}:${PORT}/api/v1/health (public)`);
|
||||
console.log(`🔐 Security: AES-256-GCM encryption with PBKDF2 key derivation`);
|
||||
|
||||
// Show base API URL
|
||||
let apiBaseUrl;
|
||||
if (HOST === '0.0.0.0') {
|
||||
const interfaces = os.networkInterfaces();
|
||||
let firstIP = '127.0.0.1';
|
||||
outer: for (const addrs of Object.values(interfaces)) {
|
||||
for (const addr of addrs) {
|
||||
if (addr.family === 'IPv4' && !addr.internal) {
|
||||
firstIP = addr.address;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
apiBaseUrl = `http://${firstIP}:${PORT}/api/v1`;
|
||||
} else {
|
||||
apiBaseUrl = `http://${HOST}:${PORT}/api/v1`;
|
||||
}
|
||||
|
||||
console.log(`API Base URL: ${apiBaseUrl}`);
|
||||
console.log(`Security: AES-256-GCM encryption with PBKDF2 key derivation`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -55,16 +55,19 @@ describe('App Component', () => {
|
||||
|
||||
test('renders version number', () => {
|
||||
render(<App />);
|
||||
const versionText = screen.getByText(/v\d+\.\d+\.\d+(-dev|-test)?/);
|
||||
// Version can be either v1.2.3 format (release), v1.2.3-dev/test format (legacy dev), or "unknown" format (new dev)
|
||||
const versionText = screen.getByText(/(v\d+\.\d+\.\d+(-dev|-test)?|unknown)/);
|
||||
expect(versionText).toBeInTheDocument();
|
||||
|
||||
// Check if it's a dev/test build
|
||||
const isDevBuild = versionText.textContent.includes('-dev') || versionText.textContent.includes('-test');
|
||||
// Check if it's a dev/test/unknown build
|
||||
const isDevBuild = versionText.textContent.includes('-dev') ||
|
||||
versionText.textContent.includes('-test') ||
|
||||
versionText.textContent.includes('unknown');
|
||||
|
||||
// Additional validations can be added here based on build type
|
||||
if (isDevBuild) {
|
||||
// Dev/test specific validations could go here
|
||||
expect(versionText.textContent).toMatch(/v\d+\.\d+\.\d+-(dev|test)/);
|
||||
// Dev/test/unknown specific validations
|
||||
expect(versionText.textContent).toMatch(/(v\d+\.\d+\.\d+-(dev|test)|unknown)/);
|
||||
} else {
|
||||
// Release build validations - just check that version pattern exists in the text
|
||||
expect(versionText.textContent).toMatch(/v\d+\.\d+\.\d+/);
|
||||
|
||||
@@ -8,7 +8,7 @@ function Footer() {
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<p className="mb-0 text-muted small">
|
||||
<strong>JMESPath Testing Tool</strong> v{VERSION} - Created for testing and validating JMESPath expressions
|
||||
<strong>JMESPath Testing Tool</strong> {VERSION === 'unknown' ? VERSION : `v${VERSION}`} - Created for testing and validating JMESPath expressions
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-md-6 text-md-end">
|
||||
|
||||
Reference in New Issue
Block a user