Initial commit
This commit is contained in:
15
.claude-plugin/plugin.json
Normal file
15
.claude-plugin/plugin.json
Normal 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
3
README.md
Normal 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
244
commands/deploy.md
Normal 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
176
commands/init.md
Normal 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
312
hooks/pre-deploy-check.js
Executable 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
53
plugin.lock.json
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user