Spaces:
Paused
Paused
| set -euo pipefail | |
| REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" | |
| dry_run=false | |
| version="" | |
| usage() { | |
| cat <<'EOF' | |
| Usage: | |
| ./scripts/rollback-latest.sh <stable-version> [--dry-run] | |
| Examples: | |
| ./scripts/rollback-latest.sh 2026.318.0 | |
| ./scripts/rollback-latest.sh 2026.318.0 --dry-run | |
| Notes: | |
| - This repoints the npm dist-tag "latest" for every public package. | |
| - It does not unpublish anything. | |
| EOF | |
| } | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --dry-run) dry_run=true ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| if [ -n "$version" ]; then | |
| echo "Error: only one version may be provided." >&2 | |
| exit 1 | |
| fi | |
| version="$1" | |
| ;; | |
| esac | |
| shift | |
| done | |
| if [ -z "$version" ]; then | |
| usage | |
| exit 1 | |
| fi | |
| if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "Error: version must be a stable calendar version like 2026.318.0." >&2 | |
| exit 1 | |
| fi | |
| if [ "$dry_run" = false ] && ! npm whoami >/dev/null 2>&1; then | |
| echo "Error: npm publish rights are required. Run 'npm login' first." >&2 | |
| exit 1 | |
| fi | |
| list_public_package_names() { | |
| node - "$REPO_ROOT" <<'NODE' | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const root = process.argv[2]; | |
| const roots = ['packages', 'server', 'ui', 'cli']; | |
| const seen = new Set(); | |
| function walk(relDir) { | |
| const absDir = path.join(root, relDir); | |
| const pkgPath = path.join(absDir, 'package.json'); | |
| if (fs.existsSync(pkgPath)) { | |
| const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); | |
| if (!pkg.private && !seen.has(pkg.name)) { | |
| seen.add(pkg.name); | |
| process.stdout.write(`${pkg.name}\n`); | |
| } | |
| return; | |
| } | |
| if (!fs.existsSync(absDir)) { | |
| return; | |
| } | |
| for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) { | |
| if (!entry.isDirectory()) continue; | |
| if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') continue; | |
| walk(path.join(relDir, entry.name)); | |
| } | |
| } | |
| for (const rel of roots) { | |
| walk(rel); | |
| } | |
| NODE | |
| } | |
| package_names="$(list_public_package_names)" | |
| if [ -z "$package_names" ]; then | |
| echo "Error: no public packages were found in the workspace." >&2 | |
| exit 1 | |
| fi | |
| while IFS= read -r package_name; do | |
| [ -z "$package_name" ] && continue | |
| if [ "$dry_run" = true ]; then | |
| echo "[dry-run] npm dist-tag add ${package_name}@${version} latest" | |
| else | |
| npm dist-tag add "${package_name}@${version}" latest | |
| echo "Updated latest -> ${package_name}@${version}" | |
| fi | |
| done <<< "$package_names" | |