diff --git a/packages/cli/docs/login.md b/packages/cli/docs/login.md new file mode 100644 index 00000000..86bcf6b4 --- /dev/null +++ b/packages/cli/docs/login.md @@ -0,0 +1,32 @@ +`preevy login` +============== + +Login to the Livecycle SaaS + +* [`preevy login`](#preevy-login) + +## `preevy login` + +Login to the Livecycle SaaS + +``` +USAGE + $ preevy login [-D] [-f ] [--system-compose-file ] [-p ] [--lc-auth-url ] + [--lc-api-url ] [--lc-client-id ] + +FLAGS + --lc-api-url= [default: https://app.livecycle.run] The Livecycle API URL' + --lc-auth-url= [default: https://auth.livecycle.dev] The login URL + --lc-client-id= [default: BHXcVtapfKPEpZtYO3AJ2Livmz6j7xK0] The client ID for the OAuth app + +GLOBAL FLAGS + -D, --debug Enable debug logging + -f, --file=... [default: ] Compose configuration file + -p, --project= Project name. Defaults to the Compose project name + --system-compose-file=... [default: ] Add extra Compose configuration file without overriding the defaults + +DESCRIPTION + Login to the Livecycle SaaS +``` + +_See code: [dist/commands/login.ts](https://github.com/livecycle/preevy/blob/v0.0.55/packages/cli/src/commands/login.ts)_ diff --git a/packages/cli/package.json b/packages/cli/package.json index 3224d1cc..ea96225e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -18,6 +18,7 @@ "/oclif.manifest.json" ], "dependencies": { + "@inquirer/confirm": "^2.0.14", "@oclif/core": "^2", "@oclif/plugin-help": "^5", "@preevy/cli-common": "0.0.55", diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 24490f92..2c15cf8d 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,9 +1,12 @@ import { Flags, Args, ux } from '@oclif/core' -import { Flag } from '@oclif/core/lib/interfaces' import inquirer from 'inquirer' +import confirm from '@inquirer/confirm' +import chalk from 'chalk' import { defaultBucketName as gsDefaultBucketName, defaultProjectId as defaultGceProjectId } from '@preevy/driver-gce' import { defaultBucketName as s3DefaultBucketName, AWS_REGIONS, awsUtils } from '@preevy/driver-lightsail' import { BaseCommand } from '@preevy/cli-common' +import { EOL } from 'os' +import { Flag } from '@oclif/core/lib/interfaces' import { DriverName, formatDriverFlagsToArgs, machineDrivers } from '../drivers' import { loadProfileConfig } from '../profile-command' import ambientAwsAccountId = awsUtils.ambientAccountId @@ -127,34 +130,21 @@ export default class Init extends BaseCommand { async run(): Promise { const profileConfig = loadProfileConfig(this.config) - const existingProfiles = await profileConfig.list() - let profileAlias = this.args['profile-alias'] - const profileExists = existingProfiles.find(p => p.alias === profileAlias) - if (profileExists) { - ux.info(`Profile ${profileAlias} already exists`) - ux.info('Use `init ` to create a new profile') - return undefined + const { 'profile-alias': profileAlias } = this.args + if (await profileConfig.get(profileAlias, { throwOnNotFound: false })) { + ux.error([ + `A profile with the alias ${chalk.bold(profileAlias)} already exists.`, + `Run ${chalk.bold(`${this.config.bin} profile ls`)} to list existing profiles.`, + `Run ${chalk.bold(`${this.config.bin} profile rm `)} to remove an existing profile.`, + `Run ${chalk.bold(`${this.config.bin} init `)} to create a profile with a different alias.`, + ].join(EOL)) } if (this.flags.from) { await this.config.runCommand('profile:import', [this.flags.from, '--name', profileAlias, '--use']) - this.log('Initialized profile') return undefined } - if (profileExists) { - if (profileAlias !== 'default') { - throw new Error(`Profile ${profileAlias} already exists`) - } - profileAlias = await inquirer.prompt<{ - profileName: string - }>([{ - type: 'input', - name: 'profileAlias', - message: 'What is the name of your profile?', - }]) - } - const driver = await chooseDriver() const driverStatic = machineDrivers[driver] @@ -174,7 +164,19 @@ export default class Init extends BaseCommand { ...formatDriverFlagsToArgs(driver, driverStatic.flags as Record>, driverFlags), ]) - this.log('Initialized profile') + ux.info(chalk.bold.cyan('Use Livecycle together with Preevy to enable easy sharing and collaboration of your environments!')) + + if (!await confirm({ + message: 'Would you like to link this profile to a Livecycle account?', + default: true, + })) { + ux.info(`You can later run ${chalk.bold(`${this.config.bin} profile link`)} to link this profile to a Livecycle account.`) + return undefined + } + + await this.config.runCommand('login') + await this.config.runCommand('profile:link') + return undefined } } diff --git a/packages/cli/src/commands/profile/create.ts b/packages/cli/src/commands/profile/create.ts index 35ccfaa3..c9ca18ca 100644 --- a/packages/cli/src/commands/profile/create.ts +++ b/packages/cli/src/commands/profile/create.ts @@ -1,5 +1,6 @@ -import { Args, Flags } from '@oclif/core' +import { Args, Flags, ux } from '@oclif/core' import { createTunnelingKey } from '@preevy/core' +import chalk from 'chalk' import { DriverName, extractDriverFlags, @@ -52,6 +53,9 @@ export default class CreateProfile extends ProfileCommand if (this.flags.use) { await this.profileConfig.setCurrent(alias) } + + ux.info(chalk.greenBright('Profile initialized 👍')) + return undefined } } diff --git a/packages/cli/src/commands/profile/import.ts b/packages/cli/src/commands/profile/import.ts index e32419a1..dc5d6c4a 100644 --- a/packages/cli/src/commands/profile/import.ts +++ b/packages/cli/src/commands/profile/import.ts @@ -2,6 +2,7 @@ import { Args, Flags, ux } from '@oclif/core' import { find, range, map } from 'iter-tools-es' import { LocalProfilesConfig } from '@preevy/core' import { BaseCommand } from '@preevy/cli-common' +import chalk from 'chalk' import { loadProfileConfig, onProfileChange } from '../../profile-command' const DEFAULT_ALIAS_PREFIX = 'default' @@ -46,9 +47,10 @@ export default class ImportProfile extends BaseCommand { const { info } = await profileConfig.importExisting(alias, this.args.location) onProfileChange(info, alias, this.args.location) - ux.info(`Profile ${info.id} imported successfully as ${alias}`) if (this.flags.use) { await profileConfig.setCurrent(alias) } + + ux.info(`Profile ${chalk.bold(info.id)} imported successfully as ${chalk.bold(alias)} 👍`) } } diff --git a/packages/cli/src/profile-command.ts b/packages/cli/src/profile-command.ts index d8d61e3a..30805235 100644 --- a/packages/cli/src/profile-command.ts +++ b/packages/cli/src/profile-command.ts @@ -87,7 +87,7 @@ abstract class ProfileCommand extends BaseCommand { #profile: Profile | undefined get profile(): Profile { if (!this.#profile) { - this.error(`Profile not initialized, run ${chalk.italic.bold.greenBright('preevy init')} to get started.`) + this.error(`Profile not initialized, run ${chalk.bold(`${this.config.bin} init`)} to get started.`) } return this.#profile } diff --git a/packages/core/src/profile/config.ts b/packages/core/src/profile/config.ts index 9a0605e7..2779f019 100644 --- a/packages/core/src/profile/config.ts +++ b/packages/core/src/profile/config.ts @@ -60,11 +60,14 @@ export const localProfilesConfig = ( async list(): Promise { return Object.entries((await readProfileList()).profiles).map(([alias, profile]) => ({ alias, ...profile })) }, - async get(alias: string) { + async get(alias: string, opts: { throwOnNotFound: boolean } = { throwOnNotFound: true }) { const { profiles } = await readProfileList() const locationUrl = profiles[alias]?.location if (!locationUrl) { - throw new Error(`Profile ${alias} not found`) + if (opts.throwOnNotFound) { + throw new Error(`Profile ${alias} not found`) + } + return undefined } const tarSnapshotStore = await tarSnapshotFromUrl(locationUrl) const profileInfo = await profileStore(tarSnapshotStore).info() diff --git a/site/docs/cli-reference/login.md b/site/docs/cli-reference/login.md new file mode 100644 index 00000000..86bcf6b4 --- /dev/null +++ b/site/docs/cli-reference/login.md @@ -0,0 +1,32 @@ +`preevy login` +============== + +Login to the Livecycle SaaS + +* [`preevy login`](#preevy-login) + +## `preevy login` + +Login to the Livecycle SaaS + +``` +USAGE + $ preevy login [-D] [-f ] [--system-compose-file ] [-p ] [--lc-auth-url ] + [--lc-api-url ] [--lc-client-id ] + +FLAGS + --lc-api-url= [default: https://app.livecycle.run] The Livecycle API URL' + --lc-auth-url= [default: https://auth.livecycle.dev] The login URL + --lc-client-id= [default: BHXcVtapfKPEpZtYO3AJ2Livmz6j7xK0] The client ID for the OAuth app + +GLOBAL FLAGS + -D, --debug Enable debug logging + -f, --file=... [default: ] Compose configuration file + -p, --project= Project name. Defaults to the Compose project name + --system-compose-file=... [default: ] Add extra Compose configuration file without overriding the defaults + +DESCRIPTION + Login to the Livecycle SaaS +``` + +_See code: [dist/commands/login.ts](https://github.com/livecycle/preevy/blob/v0.0.55/packages/cli/src/commands/login.ts)_ diff --git a/yarn.lock b/yarn.lock index 3e595783..349875c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3064,6 +3064,40 @@ resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== +"@inquirer/confirm@^2.0.14": + version "2.0.14" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-2.0.14.tgz#b87fcdf218d0ce687bd021623e091d3a80744e9e" + integrity sha512-Elzo5VX5lO1q9xy8CChDtDQNVLaucufdZBAM12qdfX1L3NQ+TypnZytGmWDXHBTpBTwuhEuwxNvUw7B0HCURkw== + dependencies: + "@inquirer/core" "^5.1.0" + "@inquirer/type" "^1.1.5" + chalk "^4.1.2" + +"@inquirer/core@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-5.1.0.tgz#2e3f6abf1dee93eae60cd85a5168c52400f73c9c" + integrity sha512-EVnific72BhMOMo8mElvrYhGFWJZ73X6j0I+fITIPTsdAz6Z9A3w3csKy+XaH87/5QAEIQHR7RSCVXvQpIqNdQ== + dependencies: + "@inquirer/type" "^1.1.5" + "@types/mute-stream" "^0.0.2" + "@types/node" "^20.8.2" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + cli-spinners "^2.9.1" + cli-width "^4.1.0" + figures "^3.2.0" + mute-stream "^1.0.0" + run-async "^3.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + +"@inquirer/type@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.1.5.tgz#b8c171f755859c8159b10e41e1e3a88f0ca99d7f" + integrity sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA== + "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" @@ -4741,6 +4775,13 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/mute-stream@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.2.tgz#5a011b17307364e48591ac6829a8e40e1c10c6b0" + integrity sha512-FpiGjk6+IOrN0lZEfUUjdra1csU1VxwYFj4S0Zj+TJpu5x5mZW30RkEZojTadrNZHNmpCHgoE62IQZAH0OeuIA== + dependencies: + "@types/node" "*" + "@types/node-fetch@^2.6.3": version "2.6.3" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.3.tgz#175d977f5e24d93ad0f57602693c435c57ad7e80" @@ -4766,6 +4807,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@^20.8.2": + version "20.8.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.6.tgz#0dbd4ebcc82ad0128df05d0e6f57e05359ee47fa" + integrity sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ== + dependencies: + undici-types "~5.25.1" + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -4906,6 +4954,11 @@ "@types/expect" "^1.20.4" "@types/node" "*" +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/ws@^8.5.3": version "8.5.5" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" @@ -6149,6 +6202,11 @@ cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== +cli-spinners@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" + integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== + cli-table@^0.3.1: version "0.3.11" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee" @@ -6169,6 +6227,11 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -7878,7 +7941,7 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -figures@3.2.0, figures@^3.0.0: +figures@3.2.0, figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -10962,6 +11025,11 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + nan@^2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" @@ -12996,6 +13064,11 @@ run-async@^2.0.0, run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-async@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" + integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -13215,6 +13288,11 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.1.tgz#96a61033896120ec9335d96851d902cc98f0ba2a" integrity sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sigstore@^1.0.0, sigstore@^1.3.0, sigstore@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.5.0.tgz#795e44b8e9ab0089daa90eff792a831ba87ffe9c" @@ -14275,6 +14353,11 @@ underscore@^1.13.6: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -14638,6 +14721,15 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"