stock-management-demo/project-setup.js

236 lines
7.1 KiB
JavaScript

import { randomBytes } from 'crypto';
const c = (colour) =>
{
if(colour === 'reset')
{
return '\x1b[0m';
}
return Bun.color(colour, 'ansi');
};
const log = (message) => console.log(message);
const logInfo = (message) => log(c('blue') + '[INFO] ' + c('reset') + message);
const logSuccess = (message) => log(c('green') + '[SUCCESS] ' + c('reset') + message);
const logWarn = (message) => log(c('yellow') + '[WARN] ' + c('reset') + message);
const logError = (message) => log(c('red') + '[ERROR] ' + c('reset') + message);
const ENV_FILE = '.env';
const ENV_EXAMPLE_FILE = '.env.example';
const DOCKER_COMPOSE_FILE = 'docker-compose.yml';
const DOCKER_COMPOSE_EXAMPLE_FILE = 'docker-compose-example.yml';
async function generateSecureRandomString(bytes = 32)
{
return randomBytes(bytes).toString('hex');
}
async function ensureFileFromExample(targetPath, examplePath)
{
logInfo(`Checking for ${targetPath}...`);
const targetExists = await Bun.file(targetPath).exists();
if (!targetExists)
{
logWarn(`${targetPath} not found. Copying from ${examplePath}...`);
const exampleExists = await Bun.file(examplePath).exists();
if (!exampleExists)
{
logError(`${examplePath} not found. Cannot create ${targetPath}. Please ensure ${examplePath} exists.`);
// Don't exit immediately, maybe other checks can proceed.
// Consider if this should be a fatal error depending on project needs.
return { copied: false, error: true };
}
try
{
const exampleContent = await Bun.file(examplePath).text();
await Bun.write(targetPath, exampleContent);
logSuccess(`Copied ${examplePath} to ${targetPath}.`);
return { copied: true, error: false }; // Indicates file was copied
}
catch (error)
{
logError(`Failed to copy ${examplePath} to ${targetPath}: ${error.message}`);
return { copied: false, error: true }; // Indicate error
}
}
else
{
logInfo(`${targetPath} already exists.`);
return { copied: false, error: false }; // Indicates file already existed
}
}
const secrets = new Map();
async function replacePlaceholders(filePath)
{
logInfo(`Checking for placeholders in ${filePath}...`);
try
{
let content = await Bun.file(filePath).text();
const placeholderRegex = /{{\s*([A-Z0-9_]+)\s*}}/g;
const placeholders = [...content.matchAll(placeholderRegex)];
const uniquePlaceholderNames = [...new Set(placeholders.map(match => match[1]))];
if (uniquePlaceholderNames.length === 0)
{
logInfo(`No placeholders found in ${filePath}.`);
return true; // Indicate success (no action needed)
}
logInfo(`Found placeholders: ${uniquePlaceholderNames.join(', ')}. Generating secrets...`);
for (const name of uniquePlaceholderNames)
{
// Reuse existing secret if already generated for another file in this run
if (!secrets.has(name))
{
secrets.set(name, await generateSecureRandomString());
}
}
let replacementsMade = false;
content = content.replace(placeholderRegex, (match, name) =>
{
const secret = secrets.get(name);
if (secret)
{
replacementsMade = true;
return secret;
}
return match; // Return original match if name not found (shouldn't happen with current logic)
});
if (replacementsMade)
{
await Bun.write(filePath, content);
logSuccess(`Replaced placeholders in ${filePath} with generated secrets.`);
}
else
{
logInfo(`No placeholder values needed replacement in ${filePath}.`);
}
return true; // Indicate success
}
catch (error)
{
logError(`Failed to process placeholders in ${filePath}: ${error.message}`);
return false; // Indicate failure
}
}
async function ensureDockerComposeRunning()
{
logInfo('Ensuring Docker Compose services are running...');
// Check if docker-compose.yml exists first
const composeFileExists = await Bun.file(DOCKER_COMPOSE_FILE).exists();
if (!composeFileExists)
{
logWarn(`Skipping Docker Compose setup because ${DOCKER_COMPOSE_FILE} does not exist.`);
return true; // Not an error, just skipping
}
try
{
logInfo(`Running docker compose -f ${DOCKER_COMPOSE_FILE} up -d...`);
const proc = Bun.spawn({
cmd: ['docker', 'compose', '-f', DOCKER_COMPOSE_FILE, 'up', '-d'],
stdout: 'inherit', // Pipe output to the setup script's stdout
stderr: 'inherit', // Pipe errors to the setup script's stderr
});
const exitCode = await proc.exited;
if (exitCode === 0)
{
logSuccess('Docker Compose services started successfully (or were already running).');
return true; // Indicate success
}
else
{
logError(`Docker Compose command failed with exit code ${exitCode}.`);
return false; // Indicate failure
}
}
catch (error)
{
logError(`Failed to run Docker Compose: ${error.message}`);
logError('Please ensure Docker is installed, running, and you have permissions to use it.');
return false; // Indicate failure
}
}
async function main()
{
let overallSuccess = true;
console.log(c('aqua') +` _____ _ _ _____ _ _
/ ____| | | | | __ \\ (_) | |
| (___ | |_ _ _| | ___| |__) |__ _ _ __ | |_
\\___ \\| __| | | | |/ _ \\ ___/ _ \\| | '_ \\| __|
____) | |_| |_| | | __/ | | (_) | | | | | |_
|_____/ \\__|\\__, |_|\\___|_| \\___/|_|_| |_|\\__|
__/ |
|___/ ` + c('reset'));
logInfo('Starting project setup validation...');
// Ensure .env file exists and replace placeholders if copied
const envResult = await ensureFileFromExample(ENV_FILE, ENV_EXAMPLE_FILE);
if (envResult.error)
{
overallSuccess = false;
}
else if (envResult.copied)
{
if (!await replacePlaceholders(ENV_FILE))
{
overallSuccess = false;
}
}
// Ensure docker-compose.yml exists and replace placeholders if copied
const composeResult = await ensureFileFromExample(DOCKER_COMPOSE_FILE, DOCKER_COMPOSE_EXAMPLE_FILE);
if (composeResult.error)
{
overallSuccess = false;
}
else if (composeResult.copied)
{
if (!await replacePlaceholders(DOCKER_COMPOSE_FILE))
{
overallSuccess = false;
}
}
// Only attempt to run docker compose if the previous steps were generally successful
// and the compose file actually exists now.
if (overallSuccess && await Bun.file(DOCKER_COMPOSE_FILE).exists())
{
if (!await ensureDockerComposeRunning())
{
overallSuccess = false;
}
}
else if (!await Bun.file(DOCKER_COMPOSE_FILE).exists())
{
logWarn(`Skipping Docker Compose execution as ${DOCKER_COMPOSE_FILE} is missing.`);
// Not necessarily a failure if the example file was also missing.
}
if (overallSuccess)
{
logSuccess('Project setup validation completed successfully.');
}
else
{
logError('Project setup validation failed. Please check the logs above.');
process.exit(1); // Exit with error code if any step failed
}
}
main().catch(err =>
{
logError(`Unhandled error during setup: ${err.message}\n${err.stack}`);
process.exit(1);
});