diff --git a/contrib/switch-versions.mjs b/contrib/switch-versions.mjs new file mode 100755 index 0000000..cb76cee --- /dev/null +++ b/contrib/switch-versions.mjs @@ -0,0 +1,180 @@ +#!/usr/bin/env node + +/** + * Switch between local and published versions of @wizardconnect packages + * + * Usage: + * node /path/to/wizardconnect/contrib/switch-versions.js [local|published] + * + * This script modifies package.json in the current working directory to switch + * between local file: references and published npm versions of @wizardconnect packages. + */ + +import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Get the wizardconnect root directory (one level up from contrib) +const LIB_ROOT = resolve(__dirname, '..'); + +const PACKAGE_SCOPE = '@wizardconnect/'; + +// Dynamically discover packages from workspace configuration +function discoverPackages() { + const packages = {}; + + try { + const rootPackageJsonPath = resolve(LIB_ROOT, 'package.json'); + const rootPackageJson = JSON.parse(readFileSync(rootPackageJsonPath, 'utf8')); + + if (rootPackageJson.workspaces) { + for (const workspace of rootPackageJson.workspaces) { + const workspacePattern = typeof workspace === 'string' ? workspace : workspace.package; + const baseDir = workspacePattern.split('*')[0].replace(/\/$/, ''); + const baseDirPath = resolve(LIB_ROOT, baseDir); + + if (existsSync(baseDirPath)) { + const entries = readdirSync(baseDirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + const workspacePath = `${baseDir}/${entry.name}`; + const packageJsonPath = resolve(LIB_ROOT, workspacePath, 'package.json'); + + if (existsSync(packageJsonPath)) { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + if (packageJson.name && packageJson.name.startsWith(PACKAGE_SCOPE)) { + packages[packageJson.name] = workspacePath; + } + } + } + } + } + } + } + } catch (error) { + console.error('Error: Could not read workspace configuration:', error.message); + process.exit(1); + } + + return packages; +} + +function getLatestPublishedVersions(packages) { + const versions = {}; + + for (const packageName of Object.keys(packages)) { + try { + console.log(`Querying npm for latest version of ${packageName}...`); + const result = execSync(`npm view ${packageName} version`, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + const latestVersion = result.trim(); + versions[packageName] = latestVersion; + console.log(` Found latest version: ${packageName}@${latestVersion}`); + } catch (error) { + console.warn(` Could not get latest version for ${packageName}: ${error.message}`); + // Fall back to local version if npm query fails + const packagePath = packages[packageName]; + const packageJsonPath = resolve(LIB_ROOT, packagePath, 'package.json'); + if (existsSync(packageJsonPath)) { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + versions[packageName] = packageJson.version; + console.log(` Using local version: ${packageName}@${packageJson.version}`); + } + } + } + + return versions; +} + +function switchDeps(deps, packages, mode, versions) { + let changed = false; + if (!deps) return changed; + + for (const [packageName, localPath] of Object.entries(packages)) { + if (!deps[packageName]) continue; + + if (mode === 'local') { + const absolutePath = resolve(LIB_ROOT, localPath); + deps[packageName] = `file:${absolutePath}`; + changed = true; + console.log(` ${packageName} -> file:${absolutePath}`); + } else { + const currentValue = deps[packageName]; + const newValue = `^${versions[packageName]}`; + if (currentValue !== newValue) { + deps[packageName] = newValue; + changed = true; + console.log(` ${packageName} -> ${newValue}`); + } + } + } + + return changed; +} + +function main() { + const args = process.argv.slice(2); + const mode = args[0]; + + if (!mode || !['local', 'published'].includes(mode)) { + console.error('Usage: node switch-versions.js [local|published]'); + console.error(''); + console.error(' local - Switch to local file: references'); + console.error(' published - Switch to latest published npm versions'); + process.exit(1); + } + + const packageJsonPath = resolve(process.cwd(), 'package.json'); + if (!existsSync(packageJsonPath)) { + console.error('Error: No package.json found in current directory'); + console.error('Please run this script from a project directory that contains package.json'); + process.exit(1); + } + + try { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + const packages = discoverPackages(); + + if (Object.keys(packages).length === 0) { + console.error('Error: No @wizardconnect packages found in workspace'); + process.exit(1); + } + + console.log(`Found packages: ${Object.keys(packages).join(', ')}`); + console.log(''); + + let versions = {}; + if (mode === 'published') { + versions = getLatestPublishedVersions(packages); + console.log(''); + } + + console.log(`Switching to ${mode} versions:`); + const changed = + switchDeps(packageJson.dependencies, packages, mode, versions) | + switchDeps(packageJson.devDependencies, packages, mode, versions); + + if (!changed) { + console.log('No @wizardconnect packages found to switch'); + return; + } + + writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); + + console.log(''); + console.log(`Successfully switched to ${mode} versions`); + console.log('Remember to run "npm install" to update your node_modules'); + + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +} + +main();