UNPKG

scf-deploy

Version:

S3 + CloudFront static deployment automation CLI

930 lines (700 loc) 22.2 kB
# scf-deploy - S3 + CloudFront Deployment CLI Automate static website deployment to AWS S3 and CloudFront with a simple CLI tool. ## Features - **Easy Setup**: Interactive `init` command with helpful post-install message - **Simple Deployment**: Deploy with a single command `npx scf-deploy deploy` - **TypeScript Configuration**: Type-safe config files with `scf.config.ts` - **Auto Build Detection**: Automatically finds your build directory (dist, build, out, etc.) - **Build Validation**: Ensures deployable files exist before creating AWS resources - **Multiple Templates**: Pre-configured templates for React, Vue, Next.js - **Incremental Deployment**: Only upload changed files (SHA-256 hash comparison) - **CloudFront Integration**: Automatic cache invalidation after deployment - **Multi-Environment Support**: Manage dev, staging, and prod environments - **AWS Credentials Integration**: Supports AWS profiles, environment variables, and IAM roles - **State Management**: Track deployed resources locally with automatic .gitignore handling - **State Recovery**: Recover lost state files from AWS resource tags - **Progress Tracking**: Real-time upload progress with visual feedback - **SSR Detection**: Prevents accidental deployment of SSR builds (.next, .nuxt) ## Installation ### Global Installation ```bash npm install -g scf-deploy ``` ### Direct Execution with npx (Recommended) ```bash npx scf-deploy deploy ``` ## Quick Start ### 1. Initialize Configuration Run the init command to create `scf.config.ts`: ```bash npx scf-deploy init ``` This will guide you through an interactive setup or you can use defaults: ```bash npx scf-deploy init --yes ``` Or manually create `scf.config.ts` in your project root: ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-static-site', region: 'ap-northeast-2', s3: { bucketName: 'my-site-bucket', // buildDir is auto-detected (dist, build, out, etc.) // You can override: buildDir: './custom-dir', indexDocument: 'index.html', errorDocument: '404.html', }, cloudfront: { enabled: true, // priceClass: 'PriceClass_100', // Optional, defaults to PriceClass_100 }, }; export default config; ``` **Benefits of type annotation:** -**IDE auto-completion**: Get suggestions for all available properties -**Type checking**: Catch errors before deployment -**Documentation**: Hover tooltips show property descriptions -**Validation**: Required properties are enforced at compile time ### 2. Build Your Site ```bash # For React, Vue, etc. npm run build # For plain HTML # Just make sure your files are in the buildDir ``` ### 3. Deploy ```bash npx scf-deploy deploy ``` That's it! Your site is now live on S3 and CloudFront. ## Configuration ### Basic Configuration ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-app', // Application name region: 'us-east-1', // AWS region s3: { bucketName: 'my-bucket', // buildDir is optional - auto-detected from: dist, build, out, .output/public, _site indexDocument: 'index.html', errorDocument: '404.html', }, cloudfront: { enabled: true, // priceClass: 'PriceClass_100', // Optional: PriceClass_100, PriceClass_200, PriceClass_All }, }; export default config; ``` ### Build Directory Auto-Detection scf-deploy automatically detects your build directory by searching for: - `dist` - Vite, Rollup, Vue, etc. - `build` - Create React App, Next.js, etc. - `out` - Next.js static export - `.output/public` - Nuxt 3 - `_site` - Jekyll, 11ty - `output` - Some SSGs **Requirements:** - Directory must contain `index.html` as the entry point - Must have deployable web files (.html, .js, .css, etc.) **SSR Build Detection:** scf-deploy will reject SSR build directories that require a server: - `.next` - Next.js SSR build - `.nuxt` - Nuxt SSR build For Next.js, use `next export` to generate static files in `./out`: ```bash # next.config.js module.exports = { output: 'export', }; # Then build npm run build # Creates ./out directory with static files ``` ### Environment-Specific Configuration ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-app', region: 'ap-northeast-2', s3: { bucketName: 'my-site-prod', }, cloudfront: { enabled: true, }, // Environment overrides environments: { dev: { s3: { bucketName: 'my-site-dev' }, cloudfront: { enabled: false }, // Skip CloudFront in dev }, staging: { s3: { bucketName: 'my-site-staging' }, }, prod: { cloudfront: { priceClass: 'PriceClass_All' }, // Use all edge locations in prod }, }, }; export default config; ``` ### Custom Domain Configuration ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-app', region: 'us-east-1', s3: { bucketName: 'my-site', }, cloudfront: { enabled: true, customDomain: { domainName: 'example.com', certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-def', }, }, }; export default config; ``` ## Commands ### init Initialize `scf.config.ts` configuration file. ```bash # Interactive mode (asks questions) scf-deploy init # Non-interactive mode (use defaults) scf-deploy init --yes # Use a template (react, vue, next, custom) scf-deploy init --template react # Overwrite existing config scf-deploy init --force ``` **Options:** - `-f, --force` - Overwrite existing config file - `-y, --yes` - Skip prompts and use default values - `-t, --template <template>` - Use template (custom, react, vue, next) **Templates:** - `custom` - Custom configuration (default build dir: `./dist`) - `react` - React/CRA configuration (build dir: `./build`) - `vue` - Vue.js configuration (build dir: `./dist`) - `next` - Next.js static export (build dir: `./out`) ### deploy Deploy your static site to S3 and CloudFront. ```bash # Basic deployment scf-deploy deploy # Deploy to specific environment scf-deploy deploy --env prod # Use specific AWS profile scf-deploy deploy --profile my-aws-profile # Preview without uploading scf-deploy deploy --dry-run # Skip CloudFront (S3 only) scf-deploy deploy --no-cloudfront # Force full deployment (ignore cached state) scf-deploy deploy --force ``` **Options:** - `-e, --env <environment>` - Environment name (default: "default") - `-c, --config <path>` - Config file path (default: "scf.config.ts") - `-p, --profile <profile>` - AWS profile name - `--dry-run` - Preview deployment without uploading - `--no-cloudfront` - Skip CloudFront deployment - `--force` - Force full deployment (ignore state) - `--skip-cache` - Skip CloudFront cache invalidation ### remove Remove deployed AWS resources. ```bash # Remove resources (with confirmation prompt) scf-deploy remove # Force remove without confirmation scf-deploy remove --force # Remove specific environment scf-deploy remove --env dev # Keep S3 bucket (only delete files) scf-deploy remove --keep-bucket # Keep CloudFront distribution scf-deploy remove --keep-distribution ``` **Options:** - `-e, --env <environment>` - Environment name (default: "default") - `-c, --config <path>` - Config file path (default: "scf.config.ts") - `-p, --profile <profile>` - AWS profile name - `-f, --force` - Skip confirmation prompt - `--keep-bucket` - Keep S3 bucket (only delete files) - `--keep-distribution` - Keep CloudFront distribution ### status Check current deployment status. ```bash # Basic status scf-deploy status # Specific environment scf-deploy status --env prod # Detailed information scf-deploy status --detailed # JSON output scf-deploy status --json ``` **Options:** - `-e, --env <environment>` - Environment name (default: "default") - `-d, --detailed` - Show detailed information - `--json` - Output as JSON ### recover Recover lost deployment state from AWS resources. If you accidentally delete the `.deploy/state.json` file, you can recover it from AWS resource tags. ```bash # Recover state for default environment scf-deploy recover # Recover specific environment scf-deploy recover --env prod # Overwrite existing state file scf-deploy recover --force ``` **How it works:** 1. Searches for S3 buckets with `scf:managed=true` tag 2. Finds associated CloudFront distributions 3. Filters by app name and environment 4. Reconstructs the state file from AWS resources **Options:** - `-e, --env <environment>` - Environment name to recover - `-c, --config <path>` - Config file path (default: "scf.config.ts") - `-p, --profile <profile>` - AWS profile name - `-f, --force` - Overwrite existing state file **Note:** All AWS resources created by scf-deploy are automatically tagged for recovery: - `scf:managed=true` - Indicates resource is managed by scf-deploy - `scf:app=<app-name>` - Application name from config - `scf:environment=<env>` - Environment name ## AWS Credentials scf-deploy looks for AWS credentials in the following order: 1. **Command-line option**: `--profile` flag 2. **Environment variables**: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` 3. **AWS CLI profiles**: `~/.aws/credentials` 4. **IAM Role**: When running on EC2, ECS, etc. ### Using AWS Profile ```bash scf-deploy deploy --profile my-company-profile ``` ### Using Environment Variables ```bash export AWS_ACCESS_KEY_ID=your_access_key export AWS_SECRET_ACCESS_KEY=your_secret_key scf-deploy deploy ``` ## Features in Detail ### Build Validation Before creating any AWS resources, scf-deploy validates your build: - **Auto-detection**: Searches for common build directories (dist, build, out, etc.) - **index.html check**: Ensures an entry point exists - **Deployable files**: Verifies web files (.html, .js, .css, etc.) are present - **SSR rejection**: Prevents deployment of SSR builds that require a server This prevents wasted time and costs by catching issues before AWS resources are created. ### Automatic .gitignore Management scf-deploy automatically manages your `.gitignore` file: - **Auto-detection**: Checks if your project is a Git repository - **Safe addition**: Adds `.deploy/` if not already present - **Non-intrusive**: Creates `.gitignore` if it doesn't exist - **One-time**: Only modifies once, won't duplicate entries This happens automatically during: - `scf-deploy init` - When initializing configuration - `scf-deploy deploy` - After first successful deployment ### State Recovery If you accidentally delete `.deploy/state.json`, you can recover it: ```bash scf-deploy recover --env prod ``` **How it works:** - All AWS resources are tagged with `scf:managed`, `scf:app`, `scf:environment` - `recover` command searches for these tagged resources - State file is reconstructed from AWS metadata - You can continue deploying without recreating resources **What can be recovered:** - S3 bucket information - CloudFront distribution ID and domain - Resource creation timestamps - Environment configuration **Note:** File hashes are not recoverable, so the next deployment will re-upload all files. ### Incremental Deployment scf-deploy uses SHA-256 hashing to detect file changes: - **First deployment**: All files are uploaded - **Subsequent deployments**: Only changed files are uploaded - **Time savings**: 80-95% faster deployment times State is stored in `.deploy/state.{env}.json` (automatically added to `.gitignore`). ### CloudFront Cache Invalidation After deployment, scf-deploy automatically: 1. Creates or updates CloudFront distribution 2. Invalidates cache for changed files 3. Waits for distribution to be fully deployed 4. Shows real-time progress ### CloudFront Cache Warming Reduce cold start latency by pre-warming CloudFront edge locations after deployment: ```typescript cloudfront: { enabled: true, cacheWarming: { enabled: true, paths: ['/', '/index.html', '/app.js'], // Critical paths only concurrency: 3, // Concurrent requests (max: 10) delay: 500, // Delay between requests (ms) }, } ``` **How it works:** - After CloudFront deployment completes, scf-deploy makes HTTP requests to specified paths - Files are downloaded and cached at edge locations - First users get cached responses immediately (no cold start) **Cost considerations:** - ⚠️ **Data transfer costs**: Downloads files, incurs CloudFront outbound traffic charges - **Example**: 10 files × 100KB each × $0.085/GB = ~$0.00009 per deployment - **Best practice**: Only warm essential files (HTML, critical JS/CSS) - **Avoid**: Large images, videos, or non-critical assets **When to use:** - ✅ Production deployments where first-load performance is critical - ✅ After major releases to ensure global availability - ❌ Development/staging environments (disable to save costs) - ❌ High-frequency deployments (costs accumulate) **Configuration tips:** ```typescript environments: { dev: { cloudfront: { enabled: false, // No CloudFront = no warming needed }, }, staging: { cloudfront: { enabled: true, cacheWarming: { enabled: false }, // Skip warming in staging }, }, prod: { cloudfront: { cacheWarming: { enabled: true, paths: ['/', '/index.html'], // Minimal paths concurrency: 3, delay: 500, }, }, }, } ``` ### Multi-Environment Support Manage multiple environments with ease: ```bash scf-deploy deploy --env dev scf-deploy deploy --env staging scf-deploy deploy --env prod ``` Each environment: - Has its own state file - Can override configuration - Is completely isolated ### Progress Tracking Visual feedback during deployment: - File scanning progress - Upload progress bar - Real-time status updates - Detailed error messages ## Examples ### React Application ```bash # Build your React app npm run build # Deploy to production scf-deploy deploy --env prod ``` Configuration: ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-react-app', region: 'us-east-1', s3: { bucketName: 'my-react-app', // buildDir auto-detected (React uses ./build by default) indexDocument: 'index.html', }, cloudfront: { enabled: true, }, }; export default config; ``` ### Vue Application ```bash # Build your Vue app npm run build # Deploy scf-deploy deploy ``` Configuration: ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-vue-app', region: 'eu-west-1', s3: { bucketName: 'my-vue-app', // buildDir auto-detected (Vue uses ./dist by default) indexDocument: 'index.html', }, cloudfront: { enabled: true, }, }; export default config; ``` ### Static HTML Site ```typescript import type { SCFConfig } from 'scf-deploy'; const config: SCFConfig = { app: 'my-website', region: 'ap-northeast-2', s3: { bucketName: 'my-website', // For custom build directory (not auto-detected) buildDir: './public', indexDocument: 'index.html', errorDocument: '404.html', }, cloudfront: { enabled: true, }, }; export default config; ``` ## Troubleshooting ### Command not found ```bash # Check if installed which scf-deploy # Reinstall globally npm uninstall -g scf-deploy npm install -g scf-deploy # Or use npx (recommended) npx scf-deploy deploy ``` ### AWS Credentials Error ```bash # Verify AWS credentials aws sts get-caller-identity # Use specific profile scf-deploy deploy --profile my-profile ``` ### Config file not found ```bash # Check if scf.config.ts exists ls -la scf.config.ts # Specify custom path scf-deploy deploy --config ./config/scf.config.ts ``` ### State file conflicts ```bash # Check state files ls -la .deploy/ # Force full redeployment scf-deploy deploy --force ``` ### Build directory not found ```bash # Error: Could not find build directory # Solution: Ensure you've built your project first npm run build # Or specify a custom build directory # In scf.config.ts: s3: { bucketName: 'my-bucket', buildDir: './my-custom-output', } ``` ### SSR build detected error ```bash # Error: Cannot deploy SSR build directory (.next, .nuxt) # For Next.js: Enable static export # next.config.js: module.exports = { output: 'export', // Generates static files in ./out }; # For Nuxt: Use static generation # nuxt.config.js: export default { ssr: false, // SPA mode target: 'static', }; ``` ### Lost state file recovery ```bash # If you accidentally deleted .deploy/state.json scf-deploy recover --env prod # Then continue deploying as normal scf-deploy deploy --env prod ``` ## Requirements - **Node.js**: >= 18.0.0 - **AWS Account**: With appropriate permissions - **AWS Credentials**: Configured via CLI, environment, or IAM role ### Required AWS Permissions ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:CreateBucket", "s3:DeleteBucket", "s3:ListBucket", "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:PutBucketWebsite", "s3:PutBucketTagging", "s3:GetBucketTagging", "s3:ListAllMyBuckets", "cloudfront:CreateDistribution", "cloudfront:GetDistribution", "cloudfront:UpdateDistribution", "cloudfront:DeleteDistribution", "cloudfront:CreateInvalidation", "cloudfront:ListDistributions", "cloudfront:TagResource", "cloudfront:ListTagsForResource" ], "Resource": "*" } ] } ``` **Note:** Tagging permissions are required for the state recovery feature. ## Best Practices 1. **Build before deploying**: Always run your build command before deployment ```bash npm run build && npx scf-deploy deploy ``` 2. **State file management**: - `.deploy/` is automatically added to `.gitignore` - Never commit state files to Git - Use `scf-deploy recover` if state is lost 3. **Use environment-specific configs**: Separate dev/staging/prod ```bash scf-deploy deploy --env dev # For development scf-deploy deploy --env prod # For production ``` 4. **Test with `--dry-run` first**: Preview changes before deploying ```bash scf-deploy deploy --dry-run ``` 5. **Use IAM roles in CI/CD**: Don't hardcode credentials - Prefer IAM roles over access keys - Use AWS profiles locally - Let EC2/ECS IAM roles work automatically 6. **Enable CloudFront in production**: Better performance and HTTPS - Disable CloudFront in dev to save costs - Use `PriceClass_100` for cost optimization - Upgrade to `PriceClass_All` for global coverage 7. **Set up custom domain with ACM certificate**: Professional appearance - Request ACM certificate in `us-east-1` for CloudFront - Verify domain ownership - Add domain to CloudFront config 8. **Static export for Next.js**: Use `output: 'export'` ```javascript // next.config.js module.exports = { output: 'export', }; ``` 9. **Monitor AWS costs**: - Check S3 storage and transfer costs - Monitor CloudFront data transfer - Use CloudWatch for usage metrics 10. **Keep your CLI updated**: ```bash npm update -g scf-deploy # Or with npx (always uses latest) npx scf-deploy@latest deploy ``` ## Testing SCF uses Jest as the testing framework with comprehensive unit tests for core functionality. ### Running Tests ```bash # Run all tests npm test # Run only unit tests npm run test:unit # Run tests in watch mode npm run test:watch # Run tests with coverage report npm run test:coverage ``` ### Test Structure ``` src/__tests__/ ├── unit/ # Unit tests for core modules │ ├── config/ # Config parsing, validation, merging │ ├── deployer/ # File scanning, hashing │ └── state/ # State management ├── integration/ # Integration tests (future) ├── e2e/ # End-to-end tests (future) └── fixtures/ # Test data and config files ``` ### Test Coverage Current test coverage for core modules: | Module | Coverage | |--------|----------| | Config Schema | 100% | | Config Merger | 100% | | Config Loader | 91.66% | | File Scanner | 100% | | State Manager | 93.1% | **Total Unit Tests**: 130 tests ### Writing Tests When contributing, please ensure: 1. **Add tests for new features**: All new functionality should include tests 2. **Maintain coverage**: Keep coverage above 90% for core modules 3. **Use fixtures**: Add test data to `src/__tests__/fixtures/` 4. **Follow patterns**: Match existing test structure and naming Example test: ```typescript import { describe, it, expect } from '@jest/globals'; import { validateConfig } from '../../../core/config/schema.js'; describe('Config Validation', () => { it('should validate a minimal config', () => { const config = { app: 'test-app', region: 'us-east-1', s3: { bucketName: 'test-bucket', buildDir: './dist' }, }; expect(() => validateConfig(config)).not.toThrow(); }); }); ``` ### Test Scripts - `test` - Run all tests - `test:unit` - Run only unit tests - `test:watch` - Run tests in watch mode - `test:coverage` - Generate coverage report (saved to `coverage/`) Coverage reports are generated in: - **HTML**: `coverage/index.html` (open in browser) - **LCOV**: `coverage/lcov-report/` (for CI/CD tools) ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request ## License MIT License - see [LICENSE](LICENSE) file for details ## Links - **Homepage**: https://github.com/SCF-org - **Issues**: https://github.com/SCF-org/scf/issues - **NPM**: https://www.npmjs.com/package/scf-deploy ## Author jeonghodong <fire13764@gmail.com>