Merge pull request 'chore: merge publish-all.sh into publish.sh' (#238) from chore/merge-publish-scripts into main

This commit is contained in:
2026-05-13 09:52:43 +00:00
3 changed files with 103 additions and 189 deletions
+2 -2
View File
@@ -13,8 +13,8 @@
"link": "./scripts/link-all.sh", "link": "./scripts/link-all.sh",
"link:consume": "./scripts/link-all.sh --consume", "link:consume": "./scripts/link-all.sh --consume",
"link:unlink": "./scripts/link-all.sh --unlink", "link:unlink": "./scripts/link-all.sh --unlink",
"publish:gitea": "./scripts/publish-all.sh", "publish:gitea": "./scripts/publish.sh patch",
"publish:gitea:dry": "./scripts/publish-all.sh --dry-run" "publish:gitea:dry": "./scripts/publish.sh --dry-run patch"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.14", "@biomejs/biome": "^2.4.14",
-137
View File
@@ -1,137 +0,0 @@
#!/usr/bin/env bash
# Publish all public @uncaged/* packages to Gitea npm registry.
#
# PITFALL: After bumping versions in package.json, bun pm pack still reads the
# old bun.lock and resolves workspace:* to the previous (stale) versions.
# This script deletes bun.lock and runs bun install before packing to force
# correct resolution of workspace:* dependencies.
#
# Usage:
# ./scripts/publish-all.sh # Publish all packages
# ./scripts/publish-all.sh --dry-run # Show what would be published
#
# Package order is auto-resolved via topological sort of workspace:* dependencies.
#
# Prerequisites:
# - .npmrc in monorepo root with Gitea auth token
# - bun (for packing with workspace:* resolution)
# - npm (for publishing tarballs)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
MONOREPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REGISTRY="https://git.shazhou.work/api/packages/uncaged/npm/"
DRY_RUN=""
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN="--dry-run"
echo "🔍 Dry run mode — no packages will be published"
echo
fi
# Topological sort: read all package.json files, build dependency graph, emit leaf-first order
ORDERED=$(python3 -c "
import json, os, sys
from pathlib import Path
pkgs_dir = Path('$MONOREPO_ROOT/packages')
# name -> dir_name, and dependency edges
name_to_dir = {}
deps_graph = {} # name -> set of @uncaged/* dependency names
for d in sorted(pkgs_dir.iterdir()):
pj = d / 'package.json'
if not pj.exists():
continue
data = json.loads(pj.read_text())
name = data.get('name', '')
if not name.startswith('@uncaged/'):
continue
if data.get('private'):
continue
name_to_dir[name] = d.name
local_deps = set()
for section in ('dependencies', 'devDependencies', 'peerDependencies'):
for dep, ver in data.get(section, {}).items():
if dep.startswith('@uncaged/') and dep in name_to_dir or ver == 'workspace:*':
local_deps.add(dep)
deps_graph[name] = local_deps
# Kahn's algorithm
in_degree = {n: 0 for n in deps_graph}
for n, ds in deps_graph.items():
for d in ds:
if d in in_degree:
in_degree[d] = in_degree.get(d, 0) # ensure exists
# Recount
in_degree = {n: 0 for n in deps_graph}
for n, ds in deps_graph.items():
for d in ds:
if d in in_degree:
in_degree[d] += 1
# Wait, direction is wrong. If A depends on B, B must be published first.
# So edge is: A -> B means B must come before A.
# in_degree[A] = number of deps A has (that are in our set)
in_degree = {n: 0 for n in deps_graph}
for n, ds in deps_graph.items():
for d in ds:
if d in in_degree:
pass # d is a dependency of n
in_degree[n] = len([d for d in ds if d in deps_graph])
queue = [n for n, deg in in_degree.items() if deg == 0]
queue.sort() # stable order
result = []
while queue:
node = queue.pop(0)
result.append(node)
for n, ds in deps_graph.items():
if node in ds:
in_degree[n] -= 1
if in_degree[n] == 0:
queue.append(n)
queue.sort()
for name in result:
print(name_to_dir[name])
")
# Regenerate lockfile so bun pm pack resolves workspace:* to freshly-bumped versions
cd "$MONOREPO_ROOT"
rm -f bun.lock
bun install
ok=0
fail=0
while IFS= read -r pkg; do
dir="$MONOREPO_ROOT/packages/$pkg"
name=$(grep -m1 '"name"' "$dir/package.json" | sed 's/.*: *"\(.*\)".*/\1/')
cd "$dir"
# bun pm pack resolves workspace:* → actual versions
tgz=$(bun pm pack 2>&1 | grep '\.tgz' | grep -v packed | head -1 | tr -d ' ')
if [[ -z "$tgz" || ! -f "$tgz" ]]; then
echo "$name — pack failed"
((fail++)) || true
continue
fi
if npm publish "$tgz" --registry="$REGISTRY" $DRY_RUN 2>&1 | tail -1 | grep -q '+'; then
echo "$name"
((ok++)) || true
else
echo "⚠️ $name (may already exist at this version)"
fi
rm -f "$tgz"
done <<< "$ORDERED"
echo
echo "Published: $ok Skipped/Failed: $fail"
+98 -47
View File
@@ -1,19 +1,31 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# publish.sh — Bump version & publish all @uncaged/workflow-* packages # publish.sh — Bump version, build, test, topologically publish @uncaged/* to Gitea npm
# #
# Usage: # Usage:
# ./scripts/publish.sh 0.4.0 # explicit version # ./scripts/publish.sh 0.4.0 # explicit version
# ./scripts/publish.sh patch # 0.3.1 → 0.3.2 # ./scripts/publish.sh patch # 0.3.1 → 0.3.2
# ./scripts/publish.sh minor # 0.3.1 → 0.4.0 # ./scripts/publish.sh minor # 0.3.1 → 0.4.0
# ./scripts/publish.sh major # 0.3.1 → 1.0.0
# ./scripts/publish.sh --dry-run patch # dry-run bun publish only (no git commit/push)
# #
# Env (via `cfg` or export): # Env (via `cfg` or export):
# GITEA_TOKEN — Gitea npm registry auth # GITEA_TOKEN — Gitea npm registry auth (see root .npmrc)
set -euo pipefail set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT" cd "$REPO_ROOT"
GITEA_TOKEN="${GITEA_TOKEN:?GITEA_TOKEN is required}" # needed by .npmrc GITEA_TOKEN="${GITEA_TOKEN:?GITEA_TOKEN is required}"
REGISTRY="https://git.shazhou.work/api/packages/uncaged/npm/"
DRY_RUN=""
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN="--dry-run"
shift
echo "🔍 Dry run — bun publish will not upload; git commit/push skipped"
echo
fi
# ─── Version ───────────────────────────────────────────────────────────────── # ─── Version ─────────────────────────────────────────────────────────────────
current_version() { current_version() {
@@ -32,10 +44,10 @@ bump_version() {
} }
CURRENT=$(current_version) CURRENT=$(current_version)
VERSION=$(bump_version "$CURRENT" "${1:?Usage: publish.sh <version|patch|minor|major>}") VERSION=$(bump_version "$CURRENT" "${1:?Usage: publish.sh [--dry-run] <version|patch|minor|major>}")
echo "📦 Publish: $CURRENT$VERSION" echo "📦 Publish: $CURRENT$VERSION"
# ─── Bump version ──────────────────────────────────────────────────────────── # ─── Bump version ────────────────────────────────────────────────────────────
echo "🔢 Bumping versions..." echo "🔢 Bumping versions..."
for dir in packages/*/; do for dir in packages/*/; do
pkg="$dir/package.json" pkg="$dir/package.json"
@@ -50,28 +62,62 @@ for dir in packages/*/; do
" "
done done
# ─── Replace workspace:* ───────────────────────────────────────────────────── # ─── Topological publish order (workspace:* deps first) ───────────────────────
echo "🔗 Replacing workspace:* → $VERSION..." ORDERED=$(python3 -c "
for dir in packages/*/; do import json, sys
pkg="$dir/package.json" from pathlib import Path
[[ -f "$pkg" ]] || continue
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('$pkg','utf8'));
let c = false;
for (const k of ['dependencies','peerDependencies','devDependencies']) {
if (!p[k]) continue;
for (const [n, v] of Object.entries(p[k])) {
if (n.startsWith('@uncaged/') && v === 'workspace:*') { p[k][n] = '$VERSION'; c = true; }
}
}
if (c) fs.writeFileSync('$pkg', JSON.stringify(p, null, 2) + '\n');
"
done
# ─── Build ─────────────────────────────────────────────────────────────────── pkgs_dir = Path('$REPO_ROOT/packages')
name_to_dir = {}
for d in sorted(pkgs_dir.iterdir()):
pj = d / 'package.json'
if not pj.exists():
continue
data = json.loads(pj.read_text())
name = data.get('name', '')
if not name.startswith('@uncaged/') or data.get('private'):
continue
name_to_dir[name] = d.name
deps_graph = {}
for name, dirname in name_to_dir.items():
pj = pkgs_dir / dirname / 'package.json'
data = json.loads(pj.read_text())
local_deps = set()
for section in ('dependencies', 'devDependencies', 'peerDependencies'):
for dep, ver in data.get(section, {}).items():
if dep.startswith('@uncaged/') and dep in name_to_dir and ver == 'workspace:*':
local_deps.add(dep)
deps_graph[name] = local_deps
in_degree = {n: 0 for n in deps_graph}
for n, ds in deps_graph.items():
in_degree[n] = len(ds)
queue = sorted([n for n, deg in in_degree.items() if deg == 0])
result = []
while queue:
node = queue.pop(0)
result.append(node)
for n, ds in deps_graph.items():
if node in ds:
in_degree[n] -= 1
if in_degree[n] == 0:
queue.append(n)
queue.sort()
if len(result) != len(deps_graph):
missing = set(deps_graph) - set(result)
sys.stderr.write('publish: cyclic @uncaged/ workspace:* dependencies among: ' + ', '.join(sorted(missing)) + '\n')
sys.exit(1)
for name in result:
print(name_to_dir[name])
")
# ─── Build ────────────────────────────────────────────────────────────────────
echo "🔨 Building..." echo "🔨 Building..."
npm run build bun run build
# ─── Self-test ──────────────────────────────────────────────────────────────── # ─── Self-test ────────────────────────────────────────────────────────────────
echo "🧪 Running tests..." echo "🧪 Running tests..."
@@ -80,30 +126,35 @@ if ! bun test; then
exit 1 exit 1
fi fi
# ─── Publish (delegate to publish-all.sh) ──────────────────────────────────── # ─── Publish (bun resolves workspace:* for publish) ──────────────────────────
echo "🚀 Publishing via publish-all.sh..." echo "🚀 Publishing to $REGISTRY ..."
"$REPO_ROOT/scripts/publish-all.sh" ok=0
fail=0
# ─── Restore workspace:* ───────────────────────────────────────────────────── while IFS= read -r pkg; do
echo "🔄 Restoring workspace:*..." [[ -n "$pkg" ]] || continue
for dir in packages/*/; do dir="$REPO_ROOT/packages/$pkg"
pkg="$dir/package.json" name=$(node -e "console.log(require('$dir/package.json').name)")
[[ -f "$pkg" ]] || continue
node -e " if ( cd "$dir" && bun publish --registry="$REGISTRY" ${DRY_RUN:+"$DRY_RUN"} ); then
const fs = require('fs'); echo "$name"
const p = JSON.parse(fs.readFileSync('$pkg','utf8')); ok=$((ok + 1))
let c = false; else
for (const k of ['dependencies','peerDependencies','devDependencies']) { echo "⚠️ $name (publish failed or version may already exist)"
if (!p[k]) continue; fail=$((fail + 1))
for (const [n, v] of Object.entries(p[k])) { fi
if (n.startsWith('@uncaged/') && v === '$VERSION') { p[k][n] = 'workspace:*'; c = true; }
} done <<< "$ORDERED"
}
if (c) fs.writeFileSync('$pkg', JSON.stringify(p, null, 2) + '\n'); echo
" echo "Published: $ok Skipped/Failed: $fail"
done
# ─── Commit ───────────────────────────────────────────────────────────────────
if [[ -n "$DRY_RUN" ]]; then
echo "⏭️ Skipping git commit/push (dry run). Revert bumps with: git checkout -- packages/*/package.json"
exit 0
fi
# ─── Commit ──────────────────────────────────────────────────────────────────
echo "📝 Committing..." echo "📝 Committing..."
git add -A git add -A
git commit -m "chore: publish v${VERSION} git commit -m "chore: publish v${VERSION}