From ca5531f3f975e8b733454ab8a549278155006fce Mon Sep 17 00:00:00 2001 From: Carl Gieringer <78054+carlgieringer@users.noreply.github.com> Date: Sat, 23 Aug 2025 13:19:21 -0700 Subject: [PATCH] feat: add yarn berry and monorepo workspace support for init command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add detectYarnVersion() to differentiate yarn classic (v1) from berry (v2+) - Add isInWorkspace() to detect if running in a monorepo workspace - Fix npm config command that fails with ENOWORKSPACES in workspaces - Update ensureLegacyPeerDeps() to handle workspace environments: - Skip npm config in workspaces (not supported) - Use appropriate yarn berry config commands - Add error handling to prevent config failures from stopping init - Add workspace-aware installation flags: - npm: -w . flag for current workspace - pnpm: -w flag for workspace root - yarn berry: handles workspaces automatically - Update postinstall commands to use workspace flags when needed - Use string literal union types for PackageManager and YarnVersion Fixes #107 - Resolves ENOWORKSPACES error when running init in monorepos 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/gluestack-cli/src/util/index.ts | 109 ++++++++++++++++-- packages/gluestack-cli/src/util/init/index.ts | 13 ++- 2 files changed, 110 insertions(+), 12 deletions(-) diff --git a/packages/gluestack-cli/src/util/index.ts b/packages/gluestack-cli/src/util/index.ts index 52eff30f..29681b19 100644 --- a/packages/gluestack-cli/src/util/index.ts +++ b/packages/gluestack-cli/src/util/index.ts @@ -183,9 +183,13 @@ const wait = (msec: number): Promise => setTimeout(resolve, msec); }); +// Type definitions for package managers +type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun' | null; +type YarnVersion = 'classic' | 'berry' | null; + //checking from cwd -export function findLockFileType(): string | null { - const lockFiles: { [key: string]: string } = { +export function findLockFileType(): PackageManager { + const lockFiles: { [key: string]: PackageManager } = { 'package-lock.json': 'npm', 'yarn.lock': 'yarn', 'pnpm-lock.yaml': 'pnpm', @@ -201,6 +205,34 @@ export function findLockFileType(): string | null { return null; } +function detectYarnVersion(): YarnVersion { + try { + const result = execSync('yarn --version', { encoding: 'utf8' }).trim(); + const majorVersion = parseInt(result.split('.')[0]); + return majorVersion >= 2 ? 'berry' : 'classic'; + } catch { + return null; + } +} + +function isInWorkspace(): boolean { + // Check for yarn workspaces + let dir = currDir; + while (dir !== dirname(dir)) { + const packageJsonPath = join(dir, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + try { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + if (packageJson.workspaces) { + return true; + } + } catch {} + } + dir = dirname(dir); + } + return false; +} + function getPackageMangerFlag(options: any) { if (options.useBun) { config.packageManager = 'bun'; @@ -239,14 +271,46 @@ export const promptVersionManager = async (): Promise => { }; async function ensureLegacyPeerDeps(): Promise { - const commands: { [key: string]: string } = { - npm: 'npm config --location=project set legacy-peer-deps=true', - yarn: 'yarn config set legacy-peer-deps true', - pnpm: 'pnpm config set legacy-peer-deps true', - }; + const packageManager = config.packageManager; + if (!packageManager) return; - const command = config.packageManager && commands[config.packageManager]; - if (command) execSync(command); + const isWorkspaceEnv = isInWorkspace(); + const yarnVersion = packageManager === 'yarn' ? detectYarnVersion() : null; + + let command: string | null = null; + + switch (packageManager) { + case 'npm': + if (isWorkspaceEnv) { + // Skip npm config in workspaces as it's not supported + console.log('Skipping npm config in workspace environment'); + return; + } + command = 'npm config --location=project set legacy-peer-deps=true'; + break; + + case 'yarn': + if (yarnVersion === 'berry') { + // Yarn berry uses different config + command = 'yarn config set npmConfigRegistry https://registry.npmjs.org/ && yarn config set npmAlwaysAuth false'; + } else { + command = 'yarn config set legacy-peer-deps true'; + } + break; + + case 'pnpm': + command = 'pnpm config set legacy-peer-deps true'; + break; + } + + if (command) { + try { + execSync(command); + } catch (error) { + console.warn(`Failed to set legacy-peer-deps config: ${error}`); + // Don't fail the entire process if config setting fails + } + } } const installDependencies = async ( @@ -312,10 +376,31 @@ const installDependencies = async ( .map(([pkg, version]) => `${pkg}@${version}`) .join(' ') + flag; + const isWorkspaceEnv = isInWorkspace(); + const yarnVersion = versionManager === 'yarn' ? detectYarnVersion() : null; + + // Determine workspace flags + let workspaceFlag = ''; + if (isWorkspaceEnv) { + switch (versionManager) { + case 'npm': + workspaceFlag = ' -w .'; // Install in current workspace + break; + case 'yarn': + if (yarnVersion === 'berry') { + workspaceFlag = ''; // Yarn berry handles workspaces differently + } + break; + case 'pnpm': + workspaceFlag = ' -w'; // Install in workspace root + break; + } + } + const commands: { [key: string]: { install: string; devFlag: string } } = { - npm: { install: 'npm install', devFlag: ' --save-dev' }, + npm: { install: `npm install${workspaceFlag}`, devFlag: ' --save-dev' }, yarn: { install: 'yarn add', devFlag: ' --dev' }, - pnpm: { install: 'pnpm i', devFlag: ' -D' }, + pnpm: { install: `pnpm i${workspaceFlag}`, devFlag: ' -D' }, bun: { install: 'bun add', devFlag: ' --dev' }, }; const { install, devFlag } = commands[versionManager]; @@ -533,4 +618,6 @@ export { ensureFilesPromise, getPackageMangerFlag, checkComponentDependencies, + detectYarnVersion, + isInWorkspace, }; diff --git a/packages/gluestack-cli/src/util/init/index.ts b/packages/gluestack-cli/src/util/init/index.ts index c1a20116..71d989a1 100644 --- a/packages/gluestack-cli/src/util/init/index.ts +++ b/packages/gluestack-cli/src/util/init/index.ts @@ -16,6 +16,8 @@ import { findLockFileType, installDependencies, promptVersionManager, + isInWorkspace, + detectYarnVersion, } from '..'; import { getProjectBasedDependencies } from '../../dependencies'; import { generateConfigNextApp } from '../config/next-config-helper'; @@ -97,7 +99,16 @@ const InitializeGlueStack = async ({ await addProvider(isNextjs15); if (isNextjs15) { - execSync(`${versionManager} run postinstall`); + // Handle workspace-aware postinstall command + const isWorkspaceEnv = isInWorkspace(); + const yarnVersion = versionManager === 'yarn' ? detectYarnVersion() : null; + + let postinstallCommand = `${versionManager} run postinstall`; + if (isWorkspaceEnv && versionManager === 'npm') { + postinstallCommand = 'npm run postinstall -w .'; + } + + execSync(postinstallCommand); } s.stop(`\x1b[32mProject configuration generated.\x1b[0m`); log.step(