@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
293 lines • 10.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const schema_1 = require("@sprucelabs/schema");
const spruce_skill_utils_1 = require("@sprucelabs/spruce-skill-utils");
const SpruceError_1 = __importDefault(require("../../../errors/SpruceError"));
const action_utility_1 = __importDefault(require("../../../utilities/action.utility"));
const AbstractAction_1 = __importDefault(require("../../AbstractAction"));
const optionsSchema = (0, schema_1.buildSchema)({
id: 'deployHeroku',
description: 'Deploy your skill to Heroku.',
fields: {
teamName: {
type: 'text',
label: 'team name',
isRequired: false,
},
shouldRunSilently: {
type: 'boolean',
isPrivate: true,
},
shouldBuildAndLint: {
type: 'boolean',
defaultValue: true,
},
shouldRunTests: {
type: 'boolean',
defaultValue: true,
},
},
});
class DeployAction extends AbstractAction_1.default {
optionsSchema = optionsSchema;
commandAliases = ['deploy.heroku'];
invocationMessage = 'Deploying to Heroku... 🚀';
async execute(options) {
let results = {};
try {
this.assertRegisteredSkill();
await this.assertDependencies();
await this.assertLoggedInToHeroku();
await this.setupGitRepo();
await this.setupGitRemote();
const procResults = await this.setupProcFile();
results = action_utility_1.default.mergeActionResults(results, procResults);
await this.assertNoPendingGitChanges();
}
catch (err) {
return {
errors: [err],
};
}
const { shouldBuildAndLint, shouldRunTests } = this.validateAndNormalizeOptions(options);
if (shouldBuildAndLint) {
results = await this.buildAndLint();
if (results.errors) {
return results;
}
}
if (shouldRunTests) {
results = await this.runTests();
if (results.errors) {
return results;
}
}
await this.deploy();
this.ui.clear();
const skill = this.Service('auth').getCurrentSkill();
results.summaryLines = [
`You are good to go!`,
"You gotta make sure that your ENV's are set on Heroku to the following:\n",
`SKILL_NAME=${skill.name}`,
`SKILL_SLUG=${skill.slug}`,
`SKILL_ID=${skill.id}`,
`SKILL_API_KEY=${skill.apiKey}`,
];
return results;
}
assertRegisteredSkill() {
const skill = this.Service('auth').getCurrentSkill();
if (!skill) {
throw new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'skill',
friendlyMessage: 'You have to register your skill. Try `spruce login && spruce register` to get going!',
});
}
}
async deploy() {
await this.Service('command').execute('git push --set-upstream heroku master');
}
async assertNoPendingGitChanges() {
const results = await this.Service('command').execute('git status');
const failed = (results.stdout ?? '').toLowerCase().search('not staged') > -1 ||
(results.stdout ?? '').toLowerCase().search('no commits') > -1;
if (failed) {
throw new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'git',
friendlyMessage: 'You have pending changes. Commit them and try again!',
});
}
}
async setupGitRemote() {
const command = this.Service('command');
try {
await command.execute('git ls-remote heroku');
return;
}
catch { }
const confirm = await this.ui.confirm(`I didn't find a a remote named "heroku", want me to create one?`);
if (!confirm) {
throw new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'remote',
friendlyMessage: 'You need to setup a remote named "heroku" that Heroku will pull from.',
});
}
let pass = false;
let label = 'What name do you wanna give your app on heroku (using-kebab-case)?';
const pkg = this.Service('pkg');
const skillName = pkg.get('name');
do {
let name = await this.ui.prompt({
type: 'text',
label,
defaultValue: skillName,
isRequired: true,
});
name = spruce_skill_utils_1.namesUtil.toKebab(name);
try {
await command.execute('heroku create', {
args: [name],
env: { HOME: process.env.HOME },
});
pass = true;
}
catch {
label = `Uh oh, "${name}" is taken, try again!`;
}
} while (!pass);
try {
await command.execute('heroku buildpacks:set heroku/nodejs', {
env: { HOME: process.env.HOME },
});
}
catch {
throw new SpruceError_1.default({ code: 'DEPLOY_FAILED', stage: 'remote' });
}
}
async setupProcFile() {
const procFile = spruce_skill_utils_1.diskUtil.resolvePath(this.cwd, 'Procfile');
const results = {
files: [],
};
if (!spruce_skill_utils_1.diskUtil.doesFileExist(procFile)) {
const confirm = await this.ui.confirm(`I don't see a Procfile, which Heroku needs to know how to run your skill. Want me to create one?`);
if (!confirm) {
throw new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'procfile',
friendlyMessage: 'You are gonna need to create a Procfile in your project root so heroku knows how to run your skill.',
});
}
spruce_skill_utils_1.diskUtil.writeFile(procFile, 'worker: npm run boot');
results.files?.push({
name: 'Procfile',
action: 'generated',
path: procFile,
description: 'Used by Heroku to know how to run your skill.',
});
}
return results;
}
async setupGitRepo() {
const command = this.Service('command');
let inRepo = true;
try {
await command.execute('git status');
}
catch {
inRepo = false;
}
if (!inRepo) {
const confirm = await this.ui.confirm('You are not in a git repo. Would you like to initialize one now?');
try {
if (confirm) {
await command.execute('git init');
return;
}
}
catch { }
throw new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'git',
friendlyMessage: 'You must be in a git repo to deploy!',
});
}
}
async assertLoggedInToHeroku() {
try {
await this.Service('command').execute('grep api.heroku.com ~/.netrc');
}
catch {
throw new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'heroku',
friendlyMessage: `You gotta be logged in using \`heroku login\` before you can deploy.!`,
});
}
}
async assertDependencies() {
const command = this.Service('command');
const missing = [];
try {
await command.execute('which heroku');
}
catch {
missing.push({
name: 'heroku',
hint: 'Follow install instructions @ https://devcenter.heroku.com/articles/heroku-cli#download-and-install',
});
}
try {
await command.execute('which git');
}
catch {
missing.push({
name: 'git',
hint: 'Follow install instructions @ https://git-scm.com/downloads',
});
}
if (missing.length > 0) {
throw new SpruceError_1.default({
code: 'MISSING_DEPENDENCIES',
dependencies: missing,
});
}
}
async runTests() {
let results = {};
const isTestInstalled = await this.features.isInstalled('test');
if (isTestInstalled) {
try {
this.ui.startLoading('Testing your skill. Hold onto your pants. 👖');
const testResults = await this.Action('test', 'test').execute({
watchMode: 'off',
shouldReportWhileRunning: false,
});
results = action_utility_1.default.mergeActionResults(results, testResults);
}
catch (err) {
results = {
errors: [
new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'testing',
}),
],
};
}
}
return results;
}
async buildAndLint() {
let results = {};
const isSkillInstalled = await this.features.isInstalled('skill');
if (isSkillInstalled) {
try {
this.ui.startLoading('Building your skill. This may take a minute...');
const buildResults = await this.Service('build').build({
shouldFixLintFirst: true,
});
results = action_utility_1.default.mergeActionResults(results, buildResults);
}
catch (err) {
results = {
errors: [
new SpruceError_1.default({
code: 'DEPLOY_FAILED',
stage: 'building',
}),
],
};
}
}
return results;
}
}
exports.default = DeployAction;
//# sourceMappingURL=HerokuAction.js.map