chore: merge publish-all.sh into publish.sh
- Use bun pm pack for workspace:* resolution (no manual replace/restore) - Topological sort replaces hardcoded PUBLISH_ORDER - Registry unified to uncaged org - Delete scripts/publish-all.sh - Add --dry-run flag support Closes #237
This commit is contained in:
@@ -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"
|
|
||||||
+117
-55
@@ -1,19 +1,32 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# publish.sh — Bump version & publish all @uncaged/workflow-* packages
|
# publish.sh — Bump version, build, test & publish all @uncaged/workflow-* packages
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./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 0.5.0 # explicit version
|
||||||
|
# ./scripts/publish.sh patch --dry-run # preview without publishing
|
||||||
#
|
#
|
||||||
# Env (via `cfg` or export):
|
# Env (via `cfg` or export):
|
||||||
# GITEA_TOKEN — Gitea npm registry auth
|
# GITEA_TOKEN — Gitea npm registry auth (used by .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=""
|
||||||
|
VERSION_ARG=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN="--dry-run" ;;
|
||||||
|
*) VERSION_ARG="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[[ -z "$VERSION_ARG" ]] && { echo "Usage: publish.sh <version|patch|minor|major> [--dry-run]"; exit 1; }
|
||||||
|
|
||||||
# ─── Version ─────────────────────────────────────────────────────────────────
|
# ─── Version ─────────────────────────────────────────────────────────────────
|
||||||
current_version() {
|
current_version() {
|
||||||
@@ -32,10 +45,11 @@ bump_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CURRENT=$(current_version)
|
CURRENT=$(current_version)
|
||||||
VERSION=$(bump_version "$CURRENT" "${1:?Usage: publish.sh <version|patch|minor|major>}")
|
VERSION=$(bump_version "$CURRENT" "$VERSION_ARG")
|
||||||
echo "📦 Publish: $CURRENT → $VERSION"
|
echo "📦 Publish: $CURRENT → $VERSION"
|
||||||
|
[[ -n "$DRY_RUN" ]] && echo "🔍 Dry run mode — no packages will be published"
|
||||||
|
|
||||||
# ─── Bump version ────────────────────────────────────────────────────────────
|
# ─── Bump version in all public packages ─────────────────────────────────────
|
||||||
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,65 +64,113 @@ for dir in packages/*/; do
|
|||||||
"
|
"
|
||||||
done
|
done
|
||||||
|
|
||||||
# ─── Replace workspace:* ─────────────────────────────────────────────────────
|
|
||||||
echo "🔗 Replacing workspace:* → $VERSION..."
|
|
||||||
for dir in packages/*/; do
|
|
||||||
pkg="$dir/package.json"
|
|
||||||
[[ -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 ───────────────────────────────────────────────────────────────────
|
# ─── Build ───────────────────────────────────────────────────────────────────
|
||||||
echo "🔨 Building..."
|
echo "🔨 Building..."
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# ─── Self-test ────────────────────────────────────────────────────────────────
|
# ─── Self-test ───────────────────────────────────────────────────────────────
|
||||||
echo "🧪 Running tests..."
|
echo "🧪 Running tests..."
|
||||||
if ! bun test; then
|
if ! bun test; then
|
||||||
echo "❌ Tests failed — aborting publish"
|
echo "❌ Tests failed — aborting publish"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── Publish (delegate to publish-all.sh) ────────────────────────────────────
|
# ─── Topological sort of public packages ─────────────────────────────────────
|
||||||
echo "🚀 Publishing via publish-all.sh..."
|
echo "📐 Resolving publish order..."
|
||||||
"$REPO_ROOT/scripts/publish-all.sh"
|
ORDERED=$(python3 -c "
|
||||||
|
import json, os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# ─── Restore workspace:* ─────────────────────────────────────────────────────
|
pkgs_dir = Path('$REPO_ROOT/packages')
|
||||||
echo "🔄 Restoring workspace:*..."
|
name_to_dir = {}
|
||||||
for dir in packages/*/; do
|
deps_graph = {}
|
||||||
pkg="$dir/package.json"
|
|
||||||
[[ -f "$pkg" ]] || continue
|
for d in sorted(pkgs_dir.iterdir()):
|
||||||
node -e "
|
pj = d / 'package.json'
|
||||||
const fs = require('fs');
|
if not pj.exists():
|
||||||
const p = JSON.parse(fs.readFileSync('$pkg','utf8'));
|
continue
|
||||||
let c = false;
|
data = json.loads(pj.read_text())
|
||||||
for (const k of ['dependencies','peerDependencies','devDependencies']) {
|
name = data.get('name', '')
|
||||||
if (!p[k]) continue;
|
if not name.startswith('@uncaged/'):
|
||||||
for (const [n, v] of Object.entries(p[k])) {
|
continue
|
||||||
if (n.startsWith('@uncaged/') && v === '$VERSION') { p[k][n] = 'workspace:*'; c = true; }
|
if data.get('private'):
|
||||||
}
|
continue
|
||||||
}
|
name_to_dir[name] = d.name
|
||||||
if (c) fs.writeFileSync('$pkg', JSON.stringify(p, null, 2) + '\n');
|
local_deps = set()
|
||||||
"
|
for section in ('dependencies', 'devDependencies', 'peerDependencies'):
|
||||||
done
|
for dep, ver in data.get(section, {}).items():
|
||||||
|
if dep.startswith('@uncaged/') and ver == 'workspace:*':
|
||||||
|
local_deps.add(dep)
|
||||||
|
deps_graph[name] = local_deps
|
||||||
|
|
||||||
|
# Kahn's algorithm — deps-first order
|
||||||
|
in_degree = {n: len([d for d in ds if d in deps_graph]) for n, ds in deps_graph.items()}
|
||||||
|
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()
|
||||||
|
|
||||||
|
for name in result:
|
||||||
|
print(name_to_dir[name])
|
||||||
|
")
|
||||||
|
|
||||||
|
# ─── Regenerate lockfile for correct workspace:* resolution ──────────────────
|
||||||
|
rm -f bun.lock
|
||||||
|
bun install
|
||||||
|
|
||||||
|
# ─── Publish via bun pm pack + npm publish ───────────────────────────────────
|
||||||
|
echo "🚀 Publishing..."
|
||||||
|
ok=0
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
while IFS= read -r pkg; do
|
||||||
|
dir="$REPO_ROOT/packages/$pkg"
|
||||||
|
name=$(node -e "console.log(require('./$dir/package.json').name)")
|
||||||
|
|
||||||
|
cd "$dir"
|
||||||
|
|
||||||
|
# bun pm pack resolves workspace:* → actual versions in the tarball
|
||||||
|
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@$VERSION"
|
||||||
|
((ok++)) || true
|
||||||
|
else
|
||||||
|
echo "⚠️ $name (may already exist at this version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$tgz"
|
||||||
|
done <<< "$ORDERED"
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Published: $ok Skipped/Failed: $fail"
|
||||||
|
|
||||||
|
# ─── Restore workspace:* (bun pack doesn't modify source, but version bump did) ─
|
||||||
|
echo "🔄 Restoring lockfile..."
|
||||||
|
rm -f bun.lock
|
||||||
|
bun install
|
||||||
|
|
||||||
# ─── Commit ──────────────────────────────────────────────────────────────────
|
# ─── Commit ──────────────────────────────────────────────────────────────────
|
||||||
echo "📝 Committing..."
|
if [[ -z "$DRY_RUN" ]]; then
|
||||||
git add -A
|
echo "📝 Committing..."
|
||||||
git commit -m "chore: publish v${VERSION}
|
git add -A
|
||||||
|
git commit -m "chore: publish v${VERSION}"
|
||||||
小橘 <xiaoju@shazhou.work>"
|
git push
|
||||||
git push
|
fi
|
||||||
|
|
||||||
echo "✅ v${VERSION} published"
|
echo "✅ v${VERSION} published"
|
||||||
|
|||||||
Reference in New Issue
Block a user