@liara/cli
Version:
The command line interface for Liara
221 lines (220 loc) • 8.82 kB
JavaScript
import ora from 'ora';
import inquirer from 'inquirer';
import { Flags } from '@oclif/core';
import Command from '../../base.js';
import parseJSON from '../../utils/json-parse.js';
import { AVAILABLE_PLATFORMS } from '../../constants.js';
import checkRegexPattern from '../../utils/name-regex.js';
import { createDebugLogger } from '../../utils/output.js';
import getBundlePlanName from '../../utils/set-bundle-plan-names.js';
class AppCreate extends Command {
async run() {
this.spinner = ora();
const { flags } = await this.parse(AppCreate);
const debug = createDebugLogger(flags.debug);
await this.setGotConfig(flags);
const name = flags.app || (await this.promptAppName());
await this.setGotConfig(flags);
const platform = flags.platform || (await this.promptPlatform());
if (!AVAILABLE_PLATFORMS.includes(platform)) {
this.error(`Unknown platform: ${platform}`);
}
const network = flags.network
? await this.getNetwork(flags.network)
: await this.promptNetwork();
const planID = flags.plan || (await this.promptPlan());
const bundlePlanID = flags['feature-plan'] || (await this.promptBundlePlan(planID));
const readOnly = flags['read-only'] === 'true'
? true
: flags['read-only'] === 'false'
? false
: undefined;
if (planID === 'free' && bundlePlanID !== 'free') {
this.error(`Only "free" feature bundle plan is available for free plan.`);
}
try {
await this.got.post('v1/projects/', {
json: {
name,
planID,
platform,
bundlePlanID,
network: network === null || network === void 0 ? void 0 : network._id,
readOnlyRootFilesystem: readOnly,
},
});
this.log(`App ${name} created.`);
}
catch (error) {
debug(error.message);
const err = parseJSON(error.response.body);
if (error.response && error.response.body) {
debug(JSON.stringify(error.response.body));
}
if (error.response &&
err.statusCode === 400 &&
err.message.includes('bundlePlan is not available for free plan.')) {
this.error(`The selected feature bundle plan is not available for free plan.`);
}
if (error.response && error.response.statusCode === 402) {
this.error(`Not enough balance. Please charge your account.`);
}
if (error.response && error.response.statusCode === 404) {
this.error(`Could not create the app.`);
}
if (error.response && error.response.statusCode === 409) {
this.error(`The app already exists. Please use a unique name for your app.`);
}
if (error.response && error.response.statusCode === 403) {
if (err.data.code === 'free_plan_platform') {
this.error(`The free plan is not available for ${platform} platform.`);
}
if (err.data.code === 'free_plan_count') {
this.error(`You are allowed to create only one app on the free plan`);
}
}
this.error(`Error: Unable to Create App
Please try the following steps:
1. Check your internet connection.
2. Ensure you have enough balance.
3. Try again later.
4. If you still have problems, please contact support by submitting a ticket at https://console.liara.ir/tickets`);
}
}
async promptBundlePlan(plan) {
this.spinner.start('Loading...');
try {
const { plans } = await this.got('v1/me').json();
this.spinner.stop();
const { bundlePlan } = (await inquirer.prompt({
name: 'bundlePlan',
type: 'list',
message: 'Please select a feature bundle plan:',
choices: [
...Object.keys(plans.projectBundlePlans)
.filter((bundlePlan) => {
return bundlePlan === plan;
})
.map((bundlePlan) => {
const planDetails = plans.projectBundlePlans[bundlePlan];
return Object.keys(planDetails)
.filter((key) => planDetails[key].available)
.map((key) => {
const { displayPrice } = planDetails[key];
return {
name: `Plan: ${getBundlePlanName(key)}, Price: ${displayPrice.toLocaleString()} Tomans/Month`,
value: key,
};
});
})
.flat(),
],
}));
return bundlePlan;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptPlan() {
this.spinner.start('Loading...');
try {
// TODO: Use proper type for plans
const { plans } = await this.got('v1/me').json();
this.spinner.stop();
const { plan } = (await inquirer.prompt({
name: 'plan',
type: 'list',
message: 'Please select a plan:',
choices: [
...Object.keys(plans.projects)
.filter((plan) => {
if ((plan === 'free' || plan.includes('g2')) &&
plans.projects[plan].available &&
plans.projects[plan].region === 'iran') {
return true;
}
})
.map((plan) => {
const availablePlan = plans.projects[plan];
const ram = availablePlan.RAM.amount;
const cpu = availablePlan.CPU.amount;
const disk = availablePlan.volume;
const price = availablePlan.price * 720;
const storageClass = availablePlan.storageClass;
return {
value: plan,
name: `RAM: ${ram}${' '.repeat(5 - ram.toString().length)} GB, CPU: ${cpu}${' '.repeat(6 - cpu.toString().length)}Core, Disk: ${disk}${' '.repeat(5 - disk.toString().length) + 'GB'}${storageClass || 'SSD'}, Price: ${price.toLocaleString()}${price
? ' '.repeat(7 - Math.floor(price).toString().length) +
'Tomans/Month'
: ''}`,
};
}),
],
}));
return plan;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptPlatform() {
this.spinner.start('Loading...');
try {
this.spinner.stop();
const { platform } = (await inquirer.prompt({
name: 'platform',
type: 'list',
message: 'Please select a platform:',
choices: [...AVAILABLE_PLATFORMS.map((platform) => platform)],
}));
return platform;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptAppName() {
const { name } = (await inquirer.prompt({
name: 'name',
type: 'input',
message: 'Enter app name:',
validate: (input) => input.length > 2,
}));
if (!checkRegexPattern(name)) {
this.error('Please enter a valid name for your app.');
}
return name;
}
}
AppCreate.description = 'create an app';
AppCreate.flags = {
...Command.flags,
app: Flags.string({
char: 'a',
description: 'app id',
}),
platform: Flags.string({
description: 'platform',
}),
plan: Flags.string({
description: 'plan',
}),
'feature-plan': Flags.string({
description: 'feature bundle plan',
}),
network: Flags.string({
char: 'n',
description: 'network',
}),
'read-only': Flags.string({
char: 'r',
description: 'read-only filesystem',
options: ['true', 'false'],
}),
};
AppCreate.aliases = ['create'];
export default AppCreate;