Skip to content

Commit

Permalink
fix(cli): Improve support for pnpm projects
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed May 2, 2024
1 parent bb10b4c commit 4eaf7ff
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 17 deletions.
10 changes: 9 additions & 1 deletion packages/cli/src/commands/add/codegen/add-codegen.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
26 changes: 20 additions & 6 deletions packages/cli/src/commands/add/ui-extensions/add-ui-extensions.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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([
Expand All @@ -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');

Expand Down
76 changes: 70 additions & 6 deletions packages/cli/src/shared/package-json-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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.`,
);
Expand Down Expand Up @@ -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() {
Expand All @@ -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();
Expand All @@ -99,14 +157,20 @@ 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);
if (isDev) {
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.';
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/shared/shared-prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/utilities/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit 4eaf7ff

Please sign in to comment.