@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
234 lines • 7.4 kB
JavaScript
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);
});
};