diff --git a/packages/composable-cli/src/commands/login/login-command.ts b/packages/composable-cli/src/commands/login/login-command.ts index e265267a..19319f31 100644 --- a/packages/composable-cli/src/commands/login/login-command.ts +++ b/packages/composable-cli/src/commands/login/login-command.ts @@ -27,6 +27,7 @@ import { WelcomeNote } from "../ui/login/welcome-note" import { render } from "ink" import { trackCommandHandler } from "../../util/track-command-handler" import { EpccRequester } from "../../util/command" +import { credentialsSchema } from "../../lib/authentication/credentials-schema" /** * Region prompts @@ -42,24 +43,6 @@ const regionPrompts = { default: "us-east", } as const -/* -choices: [ - { - name: "North America (free-trial region)", - value: "useast.api.elasticpath.com", - }, - { - name: "Europe", - value: "euwest.api.elasticpath.com", - }, - new inquirer.Separator(), - { - name: "Other", - value: "Other", - }, - ], - */ - function handleRegionUpdate(store: Conf, region: "eu-west" | "us-east"): void { store.set("region", region) } @@ -224,16 +207,27 @@ async function authenticateUserPassword( password, ) - if (checkIsErrorResponse(credentialsResp)) { + const parsedCredentialsResp = credentialsSchema.safeParse(credentialsResp) + + if (!parsedCredentialsResp.success) { + return { + success: false, + code: "authentication-failure", + name: "data parsing error", + message: parsedCredentialsResp.error.message, + } + } + + if (checkIsErrorResponse(parsedCredentialsResp.data)) { return { success: false, code: "authentication-failure", name: "epcc error", - message: credentialsResp.errors.toString(), + message: parsedCredentialsResp.data.errors.toString(), } } - storeCredentials(store, credentialsResp as any) + storeCredentials(store, parsedCredentialsResp.data) return { success: true, diff --git a/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx b/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx index 2de77c82..add0e1e1 100644 --- a/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx +++ b/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx @@ -23,6 +23,15 @@ import { EPPaymentsSetup, epPaymentsSetupSchema, } from "./util/setup-ep-payments-schema" +import { + callRule, + HostTree, + SchematicContext, +} from "@angular-devkit/schematics" +import { processUnknownError } from "../../../util/process-unknown-error" +import { Result } from "../../../types/results" +import { addEnvVariables } from "../../../lib/devkit/add-env-variables" +import { commitTree, createScopedHost } from "../../../lib/devkit/tree-util" export function createEPPaymentsCommand( ctx: CommandContext, @@ -98,31 +107,132 @@ export function createEPPaymentsCommandHandler( } if (result.data.stripe_account !== options.accountId) { + await attemptToAddEnvVariables(ctx, spinner, { + accountId: result.data.stripe_account!, + publishableKey: options.publishableKey, + }) + spinner.succeed(`EP Payments was already setup.`) + + const alreadyExistingAccountId = result.data.stripe_account! + + ctx.logger.warn( + boxen( + `EP Payments was already setup with account id: ${ctx.colors.bold.green( + alreadyExistingAccountId, + )}\n\nMake sure you add the correct account id NEXT_PUBLIC_STRIPE_ACCOUNT_ID=${alreadyExistingAccountId} and with the appropriate publishable key NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY to your .env.local file.`, + { padding: 1, borderColor: "yellow" }, + ), + ) + return { success: true, data: {}, } } + await attemptToAddEnvVariables(ctx, spinner, { + accountId: result.data.stripe_account!, + publishableKey: options.publishableKey, + }) + spinner.succeed(`EP Payments setup successfully.`) + return { success: true, data: {}, } } catch (e) { - spinner.fail(`Failed to setup Algolia integration`) + spinner.fail(`Failed to setup EP Payment gateway.`) + ctx.logger.error(processUnknownError(e)) return { success: false, error: { - code: "ALGOLIA_INTEGRATION_SETUP_FAILED", - message: "Failed to setup Algolia integration", + code: "FAILED_TO_SETUP_EP_PAYMENT_GATEWAY", + message: "Failed to setup EP Payment gateway", }, } } } } +async function attemptToAddEnvVariables( + ctx: CommandContext, + spinner: ora.Ora, + { accountId, publishableKey }: EpPaymentEnvVariableRecord, +): Promise> { + const { workspaceRoot, composableRc } = ctx + + if (!composableRc) { + return { + success: false, + error: { + code: "NO_COMPOSABLE_RC", + message: "Could not detect workspace root - missing composable.rc file", + }, + } + } + + spinner.start( + `Adding EP Payments environment variables to .env.local file...`, + ) + + if (!workspaceRoot) { + spinner.fail( + `Failed to add environment variables to .env.local file - missing workspace root`, + ) + return { + success: false, + error: { + code: "EP", + message: + "Setup of EP Payment gateway succeeded but failed to add env variables to .env.local file", + }, + } + } + + await addEpPaymentEnvVariables(workspaceRoot, { + accountId, + publishableKey, + }) + + spinner.succeed(`Added EP Payments environment variables to .env.local file.`) + + return { + success: true, + data: {}, + } +} + +type EpPaymentEnvVariableRecord = { accountId: string; publishableKey: string } + +async function addEpPaymentEnvVariables( + workspaceRoot: string, + { accountId, publishableKey }: EpPaymentEnvVariableRecord, +): Promise { + const host = createScopedHost(workspaceRoot) + + const initialTree = new HostTree(host) + + if (!initialTree.exists(".env.local")) { + initialTree.create(".env.local", "") + } + + const context = {} as unknown as SchematicContext + + const rule = addEnvVariables( + { + NEXT_PUBLIC_STRIPE_ACCOUNT_ID: accountId, + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: publishableKey, + }, + ".env.local", + ) + + const tree = await callRule(rule, initialTree, context).toPromise() + + await commitTree(host, tree) +} + async function resolveOptions( args: EPPaymentsCommandArguments, logger: logging.Logger, diff --git a/packages/composable-cli/src/commands/store/store-command.tsx b/packages/composable-cli/src/commands/store/store-command.tsx index c05b8d4b..eb840dd6 100644 --- a/packages/composable-cli/src/commands/store/store-command.tsx +++ b/packages/composable-cli/src/commands/store/store-command.tsx @@ -30,6 +30,7 @@ import { } from "../../util/epcc-error" import { trackCommandHandler } from "../../util/track-command-handler" import { EpccRequester } from "../../util/command" +import { storeUserStore } from "../../util/conf-store/store-credentials" export function createStoreCommand( ctx: CommandContext, @@ -121,14 +122,14 @@ export function createSetStoreCommandHandler( } export function createStoreCommandHandler( - _ctx: CommandContext, + ctx: CommandContext, ): CommandHandlerFunction< StoreCommandData, StoreCommandError, StoreCommandArguments > { return async function storeCommandHandler(_args) { - console.warn("command not recognized") + ctx.logger.warn("command not recognized") return { success: false, error: { @@ -184,7 +185,7 @@ export async function selectStoreById( } } - store.set("store", parsedResultData) + storeUserStore(store, parsedResultData) return { success: true, @@ -235,7 +236,7 @@ export async function storeSelectPrompt( } } - store.set("store", answers.store) + storeUserStore(store, answers.store) return { success: true, diff --git a/packages/composable-cli/src/lib/authentication/ep-client-middleware.ts b/packages/composable-cli/src/lib/authentication/ep-client-middleware.ts index 9dc85d11..4281aa34 100644 --- a/packages/composable-cli/src/lib/authentication/ep-client-middleware.ts +++ b/packages/composable-cli/src/lib/authentication/ep-client-middleware.ts @@ -3,7 +3,11 @@ import { MiddlewareFunction } from "yargs" import { getCredentials, getToken } from "./get-token" import { gateway, MemoryStorageFactory, Moltin } from "@moltin/sdk" import Conf from "conf" -import { getRegion, resolveHostFromRegion } from "../../util/resolve-region" +import { + getRegion, + resolveHostFromRegion, + resolveHostNameFromRegion, +} from "../../util/resolve-region" export function createEpClientMiddleware( ctx: CommandContext, @@ -22,14 +26,19 @@ export function createEpClientMiddleware( } function createEpccClient(store: Conf): Moltin { + const regionResult = getRegion(store) + if (!regionResult.success) { + throw new Error( + "No region found - ep client custom authenticator - are you authetnicated? - `composable-cli login`", + ) + } + + const hostname = resolveHostNameFromRegion(regionResult.data) + const resolvedRegion = resolveHostFromRegion(regionResult.data) + return gateway({ + host: hostname, custom_authenticator: async () => { - const regionResult = getRegion(store) - if (!regionResult.success) { - throw new Error("No region found - ep client custom authenticator") - } - - const resolvedRegion = resolveHostFromRegion(regionResult.data) await getToken(resolvedRegion, store) const credentialsResult = getCredentials(store) diff --git a/packages/composable-cli/src/lib/authentication/get-token.ts b/packages/composable-cli/src/lib/authentication/get-token.ts index c44afd34..e0136799 100644 --- a/packages/composable-cli/src/lib/authentication/get-token.ts +++ b/packages/composable-cli/src/lib/authentication/get-token.ts @@ -12,9 +12,16 @@ import { checkIsErrorResponse, resolveEPCCErrorMessage, } from "../../util/epcc-error" -import { handleClearCredentials } from "../../util/conf-store/store-credentials" +import { + handleClearCredentials, + storeCredentials, + storeUserStore, +} from "../../util/conf-store/store-credentials" import { getStore } from "../stores/get-store" -import { userSwitchStoreResponseSchema } from "../stores/switch-store-schema" +import { + UserSwitchStoreResponse, + userSwitchStoreResponseSchema, +} from "../stores/switch-store-schema" import { encodeObjectToQueryString } from "../../util/encode-object-to-query-str" export function getCredentials(store: Conf): Result { @@ -63,6 +70,9 @@ export async function getToken( return handleExpiredToken(store, apiUrl, refresh_token) } + // Switch EP store if there is an active store + await switchStoreIfActive(store, apiUrl, credentialsResult.data.access_token) + return { success: true, data: access_token, @@ -94,7 +104,7 @@ async function handleExpiredToken( } // Set credentials in conf store - store.set("credentials", renewedToken.data) + storeCredentials(store, renewedToken.data) // Switch EP store if there is an active store await switchStoreIfActive(store, apiUrl, renewedToken.data.access_token) @@ -115,7 +125,7 @@ async function switchStoreIfActive(store: Conf, apiUrl: string, token: string) { ) if (switchStoreResponse.success) { - store.set("store", switchStoreResponse.data) + storeUserStore(store, activeStoreResult.data) } } else { store.delete("store") @@ -161,7 +171,7 @@ export async function switchUserStore( apiUrl: string, token: string, storeId: string, -): Promise> { +): Promise> { const switchResult = await postSwitchUserStore(apiUrl, token, storeId) const parsedResult = userSwitchStoreResponseSchema.safeParse(switchResult) @@ -175,7 +185,7 @@ export async function switchUserStore( return { success: true, - data: {}, + data: parsedResult.data, } } diff --git a/packages/composable-cli/src/lib/config-middleware.ts b/packages/composable-cli/src/lib/config-middleware.ts index 500811ac..aea0f0cf 100644 --- a/packages/composable-cli/src/lib/config-middleware.ts +++ b/packages/composable-cli/src/lib/config-middleware.ts @@ -4,6 +4,7 @@ import { promises } from "node:fs" import { composableRcSchema } from "./composable-rc-schema" import findUp from "find-up" import path from "path" +import { processUnknownError } from "../util/process-unknown-error" export function createConfigMiddleware( ctx: CommandContext, @@ -16,6 +17,7 @@ export function createConfigMiddleware( if (!configPath) { ctx.logger.debug("No .composablerc file found") + ctx.workspaceRoot = process.cwd() return } @@ -43,19 +45,3 @@ export function createConfigMiddleware( } } } - -function processUnknownError(error: unknown): string { - let errorMessage = "An unknown error occurred" - - if (error instanceof Error) { - if (error.message) { - errorMessage += `: ${error.message}` - } - - if (error.stack) { - errorMessage += `\nStack Trace:\n${error.stack}` - } - } - - return errorMessage -} diff --git a/packages/composable-cli/src/lib/stores/switch-store-schema.ts b/packages/composable-cli/src/lib/stores/switch-store-schema.ts index e1c46cad..1f4004de 100644 --- a/packages/composable-cli/src/lib/stores/switch-store-schema.ts +++ b/packages/composable-cli/src/lib/stores/switch-store-schema.ts @@ -12,3 +12,7 @@ export const userSwitchStoreResponseSchema = z.union([ userSwitchStoreSuccessResponseSchema, epccErrorResponseSchema, ]) + +export type UserSwitchStoreResponse = z.infer< + typeof userSwitchStoreResponseSchema +> diff --git a/packages/composable-cli/src/util/build-store-prompts.ts b/packages/composable-cli/src/util/build-store-prompts.ts index a3cb77d8..cab52aee 100644 --- a/packages/composable-cli/src/util/build-store-prompts.ts +++ b/packages/composable-cli/src/util/build-store-prompts.ts @@ -57,6 +57,7 @@ export async function switchUserStore( requester: EpccRequester, storeId: string, ): Promise> { + console.log("post switch store") const switchResult = await postSwitchUserStore(requester, storeId) const parsedResult = userSwitchStoreResponseSchema.safeParse(switchResult) diff --git a/packages/composable-cli/src/util/conf-store/store-credentials.ts b/packages/composable-cli/src/util/conf-store/store-credentials.ts index 1691f20b..bc8539f9 100644 --- a/packages/composable-cli/src/util/conf-store/store-credentials.ts +++ b/packages/composable-cli/src/util/conf-store/store-credentials.ts @@ -1,10 +1,9 @@ import Conf from "conf" import { UserProfile } from "../../lib/epcc-user-profile-schema" +import { Credentials } from "../../lib/authentication/credentials-schema" +import { UserStore } from "../../lib/stores/stores-schema" -export function storeCredentials( - store: Conf, - credentials: { accessToken: string; refreshToken: string; expires: number } -) { +export function storeCredentials(store: Conf, credentials: Credentials) { return store.set("credentials", credentials) } @@ -18,3 +17,7 @@ export function handleClearCredentials(store: Conf): void { export function storeUserProfile(store: Conf, userProfile: UserProfile) { return store.set("profile", userProfile) } + +export function storeUserStore(store: Conf, userStore: UserStore) { + return store.set("store", userStore) +}