UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

293 lines • 10.6 kB
"use strict"; 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