bws-secure
Version:
Secure environment management with Bitwarden Secrets Manager
415 lines (347 loc) ⢠13.2 kB
JavaScript
/**
* 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
};