File size: 2,496 Bytes
b152fd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env bash
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"