From 7fef8ceb5d1cd69d9d251cf62188be1dd028ddc9 Mon Sep 17 00:00:00 2001 From: Erick Luiz Bertolini Date: Sat, 10 Feb 2024 09:45:17 -0300 Subject: [PATCH] feature:pipeline-support --- src/api/session/SessionManager.ts | 40 +++++++++++++ src/commands/deprecate.ts | 8 ++- src/commands/login.ts | 21 ++++++- src/commands/publish.ts | 8 ++- src/commands/release.ts | 9 ++- .../ApiTokenAuthenticator/index.ts | 38 ++++++++++++ src/modules/apps/deprecate.ts | 9 ++- src/modules/apps/publish.ts | 4 +- src/modules/auth/login.ts | 2 +- src/modules/auth/pipeline.ts | 58 +++++++++++++++++++ src/modules/release/index.ts | 14 +++-- 11 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 src/lib/auth/AuthProviders/ApiTokenAuthenticator/index.ts create mode 100644 src/modules/auth/pipeline.ts diff --git a/src/api/session/SessionManager.ts b/src/api/session/SessionManager.ts index e14efd806..3b679255a 100644 --- a/src/api/session/SessionManager.ts +++ b/src/api/session/SessionManager.ts @@ -1,4 +1,5 @@ import { AuthProviderBase, AuthProviders } from '../../lib/auth/AuthProviders' +import { ApiTokenAuthenticator } from '../../lib/auth/AuthProviders/ApiTokenAuthenticator' import { Token } from '../../lib/auth/Token' import { VTEXID } from '../clients/IOClients/external/VTEXID' import { ErrorKinds } from '../error/ErrorKinds' @@ -21,6 +22,14 @@ export interface LoginInput { workspaceCreation: WorkspaceCreation } + +export interface PipelineLoginInput { + vtexApiKey?: string + vtexApiToken?: string + targetWorkspace: string + workspaceCreation: WorkspaceCreation +} + export interface WorkspaceSwitchInput { targetWorkspace: string workspaceCreation: WorkspaceCreation @@ -151,6 +160,37 @@ export class SessionManager implements ISessionManager { }) } + + public async loginUsingPipeline(newAccount: string, { + vtexApiKey, + targetWorkspace = 'master', + workspaceCreation, + vtexApiToken + }: PipelineLoginInput) { + if (this.account !== newAccount) { + this.state.lastAccount = this.account + this.state.lastWorkspace = null + } + + const cachedToken = new Token(this.sessionPersister.getAccountToken(newAccount)) + + if (cachedToken.isValid()) { + this.state.tokenObj = cachedToken + } else { + const apiToken = new ApiTokenAuthenticator() + const token = await apiToken.login(vtexApiKey, vtexApiToken, newAccount) + this.state.tokenObj = new Token(token) + this.sessionPersister.saveAccountToken(newAccount, this.state.tokenObj.token) + } + + + this.state.account = newAccount + this.state.workspace = 'master' + this.saveState() + await this.workspaceSwitch({ targetWorkspace, workspaceCreation }) + } + + public async login( newAccount: string, { targetWorkspace = 'master', authMethod = 'oauth', useCachedToken = true, workspaceCreation }: LoginInput diff --git a/src/commands/deprecate.ts b/src/commands/deprecate.ts index 0b9b935a1..4219938a9 100644 --- a/src/commands/deprecate.ts +++ b/src/commands/deprecate.ts @@ -18,6 +18,10 @@ export default class Deprecate extends CustomCommand { static flags = { ...CustomCommand.globalFlags, yes: oclifFlags.boolean({ description: 'Answers yes to all prompts.', char: 'y', default: false }), + pipeline: oclifFlags.boolean({ + char: 'p', + description: `Runs the command in ${ColorifyConstants.ID('pipeline')} mode.`, + }) } static strict = false @@ -41,11 +45,11 @@ export default class Deprecate extends CustomCommand { async run() { const { raw, - flags: { yes }, + flags: { yes, pipeline }, } = this.parse(Deprecate) const allArgs = this.getAllArgs(raw) - await appsDeprecate(allArgs, { yes }) + await appsDeprecate(allArgs, { yes }, pipeline) } } diff --git a/src/commands/login.ts b/src/commands/login.ts index 050a021b7..580c3bbb6 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -4,6 +4,7 @@ import { CustomCommand } from '../api/oclif/CustomCommand' import authLogin from '../modules/auth/login' import { ColorifyConstants } from '../api/constants/Colors' +import loginWithPipeline from '../modules/auth/pipeline' export default class Login extends CustomCommand { static description = `Logs in to a ${ColorifyConstants.ID('VTEX account')}.` @@ -15,10 +16,22 @@ export default class Login extends CustomCommand { static flags = { ...CustomCommand.globalFlags, + pipeline: oclifFlags.boolean({ + char: 'p', + description: `Runs the command in ${ColorifyConstants.ID('pipeline')} mode.`, + }), workspace: oclifFlags.string({ char: 'w', description: `Logs in the specified ${ColorifyConstants.ID('workspace')}.`, }), + vtexApiKey: oclifFlags.string({ + char: 'k', + description: `VTEX API Key.`, + }), + vtexApiToken: oclifFlags.string({ + char: 't', + description: `VTEX API` + }) } static args = [ @@ -28,9 +41,13 @@ export default class Login extends CustomCommand { async run() { const { args: { account }, - flags: { workspace }, + flags: { workspace, pipeline, vtexApiKey, vtexApiToken }, } = this.parse(Login) - await authLogin({ account, workspace }) + if(!pipeline) { + await authLogin({ account, workspace }) + }else{ + await loginWithPipeline({ account, workspace }, vtexApiKey, vtexApiToken) + } } } diff --git a/src/commands/publish.ts b/src/commands/publish.ts index 2b6a6e65d..c091a90b5 100644 --- a/src/commands/publish.ts +++ b/src/commands/publish.ts @@ -17,6 +17,10 @@ export default class Publish extends CustomCommand { char: 'w', description: `Uses the specified ${ColorifyConstants.ID('workspace')} in the app registry.`, }), + pipeline: oclifFlags.boolean({ + char: 'p', + description: `Runs the command in ${ColorifyConstants.ID('pipeline')} mode.`, + }), force: oclifFlags.boolean({ char: 'f', description: 'Publishes the app independently of SemVer rules.', @@ -27,9 +31,9 @@ export default class Publish extends CustomCommand { async run() { const { args: { path }, - flags: { yes, workspace, force, tag }, + flags: { yes, workspace, force, tag, pipeline }, } = this.parse(Publish) - await appsPublish(path, { yes, workspace, force, tag }) + await appsPublish(path, { yes, workspace, force, tag }, pipeline) } } diff --git a/src/commands/release.ts b/src/commands/release.ts index 754e80abf..d67b6db1b 100644 --- a/src/commands/release.ts +++ b/src/commands/release.ts @@ -1,3 +1,4 @@ +import { flags as oclifFlags } from '@oclif/command' import { CustomCommand } from '../api/oclif/CustomCommand' import appsRelease, { releaseTypeAliases, supportedReleaseTypes, supportedTagNames } from '../modules/release' @@ -17,6 +18,10 @@ export default class Release extends CustomCommand { static flags = { ...CustomCommand.globalFlags, + pipeline: oclifFlags.boolean({ + char: 'p', + description: `Runs the command in ${ColorifyConstants.ID('pipeline')} mode.`, + }) } static args = [ @@ -32,9 +37,9 @@ export default class Release extends CustomCommand { async run() { const { + flags: { pipeline }, args: { releaseType, tagName }, } = this.parse(Release) - - await appsRelease(releaseType, tagName) + await appsRelease(releaseType, tagName, pipeline) } } diff --git a/src/lib/auth/AuthProviders/ApiTokenAuthenticator/index.ts b/src/lib/auth/AuthProviders/ApiTokenAuthenticator/index.ts new file mode 100644 index 000000000..0fefb1b1f --- /dev/null +++ b/src/lib/auth/AuthProviders/ApiTokenAuthenticator/index.ts @@ -0,0 +1,38 @@ +import axios from 'axios' + +export class ApiTokenAuthenticator { + public async login(apiKey: string, apiToken: string, account: string) { + const url = `https://api.vtexcommercestable.com.br/api/vtexid/apptoken/login?an=${account}` + + const body = { + appKey: apiKey, + appToken: apiToken + } + + try{ + const response = await axios.post(url, body, { + headers: { + 'Content-Type': 'application/json' + } + }) + + if(response.status === 200){ + const { data } = response + + if(data?.token){ + const token = data.token + return token + }else{ + throw new Error('Invalid credentials') + } + + }else{ + throw new Error('Invalid credentials') + } + + + }catch(error){ + throw new Error(error) + } + } +} \ No newline at end of file diff --git a/src/modules/apps/deprecate.ts b/src/modules/apps/deprecate.ts index d7d790979..b5093552d 100644 --- a/src/modules/apps/deprecate.ts +++ b/src/modules/apps/deprecate.ts @@ -70,7 +70,7 @@ const prepareAndDeprecateApps = async (appsList: string[]): Promise => { await returnToPreviousAccount({ previousAccount: originalAccount, previousWorkspace: originalWorkspace }) } -export default async (optionalApps: string[], options) => { +export default async (optionalApps: string[], options, pipeline: boolean = false) => { const preConfirm = options.y || options.yes const { account, workspace } = SessionManager.getSingleton() @@ -79,9 +79,12 @@ export default async (optionalApps: string[], options) => { const appsList = optionalApps.length > 0 ? optionalApps : [(await ManifestEditor.getManifestEditor()).appLocator] - if (!preConfirm && !(await promptDeprecate(appsList))) { - return + if(!pipeline){ + if (!preConfirm && !(await promptDeprecate(appsList))) { + return + } } + log.debug(`Deprecating app${appsList.length > 1 ? 's' : ''}: ${appsList.join(', ')}`) return prepareAndDeprecateApps(appsList) diff --git a/src/modules/apps/publish.ts b/src/modules/apps/publish.ts index 8e2f39e41..123b38e78 100644 --- a/src/modules/apps/publish.ts +++ b/src/modules/apps/publish.ts @@ -120,7 +120,7 @@ const publisher = (workspace = 'master') => { return { publishApp, publishApps } } -export default async (path: string, options) => { +export default async (path: string, options, pipeline: boolean) => { log.debug(`Starting to publish app in ${conf.getEnvironment()}`) const { account } = SessionManager.getSingleton() @@ -136,7 +136,7 @@ export default async (path: string, options) => { const yesFlag = options.y || options.yes - if (!yesFlag) { + if (!pipeline && !yesFlag) { const confirmVersion = await promptConfirm( `Are you sure that you want to release version ${chalk.bold(`${versionMsg} of ${appNameMsg}?`)}`, false diff --git a/src/modules/auth/login.ts b/src/modules/auth/login.ts index ef6a26661..86b0081f1 100644 --- a/src/modules/auth/login.ts +++ b/src/modules/auth/login.ts @@ -55,7 +55,7 @@ export interface LoginOptions { postLoginOps?: PostLoginOps[] } -const getTargetLogin = async ({ account: optionAccount, workspace: optionWorkspace }: LoginOptions) => { +export const getTargetLogin = async ({ account: optionAccount, workspace: optionWorkspace }: LoginOptions) => { const { account: previousAccount, workspace: previousWorkspace, diff --git a/src/modules/auth/pipeline.ts b/src/modules/auth/pipeline.ts new file mode 100644 index 000000000..ab5bd2d16 --- /dev/null +++ b/src/modules/auth/pipeline.ts @@ -0,0 +1,58 @@ +import { SessionManager } from '../../api/session/SessionManager' +import { handleErrorCreatingWorkspace, workspaceCreator } from '../../api/modules/workspace/create' +import log from '../../api/logger' + +const vtexApiKeyLabel = 'VTEX_API_KEY'; +const vtexApiTokenLabel = 'VTEX_API_TOKEN'; + +export interface LoginOptions { + account?: string + workspace?: string +} + +export default async (opts: LoginOptions, vtexApiKey?: string, vtexApiToken?: string) => { + if (!vtexApiKey || !vtexApiToken || !opts.account) { + const credentials = getCredentialsInEnvirovmentVariables(); + if (credentials) { + vtexApiKey = credentials.vtexApiKey; + vtexApiToken = credentials.vtexApiToken; + const sessionManager = SessionManager.getSingleton() + await sessionManager.loginUsingPipeline( + opts.account, + { + vtexApiKey, + targetWorkspace: opts.workspace, + workspaceCreation: { + promptCreation: true, + creator: workspaceCreator, + onError: handleErrorCreatingWorkspace, + }, + vtexApiToken + }) + log.info('You are now logged in'); + } else { + log.error('VTEX_API_KEY and VTEX_API_TOKEN must be provided'); + } + } +} + + +interface credentialsType { + vtexApiKey: string, + vtexApiToken: string, +} + +function getCredentialsInEnvirovmentVariables(): credentialsType | null { + try { + const vtexApiKey = process.env[vtexApiKeyLabel]; + const vtexApiToken = process.env[vtexApiTokenLabel]; + + if (vtexApiKey && vtexApiToken) { + return { vtexApiKey, vtexApiToken }; + } else { + return null; + } + } catch (error) { + + } +} \ No newline at end of file diff --git a/src/modules/release/index.ts b/src/modules/release/index.ts index 8341472a6..544fe27dc 100644 --- a/src/modules/release/index.ts +++ b/src/modules/release/index.ts @@ -59,7 +59,8 @@ Valid release tags are: ${supportedTagNames.join(', ')}`) export default async ( releaseType = 'patch', // This arg. can also be a valid (semver) version. - tagName = 'beta' + tagName = 'beta', + pipeline = false ) => { const utils = new ReleaseUtils() utils.checkGit() @@ -78,9 +79,12 @@ export default async ( const tagText = `v${newVersion}` const changelogVersion = `\n\n## [${newVersion}] - ${year}-${month}-${day}` - if (!(await utils.confirmRelease())) { - // Abort release. - return + + if(!pipeline){ + if (!(await utils.confirmRelease())) { + // Abort release. + return + } } log.info('Starting release...') try { @@ -93,7 +97,7 @@ export default async ( await utils.commit(tagText) await utils.tag(tagText) await utils.push(tagText) - await utils.postRelease() + if(!pipeline) await utils.postRelease(); } catch (e) { log.error(`Failed to release \n${e}`) }