UNPKG

decocms

Version:

CLI for managing deco.chat apps & projects

242 lines 10.3 kB
import { readFile, writeFile } from "fs/promises"; import { resolve } from "path"; import { existsSync } from "fs"; import { glob } from "glob"; import inquirer from "inquirer"; import chalk from "chalk"; import process from "node:process"; // Hardcoded Deco dependencies to manage const DECO_DEPENDENCIES = ["@deco/workers-runtime"]; const DECO_DEV_DEPENDENCIES = ["deco-cli"]; const getLatestVersion = async (packageName) => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { // Handle JSR packages - check JSR registry first if (packageName === "@deco/workers-runtime") { const response = await fetch("https://jsr.io/@deco/workers-runtime/meta.json", { signal: controller.signal, }); clearTimeout(timeoutId); if (response.ok) { const data = (await response.json()); return data.latest; } } // Fallback to npm registry const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, { signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`Failed to fetch latest version: ${response.statusText}`); } const data = (await response.json()); return data.version; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error && error.name === "AbortError") { throw new Error("Request timed out while checking for updates"); } throw error; } }; const parseCurrentVersion = (versionString) => { // Handle JSR format: "npm:@jsr/deco__workers-runtime@0.6.3" if (versionString.startsWith("npm:@jsr/")) { const match = versionString.match(/@([^@]+)$/); return match ? match[1] : versionString; } // Handle regular semver versions return versionString.replace(/^[\^~]/, ""); }; const formatVersionForPackageJson = (packageName, version) => { if (packageName === "@deco/workers-runtime") { return `npm:@jsr/deco__workers-runtime@${version}`; } return `^${version}`; }; const discoverWorkspaces = async (rootPath) => { const rootPackageJsonPath = resolve(rootPath, "package.json"); if (!existsSync(rootPackageJsonPath)) { throw new Error("No package.json found in the current directory"); } const rootPackageJson = JSON.parse(await readFile(rootPackageJsonPath, "utf-8")); if (!rootPackageJson.workspaces) { // Not a workspace, return just the root package.json return [rootPackageJsonPath]; } const packageJsonPaths = [rootPackageJsonPath]; // Find all package.json files in workspace directories for (const workspacePattern of rootPackageJson.workspaces) { const workspaceDirs = await glob(workspacePattern, { cwd: rootPath, }); for (const dir of workspaceDirs) { const packageJsonPath = resolve(rootPath, dir, "package.json"); if (existsSync(packageJsonPath)) { packageJsonPaths.push(packageJsonPath); } } } return packageJsonPaths; }; const findPackageJsons = async (cwd) => { return await discoverWorkspaces(cwd); }; const checkForUpdates = async (packageJsonPath) => { const packageJsonContent = await readFile(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageJsonContent); const updates = []; // Check regular dependencies if (packageJson.dependencies) { for (const depName of DECO_DEPENDENCIES) { const currentVersionString = packageJson.dependencies[depName]; if (currentVersionString) { try { const currentVersion = parseCurrentVersion(currentVersionString); const latestVersion = await getLatestVersion(depName); if (currentVersion !== latestVersion) { updates.push({ name: depName, currentVersion, latestVersion, isDev: false, packagePath: packageJsonPath, }); } } catch (error) { console.warn(chalk.yellow(`⚠️ Failed to check updates for ${depName}: ${error instanceof Error ? error.message : String(error)}`)); } } } } // Check dev dependencies if (packageJson.devDependencies) { for (const depName of DECO_DEV_DEPENDENCIES) { const currentVersionString = packageJson.devDependencies[depName]; if (currentVersionString) { try { const currentVersion = parseCurrentVersion(currentVersionString); const latestVersion = await getLatestVersion(depName); if (currentVersion !== latestVersion) { updates.push({ name: depName, currentVersion, latestVersion, isDev: true, packagePath: packageJsonPath, }); } } catch (error) { console.warn(chalk.yellow(`⚠️ Failed to check updates for ${depName}: ${error instanceof Error ? error.message : String(error)}`)); } } } } return updates; }; const checkAllPackagesForUpdates = async (packageJsonPaths) => { const allUpdates = []; for (const packageJsonPath of packageJsonPaths) { try { const updates = await checkForUpdates(packageJsonPath); allUpdates.push(...updates); } catch (error) { console.warn(chalk.yellow(`⚠️ Failed to check updates for ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`)); } } return allUpdates; }; const applyUpdates = async (updates) => { // Group updates by package path const updatesByPackage = new Map(); for (const update of updates) { if (!updatesByPackage.has(update.packagePath)) { updatesByPackage.set(update.packagePath, []); } updatesByPackage.get(update.packagePath).push(update); } // Apply updates to each package for (const [packageJsonPath, packageUpdates] of updatesByPackage) { const packageJsonContent = await readFile(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageJsonContent); for (const update of packageUpdates) { const newVersionString = formatVersionForPackageJson(update.name, update.latestVersion); if (update.isDev && packageJson.devDependencies) { packageJson.devDependencies[update.name] = newVersionString; } else if (!update.isDev && packageJson.dependencies) { packageJson.dependencies[update.name] = newVersionString; } const relativePath = packageJsonPath.replace(process.cwd(), "."); console.log(chalk.green(`✅ Updated ${update.name} in ${relativePath}: ${update.currentVersion}${update.latestVersion}`)); } await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n"); } }; export async function updateCommand(options) { try { const cwd = process.cwd(); console.log(chalk.blue("🔍 Searching for Deco dependencies to update...")); // Find all package.json files (workspace-aware) const packageJsonPaths = await findPackageJsons(cwd); if (packageJsonPaths.length === 1) { console.log(chalk.gray(`Found package.json at: ${packageJsonPaths[0]}`)); } else { console.log(chalk.gray(`Found ${packageJsonPaths.length} package.json files in workspace:`)); for (const path of packageJsonPaths) { const relativePath = path.replace(cwd, "."); console.log(chalk.gray(` - ${relativePath}`)); } } // Check for updates across all packages const updates = await checkAllPackagesForUpdates(packageJsonPaths); if (updates.length === 0) { console.log(chalk.green("✅ All Deco dependencies are up to date!")); return; } // Display available updates console.log(); console.log(chalk.yellow("📦 Available updates:")); for (const update of updates) { const depType = update.isDev ? "(dev)" : ""; const relativePath = update.packagePath.replace(cwd, "."); console.log(chalk.blue(` ${update.name} ${depType} in ${relativePath}: ${update.currentVersion}${update.latestVersion}`)); } console.log(); // Confirm updates (unless -y flag is used) let confirmed = options.yes || false; if (!confirmed) { const response = await inquirer.prompt([ { type: "confirm", name: "confirmed", message: `Update ${updates.length} Deco ${updates.length === 1 ? "dependency" : "dependencies"}?`, default: true, }, ]); confirmed = response.confirmed; } if (!confirmed) { console.log(chalk.gray("Update cancelled.")); return; } // Apply updates console.log(chalk.yellow("🔄 Updating dependencies...")); await applyUpdates(updates); console.log(); console.log(chalk.green("🎉 Dependencies updated successfully!")); console.log(chalk.blue("💡 Don't forget to run your package manager to install the new versions.")); } catch (error) { console.error(chalk.red("❌ Failed to update dependencies:"), error instanceof Error ? error.message : String(error)); process.exit(1); } } //# sourceMappingURL=update.js.map