Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 80 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,81 @@
# opensource-template
# Mieweb OpenSource CI/CD Pipeline Generator

1) Replace this readme with a good one. [Working backwards](https://docs.google.com/document/d/1zxa0Rgq56xGHOgY51DbZJVUlWMRh2pbd6AupVYI9IZc)
2) use npx create to create the framework
3) Use API first thinking (no UI first)
An interactive, `npx`-executable CLI tool that generates highly customized, production-ready GitHub Actions pipelines for your projects.

Instead of starting from a generic boilerplate template, this tool asks you about your project's framework (Node.js, Meteor, React Native, etc.), your hosting preference (Proxmox/Bare-metal, Docker), and your mobile build targets (iOS, Android, Both). It then generates the exact YAML file and deployment scripts you need.

## 🚀 Quick Start

To generate a CI/CD pipeline, go to the root of your existing repository and run:

```bash
npx @mieweb/opensource-ci-cd-template
```

*No installation is required. This will securely launch the interactive CLI within your terminal.*

## 🛠️ How It Works

The CLI wizard will guide you through:
1. **Framework Selection:** E.g., Meteor (with Cordova), standard Node.js applications, Next.js, or React Native.
2. **Deployment Target:**
* **Proxmox / Bare-Metal:** Generates advanced deployment scripts (using `systemd`) ensuring zero-downtime symlink-based deployments over SSH.
* **Docker:** Scaffold standard Dockerfile/Docker Compose GitHub Actions flows.
* **None (Mobile-only):** If you just want to build and deploy to the App Store or Google Play.
3. **Mobile Builds:** Do you need iOS (App Store/TestFlight), Android (Play Store), both, or neither?
4. **Triggers:** Configure whether the pipeline runs on Git pushes to specific branches, release tags, or manual workflow dispatches.

## 📁 What Does It Generate?

Depending on your choices, the CLI creates the following structure right inside your repository:

```text
.github/
workflows/
ci-cd.yml # The customized GitHub Actions pipeline
scripts/ # (If Proxmox/Bare-metal selected)
setup-systemd.sh # One-time server bootstrapping script
start-app.sh # Zero-downtime restart handler
app.service # systemd service template
PIPELINE_SETUP.md # ⭐ Your personalized guide!
```

### ⭐ The `PIPELINE_SETUP.md` Guide
We don't just generate YAML files and leave you to figure it out. The generator writes a custom **Markdown Guide** tailored entirely to your specific answers.

It contains **exact, step-by-step terminal commands** instructing you how to:
- Generate a new remote SSH keypair for bare-metal deployments without a passphrase.
- Create an Android release Keystore (`.keystore`) and encode it to Base64.
- Generate an Apple Distribution Certificate (`.p12`) and Provisioning Profile for iOS builds.
- Create a Google Play API Service Account JSON for automatic release track publishing.

## 💻 Development & Testing Locally

Want to contribute to the CLI or test changes?

```bash
# Clone the repository
git clone https://github.com/mieweb/template-mieweb-opensource.git
cd template-mieweb-opensource

# Install dependencies
npm install

# Link the CLI globally so you can run it anywhere on your machine locally
npm link

# In a dummy project directory somewhere else on your machine:
opensource-ci-cd-template
```

## 📦 Publishing

To publish any updates to the npm registry:

```bash
npm login
npm publish --access public
```

---
*Created and maintained by the Mieweb Team.*
159 changes: 159 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env node

import inquirer from 'inquirer';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import ejs from 'ejs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

async function run() {
console.log(chalk.blue.bold('\n🚀 Welcome to the Mieweb Advanced CI/CD Pipeline Generator!\n'));
console.log(chalk.gray('This tool will generate production-ready, cache-optimized GitHub Actions workflows.'));
console.log(chalk.gray('It includes actual build scripts, deployment configurations, and mobile publishing steps.\n'));

const answers = await inquirer.prompt([
{
type: 'list',
name: 'framework',
message: 'What framework is your project using?',
choices: [
{ name: 'Meteor.js (Full-stack + Cordova Mobile)', value: 'meteor' },
{ name: 'Node.js / Next.js (Server/Web only)', value: 'node' },
{ name: 'React Native / Expo (Mobile only)', value: 'react-native' }
]
},
{
type: 'list',
name: 'trigger',
message: 'When should the production deployment run?',
choices: [
{ name: 'On release creation (e.g., v1.0.0)', value: 'release' },
{ name: 'On push to main/master branch', value: 'push' }
]
},
{
type: 'confirm',
name: 'deployServer',
message: 'Do you need to deploy a server/backend?',
default: true,
when: (answers) => answers.framework !== 'react-native'
},
{
type: 'list',
name: 'serverMethod',
message: 'How do you want to deploy the server?',
choices: [
{ name: 'Proxmox / Bare-metal VM (SSH + systemd + Zero-downtime symlinks)', value: 'proxmox' },
{ name: 'Docker (Build image, push to GHCR, deploy via docker-compose)', value: 'docker' }
],
when: (answers) => answers.deployServer
},
{
type: 'list',
name: 'mobileBuilds',
message: 'Do you need to build and publish mobile apps?',
choices: [
{ name: 'Both Android (Play Store) and iOS (TestFlight)', value: 'both' },
{ name: 'Android only (Play Store)', value: 'android' },
{ name: 'iOS only (TestFlight)', value: 'ios' },
{ name: 'None', value: 'none' }
],
when: (answers) => answers.framework === 'meteor' || answers.framework === 'react-native'
},
{
type: 'confirm',
name: 'useFastlane',
message: 'Use Fastlane for mobile build & signing? (Recommended — handles code signing, match, and publishing)',
default: true,
when: (answers) => answers.mobileBuilds && answers.mobileBuilds !== 'none'
}
]);

// Normalize answers for templates
if (answers.framework === 'node') answers.mobileBuilds = 'none';
if (answers.framework === 'react-native') answers.deployServer = false;
if (!answers.useFastlane) answers.useFastlane = false;

console.log(chalk.yellow('\nGenerating your advanced CI/CD pipeline...\n'));

const templateDir = path.join(__dirname, '../templates');
const targetDir = process.cwd();
const githubWorkflowsDir = path.join(targetDir, '.github/workflows');
const scriptsDir = path.join(targetDir, 'scripts');

await fs.ensureDir(githubWorkflowsDir);

if (answers.serverMethod === 'proxmox') {
await fs.ensureDir(scriptsDir);
}

const renderTemplate = async (templatePath, targetPath) => {
const fullTemplatePath = path.join(templateDir, templatePath);
if (await fs.pathExists(fullTemplatePath)) {
const templateContent = await fs.readFile(fullTemplatePath, 'utf-8');
const rendered = ejs.render(templateContent, answers);
await fs.writeFile(targetPath, rendered);
console.log(chalk.green(`✅ Created ${path.relative(targetDir, targetPath)}`));
} else {
console.log(chalk.red(`❌ Template not found: ${templatePath}`));
}
};

// 1. Generate Main Workflow based on Framework
await renderTemplate(`frameworks/${answers.framework}/ci-cd.yml.ejs`, path.join(githubWorkflowsDir, 'deploy-production.yml'));

// 2. Generate Deployment Scripts (if Proxmox)
if (answers.serverMethod === 'proxmox') {
await renderTemplate('deployment/proxmox/setup-systemd.sh.ejs', path.join(scriptsDir, 'setup-systemd.sh'));
await renderTemplate('deployment/proxmox/app.service.ejs', path.join(scriptsDir, 'app.service'));
await renderTemplate('deployment/proxmox/start-app.sh.ejs', path.join(scriptsDir, 'start-app.sh'));

// Make scripts executable
await fs.chmod(path.join(scriptsDir, 'setup-systemd.sh'), 0o755);
await fs.chmod(path.join(scriptsDir, 'start-app.sh'), 0o755);
}

// 3. Generate Dockerfile (if Docker)
if (answers.serverMethod === 'docker') {
await renderTemplate(`deployment/docker/Dockerfile.${answers.framework}.ejs`, path.join(targetDir, 'Dockerfile'));
await renderTemplate('deployment/docker/docker-compose.yml.ejs', path.join(targetDir, 'docker-compose.yml'));
}

// 4. Generate Fastlane files (if Fastlane)
if (answers.useFastlane) {
const fastlaneDir = path.join(targetDir, 'fastlane');
await fs.ensureDir(fastlaneDir);

await renderTemplate('fastlane/Gemfile.ejs', path.join(targetDir, 'Gemfile'));
await renderTemplate('fastlane/Appfile.ejs', path.join(fastlaneDir, 'Appfile'));
await renderTemplate('fastlane/Matchfile.ejs', path.join(fastlaneDir, 'Matchfile'));
await renderTemplate('fastlane/Fastfile.ejs', path.join(fastlaneDir, 'Fastfile'));

// Append .gitignore additions
const gitignorePath = path.join(targetDir, '.gitignore');
const additionsPath = path.join(templateDir, 'fastlane/gitignore-additions.txt');
if (await fs.pathExists(additionsPath)) {
const additions = await fs.readFile(additionsPath, 'utf-8');
const existing = (await fs.pathExists(gitignorePath)) ? await fs.readFile(gitignorePath, 'utf-8') : '';
if (!existing.includes('# Fastlane')) {
await fs.appendFile(gitignorePath, '\n' + additions);
console.log(chalk.green('✅ Appended Fastlane entries to .gitignore'));
}
}
}

// 5. Generate Setup Guide
await renderTemplate('PIPELINE_SETUP.md.ejs', path.join(targetDir, 'PIPELINE_SETUP.md'));

console.log(chalk.blue.bold('\n🎉 Advanced Pipeline generated successfully!'));
console.log(chalk.white(`Please read ${chalk.bold('PIPELINE_SETUP.md')} for the next steps.\n`));
}

run().catch(err => {
console.error(chalk.red('Error generating pipeline:'), err);
process.exit(1);
});
Loading