bitbucket-env-manager
Version:
Deploys Bitbucket Environment
198 lines (171 loc) • 4.79 kB
JavaScript
#!/usr/bin/env node
const { select, confirm, isCancel, cancel, log, note } = require('@clack/prompts');
const fs = require('fs-extra');
const path = require('path');
const color = require('picocolors');
const { names } = require('./config');
/**
* Recursively find files matching a pattern in a directory
*/
const findFiles = (dir, filename, results = []) => {
try {
const items = fs.readdirSync(dir);
for (const item of items) {
// Skip hidden directories and node_modules
if (item.startsWith('.') || item === 'node_modules') continue;
const fullPath = path.join(dir, item);
try {
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
findFiles(fullPath, filename, results);
} else if (item === filename) {
results.push(fullPath);
}
} catch (e) {
// Skip files we can't access
}
}
} catch (e) {
// Skip directories we can't access
}
return results;
};
/**
* Find all .env files (matching pattern .env.* ) in a directory
*/
const findEnvFiles = (dir, results = []) => {
try {
const items = fs.readdirSync(dir);
for (const item of items) {
// Skip hidden directories, node_modules, and remote (download output)
if (item === 'node_modules' || item === 'remote') continue;
const fullPath = path.join(dir, item);
try {
const stat = fs.statSync(fullPath);
if (stat.isDirectory() && !item.startsWith('.')) {
findEnvFiles(fullPath, results);
} else if (item.startsWith('.env.') && stat.isFile()) {
results.push(fullPath);
}
} catch (e) {
// Skip files we can't access
}
}
} catch (e) {
// Skip directories we can't access
}
return results;
};
/**
* Select an info.json file using Clack
*/
const selectInfoJson = async (message = 'Select a repository') => {
const basePath = process.cwd();
const files = findFiles(basePath, names.infoJsonFile);
if (files.length === 0) {
log.error(`No ${names.infoJsonFile} files found in current directory`);
return null;
}
const options = files.map((f) => {
const relativePath = path.relative(basePath, f);
const dirName = path.dirname(relativePath);
return {
value: f,
label: dirName || relativePath,
hint: relativePath,
};
});
const selected = await select({
message,
options,
});
if (isCancel(selected)) {
cancel('Operation cancelled.');
return null;
}
return selected;
};
/**
* Select .env files using Clack (with multi-select simulation)
*/
const selectEnvFiles = async (message = 'Select .env files to deploy') => {
const basePath = process.cwd();
const files = findEnvFiles(basePath);
if (files.length === 0) {
log.error('No .env files found in current directory');
return null;
}
// Group files by directory
const filesByDir = {};
for (const f of files) {
const dir = path.dirname(f);
if (!filesByDir[dir]) {
filesByDir[dir] = [];
}
filesByDir[dir].push(f);
}
const selectedFiles = [];
// For each directory with env files, ask which to deploy
for (const [dir, dirFiles] of Object.entries(filesByDir)) {
const relativeDirPath = path.relative(basePath, dir);
note(`Found ${dirFiles.length} env file(s) in ${color.cyan(relativeDirPath || '.')}`, 'Files');
for (const filePath of dirFiles) {
const fileName = path.basename(filePath);
const shouldInclude = await confirm({
message: `Include ${color.cyan(fileName)}?`,
initialValue: true,
});
if (isCancel(shouldInclude)) {
cancel('Operation cancelled.');
return null;
}
if (shouldInclude) {
selectedFiles.push(filePath);
}
}
}
return selectedFiles;
};
/**
* Parse info.json file and return parameters
*/
const getInfoJsonParams = (infoJsonPath) => {
const infoRaw = fs.readFileSync(infoJsonPath, { encoding: 'utf-8' });
const infoJson = JSON.parse(infoRaw);
if (
!infoJson.workspace ||
!infoJson.repo ||
!infoJson.secured ||
!Array.isArray(infoJson.secured)
) {
throw new Error(
`Please specify {workspace:, repo:, secured:[]} under "${names.infoJsonFile}" file`
);
}
return {
workspace: infoJson.workspace,
repo: infoJson.repo,
secured: infoJson.secured,
envs_config: infoJson.envs_config || {},
};
};
/**
* Handle cancellation consistently
*/
const handleCancel = (value) => {
if (isCancel(value)) {
cancel('Operation cancelled.');
process.exit(0);
}
return value;
};
module.exports = {
findFiles,
findEnvFiles,
selectInfoJson,
selectEnvFiles,
getInfoJsonParams,
handleCancel,
isCancel,
cancel,
};