Browse Source

fix(cli): Improve support for pnpm projects

Michael Bromley 1 year ago
parent
commit
4eaf7ffe1f

+ 9 - 1
packages/cli/src/commands/add/codegen/add-codegen.ts

@@ -1,4 +1,4 @@
-import { log, note, outro, spinner } from '@clack/prompts';
+import { cancel, log, note, outro, spinner } from '@clack/prompts';
 import path from 'path';
 import { StructureKind } from 'ts-morph';
 
@@ -51,6 +51,14 @@ async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturn
             isDevDependency: true,
         });
     }
+    const packageManager = packageJson.determinePackageManager();
+    const packageJsonFile = packageJson.locatePackageJsonWithVendureDependency();
+    log.info(`Detected package manager: ${packageManager}`);
+    if (!packageJsonFile) {
+        cancel(`Could not locate package.json file with a dependency on Vendure.`);
+        process.exit(1);
+    }
+    log.info(`Detected package.json: ${packageJsonFile}`);
     try {
         await packageJson.installPackages(packagesToInstall);
     } catch (e: any) {

+ 20 - 6
packages/cli/src/commands/add/ui-extensions/add-ui-extensions.ts

@@ -1,4 +1,5 @@
-import { log, note, outro, spinner } from '@clack/prompts';
+import { cancel, log, note, outro, spinner } from '@clack/prompts';
+import fs from 'fs-extra';
 import path from 'path';
 
 import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
@@ -37,7 +38,15 @@ async function addUiExtensions(options?: AddUiExtensionsOptions): Promise<CliCom
 
     log.success('Updated the plugin class');
     const installSpinner = spinner();
-    installSpinner.start(`Installing dependencies...`);
+    const packageManager = packageJson.determinePackageManager();
+    const packageJsonFile = packageJson.locatePackageJsonWithVendureDependency();
+    log.info(`Detected package manager: ${packageManager}`);
+    if (!packageJsonFile) {
+        cancel(`Could not locate package.json file with a dependency on Vendure.`);
+        process.exit(1);
+    }
+    log.info(`Detected package.json: ${packageJsonFile}`);
+    installSpinner.start(`Installing dependencies using ${packageManager}...`);
     try {
         const version = packageJson.determineVendureVersion();
         await packageJson.installPackages([
@@ -53,10 +62,15 @@ async function addUiExtensions(options?: AddUiExtensionsOptions): Promise<CliCom
     installSpinner.stop('Dependencies installed');
 
     const pluginDir = vendurePlugin.getPluginDir().getPath();
-    const providersFile = createFile(project, path.join(__dirname, 'templates/providers.template.ts'));
-    providersFile.move(path.join(pluginDir, 'ui', 'providers.ts'));
-    const routesFile = createFile(project, path.join(__dirname, 'templates/routes.template.ts'));
-    routesFile.move(path.join(pluginDir, 'ui', 'routes.ts'));
+
+    const providersFileDest = path.join(pluginDir, 'ui', 'providers.ts');
+    if (!fs.existsSync(providersFileDest)) {
+        createFile(project, path.join(__dirname, 'templates/providers.template.ts')).move(providersFileDest);
+    }
+    const routesFileDest = path.join(pluginDir, 'ui', 'routes.ts');
+    if (!fs.existsSync(routesFileDest)) {
+        createFile(project, path.join(__dirname, 'templates/routes.template.ts')).move(routesFileDest);
+    }
 
     log.success('Created UI extension scaffold');
 

+ 70 - 6
packages/cli/src/shared/package-json-ref.ts

@@ -8,11 +8,22 @@ export interface PackageToInstall {
     pkg: string;
     version?: string;
     isDevDependency?: boolean;
+    installInRoot?: boolean;
 }
 
 export class PackageJson {
+    private _vendurePackageJsonPath: string | undefined;
+    private _rootPackageJsonPath: string | undefined;
     constructor(private readonly project: Project) {}
 
+    get vendurePackageJsonPath() {
+        return this.locatePackageJsonWithVendureDependency();
+    }
+
+    get rootPackageJsonPath() {
+        return this.locateRootPackageJson();
+    }
+
     determineVendureVersion(): string | undefined {
         const packageJson = this.getPackageJsonContent();
         return packageJson.dependencies['@vendure/core'];
@@ -42,8 +53,8 @@ export class PackageJson {
     }
 
     getPackageJsonContent() {
-        const packageJsonPath = path.join(this.getPackageRootDir().getPath(), 'package.json');
-        if (!fs.existsSync(packageJsonPath)) {
+        const packageJsonPath = this.locatePackageJsonWithVendureDependency();
+        if (!packageJsonPath || !fs.existsSync(packageJsonPath)) {
             note(
                 `Could not find a package.json in the current directory. Please run this command from the root of a Vendure project.`,
             );
@@ -73,9 +84,10 @@ export class PackageJson {
         const packageJson = this.getPackageJsonContent();
         packageJson.scripts = packageJson.scripts || {};
         packageJson.scripts[scriptName] = script;
-        const rootDir = this.getPackageRootDir();
-        const packageJsonPath = path.join(rootDir.getPath(), 'package.json');
-        fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
+        const packageJsonPath = this.vendurePackageJsonPath;
+        if (packageJsonPath) {
+            fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
+        }
     }
 
     getPackageRootDir() {
@@ -86,6 +98,52 @@ export class PackageJson {
         return rootDir;
     }
 
+    locateRootPackageJson() {
+        if (this._rootPackageJsonPath) {
+            return this._rootPackageJsonPath;
+        }
+        const rootDir = this.getPackageRootDir().getPath();
+        const rootPackageJsonPath = path.join(rootDir, 'package.json');
+        if (fs.existsSync(rootPackageJsonPath)) {
+            this._rootPackageJsonPath = rootPackageJsonPath;
+            return rootPackageJsonPath;
+        }
+        return null;
+    }
+
+    locatePackageJsonWithVendureDependency() {
+        if (this._vendurePackageJsonPath) {
+            return this._vendurePackageJsonPath;
+        }
+        const rootDir = this.getPackageRootDir().getPath();
+        const potentialMonorepoDirs = ['packages', 'apps', 'libs'];
+
+        const rootPackageJsonPath = path.join(this.getPackageRootDir().getPath(), 'package.json');
+        if (this.hasVendureDependency(rootPackageJsonPath)) {
+            return rootPackageJsonPath;
+        }
+        for (const dir of potentialMonorepoDirs) {
+            const monorepoDir = path.join(rootDir, dir);
+            // Check for a package.json in all subdirs
+            for (const subDir of fs.readdirSync(monorepoDir)) {
+                const packageJsonPath = path.join(monorepoDir, subDir, 'package.json');
+                if (this.hasVendureDependency(packageJsonPath)) {
+                    this._vendurePackageJsonPath = packageJsonPath;
+                    return packageJsonPath;
+                }
+            }
+        }
+        return null;
+    }
+
+    private hasVendureDependency(packageJsonPath: string) {
+        if (!fs.existsSync(packageJsonPath)) {
+            return false;
+        }
+        const packageJson = fs.readJsonSync(packageJsonPath);
+        return !!packageJson.dependencies?.['@vendure/core'];
+    }
+
     private async runPackageManagerInstall(dependencies: string[], isDev: boolean) {
         return new Promise<void>((resolve, reject) => {
             const packageManager = this.determinePackageManager();
@@ -99,6 +157,12 @@ export class PackageJson {
                 }
 
                 args = args.concat(dependencies);
+            } else if (packageManager === 'pnpm') {
+                command = 'pnpm';
+                args = ['add', '--save-exact'].concat(dependencies);
+                if (isDev) {
+                    args.push('--save-dev', '--workspace-root');
+                }
             } else {
                 command = 'npm';
                 args = ['install', '--save', '--save-exact', '--loglevel', 'error'].concat(dependencies);
@@ -106,7 +170,7 @@ export class PackageJson {
                     args.push('--save-dev');
                 }
             }
-            const child = spawn(command, args, { stdio: 'ignore' });
+            const child = spawn(command, args, { stdio: 'inherit' });
             child.on('close', code => {
                 if (code !== 0) {
                     const message = 'An error occurred when installing dependencies.';

+ 3 - 2
packages/cli/src/shared/shared-prompts.ts

@@ -3,7 +3,7 @@ import { ClassDeclaration, Project } from 'ts-morph';
 
 import { addServiceCommand } from '../commands/add/service/add-service';
 import { Messages } from '../constants';
-import { getPluginClasses, getTsMorphProject } from '../utilities/ast-utils';
+import { getPluginClasses, getTsMorphProject, selectTsConfigFile } from '../utilities/ast-utils';
 import { pauseForPromptDisplay } from '../utilities/utils';
 
 import { EntityRef } from './entity-ref';
@@ -20,9 +20,10 @@ export async function analyzeProject(options: {
 
     if (!providedVendurePlugin) {
         const projectSpinner = spinner();
+        const tsConfigFile = await selectTsConfigFile();
         projectSpinner.start('Analyzing project...');
         await pauseForPromptDisplay();
-        const { project: _project, tsConfigPath: _tsConfigPath } = await getTsMorphProject();
+        const { project: _project, tsConfigPath: _tsConfigPath } = await getTsMorphProject({}, tsConfigFile);
         project = _project;
         tsConfigPath = _tsConfigPath;
         projectSpinner.stop('Project analyzed');

+ 2 - 2
packages/cli/src/utilities/ast-utils.ts

@@ -29,8 +29,8 @@ export async function selectTsConfigFile() {
     return selectedConfigFile as string;
 }
 
-export async function getTsMorphProject(options: ProjectOptions = {}) {
-    const tsConfigFile = await selectTsConfigFile();
+export async function getTsMorphProject(options: ProjectOptions = {}, providedTsConfigPath?: string) {
+    const tsConfigFile = providedTsConfigPath ?? (await selectTsConfigFile());
     const tsConfigPath = path.join(process.cwd(), tsConfigFile);
     if (!fs.existsSync(tsConfigPath)) {
         throw new Error('No tsconfig.json found in current directory');