@liara/cli
Version:
The command line interface for Liara
444 lines (438 loc) ⢠16.1 kB
JavaScript
import ora from 'ora';
import path from 'path';
import fs from 'fs-extra';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { Flags } from '@oclif/core';
import Command from '../base.js';
import { getPort } from '../utils/get-port.js';
import { promptPort } from '../utils/prompt-port.js';
import { AVAILABLE_PLATFORMS } from '../constants.js';
import detectPlatform from '../utils/detect-platform.js';
import supportedVersions from '../utils/get-supported-versions.js';
import TeamNotFoundError from '../errors/team-error.js';
class Init extends Command {
async run() {
var _a;
const { args, flags } = await this.parse(Init);
try {
await this.setGotConfig(flags);
this.log(chalk.yellow(`This command interactively creates a basic liara.json configuration file.
It includes only the essential settings; additional configurations must be added manually.
š For more details on each field and its usage, visit: https://docs.liara.ir/paas/liarajson/.
Afterwards, use liara deploy to deploy your app.
š Press ^C at any time to quit.
`));
this.spinner = ora();
if (flags.y) {
try {
const dirName = path.basename(process.cwd());
const platform = detectPlatform(process.cwd());
const diskConfig = [];
const configs = this.setLiaraJsonConfigs(getPort(platform) || 3000, dirName, 'iran', platform, (_a = supportedVersions(platform)) === null || _a === void 0 ? void 0 : _a.defaultVersion, diskConfig);
await this.createLiaraJsonFile(configs);
this.exit(0);
}
catch (error) {
this.spinner.stop();
throw error;
}
}
const team = await this.getTeam(flags['team-id']);
const projects = await this.getPlatformsInfo();
const appName = await this.promptProjectName(projects, flags.name);
const buildLocation = await this.buildLocationPrompt(flags['build-location']);
const platform = await this.findPlatform(projects, appName, flags.platform);
const port = await this.getAppPort(platform, flags.port, projects);
const version = await this.promptPlatformVersion(platform, flags.version);
const disks = await this.getAppDisks(appName, projects);
const diskConfigs = await this.promptDiskConfig(disks, flags.disk, flags.path);
const cron = await this.promptCron(platform);
const healthCheck = await this.promptHealthCheck();
const configs = this.setLiaraJsonConfigs(port, appName, buildLocation, platform, version, diskConfigs, healthCheck, cron, team);
await this.createLiaraJsonFile(configs);
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async getPlatformsInfo() {
try {
this.spinner.start('Loading...');
const { projects } = await this.got('v1/projects').json();
this.spinner.stop();
return projects;
}
catch (error) {
if (error.response && error.response.statusCode === 401) {
throw new Error(`Authentication failed.
Please log in using the 'liara login' command.
If you are using an API token for authentication, please consider updating your API token.
You can still create a sample 'liara.json' file using the 'liara init -y' command.
`);
}
throw new Error(`There was something wrong while fetching your apps,
You can still use 'liara init' with its flags. Use 'liara init --help' for more details.`);
}
}
async promptProjectName(projects, flagValue) {
if (flagValue) {
return flagValue;
}
if (projects.length == 0) {
const { project } = (await inquirer.prompt({
name: 'project',
type: 'input',
message: 'Enter app name:',
}));
return project;
}
const { project } = (await inquirer.prompt({
name: 'project',
type: 'list',
message: 'Select an app:',
choices: [...projects.map((project) => project.project_id)],
}));
return project;
}
async findPlatform(projects, appName, flagsValue) {
if (projects.length == 0) {
const platform = await this.promptPlatform();
return platform;
}
if (flagsValue) {
return flagsValue;
}
const project = projects.find((project) => {
return project.project_id === appName;
});
if (!project) {
return 'static';
}
return project.type;
}
async getAppPort(platform, flagValue, projects) {
if (flagValue) {
return flagValue;
}
const defaultPort = getPort(platform);
if (!defaultPort) {
const port = await promptPort(platform);
return port;
}
return defaultPort;
}
async buildLocationPrompt(flagValue) {
if (flagValue) {
return flagValue;
}
const { location } = (await inquirer.prompt({
message: 'Specify the build location: ',
name: 'location',
type: 'list',
default: 'iran',
choices: ['iran', 'germany'],
}));
return location;
}
async promptPlatformVersion(platform, flagValue) {
if (flagValue) {
return flagValue;
}
const versions = supportedVersions(platform);
if (versions) {
let message;
if (['flask', 'django'].includes(platform)) {
message = 'Select python version';
}
if (platform === 'laravel') {
message = 'Select php version';
}
if (platform === 'next') {
message = 'Select node version';
}
if (!message) {
message = `Selcet ${platform} version: `;
}
const { version } = (await inquirer.prompt({
message: message || 'Select platform version',
name: 'version',
type: 'list',
default: versions.defaultVersion,
choices: versions.allVersions,
}));
return version;
}
}
async createLiaraJsonFile(configs) {
try {
this.spinner.start('Loading...');
await fs.writeFile(`${process.cwd()}/liara.json`, JSON.stringify(configs, null, 2));
this.spinner.succeed('liara.json is successfully created!');
}
catch (error) {
throw new Error('There was a problem while creating liara.json!');
}
}
setLiaraJsonConfigs(port, appName, buildLocation, platform, platformVersion, diskConfigs, healthCheck, cron, team) {
const versionKey = this.setVersionKey(platform, platformVersion);
const configs = {
port,
platform,
app: appName,
build: {
location: buildLocation,
},
};
if (team) {
configs['team-id'] = team._id;
}
if (cron) {
configs['cron'] = cron;
}
if (healthCheck) {
configs['healthCheck'] = healthCheck;
}
if (platformVersion) {
configs[platform] = {
[versionKey]: platformVersion,
};
}
if (diskConfigs) {
configs['disks'] = diskConfigs.map((config) => {
return { name: config.disk, mountTo: config.path };
});
}
return configs;
}
setVersionKey(platform, platformVersion) {
if (platformVersion) {
if (['flask', 'django'].includes(platform)) {
return 'pythonVersion';
}
if (platform == 'laravel') {
return 'phpVersion';
}
if (platform == 'next') {
return 'nodeVersion';
}
return 'version';
}
}
async getTeam(teamId) {
try {
this.spinner.start('Loading...');
const teams = await this.got(`v2/teams`).json();
this.spinner.stop();
if (teamId) {
const team = teams.teams.find((team) => team._id === teamId);
if (!team) {
throw new TeamNotFoundError(`You don't have a team with ID '${teamId}'`);
}
return team;
}
}
catch (error) {
if (error instanceof TeamNotFoundError) {
throw new Error(error.message);
}
if (error.response && error.response.statusCode === 401) {
throw new Error(`Authentication failed.
Please log in using the 'liara login' command.
If you are using an API token for authentication, please consider updating your API token.
You can still create a sample 'liara.json' file using the 'liara init -y' command.
`);
}
}
}
async getAppDisks(AppName, projects) {
try {
if (projects.length != 0) {
this.spinner.start('Loading...');
const project = projects.find((project) => {
return project.project_id === AppName;
});
const disks = await this.got(`v1/projects/${project === null || project === void 0 ? void 0 : project._id}/disks`).json();
this.spinner.stop();
return disks.disks;
}
}
catch (error) {
if (error.response && error.response.statusCode === 401) {
throw new Error(`Authentication failed.
Please log in using the 'liara login' command.
If you are using an API token for authentication, please consider updating your API token.
You can still create a sample 'liara.json' file using the 'liara init -y' command.
`);
}
throw new Error(`There was something wrong while fetching your app info,
You can still use 'liara init' with it's flags. Use 'liara init --help' for command details.`);
}
}
async promptPlatform() {
const { platform } = (await inquirer.prompt({
name: 'platform',
type: 'list',
message: 'Select a platform:',
choices: [...AVAILABLE_PLATFORMS.map((platform) => platform)],
}));
return platform;
}
async promptDiskConfig(disks, diskNameFlag, diskPathFlage) {
let diskConfig = [];
if (diskNameFlag && diskPathFlage) {
return [
{
disk: diskNameFlag,
path: diskPathFlage,
},
];
}
const { setDisk } = (await inquirer.prompt({
message: 'Configure disks? (Default: No)',
type: 'confirm',
name: 'setDisk',
default: false,
}));
if (setDisk) {
if (!disks || disks.length == 0) {
const { diskName } = (await inquirer.prompt({
message: 'Enter Disk name: ',
name: 'diskName',
type: 'input',
}));
const { path } = (await inquirer.prompt({
message: 'Specify the mount location: ',
name: 'path',
type: 'input',
}));
diskConfig = [{ disk: diskName, path: path }];
return diskConfig;
}
let shouldContinue = true;
while (shouldContinue && disks.length != 0) {
const { diskName } = (await inquirer.prompt({
message: 'Select a Disk: ',
name: 'diskName',
choices: disks,
type: 'list',
}));
const index = disks.findIndex((disk) => disk.name === diskName);
disks.splice(index, 1);
const { path } = (await inquirer.prompt({
message: `Mount path for ${diskName}: `,
name: 'path',
type: 'input',
}));
diskConfig.push({ disk: diskName, path });
if (disks.length != 0) {
const continueAnswer = (await inquirer.prompt({
message: 'Add another disk? (Default: No)',
type: 'confirm',
default: false,
name: 'shouldContinue',
}));
shouldContinue = continueAnswer.shouldContinue;
}
}
return diskConfig;
}
}
async promptCron(platform) {
if (['next', 'laravel', 'django', 'php', 'python', 'flask', 'go'].includes(platform)) {
const { setCronAnswer } = (await inquirer.prompt({
message: 'Configure cron? (Default: No)',
type: 'confirm',
name: 'setCronAnswer',
default: false,
}));
if (setCronAnswer) {
const { cron } = (await inquirer.prompt({
message: 'cron: ',
type: 'input',
name: 'cron',
}));
return cron.split(',').map((value) => value.trim());
}
}
}
async promptHealthCheck() {
const { setHealtCheckAnswer } = (await inquirer.prompt({
message: 'Configure healthcheck? (Default: No)',
type: 'confirm',
name: 'setHealtCheckAnswer',
default: false,
}));
if (setHealtCheckAnswer) {
const healthcheckConfigs = (await inquirer.prompt([
{
message: 'command: ',
type: 'input',
name: 'command',
},
{
message: 'interval(ms): ',
type: 'input',
name: 'interval',
},
{
message: 'timeout(ms): ',
type: 'input',
name: 'timeout',
},
{
message: 'retries: ',
type: 'input',
name: 'retries',
},
{
message: 'startPeriod(ms): ',
type: 'input',
name: 'startPeriod',
},
]));
return healthcheckConfigs;
}
}
}
Init.description = 'create a liara.json file';
Init.examples = ['<%= config.bin %> <%= command.id %>'];
Init.flags = {
...Command.flags,
y: Flags.boolean({
char: 'y',
description: 'create an example file',
aliases: [],
}),
name: Flags.string({
char: 'n',
description: 'the name of the app',
}),
port: Flags.integer({
char: 'p',
description: 'the port your app listens to',
}),
platform: Flags.string({
char: 'P',
description: 'the platform your app needs to run on',
}),
version: Flags.string({
char: 'v',
description: 'the version of the platform',
}),
'build-location': Flags.string({
description: "name of the build's location",
aliases: ['location'],
}),
disk: Flags.string({
description: 'the name of the disk',
char: 'd',
dependsOn: ['path'],
}),
path: Flags.string({
description: 'the path where the disk should be mounted',
dependsOn: ['disk'],
}),
};
export default Init;