@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
180 lines • 6.95 kB
JavaScript
import fs from 'fs';
import path from 'path';
import inquirer from 'inquirer';
import { execSync, logger, selectProject, listModuleExports, parseEnv } from './../../utils/index.js';
const getPackageJson = dir => {
const packageJsonPath = path.join(dir, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
logger.error('No package.json file found. ' + 'Make sure you run this command in the root of the Atlas project.');
}
return JSON.parse(fs.readFileSync(packageJsonPath));
};
const selectInteractive = async functionsDir => {
const functions = listModuleExports(path.join(functionsDir, 'index.js'), {
recursive: true
});
const {
selectedFunctions
} = await inquirer.prompt([{
loop: false,
type: 'checkbox',
name: 'selectedFunctions',
message: 'Select the functions you want to deploy:',
choices: functions.map(name => ({
name: ` ${name}`,
short: name,
value: name,
checked: false
}))
}]);
return selectedFunctions;
};
const fromEditor = async () => {
const {
selectedFunctions
} = await inquirer.prompt([{
loop: false,
type: 'editor',
name: 'selectedFunctions',
message: 'Paste or type the functions you want to deploy (one per line):',
default: ''
}]);
return selectedFunctions.split('\n').map(name => name.trim());
};
const selectCodebase = async () => {
const firebaseJsonPath = path.join(process.cwd(), 'firebase.json');
if (!fs.existsSync(firebaseJsonPath)) {
logger.warn('No firebase.json file found.');
return null;
}
const firebaseJson = JSON.parse(fs.readFileSync(firebaseJsonPath));
if (firebaseJson.functions?.length > 1) {
const {
codebase
} = await inquirer.prompt([{
type: 'list',
name: 'codebase',
message: 'Select a codebase to deploy functions from:',
choices: firebaseJson.functions.map(({
codebase,
source
}) => ({
name: `${codebase} (${source})`,
value: codebase,
short: codebase
}))
}]);
return codebase;
}
return null;
};
const outputDeploymentOverview = (projectId, runtimeEnv, use, codebase, packageJson, functions) => {
logger.break();
logger.log(chalk => chalk.green.bold('╔══════════════════════════════════════════════════════╗'));
logger.log(chalk => chalk.green.bold('║ Deployment Overview ║'));
logger.log(chalk => chalk.green.bold('╚══════════════════════════════════════════════════════╝'));
if (!runtimeEnv) {
logger.warning('No runtime environment found. ' + 'Assuming you are deploying to a development environment.');
logger.log(chalk => chalk.yellow(`You can set up a runtime environment by creating a ${chalk.cyan(`.env.${use}`)} ` + 'file in the functions directory ' + `and adding ${chalk.cyan('RUNTIME_ENVIRONMENT=production')} ` + `or ${chalk.cyan('RUNTIME_ENVIRONMENT=development')} to it.`));
logger.break();
}
logger.log(chalk => chalk.bold('Project: ') + chalk.yellow(projectId));
logger.log(chalk => chalk.bold('Runtime Environment: ') + chalk.yellow(runtimeEnv ?? 'development'));
if (codebase) {
logger.log(chalk => chalk.bold('Codebase: ') + chalk.yellow(codebase));
}
logger.log(chalk => chalk.bold('Node Version: ') + chalk.yellow(packageJson.engines.node));
logger.log(chalk => chalk.bold('Functions:'));
functions.forEach(name => logger.log(chalk => ` - ${chalk.cyan(name)}`));
logger.break();
};
export default async (functions, options = {}) => {
const functionsDir = path.join(process.cwd(), 'functions');
if (!fs.existsSync(functionsDir)) {
logger.error('No functions folder found. ' + 'Make sure you run this command in the root of the Atlas project.');
}
if (functions.length === 0) {
if (options['interactive']) {
functions = await selectInteractive(functionsDir);
} else if (options['editor']) {
functions = await fromEditor();
} else {
const {
inputType
} = await inquirer.prompt([{
type: 'list',
name: 'inputType',
default: 'interactive',
message: 'How do you want to select the functions to deploy?',
choices: [{
name: 'Interactive (recommended)',
value: 'interactive',
short: 'interactive'
}, {
name: 'Editor',
value: 'editor',
short: 'editor'
}]
}]);
if (inputType === 'interactive') {
functions = await selectInteractive(functionsDir);
}
if (inputType === 'editor') {
functions = await fromEditor();
}
}
}
if (functions.length === 0) {
logger.error('No functions specified. Please specify at least one function to deploy.');
}
const codebase = options['codebase'] ?? (await selectCodebase());
const packageJson = getPackageJson(path.join(functionsDir, codebase ?? ''));
if (!packageJson.engines || !packageJson.engines.node) {
logger.error('No node engine target defined. ' + 'Add it to your package.json to continue, eg: "engines": { "node": "20" } ');
}
selectProject('.firebaserc', {
promptMessage: 'Select a Google Cloud Project to deploy to:'
}).then(async ({
projectId,
config
}) => {
const spinner = logger.spinner('Setting up environment...');
const use = config.config[projectId]?.environment;
if (!use) {
spinner.fail('No environment found. ' + 'Please check your .firebaserc file and try again.');
process.exit(1);
}
const envFile = path.join(functionsDir, codebase ?? '', `.env.${use}`);
const runtimeEnv = fs.existsSync(envFile) ? parseEnv(envFile)?.RUNTIME_ENVIRONMENT : null;
execSync(`firebase use ${use}`, {
cwd: functionsDir,
stdio: 'ignore'
});
spinner.succeed('Environment setup complete.');
return [projectId, runtimeEnv, use];
}).then(async ([projectId, runtimeEnv, use]) => {
outputDeploymentOverview(projectId, runtimeEnv, use, codebase, packageJson, functions);
const confirm = await inquirer.prompt([{
type: 'confirm',
name: 'value',
default: false,
message: 'Continue deploying to Firebase Functions?'
}]);
return confirm.value;
}).then(isConfirmed => {
if (isConfirmed) {
const prefix = codebase ? `${codebase}:` : '';
try {
execSync('firebase deploy', {
cwd: functionsDir,
only: functions.map(name => `functions:${prefix}${name.replace('.', '-')}`).join(',')
});
} catch (error) {
logger.error('Deployment failed. See the error above for details.');
}
} else {
logger.warning('Deployment canceled by the user.');
process.exit(0);
}
});
};