@swell/cli
Version:
Swell's command line interface/utility
174 lines (172 loc) • 7.49 kB
JavaScript
import { confirm } from '@inquirer/prompts';
import { Flags } from '@oclif/core';
import ora from 'ora';
import * as semver from 'semver';
import config from '../../lib/config.js';
import { selectEnvironmentId, selectLoggedInStoreId, } from '../../lib/stores.js';
import style from '../../lib/style.js';
import { RemoteAppCommand } from '../../remote-app-command.js';
export default class AppInstall extends RemoteAppCommand {
static description = `Without a version specified, install the latest version of the app.
Without a store specified, install the app in the Live environment of the current store.`;
static examples = [
'swell app install',
'swell app install -v 3.2.7',
'swell app install -v 1.0.0 -s my-store-id',
];
static flags = {
env: Flags.string({
char: 'e',
description: 'target environment to install the app, defaults to live',
}),
store: Flags.string({
char: 's',
description: 'target store id to install the app, defaults to the current store',
}),
version: Flags.string({
char: 'v',
description: 'version of the app to install, defaults to latest',
async parse(input) {
if (!semver.valid(input)) {
throw new Error(`Invalid version: ${input}`);
}
return input;
},
}),
};
static orientation = {
env: 'test',
};
static summary = 'Install an existing app in another store environment.';
async run() {
const { flags } = await this.parse(AppInstall);
const { env, store, version } = flags;
this.app = await this.getAppWithConfig();
const versions = await this.getVersions(version);
if (versions.length === 0) {
this.error(`${version ? `Version ${version} not found` : 'No versions found'} for ${style.appConfigValue(this.app.name)} app. ${version
? ''
: `Create a version with the ${style.command('swell version')} command first.`}`);
}
const versionToInstall = version
? versions.find((ver) => ver.version === version)?.version
: versions[0].version;
let storeToInstall = store;
if (!storeToInstall) {
const loggedInStores = config.get('stores') || [];
if (loggedInStores.length > 0) {
storeToInstall = await selectLoggedInStoreId('Which store do you want to install the app in?');
}
if (!storeToInstall) {
return;
}
}
let envToInstall = env;
if (!envToInstall) {
envToInstall = await selectEnvironmentId();
}
if (envToInstall === 'live') {
envToInstall = undefined;
}
if (envToInstall && envToInstall !== 'test') {
this.error(`${envToInstall} is not a valid environment.`);
}
if (envToInstall === 'test' && storeToInstall === this.app.client_id) {
this.error(`You cannot install an app in the test environment of the same store.`);
}
config.ensureLoggedIn(storeToInstall);
await this.api.setStoreEnv(storeToInstall, envToInstall);
// check if app is already installed on store
const existingInstalledApp = await this.getInstalledApp();
const existingVersion = existingInstalledApp?.id
? existingInstalledApp.version
: null;
// confirm install unless params were passed explicitly
let extraMessage = '';
if (!store || !env) {
const continueInstall = await confirm({
message: this.installConfirmationMessage(storeToInstall, envToInstall, versionToInstall, existingVersion),
});
if (!continueInstall) {
// we don't want to throw an error as it is a valid exit
return;
}
}
else {
extraMessage = ` ${style.appConfigValue(this.app.name)} v${versionToInstall} on ${style.storeId(storeToInstall)} [${envToInstall || 'live'}]`;
}
const shouldContinue = existingVersion ||
(await this.confirmRemoveDevelopmentApp(storeToInstall));
if (!shouldContinue) {
return;
}
const spinner = ora();
this.log();
// install or update app depending on whether it's already installed
let request;
if (existingInstalledApp?.id) {
spinner.start(`Updating${extraMessage}`);
request = async () => this.updateInstalledApp(versionToInstall, spinner);
}
else {
spinner.start(`Installing${extraMessage}`);
request = async () => this.installApp(versionToInstall, spinner);
}
const response = await this.handleRequestErrors(request, () => spinner.fail('Something went wrong.'));
if (response?.errors) {
spinner.fail(`Error installing app: ${JSON.stringify(response.errors)}`);
}
else {
spinner.succeed(`${existingInstalledApp?.id ? 'Update' : 'Install'} complete.\n`);
this.log(`Manage app settings in your dashboard at ${style.link(this.dashboardUrl({ flags: { env: envToInstall } }, storeToInstall))}.`);
}
}
async confirmRemoveDevelopmentApp(storeToInstall) {
const existingSourcedApps = await this.api.get({ adminPath: `/client/apps` }, {
query: { source_id: this.app.id },
});
if (existingSourcedApps.results[0]?.id) {
const continueInstall = await confirm({
default: false,
message: `${style.storeId(storeToInstall)} has a development version of ${style.appConfigValue(this.app.name)}. Do you want to replace it with an installed instance?`,
});
if (!continueInstall) {
return false;
}
// uninstall the existing app
const spinner = ora();
spinner.start('Removing development app...');
try {
await this.api.put({
adminPath: `/client/apps/${existingSourcedApps.results[0].app_id}`,
}, {
body: { uninstalled: true },
});
spinner.succeed('Development app removed.');
}
catch (error) {
spinner.fail(`Error removing development app from ${style.storeId(storeToInstall)}.`);
this.logError(error.message);
return false;
}
}
return true;
}
installConfirmationMessage(storeToInstall, envToInstall, versionToInstall, existingVersion) {
const message = [];
if (existingVersion && semver.gt(versionToInstall, existingVersion)) {
message.push('Updating');
}
else if (existingVersion &&
semver.lt(versionToInstall, existingVersion)) {
message.push('Rolling back');
}
else {
message.push('Install');
}
let versionToShow = versionToInstall;
versionToShow += versionToInstall === this.app.version ? ' (latest)' : '';
message.push(style.appConfigValue(this.app.name), `v${versionToShow}`, `on ${style.storeId(storeToInstall)} [${envToInstall || 'live'}]?`);
return message.join(' ');
}
}