UNPKG

bws-secure

Version:

Secure environment management with Bitwarden Secrets Manager

415 lines (347 loc) • 13.2 kB
#!/usr/bin/env node /** * BWS Secure PostInstall Script * * This script handles the post-installation setup for the BWS Secure package. * It's designed to run after the package is installed via npm/yarn/pnpm. * * Functions: * - Creates bwsconfig.json if it doesn't exist * - Adds BWS_ACCESS_TOKEN to .env file * - Updates README.md with BWS Secure documentation * - Updates .gitignore with BWS Secure entries */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // ANSI color codes for terminal output const colors = { red: '\x1b[31m', green: '\x1b[32m', blue: '\x1b[34m', reset: '\x1b[0m' }; // Get the directory where this script is located const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Determine the project root directory const findProjectRoot = () => { // Start from the current working directory let currentDir = process.cwd(); // First, check if we're in a node_modules directory if (currentDir.includes('node_modules')) { // We're inside node_modules, need to navigate up to find project root // Go up until we're out of node_modules while (currentDir.includes('node_modules')) { currentDir = path.dirname(currentDir); } } // Check if this is a symlinked package const packageJsonPath = path.join(__dirname, 'package.json'); if (fs.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJson.name === 'bws-secure') { // This is our package, check if we're in a symlinked directory const realPath = fs.realpathSync(__dirname); if (realPath !== __dirname) { console.log( `${colors.blue}Detected symlinked package: ${__dirname} -> ${realPath}${colors.reset}` ); } } } catch (error) { console.error(`${colors.red}Error reading package.json: ${error.message}${colors.reset}`); } } // Verify this is actually a project root by checking for package.json if (!fs.existsSync(path.join(currentDir, 'package.json'))) { console.log(`${colors.red}Unable to find project root (no package.json found)${colors.reset}`); return null; } return currentDir; }; // Create bin directory symlinks const createBinSymlinks = (projectRoot) => { try { const packageDir = __dirname; const binDir = path.join(projectRoot, 'node_modules', '.bin'); // Ensure bin directory exists if (!fs.existsSync(binDir)) { fs.mkdirSync(binDir, { recursive: true }); } // Scripts to create symlinks for const scripts = [ { name: 'secure-run', source: path.join(packageDir, 'secureRun.js') }, { name: 'bws-list', source: path.join(packageDir, 'list-projects.js') }, { name: 'bws-setup', source: path.join(packageDir, 'postinstall.js') } ]; // Create symlinks for each script scripts.forEach((script) => { const targetPath = path.join(binDir, script.name); // Use absolute paths to ensure symlinks work correctly const absoluteSource = path.resolve(script.source); const scriptContent = `#!/bin/sh\nnode "${absoluteSource}" "$@"`; fs.writeFileSync(targetPath, scriptContent); fs.chmodSync(targetPath, '755'); // Make executable console.log(`${colors.blue}Created ${script.name} in ${binDir}${colors.reset}`); }); return true; } catch (error) { console.error(`${colors.red}Error creating bin symlinks: ${error.message}${colors.reset}`); return false; } }; // Create bwsconfig.json if it doesn't exist const createBwsConfig = (projectRoot) => { const configPath = path.join(projectRoot, 'bwsconfig.json'); if (fs.existsSync(configPath)) { console.log(`${colors.blue}Found existing bwsconfig.json${colors.reset}`); return true; } try { const configContent = { projects: [ { platform: 'vercel|netlify', projectName: 'firstProjectName', bwsProjectIds: { prod: 'yourBWSProdProjectID', dev: 'yourBWSDevProjectID', local: 'yourBWSLocalProjectID' }, preserveVars: ['BWS_ACCESS_TOKEN'], excludeVars: ['VERCEL_URL'] }, { platform: 'vercel|netlify', projectName: 'secondProjectName', bwsProjectIds: { prod: 'yourBWSProdProjectID', dev: 'yourBWSDevProjectID', local: 'yourBWSLocalProjectID' }, preserveVars: ['BWS_ACCESS_TOKEN'], excludeVars: ['DEPLOY_URL'] } ] }; fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2)); console.log(`${colors.green}Created bwsconfig.json${colors.reset}`); return true; } catch (error) { console.error(`${colors.red}Error creating bwsconfig.json: ${error.message}${colors.reset}`); return false; } }; // Update or create .env file with BWS_ACCESS_TOKEN const updateEnvFile = (projectRoot) => { const envPath = path.join(projectRoot, '.env'); try { let envContent = ''; // If .env exists, read its content if (fs.existsSync(envPath)) { envContent = fs.readFileSync(envPath, 'utf8'); // Check if BWS_ACCESS_TOKEN is already present if (envContent.includes('BWS_ACCESS_TOKEN=')) { console.log(`${colors.blue}BWS_ACCESS_TOKEN already exists in .env${colors.reset}`); return true; } // Ensure there's a newline at the end if (!envContent.endsWith('\n')) { envContent += '\n'; } } // Add BWS_ACCESS_TOKEN envContent += '# BWS Secure Configuration\nBWS_ACCESS_TOKEN=your_token_here\n'; fs.writeFileSync(envPath, envContent); console.log(`${colors.green}Updated .env with BWS_ACCESS_TOKEN${colors.reset}`); return true; } catch (error) { console.error(`${colors.red}Error updating .env: ${error.message}${colors.reset}`); return false; } }; // Update .gitignore with BWS entries const updateGitignore = (projectRoot) => { const gitignorePath = path.join(projectRoot, '.gitignore'); try { let gitignoreContent = ''; // Create .gitignore if it doesn't exist if (fs.existsSync(gitignorePath)) { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8'); // Ensure there's a newline at the end if (!gitignoreContent.endsWith('\n')) { gitignoreContent += '\n'; } } // BWS entries to add const bwsEntries = [ '\n# BWS Secure', '.env', '.env.*', '.env.secure.*', 'requiredVars.env', '.env-debug.html' ]; // Check if entries already exist let needsUpdate = false; for (const entry of bwsEntries) { if (!gitignoreContent.includes(entry)) { needsUpdate = true; break; } } if (!needsUpdate) { console.log(`${colors.blue}.gitignore already contains BWS entries${colors.reset}`); return true; } // Add missing entries if (!gitignoreContent.includes('# BWS Secure')) { gitignoreContent += bwsEntries.join('\n') + '\n'; } fs.writeFileSync(gitignorePath, gitignoreContent); console.log(`${colors.green}Updated .gitignore with BWS entries${colors.reset}`); return true; } catch (error) { console.error(`${colors.red}Error updating .gitignore: ${error.message}${colors.reset}`); return false; } }; // Update README.md with BWS documentation const updateReadme = (projectRoot) => { // Look for README files const readmeFiles = ['README.md', 'Readme.md', 'readme.md']; let readmePath = null; // Find existing README for (const file of readmeFiles) { const filePath = path.join(projectRoot, file); if (fs.existsSync(filePath)) { readmePath = filePath; break; } } // BWS documentation content const bwsDocContent = `<!-- BWS-SECURE-DOCS-START --> ## BWS Secure Environmental Variable Integration This project uses [BWS Secure](https://github.com/last-rev-llc/bws-secure) for managing environment variables across different environments. ### Creating an Access Token 1. Visit the [Last Rev Bitwarden Machine Accounts](https://vault.bitwarden.com/#/sm/22479128-f194-460a-884b-b24a015686c6/machine-accounts) section - **Note:** This link requires you to be a member of the Last Rev Bitwarden organization - If you don't have access, please refer to the [BWS Secure documentation](https://github.com/last-rev-llc/bws-secure) or contact your team administrator 2. After clicking the link, follow these steps: - Select the appropriate Client/Set of Machine Accounts from the list - Click on the "Access Tokens" tab - Click "+ New Access Token" button - Give the token a meaningful name (e.g., "Your Name - Local Development") - Click "Save" to generate the token 3. Copy the displayed token (you won't be able to see it again after closing) 4. Add it to your .env file in your project root: \`\`\` BWS_ACCESS_TOKEN=your_token_here \`\`\` 5. Never commit this token to version control ### Updating BWS Secure To update the BWS Secure integration to the latest version, run: \`\`\`bash npm install bws-secure@latest && npm run bws-setup \`\`\` <!-- BWS-SECURE-DOCS-END -->`; try { // If no README exists, create one if (!readmePath) { readmePath = path.join(projectRoot, 'README.md'); fs.writeFileSync(readmePath, `# Project Documentation\n\n${bwsDocContent}\n`); console.log(`${colors.green}Created README.md with BWS documentation${colors.reset}`); return true; } // Read existing README let readmeContent = fs.readFileSync(readmePath, 'utf8'); // Check if BWS section already exists if ( readmeContent.includes('<!-- BWS-SECURE-DOCS-START -->') && readmeContent.includes('<!-- BWS-SECURE-DOCS-END -->') ) { // Update existing section const startMarker = '<!-- BWS-SECURE-DOCS-START -->'; const endMarker = '<!-- BWS-SECURE-DOCS-END -->'; const startIndex = readmeContent.indexOf(startMarker); const endIndex = readmeContent.indexOf(endMarker) + endMarker.length; readmeContent = readmeContent.substring(0, startIndex) + bwsDocContent + readmeContent.substring(endIndex); } else { // Add section at the end if (!readmeContent.endsWith('\n')) { readmeContent += '\n'; } readmeContent += `\n${bwsDocContent}\n`; } fs.writeFileSync(readmePath, readmeContent); console.log( `${colors.green}Updated ${path.basename(readmePath)} with BWS documentation${colors.reset}` ); return true; } catch (error) { console.error(`${colors.red}Error updating README: ${error.message}${colors.reset}`); return false; } }; // Main function const main = () => { console.log(`${colors.blue}šŸ”’ BWS Secure PostInstall Started${colors.reset}`); // Find project root const projectRoot = findProjectRoot(); if (!projectRoot) { console.error(`${colors.red}Cannot proceed without finding project root${colors.reset}`); process.exit(1); } console.log(`${colors.blue}Project root identified as: ${projectRoot}${colors.reset}`); // Create bin symlinks if (!createBinSymlinks(projectRoot)) { console.warn( `${colors.red}Failed to create bin symlinks - some functionality may be limited${colors.reset}` ); } // Create or update required files createBwsConfig(projectRoot); updateEnvFile(projectRoot); updateGitignore(projectRoot); updateReadme(projectRoot); console.log(`${colors.green}╔════════════════════════════════════════════════════════════╗ ā•‘ āœ… BWS Secure Setup Complete! ā•‘ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•${colors.reset} šŸ“‹ Next Steps: 1. Configure your bwsconfig.json - Set platform to 'vercel' or 'netlify' - Update projectName to match your deployment - Add your BWS project IDs for each environment 2. Add your BWS token to .env: BWS_ACCESS_TOKEN=your_token_here 3. Update your build scripts to use secure-run: "dev": "secure-run next dev" "build": "secure-run next build" "start": "secure-run next start" šŸ”§ Added Scripts: - secure-run: Runs commands with secure environment variables - bws-list: Lists available BWS projects - bws-setup: Run this if setup fails during installation šŸ”’ Security: - Environment files are encrypted - Secrets are never logged šŸ“š Documentation: https://github.com/last-rev-llc/bws-secure `); }; // Run main function if this script is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main(); } // Export functions for testing or importing elsewhere export { findProjectRoot, createBinSymlinks, createBwsConfig, updateEnvFile, updateGitignore, updateReadme, main };