From 14527a680e233aab1be6a3fc918c1cfbc05ab0f0 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Thu, 28 Nov 2024 17:12:10 +0330 Subject: [PATCH 01/20] add: init command with initial implementation --- src/commands/init.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/commands/init.ts diff --git a/src/commands/init.ts b/src/commands/init.ts new file mode 100644 index 0000000..d23eabd --- /dev/null +++ b/src/commands/init.ts @@ -0,0 +1,54 @@ +import { Args, Flags } from '@oclif/core'; +import fs from 'fs-extra'; +import ora, { Ora } from 'ora'; +import inquirer from 'inquirer'; +import Command, { + IGetDomainsResponse, + IProjectDetailsResponse, +} from '../base.js'; +import IGetProjectsResponse from '../types/get-project-response.js'; + +export default class Init extends Command { + static override description = 'describe the command here'; + + static override examples = ['<%= config.bin %> <%= command.id %>']; + + static override flags = { + ...Command.flags, + force: Flags.boolean({ char: 'f' }), + name: Flags.string({ char: 'n', description: 'name to print' }), + }; + + public async run(): Promise { + const { args, flags } = await this.parse(Init); + await this.setGotConfig(flags); + await this.promptProject(); + } + async promptProject(): Promise { + // this.spinner.start('Loading...\n'); + + try { + const { projects } = + await this.got('v1/projects').json(); + // this.spinner.stop(); + if (!projects.length) { + this.warn( + 'Please go to https://console.liara.ir/apps and create an app, first.', + ); + this.exit(1); + } + + const { project } = (await inquirer.prompt({ + name: 'project', + type: 'list', + message: 'Please select an app:', + choices: [...projects.map((project) => project.project_id)], + })) as { project: string }; + + return project; + } catch (error) { + // this.spinner.stop(); + throw error; + } + } +} From c40a0101f17f2def0a97df55bfb0c6f4fa78e2ed Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sat, 30 Nov 2024 15:14:54 +0330 Subject: [PATCH 02/20] chore: adding promptPort() to base.ts --- src/base.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/base.ts b/src/base.ts index 825917b..325d93e 100644 --- a/src/base.ts +++ b/src/base.ts @@ -26,6 +26,8 @@ import { GLOBAL_CONF_PATH, GLOBAL_CONF_VERSION, } from './constants.js'; +import { getDefaultPort } from './utils/get-port.js'; +import validatePort from './utils/validate-port.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -309,7 +311,17 @@ Please check your network connection.`); throw error; } } - + async promptPort(platform: string): Promise { + const { port } = (await inquirer.prompt({ + name: 'port', + type: 'input', + default: getDefaultPort(platform), + message: 'Enter the port your app listens to:', + validate: validatePort, + })) as { port: number }; + + return port; + } async getCurrentAccount(): Promise { const accounts = (await this.readGlobalConfig()).accounts; const accName = Object.keys(accounts).find( From cab368ba046b426809a49fb311dd5a94bf0a8410 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sat, 30 Nov 2024 16:42:17 +0330 Subject: [PATCH 03/20] feat: add supportedVersions() function --- src/utils/getSupportedVersions.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/utils/getSupportedVersions.ts diff --git a/src/utils/getSupportedVersions.ts b/src/utils/getSupportedVersions.ts new file mode 100644 index 0000000..0500fd2 --- /dev/null +++ b/src/utils/getSupportedVersions.ts @@ -0,0 +1,17 @@ +const versions = { + dotnet: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0', '8.0', '9.0'], + node: ['14', '16', '18', '20', '22'], + next: ['20', '22'], + laravel: ['8.3.0', '8.2.0', '8.1.0', '8.0.0', '7.4.0', '7.3.0', '7.2.0'], + php: ['8.3.0', '8.2.0', '8.1.0', '8.0.0', '7.4.0', '7.3.0', '7.2.0'], + python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], + django: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], + flask: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], +}; + +type Platform = keyof typeof versions; + +export default function supportedVersions(platform: Platform) { + return versions[platform]; +} +supportedVersions('django'); From 6685c4e4614ed16bef29f7e9f0d71dd485993494 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 1 Dec 2024 09:52:35 +0330 Subject: [PATCH 04/20] feat: add some configs to init command --- src/commands/init.ts | 72 +++++++++++++++++++++++++++---- src/utils/getSupportedVersions.ts | 1 - 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index d23eabd..40b0e8d 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,13 +1,17 @@ import { Args, Flags } from '@oclif/core'; import fs from 'fs-extra'; +import validatePort from '../utils/validate-port.js'; import ora, { Ora } from 'ora'; +import { getPort, getDefaultPort } from '../utils/get-port.js'; import inquirer from 'inquirer'; import Command, { IGetDomainsResponse, + IProject, IProjectDetailsResponse, } from '../base.js'; import IGetProjectsResponse from '../types/get-project-response.js'; - +import ILiaraJSON from '../types/liara-json.js'; +import supportedVersions from '../utils/getSupportedVersions.js'; export default class Init extends Command { static override description = 'describe the command here'; @@ -22,15 +26,31 @@ export default class Init extends Command { public async run(): Promise { const { args, flags } = await this.parse(Init); await this.setGotConfig(flags); - await this.promptProject(); + const projects = await this.getPlatformsInfo(); + const appName = await this.promptProjectName(projects); + const buildLocation = await this.buildLocationPrompt(); + const platform = this.findPlatform(projects, appName); + const port = await this.getAppPort(platform, appName); + const configs: ILiaraJSON = { + port, + app: appName, + build: { + location: buildLocation, + }, + }; + this.createLiaraJsonFile(configs); } - async promptProject(): Promise { - // this.spinner.start('Loading...\n'); - + async getPlatformsInfo(): Promise { try { const { projects } = await this.got('v1/projects').json(); - // this.spinner.stop(); + return projects as IProject[]; + } catch (error) { + throw error; + } + } + async promptProjectName(projects: IProject[]): Promise { + try { if (!projects.length) { this.warn( 'Please go to https://console.liara.ir/apps and create an app, first.', @@ -44,11 +64,47 @@ export default class Init extends Command { message: 'Please select an app:', choices: [...projects.map((project) => project.project_id)], })) as { project: string }; - return project; } catch (error) { - // this.spinner.stop(); throw error; } } + findPlatform(projects: IProject[], appName: string): string { + const project = projects.find((project) => { + return project.project_id === appName; + }); + return project!.type; + } + async getAppPort(platform: string, appName: string): Promise { + const defaultPort = getPort(platform); + if (!defaultPort) { + const port = await this.promptPort(appName); + return port; + } + return defaultPort; + } + async buildLocationPrompt() { + try { + const { location } = (await inquirer.prompt({ + message: 'Build location', + name: 'location', + type: 'list', + default: 'iran', + choices: ['iran', 'germany'], + })) as { location: string }; + return location; + } catch (error) { + throw error; + } + } + async createLiaraJsonFile(configs: ILiaraJSON) { + try { + await fs.writeFile( + `${process.cwd()}/liara.json`, + JSON.stringify(configs, null, 2), + ); + } catch (error) { + throw new Error('There was a problem while creating liara.json file!'); + } + } } diff --git a/src/utils/getSupportedVersions.ts b/src/utils/getSupportedVersions.ts index 0500fd2..1a7a20b 100644 --- a/src/utils/getSupportedVersions.ts +++ b/src/utils/getSupportedVersions.ts @@ -14,4 +14,3 @@ type Platform = keyof typeof versions; export default function supportedVersions(platform: Platform) { return versions[platform]; } -supportedVersions('django'); From 8286d3c0956515b5646d13894fa9e01a9c0e7315 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 1 Dec 2024 12:25:14 +0330 Subject: [PATCH 05/20] feat: add platform Version config to init command --- src/commands/init.ts | 87 ++++++++++++++++++++++++++++--- src/utils/getSupportedVersions.ts | 78 ++++++++++++++++++++++----- 2 files changed, 146 insertions(+), 19 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 40b0e8d..14fac77 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -4,6 +4,8 @@ import validatePort from '../utils/validate-port.js'; import ora, { Ora } from 'ora'; import { getPort, getDefaultPort } from '../utils/get-port.js'; import inquirer from 'inquirer'; +import chalk from 'chalk'; + import Command, { IGetDomainsResponse, IProject, @@ -12,6 +14,7 @@ import Command, { import IGetProjectsResponse from '../types/get-project-response.js'; import ILiaraJSON from '../types/liara-json.js'; import supportedVersions from '../utils/getSupportedVersions.js'; + export default class Init extends Command { static override description = 'describe the command here'; @@ -26,18 +29,28 @@ export default class Init extends Command { public async run(): Promise { const { args, flags } = await this.parse(Init); await this.setGotConfig(flags); + this.log( + chalk.yellow(`This utility will guide you through creating a liara.json file. +It only covers the most common fields and tries to guess sensible defaults. +For detailed documentation on these fields and what they do, refer to the official documentation. + +Afterwards, use liara deploy to deploy your project. + +Press ^C at any time to quit.`), + ); const projects = await this.getPlatformsInfo(); const appName = await this.promptProjectName(projects); const buildLocation = await this.buildLocationPrompt(); const platform = this.findPlatform(projects, appName); const port = await this.getAppPort(platform, appName); - const configs: ILiaraJSON = { + const version = await this.promptPlatformVersion(platform); + const configs = this.setLiaraJsonConfigs( port, - app: appName, - build: { - location: buildLocation, - }, - }; + appName, + buildLocation, + platform, + version, + ); this.createLiaraJsonFile(configs); } async getPlatformsInfo(): Promise { @@ -83,7 +96,7 @@ export default class Init extends Command { } return defaultPort; } - async buildLocationPrompt() { + async buildLocationPrompt(): Promise { try { const { location } = (await inquirer.prompt({ message: 'Build location', @@ -97,6 +110,24 @@ export default class Init extends Command { throw error; } } + async promptPlatformVersion(platform: string): Promise { + try { + const versions = supportedVersions(platform); + if (versions !== null) { + const { version } = (await inquirer.prompt({ + message: `${platform} version: `, + name: 'version', + type: 'list', + default: versions.defaultVersion, + choices: versions.allVersions, + })) as { version: string }; + return version; + } + return null; + } catch (error) { + throw error; + } + } async createLiaraJsonFile(configs: ILiaraJSON) { try { await fs.writeFile( @@ -107,4 +138,46 @@ export default class Init extends Command { throw new Error('There was a problem while creating liara.json file!'); } } + setLiaraJsonConfigs( + port: number, + appName: string, + buildLocation: string, + platform: string, + platformVersion: string | null, + ) { + const versionKey = this.setVersionKey(platform, platformVersion); + const configs: ILiaraJSON = { + platform, + port, + app: appName, + build: { + location: buildLocation, + }, + }; + + if (platformVersion !== null) { + (configs as Record)[platform] = { + [versionKey!]: platformVersion, + }; + } + return configs; + } + setVersionKey( + platform: string, + platformVersion: string | null, + ): string | null { + if (platformVersion == null) { + return null; + } + if (platform in ['flask', 'django']) { + return 'pythonVersion'; + } + if (platform == 'laravel') { + return 'phpVersion'; + } + if (platform == 'next') { + return 'nodeVersion'; + } + return 'version'; + } } diff --git a/src/utils/getSupportedVersions.ts b/src/utils/getSupportedVersions.ts index 1a7a20b..53b3301 100644 --- a/src/utils/getSupportedVersions.ts +++ b/src/utils/getSupportedVersions.ts @@ -1,16 +1,70 @@ -const versions = { - dotnet: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0', '8.0', '9.0'], - node: ['14', '16', '18', '20', '22'], - next: ['20', '22'], - laravel: ['8.3.0', '8.2.0', '8.1.0', '8.0.0', '7.4.0', '7.3.0', '7.2.0'], - php: ['8.3.0', '8.2.0', '8.1.0', '8.0.0', '7.4.0', '7.3.0', '7.2.0'], - python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], - django: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], - flask: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], +interface IVersions { + [platform: string]: { + defaultVersion: string; + allVersions: string[]; + }; +} +const versions: IVersions = { + dotnet: { + defaultVersion: '6.0', + allVersions: [ + '2.1', + '2.2', + '3.0', + '3.1', + '5.0', + '6.0', + '7.0', + '8.0', + '9.0', + ], + }, + node: { defaultVersion: '20', allVersions: ['14', '16', '18', '20', '22'] }, + next: { defaultVersion: '20', allVersions: ['20', '22'] }, + laravel: { + defaultVersion: '8.0', + allVersions: [ + '8.3.0', + '8.2.0', + '8.1.0', + '8.0.0', + '7.4.0', + '7.3.0', + '7.2.0', + ], + }, + php: { + defaultVersion: '8.0', + allVersions: [ + '8.3.0', + '8.2.0', + '8.1.0', + '8.0.0', + '7.4.0', + '7.3.0', + '7.2.0', + ], + }, + python: { + defaultVersion: '3.11', + allVersions: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], + }, + django: { + defaultVersion: '3.10', + allVersions: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], + }, + flask: { + defaultVersion: '3.10', + allVersions: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], + }, }; +// export const defaultVersion={ -type Platform = keyof typeof versions; +// } -export default function supportedVersions(platform: Platform) { - return versions[platform]; +export default function supportedVersions(platform: string) { + if (platform in versions) { + return versions[platform as keyof typeof versions]; + } + return null; } From 671080bd468075bb32e4bf48fe7c3dc7622433b8 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 1 Dec 2024 13:53:37 +0330 Subject: [PATCH 06/20] chore: adding IGetDiskResponse to base.ts --- src/base.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/base.ts b/src/base.ts index 325d93e..768fe76 100644 --- a/src/base.ts +++ b/src/base.ts @@ -74,6 +74,21 @@ export interface IProject { created_at: string; isDeployed: boolean; } +interface IDisk { + name: string; + size: number; + updatedAt: string; + createdAt: string; + filebrowserUrl: string; +} +interface IMount { + name: string; + mountedTo: string; +} +export interface IGetDiskResponse { + disks: IDisk[]; + mounts: IMount[]; +} export interface IGetProjectsResponse { projects: IProject[]; From 6e5be0c21d80dfd9b1e21afb0ae5cd26b69ff978 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 1 Dec 2024 16:04:19 +0330 Subject: [PATCH 07/20] feat: add disk config to init command --- src/base.ts | 15 ++++----- src/commands/init.ts | 77 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/base.ts b/src/base.ts index 768fe76..b2b2399 100644 --- a/src/base.ts +++ b/src/base.ts @@ -74,19 +74,18 @@ export interface IProject { created_at: string; isDeployed: boolean; } -interface IDisk { - name: string; - size: number; - updatedAt: string; - createdAt: string; - filebrowserUrl: string; -} interface IMount { name: string; mountedTo: string; } export interface IGetDiskResponse { - disks: IDisk[]; + disks: { + name: string; + size: number; + updatedAt: string; + createdAt: string; + filebrowserUrl: string; + }[]; mounts: IMount[]; } diff --git a/src/commands/init.ts b/src/commands/init.ts index 14fac77..7953a4e 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,12 +1,13 @@ -import { Args, Flags } from '@oclif/core'; +import { Args, Config, Flags } from '@oclif/core'; import fs from 'fs-extra'; import validatePort from '../utils/validate-port.js'; import ora, { Ora } from 'ora'; import { getPort, getDefaultPort } from '../utils/get-port.js'; -import inquirer from 'inquirer'; +import inquirer, { Answers } from 'inquirer'; import chalk from 'chalk'; import Command, { + IGetDiskResponse, IGetDomainsResponse, IProject, IProjectDetailsResponse, @@ -36,7 +37,8 @@ For detailed documentation on these fields and what they do, refer to the offici Afterwards, use liara deploy to deploy your project. -Press ^C at any time to quit.`), +Press ^C at any time to quit. +`), ); const projects = await this.getPlatformsInfo(); const appName = await this.promptProjectName(projects); @@ -44,12 +46,15 @@ Press ^C at any time to quit.`), const platform = this.findPlatform(projects, appName); const port = await this.getAppPort(platform, appName); const version = await this.promptPlatformVersion(platform); + const disks = await this.getAppDisks(appName, projects); + const diskConfigs = await this.promptDiskConfig(disks); const configs = this.setLiaraJsonConfigs( port, appName, buildLocation, platform, version, + diskConfigs, ); this.createLiaraJsonFile(configs); } @@ -144,7 +149,8 @@ Press ^C at any time to quit.`), buildLocation: string, platform: string, platformVersion: string | null, - ) { + diskConfigs: { disk: string; path: string } | null, + ): ILiaraJSON { const versionKey = this.setVersionKey(platform, platformVersion); const configs: ILiaraJSON = { platform, @@ -160,6 +166,14 @@ Press ^C at any time to quit.`), [versionKey!]: platformVersion, }; } + if (diskConfigs !== null) { + configs['disks'] = [ + { + name: diskConfigs.disk, + mountTo: diskConfigs.path, + }, + ]; + } return configs; } setVersionKey( @@ -180,4 +194,59 @@ Press ^C at any time to quit.`), } return 'version'; } + async getAppDisks(AppName: string, projects: IProject[]) { + try { + const project = projects.find((project) => { + return project.project_id === AppName; + }); + const disks = await this.got( + `v1/projects/${project?._id}/disks`, + ).json(); + return disks.disks; + } catch (error) { + throw error; + } + } + async setDiskConfigAnswer(): Promise { + const { setDisk } = (await inquirer.prompt({ + message: 'Set disk configs? ', + type: 'confirm', + name: 'setDisk', + default: false, + })) as { setDisk: boolean }; + return setDisk; + } + async promptDiskConfig( + disks: object[], + ): Promise<{ disk: string; path: string } | null> { + try { + if (disks.length > 0) { + const { setDisk } = (await inquirer.prompt({ + message: 'Set disk configs? ', + type: 'confirm', + name: 'setDisk', + default: false, + })) as { setDisk: boolean }; + if (setDisk) { + const diskConfigs = (await inquirer.prompt([ + { + message: 'Disk name: ', + choices: disks, + name: 'disk', + type: 'list', + }, + { + message: 'MountTO: ', + name: 'path', + type: 'input', + }, + ])) as { disk: string; path: string }; + return diskConfigs; + } + } + return null; + } catch (error) { + throw error; + } + } } From 49e3cd28f20fadf3c4787d45016813afac63c921 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 1 Dec 2024 16:56:19 +0330 Subject: [PATCH 08/20] improve: ux for init command --- src/commands/init.ts | 68 +++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 7953a4e..053717f 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,7 +1,7 @@ import { Args, Config, Flags } from '@oclif/core'; import fs from 'fs-extra'; import validatePort from '../utils/validate-port.js'; -import ora, { Ora } from 'ora'; +import ora, { Ora, spinners } from 'ora'; import { getPort, getDefaultPort } from '../utils/get-port.js'; import inquirer, { Answers } from 'inquirer'; import chalk from 'chalk'; @@ -17,21 +17,26 @@ import ILiaraJSON from '../types/liara-json.js'; import supportedVersions from '../utils/getSupportedVersions.js'; export default class Init extends Command { - static override description = 'describe the command here'; + static override description = + 'With this command, you can create a liara.json file'; static override examples = ['<%= config.bin %> <%= command.id %>']; static override flags = { ...Command.flags, - force: Flags.boolean({ char: 'f' }), - name: Flags.string({ char: 'n', description: 'name to print' }), + y: Flags.boolean({ + char: 'y', + description: 'create an example file', + aliases: [], + }), }; - public async run(): Promise { const { args, flags } = await this.parse(Init); - await this.setGotConfig(flags); - this.log( - chalk.yellow(`This utility will guide you through creating a liara.json file. + + try { + await this.setGotConfig(flags); + this.log( + chalk.yellow(`This utility will guide you through creating a liara.json file. It only covers the most common fields and tries to guess sensible defaults. For detailed documentation on these fields and what they do, refer to the official documentation. @@ -39,29 +44,36 @@ Afterwards, use liara deploy to deploy your project. Press ^C at any time to quit. `), - ); - const projects = await this.getPlatformsInfo(); - const appName = await this.promptProjectName(projects); - const buildLocation = await this.buildLocationPrompt(); - const platform = this.findPlatform(projects, appName); - const port = await this.getAppPort(platform, appName); - const version = await this.promptPlatformVersion(platform); - const disks = await this.getAppDisks(appName, projects); - const diskConfigs = await this.promptDiskConfig(disks); - const configs = this.setLiaraJsonConfigs( - port, - appName, - buildLocation, - platform, - version, - diskConfigs, - ); - this.createLiaraJsonFile(configs); + ); + this.spinner = ora(); + const projects = await this.getPlatformsInfo(); + const appName = await this.promptProjectName(projects); + const buildLocation = await this.buildLocationPrompt(); + const platform = this.findPlatform(projects, appName); + const port = await this.getAppPort(platform, appName); + const version = await this.promptPlatformVersion(platform); + const disks = await this.getAppDisks(appName, projects); + const diskConfigs = await this.promptDiskConfig(disks); + const configs = this.setLiaraJsonConfigs( + port, + appName, + buildLocation, + platform, + version, + diskConfigs, + ); + this.createLiaraJsonFile(configs); + } catch (error) { + throw error; + } } async getPlatformsInfo(): Promise { try { + this.spinner.start(); + const { projects } = await this.got('v1/projects').json(); + this.spinner.stop(); return projects as IProject[]; } catch (error) { throw error; @@ -135,10 +147,12 @@ Press ^C at any time to quit. } async createLiaraJsonFile(configs: ILiaraJSON) { try { + this.spinner.start(); await fs.writeFile( `${process.cwd()}/liara.json`, JSON.stringify(configs, null, 2), ); + this.spinner.succeed('Liara.json file is successfully created!'); } catch (error) { throw new Error('There was a problem while creating liara.json file!'); } @@ -196,12 +210,14 @@ Press ^C at any time to quit. } async getAppDisks(AppName: string, projects: IProject[]) { try { + this.spinner.start(); const project = projects.find((project) => { return project.project_id === AppName; }); const disks = await this.got( `v1/projects/${project?._id}/disks`, ).json(); + this.spinner.stop(); return disks.disks; } catch (error) { throw error; From dce2bdb0a6f051915468dc7ebf79dc2d4be700e3 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Mon, 2 Dec 2024 12:20:27 +0330 Subject: [PATCH 09/20] feat: add flags to init command --- src/commands/init.ts | 181 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 36 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 053717f..598590b 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,20 +1,16 @@ import { Args, Config, Flags } from '@oclif/core'; import fs from 'fs-extra'; -import validatePort from '../utils/validate-port.js'; -import ora, { Ora, spinners } from 'ora'; -import { getPort, getDefaultPort } from '../utils/get-port.js'; -import inquirer, { Answers } from 'inquirer'; +import ora from 'ora'; +import inquirer from 'inquirer'; import chalk from 'chalk'; +import path from 'path'; -import Command, { - IGetDiskResponse, - IGetDomainsResponse, - IProject, - IProjectDetailsResponse, -} from '../base.js'; +import Command, { IGetDiskResponse, IProject } from '../base.js'; +import { getPort } from '../utils/get-port.js'; import IGetProjectsResponse from '../types/get-project-response.js'; import ILiaraJSON from '../types/liara-json.js'; import supportedVersions from '../utils/getSupportedVersions.js'; +import detectPlatform from '../utils/detect-platform.js'; export default class Init extends Command { static override description = @@ -26,9 +22,42 @@ export default class Init extends Command { ...Command.flags, y: Flags.boolean({ char: 'y', - description: 'create an example file', + description: 'Create an example file', aliases: [], }), + name: Flags.string({ + char: 'n', + description: 'Your app name', + }), + port: Flags.integer({ + char: 'p', + description: 'Port your app listens to', + }), + platform: Flags.string({ + char: 'P', + description: 'App platform', + }), + version: Flags.string({ + char: 'v', + description: 'Platform Version', + }), + 'build-location': Flags.string({ + description: 'Build location', + aliases: ['location'], + }), + 'no-disk': Flags.boolean({ + description: 'No disk config', + exclusive: ['disk', 'path'], + }), + disk: Flags.string({ + description: 'Disk name', + char: 'd', + dependsOn: ['path'], + }), + path: Flags.string({ + description: 'The path where you want to mount the disk', + dependsOn: ['disk'], + }), }; public async run(): Promise { const { args, flags } = await this.parse(Init); @@ -36,24 +65,53 @@ export default class Init extends Command { try { await this.setGotConfig(flags); this.log( - chalk.yellow(`This utility will guide you through creating a liara.json file. -It only covers the most common fields and tries to guess sensible defaults. -For detailed documentation on these fields and what they do, refer to the official documentation. + 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 project. -Press ^C at any time to quit. +🔑 Press ^C at any time to quit. `), ); this.spinner = ora(); + if (flags.y) { + const dirName = path.basename(process.cwd()); + const platform = detectPlatform(process.cwd()); + const diskConfig = { + disk: 'media', + path: '/uploads/media', + }; + const configs = this.setLiaraJsonConfigs( + getPort(platform) || 3000, + dirName, + 'iran', + platform, + supportedVersions(platform)?.defaultVersion, + diskConfig, + ); + await this.createLiaraJsonFile(configs); + this.log( + chalk.yellow( + "🚫 This file is just a sample file, don't use it for deployment.", + ), + ); + this.exit(0); + } const projects = await this.getPlatformsInfo(); - const appName = await this.promptProjectName(projects); - const buildLocation = await this.buildLocationPrompt(); - const platform = this.findPlatform(projects, appName); - const port = await this.getAppPort(platform, appName); - const version = await this.promptPlatformVersion(platform); - const disks = await this.getAppDisks(appName, projects); - const diskConfigs = await this.promptDiskConfig(disks); + const appName = await this.promptProjectName(projects, flags.name); + const buildLocation = await this.buildLocationPrompt( + flags['build-location'], + ); + const platform = this.findPlatform(projects, appName, flags.platform); + const port = await this.getAppPort(platform, appName, flags.port); + const version = await this.promptPlatformVersion(platform, flags.version); + const disks = await this.getAppDisks(appName, projects, flags['no-disk']); + const diskConfigs = await this.promptDiskConfig( + disks, + flags.disk, + flags.path, + ); const configs = this.setLiaraJsonConfigs( port, appName, @@ -79,8 +137,14 @@ Press ^C at any time to quit. throw error; } } - async promptProjectName(projects: IProject[]): Promise { + async promptProjectName( + projects: IProject[], + flagValue: string | undefined, + ): Promise { try { + if (flagValue) { + return flagValue; + } if (!projects.length) { this.warn( 'Please go to https://console.liara.ir/apps and create an app, first.', @@ -99,22 +163,46 @@ Press ^C at any time to quit. throw error; } } - findPlatform(projects: IProject[], appName: string): string { + findPlatform( + projects: IProject[], + appName: string, + flagsValue: string | undefined, + ): string { + if (flagsValue) { + return flagsValue; + } const project = projects.find((project) => { return project.project_id === appName; }); + if (!project) { + return 'static'; + } return project!.type; } - async getAppPort(platform: string, appName: string): Promise { - const defaultPort = getPort(platform); - if (!defaultPort) { - const port = await this.promptPort(appName); - return port; + async getAppPort( + platform: string, + appName: string, + flagValue: number | undefined, + ): Promise { + try { + if (flagValue) { + return flagValue; + } + const defaultPort = getPort(platform); + if (!defaultPort) { + const port = await this.promptPort(appName); + return port; + } + return defaultPort; + } catch (error) { + throw error; } - return defaultPort; } - async buildLocationPrompt(): Promise { + async buildLocationPrompt(flagValue: string | undefined): Promise { try { + if (flagValue) { + return flagValue; + } const { location } = (await inquirer.prompt({ message: 'Build location', name: 'location', @@ -127,8 +215,14 @@ Press ^C at any time to quit. throw error; } } - async promptPlatformVersion(platform: string): Promise { + async promptPlatformVersion( + platform: string, + flagValue: string | undefined, + ): Promise { try { + if (flagValue) { + return flagValue; + } const versions = supportedVersions(platform); if (versions !== null) { const { version } = (await inquirer.prompt({ @@ -162,7 +256,7 @@ Press ^C at any time to quit. appName: string, buildLocation: string, platform: string, - platformVersion: string | null, + platformVersion: string | null | undefined, diskConfigs: { disk: string; path: string } | null, ): ILiaraJSON { const versionKey = this.setVersionKey(platform, platformVersion); @@ -175,7 +269,7 @@ Press ^C at any time to quit. }, }; - if (platformVersion !== null) { + if (platformVersion != null) { (configs as Record)[platform] = { [versionKey!]: platformVersion, }; @@ -192,7 +286,7 @@ Press ^C at any time to quit. } setVersionKey( platform: string, - platformVersion: string | null, + platformVersion: string | null | undefined, ): string | null { if (platformVersion == null) { return null; @@ -208,8 +302,15 @@ Press ^C at any time to quit. } return 'version'; } - async getAppDisks(AppName: string, projects: IProject[]) { + async getAppDisks( + AppName: string, + projects: IProject[], + flagsValue: boolean, + ) { try { + if (flagsValue) { + return []; + } this.spinner.start(); const project = projects.find((project) => { return project.project_id === AppName; @@ -234,8 +335,16 @@ Press ^C at any time to quit. } async promptDiskConfig( disks: object[], + diskNameFlag: string | undefined, + diskPathFlage: string | undefined, ): Promise<{ disk: string; path: string } | null> { try { + if (diskNameFlag && diskPathFlage) { + return { + disk: diskNameFlag, + path: diskPathFlage, + }; + } if (disks.length > 0) { const { setDisk } = (await inquirer.prompt({ message: 'Set disk configs? ', From bf30178d4c2d749dd1d3af8bebabf1f81fd8023e Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Wed, 4 Dec 2024 16:57:49 +0330 Subject: [PATCH 10/20] chore: move IGetDiskResponse from base.ts to /types --- src/base.ts | 15 +-------------- src/commands/init.ts | 3 ++- src/types/getDiskResponse.ts | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 src/types/getDiskResponse.ts diff --git a/src/base.ts b/src/base.ts index b2b2399..ea9fefd 100644 --- a/src/base.ts +++ b/src/base.ts @@ -28,6 +28,7 @@ import { } from './constants.js'; import { getDefaultPort } from './utils/get-port.js'; import validatePort from './utils/validate-port.js'; +import { Interface } from 'node:readline'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -74,20 +75,6 @@ export interface IProject { created_at: string; isDeployed: boolean; } -interface IMount { - name: string; - mountedTo: string; -} -export interface IGetDiskResponse { - disks: { - name: string; - size: number; - updatedAt: string; - createdAt: string; - filebrowserUrl: string; - }[]; - mounts: IMount[]; -} export interface IGetProjectsResponse { projects: IProject[]; diff --git a/src/commands/init.ts b/src/commands/init.ts index 598590b..20317c7 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -5,12 +5,13 @@ import inquirer from 'inquirer'; import chalk from 'chalk'; import path from 'path'; -import Command, { IGetDiskResponse, IProject } from '../base.js'; +import Command, { IProject } from '../base.js'; import { getPort } from '../utils/get-port.js'; import IGetProjectsResponse from '../types/get-project-response.js'; import ILiaraJSON from '../types/liara-json.js'; import supportedVersions from '../utils/getSupportedVersions.js'; import detectPlatform from '../utils/detect-platform.js'; +import { IGetDiskResponse } from '../types/getDiskResponse.js'; export default class Init extends Command { static override description = diff --git a/src/types/getDiskResponse.ts b/src/types/getDiskResponse.ts new file mode 100644 index 0000000..c624fd2 --- /dev/null +++ b/src/types/getDiskResponse.ts @@ -0,0 +1,15 @@ +export interface IDisk { + name: string; + size: number; + updatedAt: string; + createdAt: string; + filebrowserUrl: string; +} +export interface IGetDiskResponse { + disks: IDisk[]; + mounts: IMount[]; +} +interface IMount { + name: string; + mountedTo: string; +} From 8be0a726cc7e092964bbdcfffa8a92dc74b6ca3b Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Wed, 4 Dec 2024 17:01:38 +0330 Subject: [PATCH 11/20] fix: remove wrongly imported package --- src/base.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/base.ts b/src/base.ts index ea9fefd..325d93e 100644 --- a/src/base.ts +++ b/src/base.ts @@ -28,7 +28,6 @@ import { } from './constants.js'; import { getDefaultPort } from './utils/get-port.js'; import validatePort from './utils/validate-port.js'; -import { Interface } from 'node:readline'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); From c4418b84b709f174c4424979d2fc0ab7015f3781 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sat, 7 Dec 2024 11:45:08 +0330 Subject: [PATCH 12/20] chore: refactor init.ts,base.ts,deploy.ts --- src/base.ts | 11 -------- src/commands/deploy.ts | 16 ++--------- src/commands/init.ts | 46 +++++++++++++++---------------- src/utils/getSupportedVersions.ts | 8 +----- src/utils/promptPort.ts | 15 ++++++++++ 5 files changed, 40 insertions(+), 56 deletions(-) create mode 100644 src/utils/promptPort.ts diff --git a/src/base.ts b/src/base.ts index 325d93e..1dbc151 100644 --- a/src/base.ts +++ b/src/base.ts @@ -311,17 +311,6 @@ Please check your network connection.`); throw error; } } - async promptPort(platform: string): Promise { - const { port } = (await inquirer.prompt({ - name: 'port', - type: 'input', - default: getDefaultPort(platform), - message: 'Enter the port your app listens to:', - validate: validatePort, - })) as { port: number }; - - return port; - } async getCurrentAccount(): Promise { const accounts = (await this.readGlobalConfig()).accounts; const accName = Object.keys(accounts).find( diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index fd880de..11a5784 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -45,6 +45,7 @@ import CreateArchiveException from '../errors/create-archive.js'; import IGetProjectsResponse from '../types/get-project-response.js'; import ReachedMaxSourceSizeError from '../errors/max-source-size.js'; import getPlatformVersion from '../services/get-platform-version.js'; +import { promptPort } from '../utils/promptPort.js'; export default class Deploy extends Command { static description = 'deploy an app'; @@ -147,7 +148,7 @@ export default class Deploy extends Command { if (!config.port) { config.port = - getPort(config.platform) || (await this.promptPort(config.platform)); + getPort(config.platform) || (await promptPort(config.platform)); } this.logKeyValue('App', config.app); @@ -765,19 +766,6 @@ Additionally, you can also retry the build with the debug flag: throw error; } } - - async promptPort(platform: string): Promise { - const { port } = (await inquirer.prompt({ - name: 'port', - type: 'input', - default: getDefaultPort(platform), - message: 'Enter the port your app listens to:', - validate: validatePort, - })) as { port: number }; - - return port; - } - getMergedConfig(flags: IFlags): IDeploymentConfig { const defaults = { path: flags.path ? flags.path : process.cwd(), diff --git a/src/commands/init.ts b/src/commands/init.ts index 20317c7..c53debb 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -5,6 +5,7 @@ import inquirer from 'inquirer'; import chalk from 'chalk'; import path from 'path'; +import { promptPort } from '../utils/promptPort.js'; import Command, { IProject } from '../base.js'; import { getPort } from '../utils/get-port.js'; import IGetProjectsResponse from '../types/get-project-response.js'; @@ -191,7 +192,7 @@ Afterwards, use liara deploy to deploy your project. } const defaultPort = getPort(platform); if (!defaultPort) { - const port = await this.promptPort(appName); + const port = await promptPort(appName); return port; } return defaultPort; @@ -219,13 +220,13 @@ Afterwards, use liara deploy to deploy your project. async promptPlatformVersion( platform: string, flagValue: string | undefined, - ): Promise { + ): Promise { try { if (flagValue) { return flagValue; } const versions = supportedVersions(platform); - if (versions !== null) { + if (versions) { const { version } = (await inquirer.prompt({ message: `${platform} version: `, name: 'version', @@ -235,7 +236,6 @@ Afterwards, use liara deploy to deploy your project. })) as { version: string }; return version; } - return null; } catch (error) { throw error; } @@ -257,8 +257,8 @@ Afterwards, use liara deploy to deploy your project. appName: string, buildLocation: string, platform: string, - platformVersion: string | null | undefined, - diskConfigs: { disk: string; path: string } | null, + platformVersion: string | undefined, + diskConfigs: { disk: string; path: string } | undefined, ): ILiaraJSON { const versionKey = this.setVersionKey(platform, platformVersion); const configs: ILiaraJSON = { @@ -270,12 +270,12 @@ Afterwards, use liara deploy to deploy your project. }, }; - if (platformVersion != null) { + if (platformVersion) { (configs as Record)[platform] = { [versionKey!]: platformVersion, }; } - if (diskConfigs !== null) { + if (diskConfigs) { configs['disks'] = [ { name: diskConfigs.disk, @@ -287,21 +287,20 @@ Afterwards, use liara deploy to deploy your project. } setVersionKey( platform: string, - platformVersion: string | null | undefined, - ): string | null { - if (platformVersion == null) { - return null; - } - if (platform in ['flask', 'django']) { - return 'pythonVersion'; - } - if (platform == 'laravel') { - return 'phpVersion'; - } - if (platform == 'next') { - return 'nodeVersion'; + platformVersion: string | undefined, + ): string | undefined { + if (platformVersion) { + if (platform in ['flask', 'django']) { + return 'pythonVersion'; + } + if (platform == 'laravel') { + return 'phpVersion'; + } + if (platform == 'next') { + return 'nodeVersion'; + } + return 'version'; } - return 'version'; } async getAppDisks( AppName: string, @@ -338,7 +337,7 @@ Afterwards, use liara deploy to deploy your project. disks: object[], diskNameFlag: string | undefined, diskPathFlage: string | undefined, - ): Promise<{ disk: string; path: string } | null> { + ): Promise<{ disk: string; path: string } | undefined> { try { if (diskNameFlag && diskPathFlage) { return { @@ -370,7 +369,6 @@ Afterwards, use liara deploy to deploy your project. return diskConfigs; } } - return null; } catch (error) { throw error; } diff --git a/src/utils/getSupportedVersions.ts b/src/utils/getSupportedVersions.ts index 53b3301..1fe7469 100644 --- a/src/utils/getSupportedVersions.ts +++ b/src/utils/getSupportedVersions.ts @@ -58,13 +58,7 @@ const versions: IVersions = { allVersions: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'], }, }; -// export const defaultVersion={ - -// } export default function supportedVersions(platform: string) { - if (platform in versions) { - return versions[platform as keyof typeof versions]; - } - return null; + return versions[platform as keyof typeof versions]; } diff --git a/src/utils/promptPort.ts b/src/utils/promptPort.ts new file mode 100644 index 0000000..db89417 --- /dev/null +++ b/src/utils/promptPort.ts @@ -0,0 +1,15 @@ +import inquirer from 'inquirer'; +import { getDefaultPort } from './get-port.js'; +import validatePort from './validate-port.js'; + +export async function promptPort(platform: string): Promise { + const { port } = (await inquirer.prompt({ + name: 'port', + type: 'input', + default: getDefaultPort(platform), + message: 'Enter the port your app listens to:', + validate: validatePort, + })) as { port: number }; + + return port; +} From 5dc13c87681e1bf670d1ba1add4d822140594842 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sat, 7 Dec 2024 12:34:21 +0330 Subject: [PATCH 13/20] fix: getSupportedVersions,promptPlatformVersion --- src/commands/init.ts | 17 +++++++++++++++-- src/utils/getSupportedVersions.ts | 22 +++------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index c53debb..ce2ead8 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -227,8 +227,21 @@ Afterwards, use liara deploy to deploy your project. } const versions = supportedVersions(platform); if (versions) { + let message: string | undefined; + if (['flask', 'django'].includes(platform)) { + message = 'Python version'; + } + if (platform === 'laravel') { + message = 'Php version'; + } + if (platform === 'next') { + message = 'Node version'; + } + if (!message) { + message = `${platform} version: `; + } const { version } = (await inquirer.prompt({ - message: `${platform} version: `, + message: message || 'Platform version', name: 'version', type: 'list', default: versions.defaultVersion, @@ -290,7 +303,7 @@ Afterwards, use liara deploy to deploy your project. platformVersion: string | undefined, ): string | undefined { if (platformVersion) { - if (platform in ['flask', 'django']) { + if (['flask', 'django'].includes(platform)) { return 'pythonVersion'; } if (platform == 'laravel') { diff --git a/src/utils/getSupportedVersions.ts b/src/utils/getSupportedVersions.ts index 1fe7469..083f6e9 100644 --- a/src/utils/getSupportedVersions.ts +++ b/src/utils/getSupportedVersions.ts @@ -22,28 +22,12 @@ const versions: IVersions = { node: { defaultVersion: '20', allVersions: ['14', '16', '18', '20', '22'] }, next: { defaultVersion: '20', allVersions: ['20', '22'] }, laravel: { - defaultVersion: '8.0', - allVersions: [ - '8.3.0', - '8.2.0', - '8.1.0', - '8.0.0', - '7.4.0', - '7.3.0', - '7.2.0', - ], + defaultVersion: '7.4', + allVersions: ['8.3', '8.2', '8.1', '8.0', '7.4', '7.3', '7.2'], }, php: { defaultVersion: '8.0', - allVersions: [ - '8.3.0', - '8.2.0', - '8.1.0', - '8.0.0', - '7.4.0', - '7.3.0', - '7.2.0', - ], + allVersions: ['8.3', '8.2', '8.1', '8.0', '7.4', '7.3', '7.2'], }, python: { defaultVersion: '3.11', From 44b9f849eecc0f47c09a9266ef5bff9ed1da3179 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sat, 7 Dec 2024 16:09:06 +0330 Subject: [PATCH 14/20] feat: init command for users with no app --- src/commands/init.ts | 134 ++++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 47 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index ce2ead8..49e4dfb 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -12,7 +12,8 @@ import IGetProjectsResponse from '../types/get-project-response.js'; import ILiaraJSON from '../types/liara-json.js'; import supportedVersions from '../utils/getSupportedVersions.js'; import detectPlatform from '../utils/detect-platform.js'; -import { IGetDiskResponse } from '../types/getDiskResponse.js'; +import { IDisk, IGetDiskResponse } from '../types/getDiskResponse.js'; +import { AVAILABLE_PLATFORMS } from '../constants.js'; export default class Init extends Command { static override description = @@ -105,8 +106,12 @@ Afterwards, use liara deploy to deploy your project. const buildLocation = await this.buildLocationPrompt( flags['build-location'], ); - const platform = this.findPlatform(projects, appName, flags.platform); - const port = await this.getAppPort(platform, appName, flags.port); + 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, flags['no-disk']); const diskConfigs = await this.promptDiskConfig( @@ -147,11 +152,13 @@ Afterwards, use liara deploy to deploy your project. if (flagValue) { return flagValue; } - if (!projects.length) { - this.warn( - 'Please go to https://console.liara.ir/apps and create an app, first.', - ); - this.exit(1); + if (projects.length == 0) { + const { project } = (await inquirer.prompt({ + name: 'project', + type: 'input', + message: 'Enter app name:', + })) as { project: string }; + return project; } const { project } = (await inquirer.prompt({ @@ -165,11 +172,15 @@ Afterwards, use liara deploy to deploy your project. throw error; } } - findPlatform( + async findPlatform( projects: IProject[], appName: string, flagsValue: string | undefined, - ): string { + ): Promise { + if (projects.length == 0) { + const platform = await this.promptPlatform(); + return platform; + } if (flagsValue) { return flagsValue; } @@ -183,8 +194,8 @@ Afterwards, use liara deploy to deploy your project. } async getAppPort( platform: string, - appName: string, flagValue: number | undefined, + projects: IProject[], ): Promise { try { if (flagValue) { @@ -192,7 +203,7 @@ Afterwards, use liara deploy to deploy your project. } const defaultPort = getPort(platform); if (!defaultPort) { - const port = await promptPort(appName); + const port = await promptPort(platform); return port; } return defaultPort; @@ -260,7 +271,7 @@ Afterwards, use liara deploy to deploy your project. `${process.cwd()}/liara.json`, JSON.stringify(configs, null, 2), ); - this.spinner.succeed('Liara.json file is successfully created!'); + this.spinner.succeed('Liara.json is successfully created!'); } catch (error) { throw new Error('There was a problem while creating liara.json file!'); } @@ -319,22 +330,26 @@ Afterwards, use liara deploy to deploy your project. AppName: string, projects: IProject[], flagsValue: boolean, - ) { + ): Promise { try { if (flagsValue) { return []; } - this.spinner.start(); - const project = projects.find((project) => { - return project.project_id === AppName; - }); - const disks = await this.got( - `v1/projects/${project?._id}/disks`, - ).json(); - this.spinner.stop(); - return disks.disks; + if (projects.length != 0) { + this.spinner.start(); + const project = projects.find((project) => { + return project.project_id === AppName; + }); + const disks = await this.got( + `v1/projects/${project?._id}/disks`, + ).json(); + this.spinner.stop(); + return disks.disks; + } } catch (error) { - throw error; + throw new Error( + 'There was a problem while getting your app disks, Please try again later.', + ); } } async setDiskConfigAnswer(): Promise { @@ -346,8 +361,27 @@ Afterwards, use liara deploy to deploy your project. })) as { setDisk: boolean }; return setDisk; } + 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)], + })) as { platform: string }; + + return platform; + } catch (error) { + this.spinner.stop(); + throw error; + } + } async promptDiskConfig( - disks: object[], + disks: IDisk[] | undefined, diskNameFlag: string | undefined, diskPathFlage: string | undefined, ): Promise<{ disk: string; path: string } | undefined> { @@ -358,29 +392,35 @@ Afterwards, use liara deploy to deploy your project. path: diskPathFlage, }; } - if (disks.length > 0) { - const { setDisk } = (await inquirer.prompt({ - message: 'Set disk configs? ', - type: 'confirm', - name: 'setDisk', - default: false, - })) as { setDisk: boolean }; - if (setDisk) { - const diskConfigs = (await inquirer.prompt([ - { - message: 'Disk name: ', - choices: disks, - name: 'disk', - type: 'list', - }, - { - message: 'MountTO: ', - name: 'path', - type: 'input', - }, - ])) as { disk: string; path: string }; - return diskConfigs; + const { setDisk } = (await inquirer.prompt({ + message: 'Set disk configs? ', + type: 'confirm', + name: 'setDisk', + default: false, + })) as { setDisk: boolean }; + if (setDisk) { + let disk: { disk: string }; + if (disks && disks.length > 0) { + disk = await inquirer.prompt({ + message: 'Disk name: ', + name: 'disk', + choices: disks, + type: 'list', + }); + } + if (!disks || disks.length == 0) { + disk = await inquirer.prompt({ + message: 'Disk name: ', + name: 'disk', + type: 'input', + }); } + const path = await inquirer.prompt({ + message: 'MountTo: ', + name: 'path', + type: 'input', + }); + return { disk: disk!.disk, path: path.path }; } } catch (error) { throw error; From bcb1464d708cffc84435b0b4a6eafe6ac295208e Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 8 Dec 2024 10:15:56 +0330 Subject: [PATCH 15/20] chore: add more error handling for init command --- src/commands/init.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 49e4dfb..6809dfd 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -141,7 +141,16 @@ Afterwards, use liara deploy to deploy your project. this.spinner.stop(); return projects as IProject[]; } catch (error) { - throw 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 promptProjectName( @@ -347,9 +356,16 @@ Afterwards, use liara deploy to deploy your project. return disks.disks; } } catch (error) { - throw new Error( - 'There was a problem while getting your app disks, Please try again later.', - ); + 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 setDiskConfigAnswer(): Promise { From bfd03685748baf999c911ceae037976455e7b274 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Sun, 8 Dec 2024 16:40:20 +0330 Subject: [PATCH 16/20] feat: support multiple disk configs in init command --- src/commands/init.ts | 118 ++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 6809dfd..0adbd14 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -81,10 +81,12 @@ Afterwards, use liara deploy to deploy your project. if (flags.y) { const dirName = path.basename(process.cwd()); const platform = detectPlatform(process.cwd()); - const diskConfig = { - disk: 'media', - path: '/uploads/media', - }; + const diskConfig = [ + { + disk: 'media', + path: '/uploads/media', + }, + ]; const configs = this.setLiaraJsonConfigs( getPort(platform) || 3000, dirName, @@ -173,7 +175,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman const { project } = (await inquirer.prompt({ name: 'project', type: 'list', - message: 'Please select an app:', + message: 'Select an app:', choices: [...projects.map((project) => project.project_id)], })) as { project: string }; return project; @@ -226,7 +228,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman return flagValue; } const { location } = (await inquirer.prompt({ - message: 'Build location', + message: 'Specify the build location: ', name: 'location', type: 'list', default: 'iran', @@ -249,19 +251,19 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman if (versions) { let message: string | undefined; if (['flask', 'django'].includes(platform)) { - message = 'Python version'; + message = 'Select python version'; } if (platform === 'laravel') { - message = 'Php version'; + message = 'Select php version'; } if (platform === 'next') { - message = 'Node version'; + message = 'Select node version'; } if (!message) { - message = `${platform} version: `; + message = `Selcet ${platform} version: `; } const { version } = (await inquirer.prompt({ - message: message || 'Platform version', + message: message || 'Select platform version', name: 'version', type: 'list', default: versions.defaultVersion, @@ -280,7 +282,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman `${process.cwd()}/liara.json`, JSON.stringify(configs, null, 2), ); - this.spinner.succeed('Liara.json is successfully created!'); + this.spinner.succeed('liara.json is successfully created!'); } catch (error) { throw new Error('There was a problem while creating liara.json file!'); } @@ -291,7 +293,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman buildLocation: string, platform: string, platformVersion: string | undefined, - diskConfigs: { disk: string; path: string } | undefined, + diskConfigs: { disk: string; path: string }[] | undefined, ): ILiaraJSON { const versionKey = this.setVersionKey(platform, platformVersion); const configs: ILiaraJSON = { @@ -309,12 +311,9 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman }; } if (diskConfigs) { - configs['disks'] = [ - { - name: diskConfigs.disk, - mountTo: diskConfigs.path, - }, - ]; + configs['disks'] = diskConfigs.map((config) => { + return { name: config.disk, mountTo: config.path }; + }); } return configs; } @@ -368,15 +367,6 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman You can still use 'liara init' with it's flags. Use 'liara init --help' for command details.`); } } - async setDiskConfigAnswer(): Promise { - const { setDisk } = (await inquirer.prompt({ - message: 'Set disk configs? ', - type: 'confirm', - name: 'setDisk', - default: false, - })) as { setDisk: boolean }; - return setDisk; - } async promptPlatform() { this.spinner.start('Loading...'); @@ -386,7 +376,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman const { platform } = (await inquirer.prompt({ name: 'platform', type: 'list', - message: 'Please select a platform:', + message: 'Select a platform:', choices: [...AVAILABLE_PLATFORMS.map((platform) => platform)], })) as { platform: string }; @@ -400,43 +390,65 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman disks: IDisk[] | undefined, diskNameFlag: string | undefined, diskPathFlage: string | undefined, - ): Promise<{ disk: string; path: string } | undefined> { + ): Promise<{ disk: string; path: string }[] | undefined> { try { + let diskConfig = []; if (diskNameFlag && diskPathFlage) { - return { - disk: diskNameFlag, - path: diskPathFlage, - }; + return [ + { + disk: diskNameFlag, + path: diskPathFlage, + }, + ]; } const { setDisk } = (await inquirer.prompt({ - message: 'Set disk configs? ', + message: 'Configure disks now? ', type: 'confirm', name: 'setDisk', default: false, })) as { setDisk: boolean }; if (setDisk) { - let disk: { disk: string }; - if (disks && disks.length > 0) { - disk = await inquirer.prompt({ - message: 'Disk name: ', - name: 'disk', + if (!disks || disks.length == 0) { + const { diskName } = (await inquirer.prompt({ + message: 'Enter Disk name: ', + name: 'diskName', + type: 'input', + })) as { diskName: string }; + const { path } = (await inquirer.prompt({ + message: 'Specify the mount location: ', + name: 'path', + type: 'input', + })) as { path: string }; + 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', - }); - } - if (!disks || disks.length == 0) { - disk = await inquirer.prompt({ - message: 'Disk name: ', - name: 'disk', + })) as { diskName: string }; + 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', - }); + })) as { path: string }; + diskConfig.push({ disk: diskName, path }); + if (disks.length != 0) { + const continueAnswer = (await inquirer.prompt({ + message: 'Add another disk?', + type: 'confirm', + default: false, + name: 'shouldContinue', + })) as { shouldContinue: boolean }; + shouldContinue = continueAnswer.shouldContinue; + } } - const path = await inquirer.prompt({ - message: 'MountTo: ', - name: 'path', - type: 'input', - }); - return { disk: disk!.disk, path: path.path }; + return diskConfig; } } catch (error) { throw error; From 8f26a26f834b9c561a24a74861949f1729a8224b Mon Sep 17 00:00:00 2001 From: morteza Date: Mon, 9 Dec 2024 02:18:08 +0330 Subject: [PATCH 17/20] feat: add cron and healthcheck to init command --- src/commands/init.ts | 86 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 0adbd14..783c5e2 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -14,6 +14,7 @@ import supportedVersions from '../utils/getSupportedVersions.js'; import detectPlatform from '../utils/detect-platform.js'; import { IDisk, IGetDiskResponse } from '../types/getDiskResponse.js'; import { AVAILABLE_PLATFORMS } from '../constants.js'; +import IHealthConfig from '../types/health-config.js'; export default class Init extends Command { static override description = @@ -121,6 +122,8 @@ Afterwards, use liara deploy to deploy your project. flags.disk, flags.path, ); + const cron = await this.promptCron(platform); + const healthCheck = await this.promptHealthCheck(); const configs = this.setLiaraJsonConfigs( port, appName, @@ -128,6 +131,8 @@ Afterwards, use liara deploy to deploy your project. platform, version, diskConfigs, + healthCheck, + cron, ); this.createLiaraJsonFile(configs); } catch (error) { @@ -294,6 +299,8 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman platform: string, platformVersion: string | undefined, diskConfigs: { disk: string; path: string }[] | undefined, + healthCheck?: IHealthConfig | undefined, + cron?: string[] | undefined, ): ILiaraJSON { const versionKey = this.setVersionKey(platform, platformVersion); const configs: ILiaraJSON = { @@ -304,7 +311,12 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman location: buildLocation, }, }; - + if (cron) { + configs['cron'] = cron; + } + if (healthCheck) { + configs['healthCheck'] = healthCheck; + } if (platformVersion) { (configs as Record)[platform] = { [versionKey!]: platformVersion, @@ -402,7 +414,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman ]; } const { setDisk } = (await inquirer.prompt({ - message: 'Configure disks now? ', + message: 'Configure disks? (Default: No)', type: 'confirm', name: 'setDisk', default: false, @@ -440,7 +452,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman diskConfig.push({ disk: diskName, path }); if (disks.length != 0) { const continueAnswer = (await inquirer.prompt({ - message: 'Add another disk?', + message: 'Add another disk? (Default: No)', type: 'confirm', default: false, name: 'shouldContinue', @@ -454,4 +466,72 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw error; } } + async promptCron(platform: string) { + try { + if ( + ['next', 'laravel', 'django', 'php', 'python', 'flask'].includes( + platform, + ) + ) { + const { setCronAnswer } = (await inquirer.prompt({ + message: 'Configure cron? (Default: No)', + type: 'confirm', + name: 'setCronAnswer', + default: false, + })) as { setCronAnswer: boolean }; + if (setCronAnswer) { + const { cron } = (await inquirer.prompt({ + message: 'cron: ', + type: 'input', + name: 'cron', + })) as { cron: string }; + return cron.split(',').map((value) => value.trim()); + } + } + } catch (error) { + throw error; + } + } + async promptHealthCheck(): Promise { + try { + const { setHealtCheckAnswer } = (await inquirer.prompt({ + message: 'Configure healthcheck? (Default: No)', + type: 'confirm', + name: 'setHealtCheckAnswer', + default: false, + })) as { setHealtCheckAnswer: boolean }; + 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', + }, + ])) as IHealthConfig; + return healthcheckConfigs; + } + } catch (error) { + throw error; + } + } } From 7d9e57dbb8027b3f191d25252645d66ce39f8879 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Wed, 11 Dec 2024 17:18:56 +0330 Subject: [PATCH 18/20] refactor: improve code readability --- src/base.ts | 2 - src/commands/init.ts | 88 +++++++++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/base.ts b/src/base.ts index 1dbc151..03cff76 100644 --- a/src/base.ts +++ b/src/base.ts @@ -26,8 +26,6 @@ import { GLOBAL_CONF_PATH, GLOBAL_CONF_VERSION, } from './constants.js'; -import { getDefaultPort } from './utils/get-port.js'; -import validatePort from './utils/validate-port.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/src/commands/init.ts b/src/commands/init.ts index 783c5e2..5faa83a 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -17,8 +17,7 @@ import { AVAILABLE_PLATFORMS } from '../constants.js'; import IHealthConfig from '../types/health-config.js'; export default class Init extends Command { - static override description = - 'With this command, you can create a liara.json file'; + static override description = 'create a liara.json file'; static override examples = ['<%= config.bin %> <%= command.id %>']; @@ -26,61 +25,62 @@ export default class Init extends Command { ...Command.flags, y: Flags.boolean({ char: 'y', - description: 'Create an example file', + description: 'create an example file', aliases: [], }), name: Flags.string({ char: 'n', - description: 'Your app name', + description: 'the name of the app', }), port: Flags.integer({ char: 'p', - description: 'Port your app listens to', + description: 'the port your app listens to', }), platform: Flags.string({ char: 'P', - description: 'App platform', + description: 'the platform your app needs to run on', }), version: Flags.string({ char: 'v', - description: 'Platform Version', + description: 'the version of the platform', }), 'build-location': Flags.string({ - description: 'Build location', + description: "name of the build's location", aliases: ['location'], }), - 'no-disk': Flags.boolean({ - description: 'No disk config', - exclusive: ['disk', 'path'], - }), disk: Flags.string({ - description: 'Disk name', + description: 'the name of the disk', char: 'd', dependsOn: ['path'], }), path: Flags.string({ - description: 'The path where you want to mount the disk', + description: 'the path where the disk should be mounted', dependsOn: ['disk'], }), }; + public async run(): Promise { 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 project. +Afterwards, use liara deploy to deploy your app. 🔑 Press ^C at any time to quit. `), ); + this.spinner = ora(); + if (flags.y) { const dirName = path.basename(process.cwd()); + const platform = detectPlatform(process.cwd()); const diskConfig = [ { @@ -102,8 +102,10 @@ Afterwards, use liara deploy to deploy your project. "🚫 This file is just a sample file, don't use it for deployment.", ), ); + this.exit(0); } + const projects = await this.getPlatformsInfo(); const appName = await this.promptProjectName(projects, flags.name); const buildLocation = await this.buildLocationPrompt( @@ -116,7 +118,7 @@ Afterwards, use liara deploy to deploy your project. ); const port = await this.getAppPort(platform, flags.port, projects); const version = await this.promptPlatformVersion(platform, flags.version); - const disks = await this.getAppDisks(appName, projects, flags['no-disk']); + const disks = await this.getAppDisks(appName, projects); const diskConfigs = await this.promptDiskConfig( disks, flags.disk, @@ -134,17 +136,20 @@ Afterwards, use liara deploy to deploy your project. healthCheck, cron, ); + this.createLiaraJsonFile(configs); } catch (error) { throw error; } } + async getPlatformsInfo(): Promise { try { this.spinner.start(); const { projects } = await this.got('v1/projects').json(); + this.spinner.stop(); return projects as IProject[]; } catch (error) { @@ -156,6 +161,7 @@ If you are using an API token for authentication, please consider updating your 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.`); } @@ -168,6 +174,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman if (flagValue) { return flagValue; } + if (projects.length == 0) { const { project } = (await inquirer.prompt({ name: 'project', @@ -188,6 +195,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw error; } } + async findPlatform( projects: IProject[], appName: string, @@ -197,17 +205,21 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman 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: string, flagValue: number | undefined, @@ -217,7 +229,9 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman if (flagValue) { return flagValue; } + const defaultPort = getPort(platform); + if (!defaultPort) { const port = await promptPort(platform); return port; @@ -227,11 +241,13 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw error; } } + async buildLocationPrompt(flagValue: string | undefined): Promise { try { if (flagValue) { return flagValue; } + const { location } = (await inquirer.prompt({ message: 'Specify the build location: ', name: 'location', @@ -244,6 +260,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw error; } } + async promptPlatformVersion( platform: string, flagValue: string | undefined, @@ -252,21 +269,27 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman if (flagValue) { return flagValue; } + const versions = supportedVersions(platform); + if (versions) { let message: string | undefined; 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', @@ -280,9 +303,11 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw error; } } + async createLiaraJsonFile(configs: ILiaraJSON) { try { this.spinner.start(); + await fs.writeFile( `${process.cwd()}/liara.json`, JSON.stringify(configs, null, 2), @@ -292,6 +317,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw new Error('There was a problem while creating liara.json file!'); } } + setLiaraJsonConfigs( port: number, appName: string, @@ -303,8 +329,8 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman cron?: string[] | undefined, ): ILiaraJSON { const versionKey = this.setVersionKey(platform, platformVersion); + const configs: ILiaraJSON = { - platform, port, app: appName, build: { @@ -322,6 +348,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman [versionKey!]: platformVersion, }; } + if (diskConfigs) { configs['disks'] = diskConfigs.map((config) => { return { name: config.disk, mountTo: config.path }; @@ -329,6 +356,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman } return configs; } + setVersionKey( platform: string, platformVersion: string | undefined, @@ -337,32 +365,34 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman if (['flask', 'django'].includes(platform)) { return 'pythonVersion'; } + if (platform == 'laravel') { return 'phpVersion'; } + if (platform == 'next') { return 'nodeVersion'; } return 'version'; } } + async getAppDisks( AppName: string, projects: IProject[], - flagsValue: boolean, ): Promise { try { - if (flagsValue) { - return []; - } if (projects.length != 0) { this.spinner.start(); + const project = projects.find((project) => { return project.project_id === AppName; }); + const disks = await this.got( `v1/projects/${project?._id}/disks`, ).json(); + this.spinner.stop(); return disks.disks; } @@ -375,10 +405,12 @@ If you are using an API token for authentication, please consider updating your 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() { this.spinner.start('Loading...'); @@ -398,6 +430,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman throw error; } } + async promptDiskConfig( disks: IDisk[] | undefined, diskNameFlag: string | undefined, @@ -405,6 +438,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman ): Promise<{ disk: string; path: string }[] | undefined> { try { let diskConfig = []; + if (diskNameFlag && diskPathFlage) { return [ { @@ -413,12 +447,14 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman }, ]; } + const { setDisk } = (await inquirer.prompt({ message: 'Configure disks? (Default: No)', type: 'confirm', name: 'setDisk', default: false, })) as { setDisk: boolean }; + if (setDisk) { if (!disks || disks.length == 0) { const { diskName } = (await inquirer.prompt({ @@ -426,15 +462,19 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman name: 'diskName', type: 'input', })) as { diskName: string }; + const { path } = (await inquirer.prompt({ message: 'Specify the mount location: ', name: 'path', type: 'input', })) as { path: string }; + diskConfig = [{ disk: diskName, path: path }]; return diskConfig; } + let shouldContinue = true; + while (shouldContinue && disks.length != 0) { const { diskName } = (await inquirer.prompt({ message: 'Select a Disk: ', @@ -442,14 +482,18 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman choices: disks, type: 'list', })) as { diskName: string }; + 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', })) as { path: string }; + diskConfig.push({ disk: diskName, path }); + if (disks.length != 0) { const continueAnswer = (await inquirer.prompt({ message: 'Add another disk? (Default: No)', @@ -479,6 +523,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman name: 'setCronAnswer', default: false, })) as { setCronAnswer: boolean }; + if (setCronAnswer) { const { cron } = (await inquirer.prompt({ message: 'cron: ', @@ -500,6 +545,7 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman name: 'setHealtCheckAnswer', default: false, })) as { setHealtCheckAnswer: boolean }; + if (setHealtCheckAnswer) { const healthcheckConfigs = (await inquirer.prompt([ { From 13b52b918b3f86e1425278584b0f23433f5cead3 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Thu, 12 Dec 2024 14:27:03 +0330 Subject: [PATCH 19/20] fix: error handling in init command --- src/commands/init.ts | 460 ++++++++++++++++++++----------------------- 1 file changed, 213 insertions(+), 247 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 5faa83a..2e3740f 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -79,31 +79,36 @@ Afterwards, use liara deploy to deploy your app. this.spinner = ora(); if (flags.y) { - const dirName = path.basename(process.cwd()); - - const platform = detectPlatform(process.cwd()); - const diskConfig = [ - { - disk: 'media', - path: '/uploads/media', - }, - ]; - const configs = this.setLiaraJsonConfigs( - getPort(platform) || 3000, - dirName, - 'iran', - platform, - supportedVersions(platform)?.defaultVersion, - diskConfig, - ); - await this.createLiaraJsonFile(configs); - this.log( - chalk.yellow( - "🚫 This file is just a sample file, don't use it for deployment.", - ), - ); - - this.exit(0); + try { + const dirName = path.basename(process.cwd()); + + const platform = detectPlatform(process.cwd()); + const diskConfig = [ + { + disk: 'media', + path: '/uploads/media', + }, + ]; + const configs = this.setLiaraJsonConfigs( + getPort(platform) || 3000, + dirName, + 'iran', + platform, + supportedVersions(platform)?.defaultVersion, + diskConfig, + ); + await this.createLiaraJsonFile(configs); + this.log( + chalk.yellow( + "🚫 This file is just a sample file, don't use it for deployment.", + ), + ); + + this.exit(0); + } catch (error) { + this.spinner.stop(); + throw error; + } } const projects = await this.getPlatformsInfo(); @@ -139,6 +144,7 @@ Afterwards, use liara deploy to deploy your app. this.createLiaraJsonFile(configs); } catch (error) { + this.spinner.stop(); throw error; } } @@ -170,30 +176,26 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman projects: IProject[], flagValue: string | undefined, ): Promise { - try { - if (flagValue) { - return flagValue; - } - - if (projects.length == 0) { - const { project } = (await inquirer.prompt({ - name: 'project', - type: 'input', - message: 'Enter app name:', - })) as { project: string }; - return project; - } + if (flagValue) { + return flagValue; + } + if (projects.length == 0) { const { project } = (await inquirer.prompt({ name: 'project', - type: 'list', - message: 'Select an app:', - choices: [...projects.map((project) => project.project_id)], + type: 'input', + message: 'Enter app name:', })) as { project: string }; return project; - } catch (error) { - throw error; } + + const { project } = (await inquirer.prompt({ + name: 'project', + type: 'list', + message: 'Select an app:', + choices: [...projects.map((project) => project.project_id)], + })) as { project: string }; + return project; } async findPlatform( @@ -225,82 +227,70 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman flagValue: number | undefined, projects: IProject[], ): Promise { - try { - if (flagValue) { - return flagValue; - } + if (flagValue) { + return flagValue; + } - const defaultPort = getPort(platform); + const defaultPort = getPort(platform); - if (!defaultPort) { - const port = await promptPort(platform); - return port; - } - return defaultPort; - } catch (error) { - throw error; + if (!defaultPort) { + const port = await promptPort(platform); + return port; } + return defaultPort; } async buildLocationPrompt(flagValue: string | undefined): Promise { - try { - if (flagValue) { - return flagValue; - } - - const { location } = (await inquirer.prompt({ - message: 'Specify the build location: ', - name: 'location', - type: 'list', - default: 'iran', - choices: ['iran', 'germany'], - })) as { location: string }; - return location; - } catch (error) { - throw error; + if (flagValue) { + return flagValue; } + + const { location } = (await inquirer.prompt({ + message: 'Specify the build location: ', + name: 'location', + type: 'list', + default: 'iran', + choices: ['iran', 'germany'], + })) as { location: string }; + return location; } async promptPlatformVersion( platform: string, flagValue: string | undefined, ): Promise { - try { - if (flagValue) { - return flagValue; - } - - const versions = supportedVersions(platform); + if (flagValue) { + return flagValue; + } - if (versions) { - let message: string | undefined; - if (['flask', 'django'].includes(platform)) { - message = 'Select python version'; - } + const versions = supportedVersions(platform); - if (platform === 'laravel') { - message = 'Select php version'; - } + if (versions) { + let message: string | undefined; + if (['flask', 'django'].includes(platform)) { + message = 'Select python version'; + } - if (platform === 'next') { - message = 'Select node version'; - } + if (platform === 'laravel') { + message = 'Select php version'; + } - if (!message) { - message = `Selcet ${platform} version: `; - } + if (platform === 'next') { + message = 'Select node version'; + } - const { version } = (await inquirer.prompt({ - message: message || 'Select platform version', - name: 'version', - type: 'list', - default: versions.defaultVersion, - choices: versions.allVersions, - })) as { version: string }; - return version; + if (!message) { + message = `Selcet ${platform} version: `; } - } catch (error) { - throw error; + + const { version } = (await inquirer.prompt({ + message: message || 'Select platform version', + name: 'version', + type: 'list', + default: versions.defaultVersion, + choices: versions.allVersions, + })) as { version: string }; + return version; } } @@ -412,23 +402,13 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman } async promptPlatform() { - this.spinner.start('Loading...'); - - try { - this.spinner.stop(); - - const { platform } = (await inquirer.prompt({ - name: 'platform', - type: 'list', - message: 'Select a platform:', - choices: [...AVAILABLE_PLATFORMS.map((platform) => platform)], - })) as { platform: string }; - - return platform; - } catch (error) { - this.spinner.stop(); - throw error; - } + const { platform } = (await inquirer.prompt({ + name: 'platform', + type: 'list', + message: 'Select a platform:', + choices: [...AVAILABLE_PLATFORMS.map((platform) => platform)], + })) as { platform: string }; + return platform; } async promptDiskConfig( @@ -436,148 +416,134 @@ You can still create a sample 'liara.json' file using the 'liara init -y' comman diskNameFlag: string | undefined, diskPathFlage: string | undefined, ): Promise<{ disk: string; path: string }[] | undefined> { - try { - let diskConfig = []; - - if (diskNameFlag && diskPathFlage) { - return [ - { - disk: diskNameFlag, - path: 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, + })) as { setDisk: boolean }; + + if (setDisk) { + if (!disks || disks.length == 0) { + const { diskName } = (await inquirer.prompt({ + message: 'Enter Disk name: ', + name: 'diskName', + type: 'input', + })) as { diskName: string }; + + const { path } = (await inquirer.prompt({ + message: 'Specify the mount location: ', + name: 'path', + type: 'input', + })) as { path: string }; + + diskConfig = [{ disk: diskName, path: path }]; + return diskConfig; } - const { setDisk } = (await inquirer.prompt({ - message: 'Configure disks? (Default: No)', - type: 'confirm', - name: 'setDisk', - default: false, - })) as { setDisk: boolean }; - - if (setDisk) { - if (!disks || disks.length == 0) { - const { diskName } = (await inquirer.prompt({ - message: 'Enter Disk name: ', - name: 'diskName', - type: 'input', - })) as { diskName: string }; - - const { path } = (await inquirer.prompt({ - message: 'Specify the mount location: ', - name: 'path', - type: 'input', - })) as { path: string }; - - 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', + })) as { diskName: string }; - let shouldContinue = true; - - while (shouldContinue && disks.length != 0) { - const { diskName } = (await inquirer.prompt({ - message: 'Select a Disk: ', - name: 'diskName', - choices: disks, - type: 'list', - })) as { diskName: string }; - - 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', - })) as { path: string }; - - 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', - })) as { shouldContinue: boolean }; - shouldContinue = continueAnswer.shouldContinue; - } + 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', + })) as { path: string }; + + 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', + })) as { shouldContinue: boolean }; + shouldContinue = continueAnswer.shouldContinue; } - return diskConfig; } - } catch (error) { - throw error; + return diskConfig; } } async promptCron(platform: string) { - try { - if ( - ['next', 'laravel', 'django', 'php', 'python', 'flask'].includes( - platform, - ) - ) { - const { setCronAnswer } = (await inquirer.prompt({ - message: 'Configure cron? (Default: No)', - type: 'confirm', - name: 'setCronAnswer', - default: false, - })) as { setCronAnswer: boolean }; - - if (setCronAnswer) { - const { cron } = (await inquirer.prompt({ - message: 'cron: ', - type: 'input', - name: 'cron', - })) as { cron: string }; - return cron.split(',').map((value) => value.trim()); - } + if ( + ['next', 'laravel', 'django', 'php', 'python', 'flask'].includes(platform) + ) { + const { setCronAnswer } = (await inquirer.prompt({ + message: 'Configure cron? (Default: No)', + type: 'confirm', + name: 'setCronAnswer', + default: false, + })) as { setCronAnswer: boolean }; + + if (setCronAnswer) { + const { cron } = (await inquirer.prompt({ + message: 'cron: ', + type: 'input', + name: 'cron', + })) as { cron: string }; + return cron.split(',').map((value) => value.trim()); } - } catch (error) { - throw error; } } async promptHealthCheck(): Promise { - try { - const { setHealtCheckAnswer } = (await inquirer.prompt({ - message: 'Configure healthcheck? (Default: No)', - type: 'confirm', - name: 'setHealtCheckAnswer', - default: false, - })) as { setHealtCheckAnswer: boolean }; - - 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', - }, - ])) as IHealthConfig; - return healthcheckConfigs; - } - } catch (error) { - throw error; + const { setHealtCheckAnswer } = (await inquirer.prompt({ + message: 'Configure healthcheck? (Default: No)', + type: 'confirm', + name: 'setHealtCheckAnswer', + default: false, + })) as { setHealtCheckAnswer: boolean }; + + 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', + }, + ])) as IHealthConfig; + return healthcheckConfigs; } } } From 0b4a78c3f2e49a4a5bf56bf591ba043c70aa0446 Mon Sep 17 00:00:00 2001 From: Mortza81 Date: Thu, 12 Dec 2024 15:23:16 +0330 Subject: [PATCH 20/20] fix: add missing await --- src/commands/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 2e3740f..b18e5dc 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -142,7 +142,7 @@ Afterwards, use liara deploy to deploy your app. cron, ); - this.createLiaraJsonFile(configs); + await this.createLiaraJsonFile(configs); } catch (error) { this.spinner.stop(); throw error;