2 Commits

16 changed files with 359 additions and 604 deletions

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

164
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
- `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
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in development mode. The page will reload when you make edits.
### `npm test`
Launches the test runner in interactive watch mode.
### `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
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"
``` ```
jmespath-playground/ 3. **Auto-reload**: The running app will detect new data and show a reload button
├── .github/
│ ├── workflows/ **API Endpoints:**
│ │ └── build-container.yml # CI/CD pipeline - `POST /api/v1/upload` - Upload sample data
│ └── copilot-instructions.md # AI agent instructions - `GET /api/v1/sample` - Retrieve current sample data
├── public/ - `GET /api/v1/state` - Get current state ID
│ ├── index.html - `GET /api/v1/health` - Simple health check (returns "OK")
│ ├── manifest.json - `GET /api/v1/status` - Detailed status information (JSON)
│ └── favicon.ico
├── src/ ## Server Configuration
│ ├── App.js # Main application component
│ ├── App.css # App-specific styles The server can be configured using environment variables:
│ ├── App.test.js # App tests
│ ├── index.js # React entry point **Network Settings:**
│ ├── index.css # Global styles - `LISTEN_ADDR` - Server bind address (default: `127.0.0.1`)
│ ├── setupTests.js # Test configuration - `LISTEN_PORT` - Server port (default: `3000`)
│ └── reportWebVitals.js
├── scripts/ **Session Management:**
│ ├── build.sh # Build script - `MAX_SESSIONS` - Maximum number of concurrent sessions (default: `100`)
│ └── dev.sh # Development script - `MAX_SAMPLE_SIZE` - Maximum size of uploaded sample data in bytes (default: `1048576` - 1MB)
├── Dockerfile # Docker container - `MAX_SESSION_TTL` - Session time-to-live in milliseconds (default: `3600000` - 1 hour)
├── Dockerfile.dev # Development container
├── docker-compose.yml # Container orchestration Example usage:
├── package.json # Dependencies and scripts
├── README.md # Comprehensive documentation ```bash
├── DEVELOPMENT.md # Developer guide MAX_SESSIONS=200 MAX_SAMPLE_SIZE=2097152 LISTEN_PORT=8080 node server.js
└── 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.3",
"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">