Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:59:24 +08:00
commit b704bccc5f
6 changed files with 803 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
{
"name": "deployment",
"description": "v1.0.0 - Safe deployment automation with CICD integration. Manages branch-based deployments to dev/uat/prod environments with safety checks and build validation.",
"version": "1.0.0",
"author": {
"name": "AutomateWith.Us",
"email": "team@automatewith.us"
},
"commands": [
"./commands"
],
"hooks": [
"./hooks"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# deployment
v1.0.0 - Safe deployment automation with CICD integration. Manages branch-based deployments to dev/uat/prod environments with safety checks and build validation.

244
commands/deploy.md Normal file
View File

@@ -0,0 +1,244 @@
You are managing a deployment system. The user wants to deploy to an environment.
## Task: Deploy Application
Parse the environment from arguments: $ARGUMENTS
Expected format: /deploy [environment]
Valid environments: dev, uat, prod
### Step 1: Parse and Validate Environment
Extract the environment name from $ARGUMENTS.
If no environment provided, show error and STOP:
```
❌ Error: No environment specified
Usage: /deploy [environment]
Available environments:
• dev - Development environment (deploy_dev branch)
• uat - User Acceptance Testing (deploy_uat branch)
• prod - Production environment (deploy_prod branch)
Example: /deploy dev
```
### Step 2: Load Configuration
Run CLI to get configuration:
```bash
node deployment/cli/deploy-cli.js config
```
If configuration not found (error in output), show error and STOP:
```
❌ Error: Deployment not configured
💡 Run /deploy:init to set up deployment
```
Parse the JSON output and extract:
- mainBranch
- buildCommand
- environments.{env} configuration
If the requested environment doesn't exist in config, show available environments and STOP:
```
❌ Error: Unknown environment "{env}"
Available environments: {list from config}
💡 Edit .claude/deployment.config.json to add custom environments
```
### Step 3: Run Pre-Deployment Validation
Run CLI validation for the target environment:
```bash
node deployment/cli/deploy-cli.js validate --check-git --env {environment}
```
Parse the JSON output.
If `success: false`, show all errors and STOP:
```
🚫 Deployment blocked by safety checks:
{for each error:}
❌ {error.message}
💡 Fix: {error.fix}
Please resolve these issues before deploying.
```
If warnings exist (success: true but warnings present), show warnings but continue:
```
⚠️ Warnings detected:
{for each warning:}
• {warning.message}
💡 {warning.fix}
Continuing with deployment...
```
### Step 4: Determine Source Branch
Based on environment configuration:
- If environment has `sourceBranch`: Use that branch
- If environment has `sourceEnvironment`: Use that environment's deployment branch
Example:
- dev: source is "main" (sourceBranch)
- uat: source is "deploy_dev" (from sourceEnvironment: "dev")
- prod: source is "deploy_uat" (from sourceEnvironment: "uat")
Store the source branch and deployment branch for later steps.
### Step 5: Run Build Validation
Show progress:
```
🔨 Running build validation...
Command: {buildCommand}
```
Execute the build command:
```bash
{buildCommand}
```
Monitor the output.
**If build succeeds:**
```
✓ Build completed successfully
```
Proceed to Step 6.
**If build fails:**
Show the build errors:
```
❌ Build failed with errors:
{build_error_output}
What would you like to do?
```
Use AskUserQuestion:
```json
{
"questions": [{
"question": "Build failed. How should we proceed?",
"header": "Action",
"multiSelect": false,
"options": [
{"label": "Show me the errors, I'll fix them", "description": "Stop deployment, let me fix manually"},
{"label": "Try to auto-fix", "description": "Let Claude attempt to fix the errors"},
{"label": "Cancel deployment", "description": "Stop the deployment process"}
]
}]
}
```
- If "Show me" or "Cancel": STOP with guidance
- If "Try to auto-fix": Attempt to fix, then re-run build
- If second build fails: STOP and ask user to fix manually
### Step 6: Checkout and Update Source Branch
Ensure we're on the correct source branch and it's up-to-date:
```bash
git fetch origin && git checkout {source_branch} && git pull origin {source_branch}
```
Verify the branch is clean and up-to-date.
### Step 7: Merge to Deployment Branch
Get the deployment branch from config: `environments.{env}.branch`
```bash
git checkout {deployment_branch} && git pull origin {deployment_branch} && git merge {source_branch} --no-ff -m "Deploy {source_branch} to {environment} environment"
```
**If merge conflicts occur:**
```
❌ Merge conflict detected
Conflicting files:
{list files from git status}
You need to resolve these conflicts manually:
1. The merge is in progress with conflicts
2. Resolve conflicts in the files listed above
3. Run: git add . && git commit
4. Then retry: /deploy {environment}
Aborting deployment.
```
Run: `git merge --abort`
STOP execution.
**If merge succeeds:**
```
✓ Merged {source_branch} → {deployment_branch}
```
### Step 8: Push to Trigger Deployment
Push the deployment branch to trigger Netlify auto-deploy:
```bash
git push origin {deployment_branch}
```
If push fails, show error:
```
❌ Push failed
{error output}
💡 Check your remote connection and permissions
```
STOP execution.
### Step 9: Display Success Message
Show deployment confirmation:
```
✓ Deployment initiated successfully
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚀 Environment: {environment}
📦 Branch: {deployment_branch}
🔗 Source: {source_branch}
📊 Netlify will now build and deploy automatically
Check your Netlify dashboard for deployment status
💡 Next steps:
{if dev} → After testing, deploy to UAT: /deploy uat
{if uat} → After approval, deploy to prod: /deploy prod
{if prod} → Monitor production for any issues
🔍 To check status: Visit your Netlify dashboard
```
---
**IMPORTANT:**
- Always validate before executing
- Show clear progress updates
- Handle errors gracefully with recovery options
- Enforce environment progression (dev → uat → prod)
- Never skip safety checks

176
commands/init.md Normal file
View File

@@ -0,0 +1,176 @@
You are managing a deployment configuration system. The user wants to initialize deployment settings for their project.
## Task: Initialize Deployment Configuration
This command sets up deployment configuration for the project. This is a one-time setup.
### Step 1: Check for Existing Configuration
Run the CLI to check if configuration already exists:
```bash
node deployment/cli/deploy-cli.js config 2>&1
```
If the output contains "Configuration not found", proceed to Step 2.
If configuration exists, show this error and STOP:
```
❌ Error: Deployment configuration already exists
📁 Location: .claude/deployment.config.json
💡 To view current config: Run node deployment/cli/deploy-cli.js config
💡 To modify: Edit .claude/deployment.config.json directly
```
### Step 2: Gather Configuration Details
Ask the user the following questions using AskUserQuestion tool:
```json
{
"questions": [
{
"question": "What is your main development branch?",
"header": "Main Branch",
"multiSelect": false,
"options": [
{"label": "main", "description": "Default branch named 'main'"},
{"label": "master", "description": "Legacy default branch 'master'"},
{"label": "develop", "description": "Use 'develop' as main branch"}
]
},
{
"question": "What command should run to build your project?",
"header": "Build Command",
"multiSelect": false,
"options": [
{"label": "npm run build", "description": "Node.js project with npm"},
{"label": "yarn build", "description": "Node.js project with yarn"},
{"label": "pnpm build", "description": "Node.js project with pnpm"},
{"label": "make build", "description": "Project with Makefile"}
]
}
]
}
```
Store the user's answers for Step 3.
### Step 3: Create Configuration
Use the CLI to initialize the configuration with user's choices:
```bash
node deployment/cli/deploy-cli.js init --main-branch {user_main_branch} --build-command "{user_build_command}"
```
**Expected output**: JSON with success: true
If the command fails, show error and STOP:
```
❌ Error: Failed to initialize configuration
{error_message}
💡 Check that .claude/ directory is writable
```
### Step 4: Verify Configuration Created
Read the created configuration to verify:
```bash
node deployment/cli/deploy-cli.js config
```
Parse the JSON output and extract the environments.
### Step 5: Display Configuration Summary
Show the user what was configured:
```
✓ Deployment configuration initialized
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📁 Location: .claude/deployment.config.json
📋 Configuration:
Main Branch: {main_branch}
Build Command: {build_command}
🌍 Environments configured:
• dev (deploy_dev) ← {main_branch}
• uat (deploy_uat) ← deploy_dev
• prod (deploy_prod) ← deploy_uat
🔒 Safety Features:
✓ Uncommitted files check
✓ Branch validation
✓ Clean build requirement
💡 Next Steps:
1. Review config: Edit .claude/deployment.config.json if needed
2. Create deployment branches (see below)
3. Deploy to dev: /deploy dev
```
### Step 6: Offer to Create Deployment Branches
Ask the user:
```
The following deployment branches need to exist in your repository:
- deploy_dev
- deploy_uat
- deploy_prod
Would you like me to create these branches now?
```
Use AskUserQuestion:
```json
{
"questions": [{
"question": "Create deployment branches?",
"header": "Setup",
"multiSelect": false,
"options": [
{"label": "Yes", "description": "Create all deployment branches from main"},
{"label": "No", "description": "I'll create them manually later"}
]
}]
}
```
If user selects "Yes":
```bash
git checkout {main_branch} && git pull origin {main_branch} && git checkout -b deploy_dev && git push -u origin deploy_dev && git checkout -b deploy_uat && git push -u origin deploy_uat && git checkout -b deploy_prod && git push -u origin deploy_prod && git checkout {main_branch}
```
Show success message:
```
✓ Deployment branches created successfully
• deploy_dev
• deploy_uat
• deploy_prod
All branches have been pushed to origin.
You're ready to deploy!
```
If user selects "No":
```
💡 Remember to create these branches manually:
git checkout {main_branch}
git checkout -b deploy_dev && git push -u origin deploy_dev
git checkout -b deploy_uat && git push -u origin deploy_uat
git checkout -b deploy_prod && git push -u origin deploy_prod
```
---
**IMPORTANT:**
- Use CLI for all config operations (plan mode support)
- Validate user input before proceeding
- Provide clear next steps
- Make it interactive and user-friendly

312
hooks/pre-deploy-check.js Executable file
View File

@@ -0,0 +1,312 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
/**
* PreToolUse Hook: Pre-deployment Safety Checks
*
* Validates deployment operations before execution:
* - Detects deployment-related commands
* - Checks for uncommitted files
* - Validates current branch
* - Blocks execution if unsafe
*/
async function main() {
try {
// Read input from stdin
const input = fs.readFileSync(0, 'utf8').trim();
if (!input) {
process.exit(0); // No input, exit silently
}
const eventData = JSON.parse(input);
// Extract tool input
const toolInput = eventData.tool_input || {};
const cwd = eventData.cwd || process.cwd();
// Change to correct directory
process.chdir(cwd);
// Check if this is a deployment-related command
if (!isDeploymentCommand(toolInput)) {
process.exit(0); // Not a deployment, allow execution
}
// Run safety checks
const issues = await runSafetyChecks(toolInput);
if (issues.length === 0) {
process.exit(0); // All checks passed, allow execution
}
// Checks failed - block execution
const output = {
hookSpecificOutput: {
blockExecution: true,
additionalContext: formatIssues(issues, toolInput)
}
};
console.log(JSON.stringify(output));
process.exit(0);
} catch (error) {
// Silent failure - never block Claude Code
// Log error to stderr for debugging (optional)
process.exit(0);
}
}
/**
* Detect if command is deployment-related
*/
function isDeploymentCommand(toolInput) {
const command = toolInput.command || '';
const description = toolInput.description || '';
// Check for deployment-related keywords
const deploymentKeywords = [
'deploy_dev',
'deploy_uat',
'deploy_prod',
'/deploy',
'deployment'
];
const commandLower = command.toLowerCase();
const descriptionLower = description.toLowerCase();
// Check if command or description contains deployment keywords
for (const keyword of deploymentKeywords) {
if (commandLower.includes(keyword) || descriptionLower.includes(keyword)) {
return true;
}
}
// Check for git operations on deployment branches
if (command.includes('git merge') || command.includes('git push')) {
// Check if targeting a deployment branch
const deployBranchPattern = /deploy_(dev|uat|prod)/;
if (deployBranchPattern.test(command)) {
return true;
}
}
return false;
}
/**
* Extract environment from command if present
*/
function extractEnvironment(toolInput) {
const command = toolInput.command || '';
const description = toolInput.description || '';
const envPattern = /(dev|uat|prod)/i;
// Try to find environment in command
const commandMatch = command.match(envPattern);
if (commandMatch) {
return commandMatch[1].toLowerCase();
}
// Try to find in description
const descMatch = description.match(envPattern);
if (descMatch) {
return descMatch[1].toLowerCase();
}
return null;
}
/**
* Run all safety checks
* Returns array of issues found
*/
async function runSafetyChecks(toolInput) {
const issues = [];
// Load configuration
const configPath = path.join(process.cwd(), '.claude/deployment.config.json');
if (!fs.existsSync(configPath)) {
// No config = not a configured deployment project
// Don't block, user might be doing manual git operations
return [];
}
let config;
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (error) {
// Malformed config - warn but don't block
return [{
type: 'warning',
message: 'Deployment config malformed',
fix: 'Check .claude/deployment.config.json syntax'
}];
}
// Extract environment from command
const environment = extractEnvironment(toolInput);
// Check 1: Uncommitted files
if (config.safeguards?.checkUncommittedFiles) {
const uncommitted = checkUncommittedFiles();
if (uncommitted.count > 0) {
issues.push({
type: 'error',
message: `${uncommitted.count} uncommitted file(s) detected`,
details: uncommitted.files.slice(0, 5), // Show first 5 files
fix: 'Commit or stash changes: git add . && git commit -m "..."',
count: uncommitted.count
});
}
}
// Check 2: Branch validation
if (config.safeguards?.checkBranch && environment) {
const branchIssue = checkBranchValidity(config, environment);
if (branchIssue) {
issues.push(branchIssue);
}
}
// Check 3: Environment exists
if (environment && !config.environments[environment]) {
issues.push({
type: 'error',
message: `Unknown environment: ${environment}`,
fix: `Valid environments: ${Object.keys(config.environments).join(', ')}`
});
}
return issues;
}
/**
* Check for uncommitted files
*/
function checkUncommittedFiles() {
try {
const output = execSync('git status --porcelain', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
}).trim();
if (!output) {
return { count: 0, files: [] };
}
const files = output.split('\n').map(line => {
const status = line.substring(0, 2);
const file = line.substring(3);
return { status, file };
});
return { count: files.length, files };
} catch (error) {
// Not a git repo or git command failed
return { count: 0, files: [] };
}
}
/**
* Check if on correct branch for deployment
*/
function checkBranchValidity(config, environment) {
try {
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
}).trim();
const envConfig = config.environments[environment];
if (!envConfig) {
return null; // Environment doesn't exist
}
// Determine expected source branch
let expectedBranch;
if (envConfig.sourceBranch) {
expectedBranch = envConfig.sourceBranch;
} else if (envConfig.sourceEnvironment) {
const sourceEnv = config.environments[envConfig.sourceEnvironment];
expectedBranch = sourceEnv?.branch;
}
if (!expectedBranch) {
return null; // Can't determine expected branch
}
if (currentBranch !== expectedBranch) {
return {
type: 'warning',
message: `On branch "${currentBranch}", expected "${expectedBranch}"`,
fix: `Switch to correct branch: git checkout ${expectedBranch}`
};
}
return null; // Branch is correct
} catch (error) {
return null; // Git command failed, don't block
}
}
/**
* Format issues into user-friendly message
*/
function formatIssues(issues, toolInput) {
const errors = issues.filter(i => i.type === 'error');
const warnings = issues.filter(i => i.type === 'warning');
let message = '🚫 Deployment blocked by safety checks:\n\n';
// Show errors
if (errors.length > 0) {
message += '**Errors** (must fix):\n';
for (const error of errors) {
message += `\n${error.message}\n`;
if (error.details && error.details.length > 0) {
message += ' Files:\n';
for (const detail of error.details) {
message += ` - ${detail.file} (${detail.status})\n`;
}
if (error.count && error.details.length < error.count) {
message += ` ... and ${error.count - error.details.length} more\n`;
}
}
if (error.fix) {
message += ` 💡 Fix: ${error.fix}\n`;
}
}
}
// Show warnings
if (warnings.length > 0) {
message += '\n**Warnings** (recommended to fix):\n';
for (const warning of warnings) {
message += `\n⚠️ ${warning.message}\n`;
if (warning.fix) {
message += ` 💡 Suggestion: ${warning.fix}\n`;
}
}
}
message += '\n---\n\n';
message += 'Fix the issues above before deploying.\n';
message += 'Once fixed, retry the deployment command.\n';
return message;
}
main();

53
plugin.lock.json Normal file
View File

@@ -0,0 +1,53 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:awudevelop/claude-plugins:deployment",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "824a49ec5abf11b0d811fda9c8861515015582ba",
"treeHash": "d715bf800868f8068f8a160d47f09a378e934fae7a013e7f41d5aa4dc6855b50",
"generatedAt": "2025-11-28T10:14:05.577412Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "deployment",
"description": "v1.0.0 - Safe deployment automation with CICD integration. Manages branch-based deployments to dev/uat/prod environments with safety checks and build validation.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "8150f9ee2d32f94c14c5ca28064341b078e6c021b10a88952ab1169a41be5385"
},
{
"path": "hooks/pre-deploy-check.js",
"sha256": "a716ecb7bd8d4bc154f1fe9565bafaf48b83a70c71f5fc7bea0c42b23ff54c58"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "d1e6223a482459eb57e397cdd73dc4e72aebe884cd8977630f3c75e91b42cc85"
},
{
"path": "commands/init.md",
"sha256": "0b9b55305d20b47f46bcbcd83f9d50a60df8f1d8cd7b22cf9b77b933ab3f108f"
},
{
"path": "commands/deploy.md",
"sha256": "e504114e294c4d4600d753f940d1e8f7bd9e3f7ec515c7828b978355d5d1f52a"
}
],
"dirSha256": "d715bf800868f8068f8a160d47f09a378e934fae7a013e7f41d5aa4dc6855b50"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}