From 000dfe1bf44f0a0b2280633e02b242554d2b75ef Mon Sep 17 00:00:00 2001 From: Phoomparin Mano <poom@metabase.com> Date: Wed, 9 Oct 2024 23:21:06 +0700 Subject: [PATCH] fix(sdk): allow CLI to check React version without installing and allow continuing setup if React is missing (#48491) * check sdk version via package.json and allow continuing setup * make package version checks more flexible --- .../embedding-sdk/cli/constants/messages.ts | 2 + .../cli/steps/check-if-react-project.ts | 66 ++++++++----------- .../cli/steps/check-sdk-available.ts | 18 ++--- .../cli/utils/get-package-version.ts | 50 ++++++++++---- .../src/embedding-sdk/cli/utils/print.ts | 4 ++ .../cli/utils/show-warning-prompt.ts | 23 +++++++ 6 files changed, 102 insertions(+), 61 deletions(-) create mode 100644 enterprise/frontend/src/embedding-sdk/cli/utils/show-warning-prompt.ts diff --git a/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts b/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts index a55f63b3898..4dc83f4ffa9 100644 --- a/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts +++ b/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts @@ -74,3 +74,5 @@ export const SETUP_PRO_LICENSE_MESSAGE = ` export const SDK_LEARN_MORE_MESSAGE = `All done! 🚀 Learn more about the SDK here: ${green( SDK_NPM_LINK, )}`; + +export const CONTINUE_SETUP_ON_WARNING_MESSAGE = `Do you want to continue setup?`; diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/check-if-react-project.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/check-if-react-project.ts index 4eb62f70a56..3ffc634ffa4 100644 --- a/enterprise/frontend/src/embedding-sdk/cli/steps/check-if-react-project.ts +++ b/enterprise/frontend/src/embedding-sdk/cli/steps/check-if-react-project.ts @@ -6,12 +6,13 @@ import { PACKAGE_JSON_NOT_FOUND_MESSAGE, UNSUPPORTED_REACT_VERSION, } from "embedding-sdk/cli/constants/messages"; -import { - getPackageVersion, - hasPackageJson, -} from "embedding-sdk/cli/utils/get-package-version"; import type { CliStepMethod } from "../types/cli"; +import { + getPackageVersions, + hasPackageJson, +} from "../utils/get-package-version"; +import { showWarningAndAskToContinue } from "../utils/show-warning-prompt"; const isReactVersionSupported = (version: string) => semver.satisfies(semver.coerce(version)!, "18.x"); @@ -21,49 +22,40 @@ export const checkIfReactProject: CliStepMethod = async state => { if (!(await hasPackageJson())) { spinner.fail(); - return [ - { - type: "error", - message: PACKAGE_JSON_NOT_FOUND_MESSAGE, - }, - state, - ]; + return [{ type: "error", message: PACKAGE_JSON_NOT_FOUND_MESSAGE }, state]; } - const reactDep = await getPackageVersion("react"); - const reactDomDep = await getPackageVersion("react-dom"); + const dependencyVersions = await getPackageVersions("react", "react-dom"); + + const reactDep = dependencyVersions["react"]; + const reactDomDep = dependencyVersions["react-dom"]; const hasReactDependency = reactDep && reactDomDep; + const hasSupportedReactVersion = - isReactVersionSupported(reactDep) && isReactVersionSupported(reactDomDep); + hasReactDependency && + isReactVersionSupported(reactDep) && + isReactVersionSupported(reactDomDep); + + let warningMessage: string | null = null; if (!hasReactDependency) { - spinner.fail(); - return [ - { - type: "error", - message: MISSING_REACT_DEPENDENCY, - }, - state, - ]; + warningMessage = MISSING_REACT_DEPENDENCY; + } else if (!hasSupportedReactVersion) { + warningMessage = UNSUPPORTED_REACT_VERSION; } - if (!hasSupportedReactVersion) { + if (warningMessage) { spinner.fail(); - return [ - { - type: "error", - message: UNSUPPORTED_REACT_VERSION, - }, - state, - ]; + + const shouldContinue = await showWarningAndAskToContinue(warningMessage); + + if (!shouldContinue) { + return [{ type: "error", message: "Canceled." }, state]; + } + } else { + spinner.succeed(`React ${reactDep} and React DOM ${reactDomDep} found`); } - spinner.succeed(`React v${reactDep} and React DOM v${reactDomDep} found`); - return [ - { - type: "success", - }, - state, - ]; + return [{ type: "success" }, state]; }; diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/check-sdk-available.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/check-sdk-available.ts index 3bfbd753a97..90ac5ebd051 100644 --- a/enterprise/frontend/src/embedding-sdk/cli/steps/check-sdk-available.ts +++ b/enterprise/frontend/src/embedding-sdk/cli/steps/check-sdk-available.ts @@ -1,24 +1,20 @@ import ora from "ora"; -import { SDK_PACKAGE_NAME } from "embedding-sdk/cli/constants/config"; -import { installSdk } from "embedding-sdk/cli/steps/install-sdk"; -import { getPackageVersion } from "embedding-sdk/cli/utils/get-package-version"; - +import { SDK_PACKAGE_NAME } from "../constants/config"; +import { installSdk } from "../steps/install-sdk"; import type { CliStepMethod } from "../types/cli"; +import { getPackageVersions } from "../utils/get-package-version"; export const checkSdkAvailable: CliStepMethod = async state => { const spinner = ora("Checking if SDK is installed…").start(); - const sdkVersion = await getPackageVersion(SDK_PACKAGE_NAME); + + const projectDependencies = await getPackageVersions(SDK_PACKAGE_NAME); + const sdkVersion = projectDependencies?.[SDK_PACKAGE_NAME]; // skip install step if we already have the SDK installed if (sdkVersion) { spinner.succeed(`SDK v${sdkVersion} found`); - return [ - { - type: "success", - }, - state, - ]; + return [{ type: "success" }, state]; } spinner.fail("SDK not found"); diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/get-package-version.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/get-package-version.ts index 6fbf46b7610..8176324c4d9 100644 --- a/enterprise/frontend/src/embedding-sdk/cli/utils/get-package-version.ts +++ b/enterprise/frontend/src/embedding-sdk/cli/utils/get-package-version.ts @@ -11,28 +11,52 @@ export const hasPackageJson = async () => { } }; -export const getPackageVersion = async (packageName: string) => { +type DependencyMap = Record<string, string>; + +export const getPackageVersions = async (...packageNames: string[]) => { + const versions: Record<string, string> = {}; + + const projectDeps = await getProjectDependenciesFromPackageJson(); + + for (const name of packageNames) { + versions[name] = + (await getPackageVersionFromModule(name)) || projectDeps?.[name] || null; + } + + return versions; +}; + +export const readJson = async (path: string) => { + const packageJson = await fs.readFile(path, "utf8"); + + return JSON.parse(packageJson); +}; + +export const getProjectDependenciesFromPackageJson = + async (): Promise<DependencyMap | null> => { + try { + const packageJsonPath = path.join(process.cwd(), "package.json"); + const packageInfo = await readJson(packageJsonPath); + + return packageInfo?.dependencies || null; + } catch (error) { + return null; + } + }; + +export const getPackageVersionFromModule = async (packageName: string) => { try { - const packagePath = path.join( + const packageJsonPath = path.join( process.cwd(), "node_modules", packageName, "package.json", ); - const packageJson = await fs.readFile(packagePath, "utf8"); - const packageInfo = JSON.parse(packageJson); + const packageInfo = await readJson(packageJsonPath); + return packageInfo.version || null; } catch (error) { - if ((error as { code: string }).code === "ENOENT") { - // Package not found - return null; - } - console.error( - `Error checking package version: ${ - (error as { message: string }).message - }`, - ); return null; } }; diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/print.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/print.ts index 2da41633271..085cc6d09e1 100644 --- a/enterprise/frontend/src/embedding-sdk/cli/utils/print.ts +++ b/enterprise/frontend/src/embedding-sdk/cli/utils/print.ts @@ -7,6 +7,7 @@ export const OUTPUT_STYLES = { version: chalk.hex("#509EE3"), link: chalk.underline.blueBright, error: chalk.red.bold, + warning: chalk.yellow.bold, success: chalk.green.bold, info: chalk.bold, }; @@ -37,6 +38,9 @@ export const printLink = (text: string) => _print(OUTPUT_STYLES.link, text); export const printError = (message: string) => console.error(OUTPUT_STYLES.error(message)); +export const printWarning = (message: string) => + console.warn(OUTPUT_STYLES.warning(message)); + export const printSuccess = (message: string) => _print(OUTPUT_STYLES.success, message); diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/show-warning-prompt.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/show-warning-prompt.ts new file mode 100644 index 00000000000..884938f1723 --- /dev/null +++ b/enterprise/frontend/src/embedding-sdk/cli/utils/show-warning-prompt.ts @@ -0,0 +1,23 @@ +import { select } from "@inquirer/prompts"; + +import { CONTINUE_SETUP_ON_WARNING_MESSAGE } from "../constants/messages"; + +import { printWarning } from "./print"; + +/** + * @returns {boolean} whether the user wants to continue setup or not + */ +export async function showWarningAndAskToContinue( + message: string, +): Promise<boolean> { + printWarning(message); + + return select({ + message: CONTINUE_SETUP_ON_WARNING_MESSAGE, + choices: [ + { name: "Continue", value: true }, + { name: "Exit setup", value: false }, + ], + default: true, + }); +} -- GitLab