UNPKG

@puls-atlas/cli

Version:

The Puls Atlas CLI tool for managing Atlas projects

180 lines 6.95 kB
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); } }); };