diff --git a/internals/ci/src/cmds/version/index.ts b/internals/ci/src/cmds/version/index.ts index 2916fc922..b88156169 100644 --- a/internals/ci/src/cmds/version/index.ts +++ b/internals/ci/src/cmds/version/index.ts @@ -8,8 +8,6 @@ import { doBuildPkgs } from "@oko-wallet-ci/cmds/build_pkgs"; import { expectSuccess } from "@oko-wallet-ci/expect"; import { doBuildSDK } from "@oko-wallet-ci/cmds/build_sdk"; -const WILD_CHARACTER_VERSION = "workspace:*"; - function getPackageJsonPaths(): string[] { const lernaJsonPath = path.join(paths.root, "lerna.json"); const lernaJson = JSON.parse(fs.readFileSync(lernaJsonPath, "utf-8")); @@ -18,107 +16,181 @@ function getPackageJsonPaths(): string[] { return packages.map((pkg) => path.join(paths.root, pkg, "package.json")); } -interface WorkspaceDepInfo { - filePath: string; - depName: string; - depVersion: string; -} +function buildWorkspaceVersionMap(): Map { + const versionMap = new Map(); + const packageJsonPaths = getPackageJsonPaths(); -interface WorkspaceDep { - name: string; - version: string; + for (const pkgPath of packageJsonPaths) { + try { + const content = fs.readFileSync(pkgPath, "utf-8"); + const pkg = JSON.parse(content); + if (pkg.name && pkg.version) { + versionMap.set(pkg.name, pkg.version); + } + } catch (err) { + console.warn(" Failed to read %s: %s", pkgPath, err); + } + } + + return versionMap; } -function findWorkspaceDeps(pkg: Record): WorkspaceDep[] { +function replaceWorkspaceVersions() { + console.log("Replacing workspace:* with actual versions..."); + + const versionMap = buildWorkspaceVersionMap(); + const packageJsonPaths = getPackageJsonPaths(); const depFields = [ "dependencies", "devDependencies", "peerDependencies", "optionalDependencies", ]; - const found: { name: string; version: string }[] = []; - - for (const field of depFields) { - const deps = pkg[field] as Record; - - if (deps && typeof deps === "object") { - for (const [name, version] of Object.entries(deps)) { - if ( - typeof version === "string" && - version.startsWith(WILD_CHARACTER_VERSION) - ) { - found.push({ name, version }); - } - } - } - } - - return found; -} - -function checkWorkspaceVersions() { - console.log('Checking for "workspace:" versions in publishable pkgs'); - const packageJsonFiles = getPackageJsonPaths(); - const issues: WorkspaceDepInfo[] = []; + let replacedCount = 0; - for (const filePath of packageJsonFiles) { + for (const pkgPath of packageJsonPaths) { try { - const content = fs.readFileSync(filePath, "utf-8"); + const content = fs.readFileSync(pkgPath, "utf-8"); const pkg = JSON.parse(content); + // Skip private packages if (pkg.private === true) { continue; } - const workspaceDeps = findWorkspaceDeps(pkg); - for (const dep of workspaceDeps) { - issues.push({ filePath, depName: dep.name, depVersion: dep.version }); + let modified = false; + + for (const field of depFields) { + const deps = pkg[field]; + if (!deps || typeof deps !== "object") { + continue; + } + + for (const [depName, depVersion] of Object.entries(deps)) { + if ( + typeof depVersion === "string" && + depVersion.startsWith("workspace:") + ) { + const actualVersion = versionMap.get(depName); + if (actualVersion) { + // workspace:* → ^X.Y.Z, workspace:^ → ^X.Y.Z, workspace:~ → ~X.Y.Z + let prefix = "^"; + if (depVersion === "workspace:~") { + prefix = "~"; + } else if (depVersion === "workspace:*") { + prefix = "^"; + } else if (depVersion.startsWith("workspace:^")) { + prefix = "^"; + } + deps[depName] = `${prefix}${actualVersion}`; + modified = true; + replacedCount += 1; + } + } + } + } + + if (modified) { + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); + console.log(" Updated: %s", pkgPath); } } catch (err) { - console.warn( - "%s failed to read %s, err: %s", - chalk.bold.red("error"), - filePath, - err instanceof Error ? err.message : err, - ); - - process.exit(1); + console.warn(" Failed to process %s: %s", pkgPath, err); } } - if (issues.length > 0) { - console.error( - `%s Found "workspace:" versions in publishable packages`, - chalk.bold.red("error"), - ); + console.log( + "%s Replaced %d workspace:* references", + chalk.green.bold("Done"), + replacedCount, + ); +} + +function getChangedPackages( + beforeMap: Map, + afterMap: Map, +): Array<{ name: string; version: string }> { + const changed: Array<{ name: string; version: string }> = []; - for (const issue of issues) { - console.error( - ` ${issue.filePath}: ${issue.depName} -> ${issue.depVersion}`, - ); + for (const [name, version] of afterMap) { + const beforeVersion = beforeMap.get(name); + if (beforeVersion !== version) { + changed.push({ name, version }); } + } - console.error( - `Please replace wildcard versions ("%s") with actual version numbers -beforeversioning.`, - WILD_CHARACTER_VERSION, - ); + return changed; +} + +function createGitCommitAndTags( + changedPackages: Array<{ name: string; version: string }>, +) { + console.log("Creating git commit and tags..."); - process.exit(1); + if (changedPackages.length === 0) { + console.log("No packages were changed, skipping commit and tags"); + return; + } + + // Stage all changes + const addRet = spawnSync("git", ["add", "."], { + cwd: paths.root, + stdio: "inherit", + }); + expectSuccess(addRet, "git add failed"); + + // Build commit message with version info + const versionList = changedPackages + .map((pkg) => `- ${pkg.name}@${pkg.version}`) + .join("\n"); + const commitMessage = `chore: publish + +${versionList}`; + + // Create commit + const commitRet = spawnSync("git", ["commit", "-m", commitMessage], { + cwd: paths.root, + stdio: "inherit", + }); + expectSuccess(commitRet, "git commit failed"); + + // Create tags only for changed packages + for (const pkg of changedPackages) { + const tag = `${pkg.name}@${pkg.version}`; + console.log(" Creating tag: %s", tag); + const tagRet = spawnSync("git", ["tag", "-a", tag, "-m", tag], { + cwd: paths.root, + stdio: "inherit", + }); + if (tagRet.status !== 0) { + console.warn(" Tag %s already exists, skipping", tag); + } } console.log( - `%s No "workspace:" versions found in publishable packages`, - chalk.bold.green("Done"), + "%s Created commit and %d tags", + chalk.green.bold("Done"), + changedPackages.length, + ); + + // Push commit and tags to remote + console.log("Pushing commit and tags to origin..."); + const pushRet = spawnSync( + "git", + ["push", "-u", "origin", "HEAD", "--follow-tags"], + { + cwd: paths.root, + stdio: "inherit", + }, ); + expectSuccess(pushRet, "git push failed"); + console.log("%s Pushed to origin", chalk.green.bold("Done")); } export async function version(..._args: any[]) { console.log("Start versioning packages"); - checkWorkspaceVersions(); - console.log("We will re-build the packages now just to make sure\n"); await doBuildPkgs(); @@ -139,8 +211,35 @@ export async function version(..._args: any[]) { }); expectSuccess(fetchRet, "publish failed"); - spawnSync("yarn", ["lerna", "version", "--no-private"], { - cwd: paths.root, - stdio: "inherit", - }); + // Save version map before lerna version + const beforeVersionMap = buildWorkspaceVersionMap(); + + spawnSync( + "yarn", + ["lerna", "version", "--no-private", "--no-git-tag-version"], + { + cwd: paths.root, + stdio: "inherit", + }, + ); + + // Get version map after lerna version and find changed packages + const afterVersionMap = buildWorkspaceVersionMap(); + const changedPackages = getChangedPackages(beforeVersionMap, afterVersionMap); + + try { + replaceWorkspaceVersions(); + } catch (err) { + console.error( + "%s replaceWorkspaceVersions failed, rolling back...", + chalk.bold.red("Error"), + ); + spawnSync("git", ["checkout", "--", "."], { + cwd: paths.root, + stdio: "inherit", + }); + throw err; + } + + createGitCommitAndTags(changedPackages); }