Microservice Scaffolder
Run ID: 69bca5d277c0421c0bf499b32026-03-29Development
PantheraHive BOS
BOS Dashboard

PantheraHive Workflow Execution: Microservice Scaffolder (Step 1 of 2: Generate)

The "Microservice Scaffolder" workflow has successfully generated the comprehensive structure and initial files for your "Test Service Name" microservice, leveraging Node.js (Express) with MongoDB, Docker, and Kubernetes for deployment.


1. Project Structure

The following directory and file structure has been generated for your microservice:

text • 1,511 chars
    *Note: Ingress requires an Ingress Controller (e.g., Nginx Ingress) to be installed in your cluster.*

## CI/CD

The `.github/workflows/` directory contains GitHub Actions workflows:

-   `build-and-test.yml`: Triggers on push to `main` and pull requests. It builds the Docker image and runs tests.
-   `deploy-k8s.yml`: Triggers on push to `main` after `build-and-test.yml` passes. It pushes the Docker image to a registry and applies Kubernetes manifests.
    *You will need to configure GitHub Secrets for your Docker registry credentials and Kubernetes access.*

## Project Structure

-   `src/`: Contains the core application logic.
    -   `app.js`: Express application setup.
    -   `server.js`: Application entry point.
    -   `config/`: Environment-specific configurations and database connection.
    -   `models/`: Mongoose schemas.
    -   `routes/`: API route definitions.
    -   `controllers/`: Request handlers for routes.
    -   `middleware/`: Custom Express middleware (e.g., error handling).
    -   `utils/`: Utility functions and custom error classes.
-   `tests/`: Unit and integration tests.
-   `docker/`: Docker Compose configuration for local development.
-   `k8s/`: Kubernetes deployment manifests.
-   `.github/workflows/`: GitHub Actions CI/CD pipelines.

## Environment Variables

The service relies on environment variables for configuration. See `.env.example` for a list of required variables.

## Linting

This project uses ESLint for code linting.

To lint your code:
Sandboxed live preview

javascript

const request = require('supertest');

const app = require('../../src/app');

const TestItem = require('../../src/models/TestItem');

describe('TestItem API Endpoints', () => {

// Test GET all items

it('should get all test items', async () => {

await TestItem.create({ name: 'Item 1', description: 'Desc 1' });

await TestItem.create({ name: 'Item 2', description: 'Desc 2' });

const res = await request(app).get('/api/v1/test-items');

expect(res.statusCode).toEqual(200);

expect(res.body.status).toBe('success');

expect(res.body.results).toBe(2);

expect(res.body.data.data.length).toBe(2);

Step 2: projectmanager

The "Microservice Scaffolder" workflow has successfully generated a comprehensive microservice blueprint based on your specifications. Below is the detailed output, including project structure, core files, API routes, database models, testing setup, Dockerization, CI/CD configuration, and Kubernetes deployment scripts.


Microservice Scaffolding Complete: Test Service Name

Your new Node.js (Express) microservice, "Test Service Name," is ready! This output provides a complete foundation for development, including API structure, MongoDB integration, testing, Docker setup, and Kubernetes deployment configurations.


1. Project Structure


.
├── .github/
│   └── workflows/
│       └── main.yml
├── k8s/
│   ├── configmap.yaml
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
├── src/
│   ├── config/
│   │   ├── db.js
│   │   └── index.js
│   ├── controllers/
│   │   └── example.controller.js
│   ├── middlewares/
│   │   ├── auth.middleware.js
│   │   └── error.middleware.js
│   ├── models/
│   │   └── example.model.js
│   ├── routes/
│   │   └── api/
│   │       └── v1/
│   │           ├── example.routes.js
│   │           └── health.routes.js
│   ├── services/
│   │   └── example.service.js
│   ├── tests/
│   │   ├── integration/
│   │   │   └── example.api.test.js
│   │   └── unit/
│   │       └── example.service.test.js
│   ├── utils/
│   │   └── logger.js
│   └── app.js
├── .dockerignore
├── .env.example
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── jest.config.js
├── Makefile
├── package.json
├── README.md
└── server.js

2. Core Service Files (Node.js Express)

package.json


{
  "name": "test-service-name",
  "version": "1.0.0",
  "description": "A microservice for Test Service Name",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "cross-env NODE_ENV=development nodemon server.js",
    "test": "cross-env NODE_ENV=test jest --detectOpenHandles --forceExit",
    "test:watch": "cross-env NODE_ENV=test jest --watchAll",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  },
  "keywords": [
    "microservice",
    "node",
    "express",
    "mongodb"
  ],
  "author": "PantheraHive AI Assistant",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "helmet": "^7.1.0",
    "mongoose": "^8.4.1",
    "morgan": "^1.10.0",
    "winston": "^3.13.0"
  },
  "devDependencies": {
    "cross-env": "^7.0.3",
    "eslint": "^8.57.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-prettier": "^5.1.3",
    "jest": "^29.7.0",
    "nodemon": "^3.1.3",
    "prettier": "^3.3.1",
    "supertest": "^7.0.0"
  },
  "jest": {
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": [
      "/node_modules/"
    ]
  }
}

.env.example


# Application Configuration
PORT=3000
NODE_ENV=development # development, production, test

# MongoDB Configuration
MONGODB_URI=mongodb://localhost:27017/test_service_db_dev
MONGODB_URI_TEST=mongodb://localhost:27017/test_service_db_test

# JWT Secret (for authentication, if implemented)
JWT_SECRET=your_super_secret_jwt_key
JWT_EXPIRES_IN=1h

# Other example configurations
SERVICE_NAME="Test Service Name"
LOG_LEVEL=info # debug, info, warn, error

server.js


require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/db');
const logger = require('./src/utils/logger');
const config = require('./src/config');

const PORT = config.port;

// Connect to MongoDB
connectDB();

// Start the server
const server = app.listen(PORT, () => {
  logger.info(`Server running on port ${PORT} in ${config.env} mode`);
  logger.info(`Access API at http://localhost:${PORT}/api/v1`);
});

// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
  logger.error('UNHANDLED REJECTION! 💥 Shutting down...');
  logger.error(err.name, err.message);
  server.close(() => {
    process.exit(1);
  });
});

// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
  logger.error('UNCAUGHT EXCEPTION! 💥 Shutting down...');
  logger.error(err.name, err.message);
  process.exit(1);
});

src/app.js


const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const config = require('./config');
const { errorHandler } = require('./middlewares/error.middleware');
const apiV1Routes = require('./routes/api/v1'); // Assuming an index.js in v1 folder
const logger = require('./utils/logger');

const app = express();

// Security Middleware
app.use(helmet());

// CORS
app.use(cors());

// Request logging (Morgan)
if (config.env === 'development') {
  app.use(morgan('dev'));
} else {
  app.use(morgan('combined', { stream: { write: message => logger.info(message.trim()) } }));
}

// Body parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// API V1 Routes
app.use('/api/v1', apiV1Routes);

// Catch-all for undefined routes
app.use((req, res, next) => {
  res.status(404).json({
    status: 'fail',
    message: `Can't find ${req.originalUrl} on this server!`,
  });
});

// Global Error Handling Middleware
app.use(errorHandler);

module.exports = app;

src/config/index.js


const dotenv = require('dotenv');
dotenv.config();

module.exports = {
  env: process.env.NODE_ENV || 'development',
  port: process.env.PORT || 3000,
  mongodb: {
    uri: process.env.NODE_ENV === 'test' ? process.env.MONGODB_URI_TEST : process.env.MONGODB_URI,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN,
  },
  serviceName: process.env.SERVICE_NAME || 'Test Service Name',
  logLevel: process.env.LOG_LEVEL || 'info',
};

src/utils/logger.js


const winston = require('winston');
const config = require('../config');

const enumerateErrorFormat = winston.format((info) => {
  if (info instanceof Error) {
    Object.assign(info, { message: info.stack });
  }
  return info;
});

const logger = winston.createLogger({
  level: config.logLevel,
  format: winston.format.combine(
    enumerateErrorFormat(),
    config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
    winston.format.splat(),
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.printf(({ level, message, timestamp }) => `${timestamp} ${level}: ${message}`)
  ),
  transports: [
    new winston.transports.Console({
      stderrLevels: ['error'],
    }),
  ],
});

module.exports = logger;

src/middlewares/error.middleware.js


const logger = require('../utils/logger');
const config = require('../config');

const errorHandler = (err, req, res, next) => {
  let { statusCode, message } = err;

  if (config.env === 'production' && !err.isOperational) {
    statusCode = 500;
    message = 'Internal Server Error';
  }

  res.locals.errorMessage = err.message;

  const response = {
    code: statusCode,
    message,
    ...(config.env === 'development' && { stack: err.stack }),
  };

  if (config.env === 'development') {
    logger.error(err);
  }

  res.status(statusCode).send(response);
};

module.exports = {
  errorHandler,
};

src/middlewares/auth.middleware.js (Example)


const jwt = require('jsonwebtoken');
const config = require('../config');

const protect = (req, res, next) => {
  let token;
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
    token = req.headers.authorization.split(' ')[1];
  }

  if (!token) {
    return res.status(401).json({ message: 'Not authorized, no token' });
  }

  try {
    const decoded = jwt.verify(token, config.jwt.secret);
    // Attach user to req object if user data is available in the token
    // req.user = decoded.id; 
    next();
  } catch (error) {
    res.status(401).json({ message: 'Not authorized, token failed' });
  }
};

// Example for role-based authorization
const authorize = (...roles) => {
  return (req, res, next) => {
    // Assuming req.user exists from the 'protect' middleware and has a 'role' property
    if (!req.user || !roles.includes(req.user.role)) {
      return res.status(403).json({ message: 'Forbidden, insufficient permissions' });
    }
    next();
  };
};

module.exports = { protect, authorize };

3. API Routes

src/routes/api/v1/index.js (Entry point for V1 API)


const express = require('express');
const healthRoutes = require('./health.routes');
const exampleRoutes = require('./example.routes');

const router = express.Router();

router.use('/health', healthRoutes);
router.use('/examples', exampleRoutes);

module.exports = router;

src/routes/api/v1/health.routes.js


const express = require('express');
const router = express.Router();

/**
 * @route GET /api/v1/health
 * @description Health check endpoint
 * @access Public
 */
router.get('/', (req, res) => {
  res.status(200).json({
    status: 'ok',
    message: 'Test Service Name is healthy!',
    timestamp: new Date().toISOString(),
  });
});

module.exports = router;

src/routes/api/v1/example.routes.js


const express = require('express');
const exampleController = require('../../controllers/example.controller');
const { protect } = require('../../middlewares/auth.middleware'); // Assuming authentication is needed

const router = express.Router();

router
  .route('/')
  .post(protect, exampleController.createExample) // Example: requires authentication
  .get(exampleController.getAllExamples); // Example: public access

router
  .route('/:id')
  .get(exampleController.getExampleById)
  .put(protect, exampleController.updateExample)
  .delete(protect, exampleController.deleteExample);

module.exports = router;

src/controllers/example.controller.js


const exampleService = require('../services/example.service');
const logger = require('../utils/logger');

// Example: Create a new example
exports.createExample = async (req, res, next) => {
  try {
    const example = await exampleService.createExample(req.body);
    res.status(201).json({
      status: 'success',
      data: example,
    });
  } catch (error) {
    logger.error('Error creating example:', error);
    next(error);
  }
};

// Example: Get all examples
exports.getAllExamples = async (req, res, next) => {
  try {
    const examples = await exampleService.getAllExamples();
    res.status(200).json({
      status: 'success',
      results: examples.length,
      data: examples,
    });
  } catch (error) {
    logger.error('Error fetching all examples:', error);
    next(error);
  }
};

// Example: Get an example by ID
exports.getExampleById = async (req, res, next) => {
  try {
    const example = await exampleService.getExampleById(req.params.id);
    if (!example) {
      return res.status(404).json({
        status: 'fail',
        message: 'Example not found',
      });
    }
    res.status(200).json({
      status: 'success',
      data: example,
    });
  } catch (error) {
    logger.error(`Error fetching example with ID ${req.params.id}:`, error);
    next(error);
  }
};

// Example: Update an example
exports.updateExample = async (req, res, next) => {
  try {
    const example = await exampleService.updateExample(req.params.id, req.body);
    if (!example) {
      return res.status(404).json({
        status: 'fail',
        message: 'Example not found',
      });
    }
    res.status(200).json({
      status: 'success',
      data: example,
    });
  } catch (error) {
    logger.error(`Error updating example with ID ${req.params.id}:`, error);
    next(error);
  }
};

// Example: Delete an example
exports.deleteExample = async (req, res, next) => {
  try {
    const example = await exampleService.deleteExample(req.params.id);
    if (!example) {
      return res.status(404).json({
        status: 'fail',
        message: 'Example not found',
      });
    }
    res.status(204).json({
      status: 'success',
      data: null, // No content for 204
    });
  } catch (error) {
    logger.error(`Error deleting example with ID ${req.params.id}:`, error);
    next(error);
  }
};

src/services/example.service.js


const Example = require('../models/example.model');

// Service function to create a new example
exports.createExample = async (exampleData) => {
  const example = new Example(exampleData);
  await example.save();
  return example;
};

// Service function to get all examples
exports.getAllExamples = async () => {
  const examples = await Example.find();
  return examples;
};

// Service function to get an example by ID
exports.getExampleById = async (id) => {
  const example = await Example.findById(id);
  return example;
};

// Service function to update an example
exports.updateExample = async (id, updateData) => {
  const example = await Example.findByIdAndUpdate(id, updateData, { new: true, runValidators: true });
  return example;
};

// Service function to delete an example
exports.deleteExample = async (id) => {
  const example = await Example.findByIdAndDelete(id);
  return example;
};

4. Database Models (MongoDB with Mongoose)

src/config/db.js


const mongoose = require('mongoose');
const config = require('./index');
const logger = require('../utils/logger');

const connectDB = async () => {
  try {
    await mongoose.connect(config.mongodb.uri, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    logger.info('MongoDB connected successfully!');
  } catch (err) {
    logger.error('MongoDB connection error:', err);
    process.exit(1); // Exit process with failure
  }
};

module.exports = connectDB;

src/models/example.model.js


const mongoose = require('mongoose');

const exampleSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, 'Example name is required'],
      trim: true,
      minlength: [3, 'Name must be at least 3 characters long'],
      maxlength: [100, 'Name cannot exceed 100 characters'],
    },
    description: {
      type: String,
      trim: true,
      maxlength: [500, 'Description cannot exceed 500 characters'],
    },
    status: {
      type: String,
      enum: ['active', 'inactive', 'pending'],
      default: 'active',
    },
    // Add more fields as needed
  },
  {
    timestamps: true, // Adds createdAt and updatedAt fields
  }
);

const Example = mongoose.model('Example', exampleSchema);

module.exports = Example;

5. Tests (Jest & Supertest)

jest.config.js


module.exports = {
  testEnvironment: 'node',
  // Automatically clear mock calls and instances between every test
  clearMocks: true,
  // The directory where Jest should output its coverage files
  coverageDirectory: 'coverage',
  // An array of glob patterns that Jest should use to find test files
  testMatch: [
    '**/src/tests/**/*.test.js',
  ],
  // Ignore coverage for node_modules and test files themselves
  coveragePathIgnorePatterns: [
    '/node_modules/',
    'src/tests/',
  ],
  // Setup file to run before all tests
  setupFilesAfterEnv: ['./src/tests/setupTests.js'],
};

src/tests/setupTests.js


// This file can be used for global test setup, e.g., connecting/disconnecting from a test database
const mongoose = require('mongoose');
const config = require('../config');
const logger = require('../utils/logger');

// Ensure MongoDB connection for tests
beforeAll(async () => {
  if (mongoose.connection.readyState === 0) {
    await mongoose.connect(config.mongodb.uri, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    logger.info('MongoDB Test DB connected.');
  }
});

// Clear the database after each test
afterEach(async () => {
  await mongoose.connection.db.dropDatabase();
});

// Disconnect from MongoDB after all tests are done
afterAll(async () => {
  await mongoose.disconnect();
  logger.info('MongoDB Test DB disconnected.');
});

src/tests/unit/example.service.test.js


const Example = require('../../models/example.model');
const exampleService = require('../../services/example.service');
const mongoose = require('mongoose');

// Mock the Example model to prevent actual database interaction for unit tests
jest.mock('../../models/example.model');

describe('Example Service', () => {
  beforeEach(() => {
    // Clear all mocks before each test
    jest.clearAllMocks();
  });

  describe('createExample', () => {
    it('should create and return a new example', async () => {
      const exampleData = { name: 'Test Example', description: 'A description' };
      const mockExample = { ...exampleData, _id: new mongoose.Types.ObjectId(), save: jest.fn().mockResolvedValue(true) };
      
      Example.mockImplementation(() => mockExample);

      const result = await exampleService.createExample(exampleData);

      expect(Example).toHaveBeenCalledWith(exampleData);
      expect(mockExample.save).toHaveBeenCalledTimes(1);
      expect(result).toEqual(mockExample);
    });
  });

  describe('getAllExamples', () => {
    it('should return all examples', async () => {
      const mockExamples = [{ name: 'Ex1' }, { name: 'Ex2' }];
      Example.find.mockResolvedValue(mockExamples);

      const result = await exampleService.getAllExamples();

      expect(Example.find).toHaveBeenCalledTimes(1);
      expect(result).toEqual(mockExamples);
    });
  });

  // Add more unit tests for getExampleById, updateExample, deleteExample
});

src/tests/integration/example.api.test.js


const request = require('supertest');
const app = require('../../app');
const Example = require('../../models/example.model');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const config = require('../../config');

// Helper to generate a test token (for authenticated routes)
const generateAuthToken = (userId = new mongoose.Types.ObjectId(), role = 'user') => {
  return jwt.sign({ id: userId, role }, config.jwt.secret, { expiresIn: '1h' });
};

describe('Example API Integration Tests', () => {
  let authToken;
  let testExampleId;

  beforeAll(async () => {
    // Generate a token for authenticated requests
    authToken = generateAuthToken();
  });

  beforeEach(async () => {
    // Clean up DB and create a fresh example for each test
    await Example.deleteMany({});
    const example = await Example.create({ name: 'Initial Example', description: 'Description for initial example' });
    testExampleId = example._id;
  });

  describe('GET /api/v1/examples', () => {
    it('should return all examples', async () => {
      const res = await request(app).get('/api/v1/examples');
      expect(res.statusCode).toEqual(200);
      expect(res.body.status).toEqual('success');
      expect(res.body.results).toBe(1);
      expect(res.body.data[0].name).toEqual('Initial Example');
    });
  });

  describe('POST /api/v1/examples', () => {
    it('should create a new example if authenticated', async () => {
      const newExample = { name: 'New Example', description: 'A brand new example' };
      const res = await request(app)
        .post('/api/v1/examples')
        .set('Authorization', `Bearer ${authToken}`)
        .send(newExample);

      expect(res.statusCode).toEqual(201);
      expect(res.body.status).toEqual('success');
      expect(res.body.data.name).toEqual(newExample.name);
      const count = await Example.countDocuments();
      expect(count).toBe(2); // Initial + new
    });

    it('should return 401 if not authenticated', async () => {
      const newExample = { name: 'Unauthorized Example' };
      const res = await request(app)
        .post('/api/v1/examples')
        .send(newExample);

      expect(res.statusCode).toEqual(401);
      expect(res.body.message).toEqual('Not authorized, no token');
    });
  });

  describe('GET /api/v1/examples/:id', () => {
    it('should return an example by ID', async () => {
      const res = await request(app).get(`/api/v1/examples/${testExampleId}`);
      expect(res.statusCode).toEqual(200);
      expect(res.body.status).toEqual('success');
      expect(res.body.data.name).toEqual('Initial Example');
    });

    it('should return 404 if example not found', async () => {
      const nonExistentId = new mongoose.Types.ObjectId();
      const res = await request(app).get(`/api/v1/examples/${nonExistentId}`);
      expect(res.statusCode).toEqual(404);
      expect(res.body.message).toEqual('Example not found');
    });
  });

  // Add more integration tests for PUT and DELETE
});

6. Docker Setup

.dockerignore


node_modules
npm-debug.log
.env
.git
.gitignore
Dockerfile
docker-compose.yml
README.md
coverage
src/tests

Dockerfile


# Stage 1: Build the application
FROM node:20-alpine AS builder

WORKDIR /app

# Copy package.json and package-lock.json first to leverage Docker cache
COPY package*.json ./

# Install dependencies
RUN npm install --production

# Copy the rest of the application code
COPY . .

# Stage 2: Create the production-ready image
FROM node:20-alpine

WORKDIR /app

# Copy only necessary files from the builder stage
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/server.js ./server.js
COPY --from=builder /app/src ./src
COPY --from=builder /app/.env.example ./.env.example

# Expose the port the app runs on
EXPOSE 3000

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Command to run the application
CMD ["node", "server.js"]

docker-compose.yml


version: '3.8'

services:
  app:
    container_name: test-service-name-app
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      # Load variables from .env file. For production, consider using Kubernetes Secrets or ConfigMaps.
      # These are default values, you can override them in .env
      MONGODB_URI: mongodb://mongodb:27017/test_service_db_dev
      PORT: 3000
      NODE_ENV: development
      JWT_SECRET: ${JWT_SECRET} # Ensure JWT_SECRET is set in your host's .env file
    depends_on:
      - mongodb
    volumes:
      - .:/app
      - /app/node_modules # Prevents host's node_modules from overwriting container's
    command: npm run dev # Use nodemon for development auto-reloads

  mongodb:
    container_name: test-service-name-mongodb
    image: mongo:latest
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:-password}
      MONGO_INITDB_DATABASE: test_service_db_dev # Initial database

volumes:
  mongodb_data:

7. CI/CD Pipeline Configuration (GitHub Actions Example)

.github/workflows/main.yml


name: CI/CD Pipeline

on:
  push:
    branches:
      - main
      - develop
  pull_request:
    branches:
      - main
      - develop

env:
  SERVICE_NAME: test-service-name
  REGISTRY: ghcr.io # GitHub Container Registry
  IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/${{ env.SERVICE_NAME }}

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Set
microservice_scaffolder.txt
Download source file
Copy all content
Full output as text
Download ZIP
IDE-ready project ZIP
Copy share link
Permanent URL for this run
Get Embed Code
Embed this result on any website
Print / Save PDF
Use browser print dialog
\n\n\n"); var hasSrcMain=Object.keys(extracted).some(function(k){return k.indexOf("src/main")>=0;}); if(!hasSrcMain) zip.file(folder+"src/main."+ext,"import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n \n \n \n)\n"); var hasSrcApp=Object.keys(extracted).some(function(k){return k==="src/App."+ext||k==="App."+ext;}); if(!hasSrcApp) zip.file(folder+"src/App."+ext,"import React from 'react'\nimport './App.css'\n\nfunction App(){\n return(\n
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n
\n )\n}\nexport default App\n"); zip.file(folder+"src/index.css","*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;color:#1a1a2e}\n.app{min-height:100vh;display:flex;flex-direction:column}\n.app-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px}\nh1{font-size:2.5rem;font-weight:700}\n"); zip.file(folder+"src/App.css",""); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/pages/.gitkeep",""); zip.file(folder+"src/hooks/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\n## Open in IDE\nOpen the project folder in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Vue (Vite + Composition API + TypeScript) --- */ function buildVue(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vue-tsc -b && vite build",\n "preview": "vite preview"\n },\n "dependencies": {\n "vue": "^3.5.13",\n "vue-router": "^4.4.5",\n "pinia": "^2.3.0",\n "axios": "^1.7.9"\n },\n "devDependencies": {\n "@vitejs/plugin-vue": "^5.2.1",\n "typescript": "~5.7.3",\n "vite": "^6.0.5",\n "vue-tsc": "^2.2.0"\n }\n}\n'); zip.file(folder+"vite.config.ts","import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { resolve } from 'path'\n\nexport default defineConfig({\n plugins: [vue()],\n resolve: { alias: { '@': resolve(__dirname,'src') } }\n})\n"); zip.file(folder+"tsconfig.json",'{"files":[],"references":[{"path":"./tsconfig.app.json"},{"path":"./tsconfig.node.json"}]}\n'); zip.file(folder+"tsconfig.app.json",'{\n "compilerOptions":{\n "target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],\n "skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,\n "isolatedModules":true,"moduleDetection":"force","noEmit":true,"jsxImportSource":"vue",\n "strict":true,"paths":{"@/*":["./src/*"]}\n },\n "include":["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.vue"]\n}\n'); zip.file(folder+"env.d.ts","/// \n"); zip.file(folder+"index.html","\n\n\n \n \n "+slugTitle(pn)+"\n\n\n
\n \n\n\n"); var hasMain=Object.keys(extracted).some(function(k){return k==="src/main.ts"||k==="main.ts";}); if(!hasMain) zip.file(folder+"src/main.ts","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport './assets/main.css'\n\nconst app = createApp(App)\napp.use(createPinia())\napp.mount('#app')\n"); var hasApp=Object.keys(extracted).some(function(k){return k.indexOf("App.vue")>=0;}); if(!hasApp) zip.file(folder+"src/App.vue","\n\n\n\n\n"); zip.file(folder+"src/assets/main.css","*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,sans-serif;background:#fff;color:#213547}\n"); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/views/.gitkeep",""); zip.file(folder+"src/stores/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\nOpen in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Angular (v19 standalone) --- */ function buildAngular(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var sel=pn.replace(/_/g,"-"); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "scripts": {\n "ng": "ng",\n "start": "ng serve",\n "build": "ng build",\n "test": "ng test"\n },\n "dependencies": {\n "@angular/animations": "^19.0.0",\n "@angular/common": "^19.0.0",\n "@angular/compiler": "^19.0.0",\n "@angular/core": "^19.0.0",\n "@angular/forms": "^19.0.0",\n "@angular/platform-browser": "^19.0.0",\n "@angular/platform-browser-dynamic": "^19.0.0",\n "@angular/router": "^19.0.0",\n "rxjs": "~7.8.0",\n "tslib": "^2.3.0",\n "zone.js": "~0.15.0"\n },\n "devDependencies": {\n "@angular-devkit/build-angular": "^19.0.0",\n "@angular/cli": "^19.0.0",\n "@angular/compiler-cli": "^19.0.0",\n "typescript": "~5.6.0"\n }\n}\n'); zip.file(folder+"angular.json",'{\n "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\n "version": 1,\n "newProjectRoot": "projects",\n "projects": {\n "'+pn+'": {\n "projectType": "application",\n "root": "",\n "sourceRoot": "src",\n "prefix": "app",\n "architect": {\n "build": {\n "builder": "@angular-devkit/build-angular:application",\n "options": {\n "outputPath": "dist/'+pn+'",\n "index": "src/index.html",\n "browser": "src/main.ts",\n "tsConfig": "tsconfig.app.json",\n "styles": ["src/styles.css"],\n "scripts": []\n }\n },\n "serve": {"builder":"@angular-devkit/build-angular:dev-server","configurations":{"production":{"buildTarget":"'+pn+':build:production"},"development":{"buildTarget":"'+pn+':build:development"}},"defaultConfiguration":"development"}\n }\n }\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "compileOnSave": false,\n "compilerOptions": {"baseUrl":"./","outDir":"./dist/out-tsc","forceConsistentCasingInFileNames":true,"strict":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":true,"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"paths":{"@/*":["src/*"]},"skipLibCheck":true,"esModuleInterop":true,"sourceMap":true,"declaration":false,"experimentalDecorators":true,"moduleResolution":"bundler","importHelpers":true,"target":"ES2022","module":"ES2022","useDefineForClassFields":false,"lib":["ES2022","dom"]},\n "references":[{"path":"./tsconfig.app.json"}]\n}\n'); zip.file(folder+"tsconfig.app.json",'{\n "extends":"./tsconfig.json",\n "compilerOptions":{"outDir":"./dist/out-tsc","types":[]},\n "files":["src/main.ts"],\n "include":["src/**/*.d.ts"]\n}\n'); zip.file(folder+"src/index.html","\n\n\n \n "+slugTitle(pn)+"\n \n \n \n\n\n \n\n\n"); zip.file(folder+"src/main.ts","import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nbootstrapApplication(AppComponent, appConfig)\n .catch(err => console.error(err));\n"); zip.file(folder+"src/styles.css","* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; color: #111827; }\n"); var hasComp=Object.keys(extracted).some(function(k){return k.indexOf("app.component")>=0;}); if(!hasComp){ zip.file(folder+"src/app/app.component.ts","import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [RouterOutlet],\n templateUrl: './app.component.html',\n styleUrl: './app.component.css'\n})\nexport class AppComponent {\n title = '"+pn+"';\n}\n"); zip.file(folder+"src/app/app.component.html","
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n \n
\n"); zip.file(folder+"src/app/app.component.css",".app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:16px}h1{font-size:2.5rem;font-weight:700;color:#6366f1}\n"); } zip.file(folder+"src/app/app.config.ts","import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes)\n ]\n};\n"); zip.file(folder+"src/app/app.routes.ts","import { Routes } from '@angular/router';\n\nexport const routes: Routes = [];\n"); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nng serve\n# or: npm start\n\`\`\`\n\n## Build\n\`\`\`bash\nng build\n\`\`\`\n\nOpen in VS Code with Angular Language Service extension.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n.angular/\n"); } /* --- Python --- */ function buildPython(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var reqMap={"numpy":"numpy","pandas":"pandas","sklearn":"scikit-learn","tensorflow":"tensorflow","torch":"torch","flask":"flask","fastapi":"fastapi","uvicorn":"uvicorn","requests":"requests","sqlalchemy":"sqlalchemy","pydantic":"pydantic","dotenv":"python-dotenv","PIL":"Pillow","cv2":"opencv-python","matplotlib":"matplotlib","seaborn":"seaborn","scipy":"scipy"}; var reqs=[]; Object.keys(reqMap).forEach(function(k){if(src.indexOf("import "+k)>=0||src.indexOf("from "+k)>=0)reqs.push(reqMap[k]);}); var reqsTxt=reqs.length?reqs.join("\n"):"# add dependencies here\n"; zip.file(folder+"main.py",src||"# "+title+"\n# Generated by PantheraHive BOS\n\nprint(title+\" loaded\")\n"); zip.file(folder+"requirements.txt",reqsTxt); zip.file(folder+".env.example","# Environment variables\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\`\`\`\n\n## Run\n\`\`\`bash\npython main.py\n\`\`\`\n"); zip.file(folder+".gitignore",".venv/\n__pycache__/\n*.pyc\n.env\n.DS_Store\n"); } /* --- Node.js --- */ function buildNode(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var depMap={"mongoose":"^8.0.0","dotenv":"^16.4.5","axios":"^1.7.9","cors":"^2.8.5","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2","socket.io":"^4.7.4","uuid":"^9.0.1","zod":"^3.22.4","express":"^4.18.2"}; var deps={}; Object.keys(depMap).forEach(function(k){if(src.indexOf(k)>=0)deps[k]=depMap[k];}); if(!deps["express"])deps["express"]="^4.18.2"; var pkgJson=JSON.stringify({"name":pn,"version":"1.0.0","main":"src/index.js","scripts":{"start":"node src/index.js","dev":"nodemon src/index.js"},"dependencies":deps,"devDependencies":{"nodemon":"^3.0.3"}},null,2)+"\n"; zip.file(folder+"package.json",pkgJson); var fallback="const express=require(\"express\");\nconst app=express();\napp.use(express.json());\n\napp.get(\"/\",(req,res)=>{\n res.json({message:\""+title+" API\"});\n});\n\nconst PORT=process.env.PORT||3000;\napp.listen(PORT,()=>console.log(\"Server on port \"+PORT));\n"; zip.file(folder+"src/index.js",src||fallback); zip.file(folder+".env.example","PORT=3000\n"); zip.file(folder+".gitignore","node_modules/\n.env\n.DS_Store\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\n\`\`\`\n\n## Run\n\`\`\`bash\nnpm run dev\n\`\`\`\n"); } /* --- Vanilla HTML --- */ function buildVanillaHtml(zip,folder,app,code){ var title=slugTitle(app); var isFullDoc=code.trim().toLowerCase().indexOf("=0||code.trim().toLowerCase().indexOf("=0; var indexHtml=isFullDoc?code:"\n\n\n\n\n"+title+"\n\n\n\n"+code+"\n\n\n\n"; zip.file(folder+"index.html",indexHtml); zip.file(folder+"style.css","/* "+title+" — styles */\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#fff;color:#1a1a2e}\n"); zip.file(folder+"script.js","/* "+title+" — scripts */\n"); zip.file(folder+"assets/.gitkeep",""); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Open\nDouble-click \`index.html\` in your browser.\n\nOr serve locally:\n\`\`\`bash\nnpx serve .\n# or\npython3 -m http.server 3000\n\`\`\`\n"); zip.file(folder+".gitignore",".DS_Store\nnode_modules/\n.env\n"); } /* ===== MAIN ===== */ var sc=document.createElement("script"); sc.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"; sc.onerror=function(){ if(lbl)lbl.textContent="Download ZIP"; alert("JSZip load failed — check connection."); }; sc.onload=function(){ var zip=new JSZip(); var base=(_phFname||"output").replace(/\.[^.]+$/,""); var app=base.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; var folder=app+"/"; var vc=document.getElementById("panel-content"); var panelTxt=vc?(vc.innerText||vc.textContent||""):""; var lang=detectLang(_phCode,panelTxt); if(_phIsHtml){ buildVanillaHtml(zip,folder,app,_phCode); } else if(lang==="flutter"){ buildFlutter(zip,folder,app,_phCode,panelTxt); } else if(lang==="react-native"){ buildReactNative(zip,folder,app,_phCode,panelTxt); } else if(lang==="swift"){ buildSwift(zip,folder,app,_phCode,panelTxt); } else if(lang==="kotlin"){ buildKotlin(zip,folder,app,_phCode,panelTxt); } else if(lang==="react"){ buildReact(zip,folder,app,_phCode,panelTxt); } else if(lang==="vue"){ buildVue(zip,folder,app,_phCode,panelTxt); } else if(lang==="angular"){ buildAngular(zip,folder,app,_phCode,panelTxt); } else if(lang==="python"){ buildPython(zip,folder,app,_phCode); } else if(lang==="node"){ buildNode(zip,folder,app,_phCode); } else { /* Document/content workflow */ var title=app.replace(/_/g," "); var md=_phAll||_phCode||panelTxt||"No content"; zip.file(folder+app+".md",md); var h=""+title+""; h+="

"+title+"

"; var hc=md.replace(/&/g,"&").replace(//g,">"); hc=hc.replace(/^### (.+)$/gm,"

$1

"); hc=hc.replace(/^## (.+)$/gm,"

$1

"); hc=hc.replace(/^# (.+)$/gm,"

$1

"); hc=hc.replace(/\*\*(.+?)\*\*/g,"$1"); hc=hc.replace(/\n{2,}/g,"

"); h+="

"+hc+"

Generated by PantheraHive BOS
"; zip.file(folder+app+".html",h); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\nFiles:\n- "+app+".md (Markdown)\n- "+app+".html (styled HTML)\n"); } zip.generateAsync({type:"blob"}).then(function(blob){ var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=app+".zip"; a.click(); URL.revokeObjectURL(a.href); if(lbl)lbl.textContent="Download ZIP"; }); }; document.head.appendChild(sc); } function phShare(){navigator.clipboard.writeText(window.location.href).then(function(){var el=document.getElementById("ph-share-lbl");if(el){el.textContent="Link copied!";setTimeout(function(){el.textContent="Copy share link";},2500);}});}function phEmbed(){var runId=window.location.pathname.split("/").pop().replace(".html","");var embedUrl="https://pantherahive.com/embed/"+runId;var code='';navigator.clipboard.writeText(code).then(function(){var el=document.getElementById("ph-embed-lbl");if(el){el.textContent="Embed code copied!";setTimeout(function(){el.textContent="Get Embed Code";},2500);}});}