UNPKG

@puls-atlas/cli

Version:

The Puls Atlas CLI tool for managing Atlas projects

234 lines 7.4 kB
import fs from 'fs'; import chalk from 'chalk'; import inquirer from 'inquirer'; import { execSync, logger, selectProject, workspace } from './../../utils/index.js'; const VERSION_FORMAT = /^(\d+)\.(\d+)\.(\d+)(\-(\d+))?$/; const getAvailableVersions = latest => { const [major, minor, patch] = getVersionParts(latest); return [{ name: 'Patch', version: `${major}.${minor}.${patch + 1}` }, { name: 'Minor', version: `${major}.${minor + 1}.0` }, { name: 'Major', version: `${major + 1}.0.0` }]; }; const checkCloudFunctionsChanges = latestVersion => { const hasChanges = workspace.diff(workspace.getCurrentBranch(), latestVersion, { path: 'functions' }); if (hasChanges) { logger.break(); logger.log(chalk => chalk.bgRed.white.bold(' WARNING ')); logger.log(chalk => chalk.red.bold('╔════════════════════════════════════════════════════════════════════════╗')); logger.log(chalk => chalk.red.bold('║ THERE ARE CHANGES IN THE FUNCTIONS DIRECTORY SINCE THE LAST RELEASE. ║')); logger.log(chalk => chalk.red.bold('║ MAKE SURE TO DEPLOY CHANGED FUNCTIONS MANUALLY! ║')); logger.log(chalk => chalk.red.bold('╚════════════════════════════════════════════════════════════════════════╝')); logger.break(); } }; const getVersionParts = version => { const parts = VERSION_FORMAT.exec(version); if (parts !== null) { return [parseInt(parts[1]), parseInt(parts[2]), parseInt(parts[3])]; } return [0, 0, 0, 0]; }; export default () => { let latestVersion = ''; let isFirstRelease = true; if (!workspace.isOnBranch('master', 'main')) { logger.error('You can only deploy from the master or main branch!'); } if (workspace.hasLocalChanges()) { logger.banner(`WARNING: ${chalk.red('YOU HAVE LOCAL CHANGES! THESE WILL NOT BE INCLUDED!')}`); } if (!fs.existsSync('./cloudbuild.yaml')) { logger.error('No cloudbuild.yaml file found. Make sure you run this command in the root of the Atlas project.'); } selectProject('.firebaserc', { environment: 'production' }).then(({ projectId }) => { try { execSync('gh auth status', { stdio: 'ignore' }); } catch (error) { logger.warning('You are NOT logged into any GitHub hosts'); logger.warning('Login to GitHub to continue'); execSync('gh auth login', { web: true, stdio: 'inherit' }); } const spinner = logger.spinner('Fetching current release from GitHub...'); const latestRelease = execSync('gh release list', { limit: 1, stdio: 'pipe', excludeDrafts: true }).toString(); [latestVersion] = latestRelease.split('\t'); isFirstRelease = latestVersion === ''; if (isFirstRelease) { spinner.warn('No releases found on GitHub. This is the first release!'); } else { spinner.succeed(`Latest release: ${latestVersion}`); } const availableVersions = getAvailableVersions(latestVersion ?? '0.0.0'); return inquirer.prompt([{ type: 'list', name: 'value', message: 'Select the version you want to release:', choices: availableVersions.map(({ version, name }) => ({ name: `${version} (${name})`, value: { version, name, projectId }, short: version })) }]); }).then(async ({ value: { version, name, projectId } }) => { const { isConfirmed } = await inquirer.prompt([{ type: 'confirm', name: 'isConfirmed', message: `Releasing version ${chalk.yellow(version)} (${name}) ` + `in ${chalk.yellow(projectId)}. Continue?`, default: false }]); return { isConfirmed, projectId, version }; }).then(async ({ isConfirmed, projectId, version }) => { if (isConfirmed) { const releaseOptions = { title: version, generateNotes: true, target: workspace.getCurrentBranch() }; const { isConfirmed } = await inquirer.prompt([{ type: 'confirm', name: 'isConfirmed', default: true, message: 'Create a release in GitHub? ' + 'If not, a draft will be created instead.' }]); if (!isConfirmed) { releaseOptions.draft = true; } if (!isFirstRelease) { releaseOptions.notesStartTag = latestVersion; } try { execSync(`gh release create ${version}`, releaseOptions); } catch (error) { logger.warning(error.message); logger.break(); logger.error('Could not create a release (on GitHub)...'); } logger.break(); logger.info('A new build will start automatically on Google Cloud Build'); logger.info(`Visit: https://console.cloud.google.com/cloud-build/builds?project=${projectId}`); logger.break(); const { deployFirestoreRules } = await inquirer.prompt([{ type: 'confirm', name: 'deployFirestoreRules', default: false, message: `Do you want to deploy ${chalk.yellow('Firestore rules')} ` + 'from firebase.json?' }]); const { deployFirestoreIndexes } = await inquirer.prompt([{ type: 'confirm', name: 'deployFirestoreIndexes', default: false, message: `Do you want to deploy ${chalk.yellow('Firestore indexes')} ` + 'from firebase.json?' }]); const { deployCronJobs } = await inquirer.prompt([{ type: 'confirm', name: 'deployCronJobs', default: false, message: `Do you want to deploy ${chalk.yellow('cron jobs')} ` + 'from cron.yaml?' }]); return { deployFirestoreRules, deployFirestoreIndexes, deployCronJobs, projectId }; } logger.break(); logger.warning('Deployment cancelled by the user.'); return { deployFirestoreRules: false, deployFirestoreIndexes: false, deployCronJobs: false }; }).then(({ deployFirestoreRules, deployFirestoreIndexes, deployCronJobs, projectId }) => { if (deployFirestoreRules || deployFirestoreIndexes) { const toDeploy = []; if (deployFirestoreRules) { toDeploy.push('firestore:rules'); } if (deployFirestoreIndexes) { toDeploy.push('firestore:indexes'); } execSync('firebase deploy', { project: projectId, only: toDeploy.join(',') }); } if (deployCronJobs) { execSync('gcloud app deploy cron.yaml', { project: projectId }); } checkCloudFunctionsChanges(latestVersion); logger.break(); logger.success('Done!'); return true; }).catch(error => { if (error instanceof Error && error.name === 'ExitPromptError') { logger.error('Deployment cancelled by the user.'); } else { logger.error(error); } }).finally(() => { if (fs.existsSync('release.txt')) { fs.unlinkSync('release.txt'); } process.exit(0); }); };