mup-aws-beanstalk
Version:
Deploy apps to AWS Elastic Beanstalk using Meteor Up
774 lines (666 loc) • 26.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setup = setup;
exports.deploy = deploy;
exports.logs = logs;
exports.logsNginx = logsNginx;
exports.logsEb = logsEb;
exports.start = start;
exports.stop = stop;
exports.restart = restart;
exports.clean = clean;
exports.reconfig = reconfig;
exports.events = events;
exports.status = status;
exports.ssl = ssl;
exports.shell = shell;
exports.debug = debug;
var _chalk = _interopRequireDefault(require("chalk"));
var _ssh = require("ssh2");
var _aws = require("./aws");
var _certificates = _interopRequireDefault(require("./certificates"));
var _policies = require("./policies");
var _upload = _interopRequireWildcard(require("./upload"));
var _prepareBundle = require("./prepare-bundle");
var _utils = require("./utils");
var _versions = require("./versions");
var _envSettings = require("./env-settings");
var _ebConfig = require("./eb-config");
var _envReady = require("./env-ready");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
async function setup(api) {
const config = api.getConfig();
const appConfig = config.app;
const {
bucket: bucketName,
app: appName,
instanceProfile,
serviceRole: serviceRoleName,
trailBucketPrefix,
trailName,
deregisterRuleName,
environment: environmentName,
eventTargetRole: eventTargetRoleName,
eventTargetPolicyName,
eventTargetPassRoleName,
automationDocument
} = (0, _utils.names)(config);
(0, _utils.logStep)('=> Setting up'); // Create bucket if needed
const {
Buckets
} = await _aws.s3.listBuckets().promise();
const beanstalkBucketCreated = await (0, _utils.ensureBucketExists)(Buckets, bucketName, appConfig.region);
if (beanstalkBucketCreated) {
console.log(' Created Bucket');
}
(0, _utils.logStep)('=> Ensuring IAM Roles and Instance Profiles are setup'); // Create role and instance profile
await (0, _utils.ensureRoleExists)(instanceProfile, _policies.rolePolicy);
await (0, _utils.ensureInstanceProfileExists)(config, instanceProfile);
await (0, _utils.ensurePoliciesAttached)(config, instanceProfile, ['arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier', 'arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker', 'arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier', ...(appConfig.gracefulShutdown ? ['arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM'] : [])]);
await (0, _utils.ensureRoleAdded)(config, instanceProfile, instanceProfile); // Create role used by enhanced health
await (0, _utils.ensureRoleExists)(serviceRoleName, _policies.serviceRole);
await (0, _utils.ensurePoliciesAttached)(config, serviceRoleName, ['arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth', 'arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkService']);
if (appConfig.gracefulShutdown) {
const accountId = await (0, _utils.getAccountId)();
const policy = (0, _policies.eventTargetRolePolicy)(accountId, environmentName, appConfig.region || 'us-east-1');
const passPolicy = (0, _policies.passRolePolicy)(accountId, eventTargetRoleName);
await (0, _utils.ensureRoleExists)(eventTargetRoleName, _policies.eventTargetRole, true);
await (0, _utils.ensureInlinePolicyAttached)(eventTargetRoleName, eventTargetPolicyName, policy);
await (0, _utils.ensureInlinePolicyAttached)(eventTargetRoleName, eventTargetPassRoleName, passPolicy);
} // Create beanstalk application if needed
const {
Applications
} = await _aws.beanstalk.describeApplications().promise();
if (!Applications.find(app => app.ApplicationName === appName)) {
const params = {
ApplicationName: appName,
Description: `App "${appConfig.name}" managed by Meteor Up`
};
await _aws.beanstalk.createApplication(params).promise();
console.log(' Created Beanstalk application');
}
if (appConfig.gracefulShutdown) {
(0, _utils.logStep)('=> Ensuring Graceful Shutdown is setup');
const existingBucket = (0, _utils.findBucketWithPrefix)(Buckets, trailBucketPrefix);
const trailBucketName = existingBucket ? existingBucket.Name : (0, _utils.createUniqueName)(trailBucketPrefix);
const region = appConfig.region || 'us-east-1';
const accountId = await (0, _utils.getAccountId)();
const policy = (0, _policies.trailBucketPolicy)(accountId, trailBucketName);
const trailBucketCreated = await (0, _utils.ensureBucketExists)(Buckets, trailBucketName, appConfig.region);
await (0, _utils.ensureBucketPolicyAttached)(trailBucketName, policy);
if (trailBucketCreated) {
console.log(' Created bucket for Cloud Trail');
}
const params = {
trailNameList: [trailName]
};
const {
trailList
} = await _aws.cloudTrail.describeTrails(params).promise();
if (trailList.length === 0) {
const createParams = {
Name: trailName,
S3BucketName: trailBucketName
};
await _aws.cloudTrail.createTrail(createParams).promise();
console.log(' Created CloudTrail trail');
}
const createdDocument = await (0, _utils.ensureSsmDocument)(automationDocument, (0, _policies.gracefulShutdownAutomationDocument)());
if (createdDocument) {
console.log(' Created SSM Automation Document');
}
const createdRule = await (0, _utils.ensureCloudWatchRule)(deregisterRuleName, 'Used by Meteor Up for graceful shutdown', _policies.DeregisterEvent);
if (createdRule) {
console.log(' Created Cloud Watch rule');
}
const target = (0, _policies.deregisterEventTarget)(environmentName, eventTargetRoleName, accountId, region);
const createdTarget = await (0, _utils.ensureRuleTargetExists)(deregisterRuleName, target, accountId);
if (createdTarget) {
console.log(' Created target for Cloud Watch rule');
}
}
}
async function deploy(api) {
await api.runCommand('beanstalk.setup');
const config = api.getConfig();
const {
app,
bucket,
bundlePrefix,
environment
} = (0, _utils.names)(config);
const version = await (0, _versions.largestVersion)(api);
const nextVersion = version + 1; // Mutates the config, so the meteor.build command will have the correct build location
config.app.buildOptions.buildLocation = config.app.buildOptions.buildLocation || (0, _utils.tmpBuildPath)(config.app.path, api);
const bundlePath = api.resolvePath(config.app.buildOptions.buildLocation, 'bundle.zip');
const willBuild = (0, _utils.shouldRebuild)(bundlePath, api.getOptions()['cached-build']);
if (willBuild) {
await api.runCommand('meteor.build');
(0, _prepareBundle.injectFiles)(api, app, nextVersion, config.app);
await (0, _prepareBundle.archiveApp)(config.app.buildOptions.buildLocation, api);
}
(0, _utils.logStep)('=> Uploading bundle');
const key = `${bundlePrefix}${nextVersion}`;
await (0, _upload.default)(config.app, bucket, `${bundlePrefix}${nextVersion}`, bundlePath);
(0, _utils.logStep)('=> Creating version');
await _aws.beanstalk.createApplicationVersion({
ApplicationName: app,
VersionLabel: nextVersion.toString(),
Description: (0, _utils.createVersionDescription)(api, config.app),
SourceBundle: {
S3Bucket: bucket,
S3Key: key
}
}).promise();
await api.runCommand('beanstalk.reconfig');
await (0, _envReady.waitForEnvReady)(config, true);
(0, _utils.logStep)('=> Deploying new version');
const {
toRemove,
toUpdate
} = await (0, _ebConfig.prepareUpdateEnvironment)(api);
if (api.verbose) {
console.log('EB Config changes:');
console.dir({
toRemove,
toUpdate
});
}
await _aws.beanstalk.updateEnvironment({
EnvironmentName: environment,
VersionLabel: nextVersion.toString(),
OptionSettings: toUpdate,
OptionsToRemove: toRemove
}).promise();
await (0, _envReady.waitForEnvReady)(config, true);
const {
Environments
} = await _aws.beanstalk.describeEnvironments({
ApplicationName: app,
EnvironmentNames: [environment]
}).promise();
await api.runCommand('beanstalk.clean');
await api.runCommand('beanstalk.ssl'); // Check if deploy succeeded
const {
Environments: finalEnvironments
} = await _aws.beanstalk.describeEnvironments({
ApplicationName: app,
EnvironmentNames: [environment]
}).promise();
if (nextVersion.toString() === finalEnvironments[0].VersionLabel) {
console.log(_chalk.default.green(`App is running at ${Environments[0].CNAME}`));
} else {
console.log(_chalk.default.red`Deploy Failed. Visit the Aws Elastic Beanstalk console to view the logs from the failed deploy.`);
process.exitCode = 1;
}
}
async function logs(api) {
const logsContent = await (0, _utils.getLogs)(api, ['web.stdout.log', 'nodejs/nodejs.log']);
logsContent.forEach(({
instance,
data
}) => {
console.log(`${instance} `, data[0] || data[1]);
});
}
async function logsNginx(api) {
const logsContent = await (0, _utils.getLogs)(api, ['nginx/error.log', 'nginx/access.log']);
logsContent.forEach(({
instance,
data
}) => {
console.log(`${instance} `, data[0]);
console.log(`${instance} `, data[1]);
});
}
async function logsEb(api) {
const logsContent = await (0, _utils.getLogs)(api, ['eb-engine.log', 'eb-activity.log']);
logsContent.forEach(({
data,
instance
}) => {
console.log(`${instance} `, data[0] || data[1]);
});
}
async function start(api) {
const config = api.getConfig();
const {
environment
} = (0, _utils.names)(config);
(0, _utils.logStep)('=> Starting App');
const {
EnvironmentResources
} = await _aws.beanstalk.describeEnvironmentResources({
EnvironmentName: environment
}).promise();
const autoScalingGroup = EnvironmentResources.AutoScalingGroups[0].Name;
const {
minInstances,
maxInstances
} = config.app;
await _aws.autoScaling.updateAutoScalingGroup({
AutoScalingGroupName: autoScalingGroup,
MaxSize: maxInstances,
MinSize: minInstances,
DesiredCapacity: minInstances
}).promise();
await (0, _envReady.waitForHealth)(config);
}
async function stop(api) {
const config = api.getConfig();
const {
environment
} = (0, _utils.names)(config);
(0, _utils.logStep)('=> Stopping App');
const {
EnvironmentResources
} = await _aws.beanstalk.describeEnvironmentResources({
EnvironmentName: environment
}).promise();
const autoScalingGroup = EnvironmentResources.AutoScalingGroups[0].Name;
await _aws.autoScaling.updateAutoScalingGroup({
AutoScalingGroupName: autoScalingGroup,
MaxSize: 0,
MinSize: 0,
DesiredCapacity: 0
}).promise();
await (0, _envReady.waitForHealth)(config, 'Grey');
}
async function restart(api) {
const config = api.getConfig();
const {
environment
} = (0, _utils.names)(config);
(0, _utils.logStep)('=> Restarting App');
await _aws.beanstalk.restartAppServer({
EnvironmentName: environment
}).promise();
await (0, _envReady.waitForEnvReady)(config, false);
}
async function clean(api) {
const config = api.getConfig();
const {
app,
bucket
} = (0, _utils.names)(config);
(0, _utils.logStep)('=> Finding old versions');
const {
versions
} = await (0, _versions.oldVersions)(api);
const envVersions = await (0, _versions.oldEnvVersions)(api);
(0, _utils.logStep)('=> Removing old versions');
const promises = [];
for (let i = 0; i < versions.length; i++) {
promises.push(_aws.beanstalk.deleteApplicationVersion({
ApplicationName: app,
VersionLabel: versions[i].toString(),
DeleteSourceBundle: true
}).promise());
}
for (let i = 0; i < envVersions.length; i++) {
promises.push(_aws.s3.deleteObject({
Bucket: bucket,
Key: `env/${envVersions[i]}.txt`
}).promise());
} // TODO: remove bundles
await Promise.all(promises);
}
async function reconfig(api) {
const config = api.getConfig();
const {
app,
environment,
bucket
} = (0, _utils.names)(config);
const deploying = !!api.commandHistory.find(entry => entry.name === 'beanstalk.deploy');
(0, _utils.logStep)('=> Configuring Beanstalk environment'); // check if env exists
const {
Environments
} = await _aws.beanstalk.describeEnvironments({
ApplicationName: app,
EnvironmentNames: [environment]
}).promise();
if (!Environments.find(env => env.Status !== 'Terminated')) {
const desiredEbConfig = (0, _ebConfig.createDesiredConfig)(api.getConfig(), api.getSettings(), config.app.longEnvVars ? 1 : false);
if (config.app.longEnvVars) {
const envContent = (0, _envSettings.createEnvFile)(config.app.env, api.getSettings());
await (0, _upload.uploadEnvFile)(bucket, 1, envContent);
}
const platformArn = await (0, _utils.selectPlatformArn)();
const [version] = await (0, _versions.ebVersions)(api);
await _aws.beanstalk.createEnvironment({
ApplicationName: app,
EnvironmentName: environment,
Description: `Environment for ${config.app.name}, managed by Meteor Up`,
VersionLabel: version.toString(),
PlatformArn: platformArn,
OptionSettings: desiredEbConfig.OptionSettings
}).promise();
console.log(' Created Environment');
await (0, _envReady.waitForEnvReady)(config, false);
} else if (!deploying) {
// If we are deploying, the environment will be updated
// at the same time we update the environment version
const {
toRemove,
toUpdate
} = await (0, _ebConfig.prepareUpdateEnvironment)(api);
if (api.verbose) {
console.log('EB Config changes:');
console.dir({
toRemove,
toUpdate
});
}
if (toRemove.length > 0 || toUpdate.length > 0) {
await (0, _envReady.waitForEnvReady)(config, true);
await _aws.beanstalk.updateEnvironment({
EnvironmentName: environment,
OptionSettings: toUpdate,
OptionsToRemove: toRemove
}).promise();
console.log(' Updated Environment');
await (0, _envReady.waitForEnvReady)(config, true);
}
}
const {
ConfigurationSettings
} = await _aws.beanstalk.describeConfigurationSettings({
EnvironmentName: environment,
ApplicationName: app
}).promise();
if ((0, _ebConfig.scalingConfigChanged)(ConfigurationSettings[0].OptionSettings, config)) {
(0, _utils.logStep)('=> Configuring scaling');
await _aws.beanstalk.updateEnvironment({
EnvironmentName: environment,
OptionSettings: (0, _ebConfig.scalingConfig)(config.app).OptionSettings
}).promise();
await (0, _envReady.waitForEnvReady)(config, true);
}
}
async function events(api) {
const {
environment
} = (0, _utils.names)(api.getConfig());
const {
Events: envEvents
} = await _aws.beanstalk.describeEvents({
EnvironmentName: environment
}).promise();
console.log(envEvents.map(ev => `${ev.EventDate}: ${ev.Message}`).join('\n'));
}
async function status(api) {
const {
environment
} = (0, _utils.names)(api.getConfig());
let result;
try {
result = await _aws.beanstalk.describeEnvironmentHealth({
AttributeNames: ['All'],
EnvironmentName: environment
}).promise();
} catch (e) {
if (e.message.includes('No Environment found for EnvironmentName')) {
console.log(' AWS Beanstalk environment does not exist');
return;
}
throw e;
}
const {
InstanceHealthList
} = await _aws.beanstalk.describeInstancesHealth({
AttributeNames: ['All'],
EnvironmentName: environment
}).promise();
const {
RequestCount,
Duration,
StatusCodes,
Latency
} = result.ApplicationMetrics;
console.log(`Environment Status: ${result.Status}`);
console.log(`Health Status: ${(0, _utils.coloredStatusText)(result.Color, result.HealthStatus)}`);
if (result.Causes.length > 0) {
console.log('Causes: ');
result.Causes.forEach(cause => console.log(` ${cause}`));
}
console.log('');
console.log(`=== Metrics For Last ${Duration || 'Unknown'} Minutes ===`);
console.log(` Requests: ${RequestCount}`);
if (StatusCodes) {
console.log(' Status Codes');
console.log(` 2xx: ${StatusCodes.Status2xx}`);
console.log(` 3xx: ${StatusCodes.Status3xx}`);
console.log(` 4xx: ${StatusCodes.Status4xx}`);
console.log(` 5xx: ${StatusCodes.Status5xx}`);
}
if (Latency) {
console.log(' Latency');
console.log(` 99.9%: ${Latency.P999}`);
console.log(` 99% : ${Latency.P99}`);
console.log(` 95% : ${Latency.P95}`);
console.log(` 90% : ${Latency.P90}`);
console.log(` 85% : ${Latency.P85}`);
console.log(` 75% : ${Latency.P75}`);
console.log(` 50% : ${Latency.P50}`);
console.log(` 10% : ${Latency.P10}`);
}
console.log('');
console.log('=== Instances ===');
InstanceHealthList.forEach(instance => {
console.log(` ${instance.InstanceId}: ${(0, _utils.coloredStatusText)(instance.Color, instance.HealthStatus)}`);
});
if (InstanceHealthList.length === 0) {
console.log(' 0 Instances');
}
}
async function ssl(api) {
const config = api.getConfig();
await (0, _envReady.waitForEnvReady)(config, true);
if (!config.app || !config.app.sslDomains) {
(0, _utils.logStep)('=> Updating Beanstalk SSL Config');
await (0, _certificates.default)(config);
return;
}
(0, _utils.logStep)('=> Checking Certificate Status');
const domains = config.app.sslDomains;
const {
CertificateSummaryList
} = await _aws.acm.listCertificates().promise();
let found = null;
for (let i = 0; i < CertificateSummaryList.length; i++) {
const {
DomainName,
CertificateArn
} = CertificateSummaryList[i];
if (DomainName === domains[0]) {
const {
Certificate
} = await _aws.acm.describeCertificate({
// eslint-disable-line no-await-in-loop
CertificateArn
}).promise();
if (domains.join(',') === Certificate.SubjectAlternativeNames.join(',')) {
found = CertificateSummaryList[i];
}
}
}
let certificateArn;
if (!found) {
(0, _utils.logStep)('=> Requesting Certificate');
const result = await _aws.acm.requestCertificate({
DomainName: domains.shift(),
SubjectAlternativeNames: domains.length > 0 ? domains : null
}).promise();
certificateArn = result.CertificateArn;
}
if (found) {
certificateArn = found.CertificateArn;
}
let emailsProvided = false;
let checks = 0;
let certificate;
/* eslint-disable no-await-in-loop */
while (!emailsProvided && checks < 5) {
const {
Certificate
} = await _aws.acm.describeCertificate({
CertificateArn: certificateArn
}).promise();
const validationOptions = Certificate.DomainValidationOptions[0];
if (typeof validationOptions.ValidationEmails === 'undefined') {
emailsProvided = true;
certificate = Certificate;
} else if (validationOptions.ValidationEmails.length > 0 || checks === 6) {
emailsProvided = true;
certificate = Certificate;
} else {
checks += 1;
await new Promise(resolve => {
setTimeout(resolve, 1000 * 10);
});
}
}
if (certificate.Status === 'PENDING_VALIDATION') {
console.log('Certificate is pending validation.');
certificate.DomainValidationOptions.forEach(({
DomainName,
ValidationEmails,
ValidationDomain,
ValidationStatus
}) => {
if (ValidationStatus === 'SUCCESS') {
console.log(_chalk.default.green(`${ValidationDomain || DomainName} has been verified`));
return;
}
console.log(_chalk.default.yellow(`${ValidationDomain || DomainName} is pending validation`));
if (ValidationEmails) {
console.log('Emails with instructions have been sent to:');
ValidationEmails.forEach(email => {
console.log(` ${email}`);
});
}
console.log('Run "mup beanstalk ssl" after you have verified the domains, or to check the verification status');
});
} else if (certificate.Status === 'ISSUED') {
console.log(_chalk.default.green('Certificate has been issued'));
(0, _utils.logStep)('=> Updating Beanstalk SSL config');
await (0, _certificates.default)(config, certificateArn);
}
}
async function shell(api) {
const {
selected,
description
} = await (0, _utils.pickInstance)(api.getConfig(), api.getArgs()[2]);
if (!selected) {
console.log(description);
console.log('Run "mup beanstalk shell <instance id>"');
process.exitCode = 1;
return;
}
const {
sshOptions,
removeSSHAccess
} = await (0, _utils.connectToInstance)(api, selected, 'mup beanstalk shell');
const conn = new _ssh.Client();
conn.on('ready', () => {
conn.exec('sudo node /home/webapp/meteor-shell.js', {
pty: true
}, (err, stream) => {
if (err) {
throw err;
}
stream.on('close', async () => {
conn.end();
await removeSSHAccess();
process.exit();
});
process.stdin.setRawMode(true);
process.stdin.pipe(stream);
stream.pipe(process.stdout);
stream.stderr.pipe(process.stderr);
stream.setWindow(process.stdout.rows, process.stdout.columns);
process.stdout.on('resize', () => {
stream.setWindow(process.stdout.rows, process.stdout.columns);
});
});
}).connect(sshOptions);
}
async function debug(api) {
const config = api.getConfig();
const {
selected,
description
} = await (0, _utils.pickInstance)(config, api.getArgs()[2]);
if (!selected) {
console.log(description);
console.log('Run "mup beanstalk debug <instance id>"');
process.exitCode = 1;
return;
}
const {
sshOptions,
removeSSHAccess
} = await (0, _utils.connectToInstance)(api, selected, 'mup beanstalk debug');
const conn = new _ssh.Client();
conn.on('ready', async () => {
const result = await (0, _utils.executeSSHCommand)(conn, 'sudo pkill -USR1 -u webapp -n node || sudo pkill -USR1 -u nodejs -n node');
if (api.verbose) {
console.log(result.output);
}
const server = _objectSpread(_objectSpread({}, sshOptions), {}, {
pem: api.resolvePath(config.app.sshKey.privateKey)
});
let loggedConnection = false;
api.forwardPort({
server,
localAddress: '0.0.0.0',
localPort: 9229,
remoteAddress: '127.0.0.1',
remotePort: 9229,
onError(error) {
console.error(error);
},
onReady() {
console.log('Connected to server');
console.log('');
console.log('Debugger listening on ws://127.0.0.1:9229');
console.log('');
console.log('To debug:');
console.log('1. Open chrome://inspect in Chrome');
console.log('2. Select "Open dedicated DevTools for Node"');
console.log('3. Wait a minute while it connects and loads the app.');
console.log(' When it is ready, the app\'s files will appear in the Sources tab');
console.log('');
console.log('Warning: Do not use breakpoints when debugging a production server.');
console.log('They will pause your server when hit, causing it to not handle methods or subscriptions.');
console.log('Use logpoints or something else that does not pause the server');
console.log('');
console.log('The debugger will be enabled until the next time the app is restarted,');
console.log('though only accessible while this command is running');
},
onConnection() {
if (!loggedConnection) {
// It isn't guaranteed the debugger is connected, but not many
// other tools will try to connect to port 9229.
console.log('');
console.log('Detected by debugger');
loggedConnection = true;
}
}
});
}).connect(sshOptions);
process.on('SIGINT', async () => {
await removeSSHAccess();
process.exit();
});
}
//# sourceMappingURL=command-handlers.js.map
;