3 Commits

17 changed files with 373 additions and 604 deletions

View File

@@ -93,3 +93,17 @@ The application exposes a REST API for remotly uploading sample data. The API en
## Containerization ## Containerization
The application should be prepared for deployment using containerization. It should extend minimal Node 24 LTS container image. The application should be prepared for deployment using containerization. It should extend minimal Node 24 LTS container image.
## Updates
Always use `scripts/new-version.js` script to make a new release.
Correct procedure to make a new release:
- Review the code changes and ensure everything is working.
- Run `npm run build` to build the React application.
- Run `npm test` to execute the test suite and ensure all tests pass.
- Prepare a commit message describing the changes made.
- Use `scripts/new-version.js` to create a new version and commit the changes. Use `--force` option if repository is not clean.
- Don't push the changes without approval.
- Don't build docker image without approval.

View File

@@ -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.

View File

@@ -56,6 +56,10 @@ RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app/build ./build COPY --from=builder /app/build ./build
COPY --from=builder /app/server.js ./server.js 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 port 3000
EXPOSE 3000 EXPOSE 3000
@@ -64,4 +68,4 @@ ENV LISTEN_ADDR=0.0.0.0
ENV LISTEN_PORT=3000 ENV LISTEN_PORT=3000
# Start the integrated server # Start the integrated server
CMD ["node", "server.js"] ENTRYPOINT ["./entrypoint.sh"]

152
README.md
View File

@@ -8,62 +8,53 @@ A React-based web application for testing and validating JMESPath expressions ag
## Features ## Features
- 🎯 **Real-time Evaluation**: JMESPath expressions are evaluated instantly as you type - **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)
- 📁 **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
- 🎨 **Bootstrap UI**: Clean, responsive interface with Bootstrap styling - **Container Ready**: Containerized for easy deployment
- 🔄 **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
## Quick Start ## Quick Start
### Prerequisites ### Prerequisites
- Node.js 24 LTS or higher - Node.js 24 LTS or higher
- npm or yarn package manager - npm package manager
### Local Development ### Local Development
1. **Clone the repository**: 1. **Clone the repository**:
```bash ```bash
git clone <repository-url> git clone <repository-url>
cd jmespath-playground cd jmespath-playground
``` ```
2. **Install dependencies**: 2. **Install dependencies**:
```bash ```bash
npm install npm install
``` ```
3. **Start the development server**: 3. **Start the development server**:
```bash ```bash
npm start npm start
``` ```
4. **Open your browser** and navigate to `http://localhost:3000` 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 ```bash
# Build the Docker image # Build the container image
docker build -t jmespath-playground . npm run build-image
# Run the container # Run the container (Docker or Apple Container Tools)
docker run -p 3000:3000 jmespath-playground docker run -p 3000:3000 jmespath-playground
# or
container run -p 3000:3000 jmespath-playground
``` ```
## Usage ## Usage
@@ -82,93 +73,56 @@ docker run -p 3000:3000 jmespath-playground
- Format JSON for better readability - Format JSON for better readability
- Clear all inputs - 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 1. **Access API Key**: Click the key-lock button (🔒) to view your unique API key
- `people[0]` - Get the first person 2. **Upload Data**: Use curl or any HTTP client to upload JSON data:
- `people[?age > 30]` - Filter people older than 30 ```bash
- `people[*].skills[0]` - Get the first skill of each person curl -X POST \
- `length(people)` - Count the number of people -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` ```bash
MAX_SESSIONS=200 MAX_SAMPLE_SIZE=2097152 LISTEN_PORT=8080 node server.js
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
``` ```
## Technology Stack ## Technology Stack
- **React 18.2.0**: Frontend framework - **React 18.2.0**: Frontend framework with modern hooks and components
- **Bootstrap 5.3.2**: CSS framework for styling - **Bootstrap 5.3.2**: CSS framework with dark/light theme support
- **JMESPath 0.16.0**: JMESPath expression evaluation - **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 - **Node.js 24 LTS**: Runtime environment
- **Docker**: Optional containerization - **UUID 9.0.0**: Cryptographically secure session IDs
- **Container**: Containerization for easy deployment
## 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
## License ## License
@@ -177,7 +131,3 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## About JMESPath ## 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/). 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
View File

@@ -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
View File

@@ -0,0 +1,3 @@
#!/bin/sh
set -e
exec node server.js

View File

@@ -1,6 +1,6 @@
{ {
"name": "jmespath-playground", "name": "jmespath-playground",
"version": "1.2.2", "version": "1.2.4",
"description": "A React-based web application for testing JMESPath expressions against JSON data", "description": "A React-based web application for testing JMESPath expressions against JSON data",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -9,7 +9,8 @@
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --watchAll=false", "test": "react-scripts test --watchAll=false",
"test:watch": "react-scripts test", "test:watch": "react-scripts test",
"server": "node server.js" "server": "node server.js",
"build-image": "node scripts/build-image.js"
}, },
"engines": { "engines": {
"node": ">=24.0.0" "node": ">=24.0.0"

89
scripts/build-image.js Normal file
View 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();
}

View File

@@ -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!"

View File

@@ -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"

View File

@@ -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

View File

@@ -2,63 +2,57 @@
/** /**
* JMESPath Playground Upload Script (JavaScript) * 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 fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https');
const http = require('http');
const { URL } = require('url');
const { parseArgs } = require('util');
function showUsage() { function showUsage() {
const scriptName = path.basename(process.argv[1]); 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('');
console.log('Options:'); console.log('Options:');
console.log(' -u, --url URL API URL (default: http://localhost:3000)'); 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(' -h, --help Show this help message');
console.log(''); console.log('');
console.log('Example:'); console.log('Examples:');
console.log(` node ${scriptName} data.json`); 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() { function getArguments() {
const args = process.argv.slice(2); const { values, positionals } = parseArgs({
let apiUrl = 'http://localhost:3000'; args: process.argv.slice(2),
let jsonFile = ''; 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++) { if (values.help) {
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(); showUsage();
process.exit(0); 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 (!jsonFile) { if (positionals.length !== 1) {
console.error('Error: JSON file required'); console.error('Error: JSON file required');
showUsage(); showUsage();
process.exit(1); process.exit(1);
} }
return { apiUrl, jsonFile }; return {
apiUrl: values.url,
apiKey: values.key || '',
jsonFile: positionals[0]
};
} }
async function validateJsonFile(jsonFile) { async function validateJsonFile(jsonFile) {
@@ -80,28 +74,84 @@ async function validateJsonFile(jsonFile) {
} }
} }
async function uploadData(apiUrl, jsonFile, jsonData) { function isLocalhost(url) {
console.log('Uploading sample data to JMESPath Playground...');
console.log(`JSON file: ${jsonFile}`);
console.log(`API URL: ${apiUrl}`);
console.log('');
try { try {
const response = await fetch(`${apiUrl}/api/v1/upload`, { const parsed = new URL(url);
method: 'POST', const hostname = parsed.hostname;
headers: { 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', '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: headers,
body: jsonData body: jsonData
}); });
if (!response.ok) { 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!'); const result = await response.json();
console.log(`Open ${apiUrl} in your browser to see the reload button.`); console.log(JSON.stringify(result));
console.log('You can then enter your JMESPath expression in the web interface.');
} catch (error) { } catch (error) {
console.error('Error uploading data:', error.message); console.error('Error uploading data:', error.message);
@@ -110,9 +160,9 @@ async function uploadData(apiUrl, jsonFile, jsonData) {
} }
async function main() { async function main() {
const { apiUrl, jsonFile } = parseArguments(); const { apiUrl, apiKey, jsonFile } = getArguments();
const jsonData = await validateJsonFile(jsonFile); const jsonData = await validateJsonFile(jsonFile);
await uploadData(apiUrl, jsonFile, jsonData); await uploadData(apiUrl, apiKey, jsonFile, jsonData);
} }
// Run the script // Run the script

View File

@@ -1,82 +0,0 @@
#!/bin/bash
# JMESPath Playground Upload Script
# Usage: ./upload.sh [-u URL] "json_file.json"
show_usage() {
echo "Usage: $0 [-u|--url URL] <json_file>"
echo ""
echo "Options:"
echo " -u, --url URL API URL (default: http://localhost:3000)"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 data.json"
echo " $0 -u http://example.com:3000 data.json"
}
# Parse command line options
API_URL="http://localhost:3000"
JSON_FILE=""
while [[ $# -gt 0 ]]; do
case $1 in
-u|--url)
API_URL="$2"
shift 2
;;
-h|--help)
show_usage
exit 0
;;
-*)
echo "Error: Unknown option $1"
show_usage
exit 1
;;
*)
if [ -z "$JSON_FILE" ]; then
JSON_FILE="$1"
else
echo "Error: Multiple JSON files specified"
exit 1
fi
shift
;;
esac
done
if [ -z "$JSON_FILE" ]; then
echo "Error: JSON file required"
show_usage
exit 1
fi
if [ ! -f "$JSON_FILE" ]; then
echo "Error: JSON file '$JSON_FILE' not found"
exit 1
fi
# Validate JSON with jq if available
if command -v jq >/dev/null 2>&1; then
if ! jq . "$JSON_FILE" >/dev/null 2>&1; then
echo "Error: '$JSON_FILE' contains invalid JSON"
exit 1
fi
fi
echo "Uploading sample data to JMESPath Playground..."
echo "JSON file: $JSON_FILE"
echo "API URL: $API_URL"
echo
# Upload the JSON data
curl -s -X POST \
-H "Content-Type: application/json" \
--data @"$JSON_FILE" \
"$API_URL/api/v1/upload"
echo
echo "Sample data uploaded successfully!"
echo "Open $API_URL in your browser to see the reload button."
echo "You can then enter your JMESPath expression in the web interface."

View File

@@ -22,15 +22,15 @@ try {
console.log(`✅ Building release version ${version} (tagged: ${gitTag})`); console.log(`✅ Building release version ${version} (tagged: ${gitTag})`);
isRelease = true; isRelease = true;
} else { } else {
// We're not at a tagged commit - add -dev suffix // We're not at a tagged commit - use unknown version
version = `${version}-dev`; version = 'unknown';
console.log(`📦 Building development version ${version}`); console.log(`📦 Building development version with unknown version`);
isRelease = false; isRelease = false;
} }
} catch (error) { } catch (error) {
// Git command failed (maybe not in a git repo) // Git command failed (maybe not in a git repo)
version = `${version}-dev`; version = 'unknown';
console.log(`⚠️ Cannot determine git status, using development version ${version}`); console.log(`⚠️ Cannot determine git status, using unknown version`);
isRelease = false; isRelease = false;
} }

View File

@@ -1,7 +1,9 @@
const express = require('express'); const express = require('express');
const path = require('path'); const path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const os = require('os');
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
const { parseArgs } = require('util');
// Environment configuration // Environment configuration
const MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS) || 100; const MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS) || 100;
@@ -357,9 +359,9 @@ function createApp() {
} }
}); });
// Health endpoint (no auth required) // Status endpoint (no auth required) - detailed information
app.get('/api/v1/health', (req, res) => { app.get('/api/v1/status', (req, res) => {
cleanupExpiredSessions(); // Cleanup on health check cleanupExpiredSessions(); // Cleanup on status check
res.json({ res.json({
status: 'healthy', status: 'healthy',
sessions: { 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 // Serve React app for all other routes
app.get('*', (req, res) => { app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html')); res.sendFile(path.join(__dirname, 'build', 'index.html'));
@@ -386,37 +393,62 @@ function createApp() {
// Start server if this file is run directly // Start server if this file is run directly
if (require.main === module) { if (require.main === module) {
// Parse command line arguments const { values } = parseArgs({
const args = process.argv.slice(2); options: {
let listenAddr = process.env.LISTEN_ADDR || '127.0.0.1'; 'listen-addr': { type: 'string', short: 'h', default: process.env.LISTEN_ADDR || '127.0.0.1' },
let listenPort = process.env.LISTEN_PORT || 3000; 'port': { type: 'string', short: 'p', default: 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 app = createApp(); const app = createApp();
const PORT = parseInt(listenPort); const PORT = parseInt(values.port);
const HOST = listenAddr; const HOST = values['listen-addr'];
app.listen(PORT, HOST, () => { app.listen(PORT, HOST, () => {
console.log(`🚀 JMESPath Playground Server running on http://${HOST}:${PORT}`); console.log(`JMESPath Playground Server running`);
console.log(`📊 Configuration:`);
// 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 Sessions: ${MAX_SESSIONS}`);
console.log(` Max Sample Size: ${(MAX_SAMPLE_SIZE / 1024 / 1024).toFixed(1)}MB`); 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(` 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)`); // Show base API URL
console.log(` GET http://${HOST}:${PORT}/api/v1/sample (requires X-API-Key)`); let apiBaseUrl;
console.log(` GET http://${HOST}:${PORT}/api/v1/state (requires X-API-Key)`); if (HOST === '0.0.0.0') {
console.log(` GET http://${HOST}:${PORT}/api/v1/health (public)`); const interfaces = os.networkInterfaces();
console.log(`🔐 Security: AES-256-GCM encryption with PBKDF2 key derivation`); 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`);
}); });
} }

View File

@@ -55,16 +55,19 @@ describe('App Component', () => {
test('renders version number', () => { test('renders version number', () => {
render(<App />); 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(); expect(versionText).toBeInTheDocument();
// Check if it's a dev/test build // Check if it's a dev/test/unknown build
const isDevBuild = versionText.textContent.includes('-dev') || versionText.textContent.includes('-test'); const isDevBuild = versionText.textContent.includes('-dev') ||
versionText.textContent.includes('-test') ||
versionText.textContent.includes('unknown');
// Additional validations can be added here based on build type // Additional validations can be added here based on build type
if (isDevBuild) { if (isDevBuild) {
// Dev/test specific validations could go here // Dev/test/unknown specific validations
expect(versionText.textContent).toMatch(/v\d+\.\d+\.\d+-(dev|test)/); expect(versionText.textContent).toMatch(/(v\d+\.\d+\.\d+-(dev|test)|unknown)/);
} else { } else {
// Release build validations - just check that version pattern exists in the text // Release build validations - just check that version pattern exists in the text
expect(versionText.textContent).toMatch(/v\d+\.\d+\.\d+/); expect(versionText.textContent).toMatch(/v\d+\.\d+\.\d+/);

View File

@@ -8,7 +8,7 @@ function Footer() {
<div className="row"> <div className="row">
<div className="col-md-6"> <div className="col-md-6">
<p className="mb-0 text-muted small"> <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> </p>
</div> </div>
<div className="col-md-6 text-md-end"> <div className="col-md-6 text-md-end">