storkit: create 365_story_surface_api_rate_limit_warnings_in_chat
This commit is contained in:
238
script/release
238
script/release
@@ -1,238 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ── Configuration ──────────────────────────────────────────────
|
||||
GITEA_URL="https://code.crashlabs.io"
|
||||
REPO="dave/storkit"
|
||||
BINARY_NAME="storkit"
|
||||
|
||||
# ── Load .env if present ───────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
if [ -f "${SCRIPT_DIR}/.env" ]; then
|
||||
set -a
|
||||
source "${SCRIPT_DIR}/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
# ── Preflight ──────────────────────────────────────────────────
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
echo "Error: GITEA_TOKEN is not set."
|
||||
echo "Create a token at ${GITEA_URL}/user/settings/applications"
|
||||
echo "Then add to .env: GITEA_TOKEN=your_token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${1:-}"
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Usage: script/release <version>"
|
||||
echo "Example: script/release 0.2.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG="v${VERSION}"
|
||||
|
||||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
echo "Error: Tag ${TAG} already exists."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Bump version in Cargo.toml ────────────────────────────────
|
||||
CARGO_TOML="${SCRIPT_DIR}/server/Cargo.toml"
|
||||
if ! grep -q "^version = " "$CARGO_TOML"; then
|
||||
echo "Error: Could not find version field in ${CARGO_TOML}"
|
||||
exit 1
|
||||
fi
|
||||
sed -i '' "s/^version = \".*\"/version = \"${VERSION}\"/" "$CARGO_TOML"
|
||||
echo "==> Bumped ${CARGO_TOML} to ${VERSION}"
|
||||
|
||||
PACKAGE_JSON="${SCRIPT_DIR}/frontend/package.json"
|
||||
sed -i '' "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" "$PACKAGE_JSON"
|
||||
echo "==> Bumped ${PACKAGE_JSON} to ${VERSION}"
|
||||
|
||||
# Regenerate lock files so they stay in sync with the version bump.
|
||||
CARGO_LOCK="${SCRIPT_DIR}/Cargo.lock"
|
||||
(cd "${SCRIPT_DIR}/server" && cargo generate-lockfile)
|
||||
echo "==> Regenerated Cargo.lock"
|
||||
|
||||
PACKAGE_LOCK="${SCRIPT_DIR}/frontend/package-lock.json"
|
||||
(cd "${SCRIPT_DIR}/frontend" && npm install --package-lock-only --ignore-scripts --silent 2>/dev/null)
|
||||
echo "==> Regenerated package-lock.json"
|
||||
|
||||
git add "$CARGO_TOML" "$CARGO_LOCK" "$PACKAGE_JSON" "$PACKAGE_LOCK"
|
||||
git commit -m "Bump version to ${VERSION}"
|
||||
|
||||
if ! command -v cross >/dev/null 2>&1; then
|
||||
echo "Error: 'cross' is not installed. Run: cargo install cross"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "Error: Docker is not running. Start Docker Desktop first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Releasing ${TAG}"
|
||||
|
||||
# ── Build ──────────────────────────────────────────────────────
|
||||
echo "==> Building macOS (native)..."
|
||||
cargo build --release
|
||||
|
||||
echo "==> Building Linux (static musl via cross)..."
|
||||
cross build --release --target x86_64-unknown-linux-musl
|
||||
|
||||
# ── Package ────────────────────────────────────────────────────
|
||||
DIST="target/dist"
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"
|
||||
|
||||
cp "target/release/${BINARY_NAME}" "${DIST}/${BINARY_NAME}-macos-arm64"
|
||||
cp "target/x86_64-unknown-linux-musl/release/${BINARY_NAME}" "${DIST}/${BINARY_NAME}-linux-amd64"
|
||||
chmod +x "${DIST}"/*
|
||||
|
||||
echo "==> Binaries:"
|
||||
ls -lh "${DIST}"/
|
||||
|
||||
# ── Changelog ──────────────────────────────────────────────────
|
||||
echo "==> Generating changelog..."
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
LOG_RANGE="${PREV_TAG}..HEAD"
|
||||
RANGE="${PREV_TAG}...${TAG}"
|
||||
else
|
||||
LOG_RANGE=""
|
||||
RANGE="initial...${TAG}"
|
||||
fi
|
||||
|
||||
# Extract completed stories/bugs/refactors from merge commits.
|
||||
# Matches both the current "storkit:" prefix and the legacy "story-kit:" prefix.
|
||||
# Deduplicate (a story may have been merged more than once after reverts).
|
||||
MERGE_RE="^(storkit|story-kit): merge "
|
||||
if [ -n "$LOG_RANGE" ]; then
|
||||
MERGED_RAW=$(git log "$LOG_RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -E "$MERGE_RE" | sed -E "s/$MERGE_RE//" | sort -u)
|
||||
else
|
||||
MERGED_RAW=$(git log --pretty=format:"%s" --no-merges \
|
||||
| grep -E "$MERGE_RE" | sed -E "s/$MERGE_RE//" | sort -u)
|
||||
fi
|
||||
|
||||
# Categorise merged work items and format names.
|
||||
FEATURES=""
|
||||
FIXES=""
|
||||
REFACTORS=""
|
||||
while IFS= read -r item; do
|
||||
[ -z "$item" ] && continue
|
||||
# Strip the numeric prefix and type to get the human name.
|
||||
name=$(echo "$item" | sed -E 's/^[0-9]+_(story|bug|refactor|spike)_//' | tr '_' ' ')
|
||||
# Capitalise first letter.
|
||||
name="$(echo "${name:0:1}" | tr '[:lower:]' '[:upper:]')${name:1}"
|
||||
case "$item" in
|
||||
*_bug_*) FIXES="${FIXES}- ${name}\n" ;;
|
||||
*_refactor_*) REFACTORS="${REFACTORS}- ${name}\n" ;;
|
||||
*) FEATURES="${FEATURES}- ${name}\n" ;;
|
||||
esac
|
||||
done <<< "$MERGED_RAW"
|
||||
|
||||
# Collect non-automation manual commits (direct fixes, version bumps, etc).
|
||||
if [ -n "$LOG_RANGE" ]; then
|
||||
MANUAL=$(git log "$LOG_RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -Ev "^(storkit|story-kit): " \
|
||||
| grep -Ev "^Revert \"(storkit|story-kit): " \
|
||||
| grep -v "^Bump version" \
|
||||
| sed 's/^/- /')
|
||||
else
|
||||
MANUAL=$(git log --pretty=format:"%s" --no-merges \
|
||||
| grep -Ev "^(storkit|story-kit): " \
|
||||
| grep -Ev "^Revert \"(storkit|story-kit): " \
|
||||
| grep -v "^Bump version" \
|
||||
| sed 's/^/- /')
|
||||
fi
|
||||
|
||||
# Assemble the release body.
|
||||
RELEASE_BODY="## What's Changed"
|
||||
|
||||
if [ -n "$FEATURES" ]; then
|
||||
RELEASE_BODY="${RELEASE_BODY}
|
||||
|
||||
### Features
|
||||
$(echo -e "$FEATURES")"
|
||||
fi
|
||||
|
||||
if [ -n "$FIXES" ]; then
|
||||
RELEASE_BODY="${RELEASE_BODY}
|
||||
|
||||
### Bug Fixes
|
||||
$(echo -e "$FIXES")"
|
||||
fi
|
||||
|
||||
if [ -n "$REFACTORS" ]; then
|
||||
RELEASE_BODY="${RELEASE_BODY}
|
||||
|
||||
### Refactors
|
||||
$(echo -e "$REFACTORS")"
|
||||
fi
|
||||
|
||||
if [ -n "$MANUAL" ]; then
|
||||
RELEASE_BODY="${RELEASE_BODY}
|
||||
|
||||
### Other Changes
|
||||
${MANUAL}"
|
||||
fi
|
||||
|
||||
if [ -z "$FEATURES" ] && [ -z "$FIXES" ] && [ -z "$REFACTORS" ] && [ -z "$MANUAL" ]; then
|
||||
RELEASE_BODY="${RELEASE_BODY}
|
||||
|
||||
- No changes since last release"
|
||||
fi
|
||||
|
||||
RELEASE_BODY="${RELEASE_BODY}
|
||||
|
||||
**Full diff:** ${GITEA_URL}/${REPO}/compare/${RANGE}"
|
||||
|
||||
echo "$RELEASE_BODY"
|
||||
|
||||
# ── Tag & Push ─────────────────────────────────────────────────
|
||||
echo "==> Tagging ${TAG}..."
|
||||
git tag -a "$TAG" -m "Release ${TAG}"
|
||||
git push origin "$TAG"
|
||||
|
||||
# ── Create Gitea Release ──────────────────────────────────────
|
||||
echo "==> Creating release on Gitea..."
|
||||
RELEASE_JSON_FILE=$(mktemp)
|
||||
trap "rm -f '$RELEASE_JSON_FILE'" EXIT
|
||||
python3 -c "
|
||||
import json, sys
|
||||
with open(sys.argv[3], 'w') as f:
|
||||
json.dump({
|
||||
'tag_name': sys.argv[1],
|
||||
'name': sys.argv[1],
|
||||
'body': sys.argv[2]
|
||||
}, f)
|
||||
" "$TAG" "$RELEASE_BODY" "$RELEASE_JSON_FILE"
|
||||
|
||||
RELEASE_RESPONSE=$(curl -s --fail-with-body -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases" \
|
||||
-d "@${RELEASE_JSON_FILE}")
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to create Gitea release."
|
||||
echo "Response: ${RELEASE_RESPONSE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||
|
||||
# ── Upload Binaries ───────────────────────────────────────────
|
||||
for file in "${DIST}"/*; do
|
||||
filename=$(basename "$file")
|
||||
echo "==> Uploading ${filename}..."
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-F "attachment=@${file};filename=${filename}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets" > /dev/null
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "==> Done! Release ${TAG} published:"
|
||||
echo " ${GITEA_URL}/${REPO}/releases/tag/${TAG}"
|
||||
23
script/test
23
script/test
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
echo "=== Running Rust tests ==="
|
||||
cargo test --manifest-path "$PROJECT_ROOT/Cargo.toml"
|
||||
|
||||
echo "=== Running frontend unit tests ==="
|
||||
if [ -d "$PROJECT_ROOT/frontend" ]; then
|
||||
cd "$PROJECT_ROOT/frontend"
|
||||
npm test
|
||||
else
|
||||
echo "Skipping frontend tests (no frontend directory)"
|
||||
fi
|
||||
|
||||
# Disabled: e2e tests may be causing merge pipeline hangs (no running server
|
||||
# in merge workspace → Playwright blocks indefinitely). Re-enable once confirmed.
|
||||
# Disabled: e2e tests cause merge pipeline hangs (no running server
|
||||
# in merge workspace → Playwright blocks indefinitely).
|
||||
# echo "=== Running e2e tests ==="
|
||||
# npm run test:e2e
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test coverage collection and threshold enforcement.
|
||||
#
|
||||
# Runs Rust tests with llvm-cov and frontend tests with vitest --coverage.
|
||||
# Reports line coverage percentages for each.
|
||||
#
|
||||
# Threshold: reads from COVERAGE_THRESHOLD env var, or .coverage_baseline file.
|
||||
# Default: 0% (any coverage passes; baseline is written on first run).
|
||||
#
|
||||
# Coverage can only go up: if current coverage is above the stored baseline,
|
||||
# the baseline is updated automatically.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 — all coverage at or above threshold
|
||||
# 1 — coverage below threshold
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BASELINE_FILE="$PROJECT_ROOT/.coverage_baseline"
|
||||
|
||||
# ── Load threshold ────────────────────────────────────────────────────────────
|
||||
if [ -n "${COVERAGE_THRESHOLD:-}" ]; then
|
||||
THRESHOLD="$COVERAGE_THRESHOLD"
|
||||
elif [ -f "$BASELINE_FILE" ]; then
|
||||
THRESHOLD=$(cat "$BASELINE_FILE")
|
||||
else
|
||||
THRESHOLD=0
|
||||
fi
|
||||
|
||||
echo "=== Coverage threshold: ${THRESHOLD}% ==="
|
||||
echo ""
|
||||
|
||||
PASS=true
|
||||
RUST_LINE_COV=0
|
||||
FRONTEND_LINE_COV=0
|
||||
|
||||
# ── Rust coverage ─────────────────────────────────────────────────────────────
|
||||
echo "=== Running Rust tests with coverage ==="
|
||||
RUST_REPORT=""
|
||||
if cargo llvm-cov --version >/dev/null 2>&1; then
|
||||
RUST_REPORT=$(cargo llvm-cov \
|
||||
--manifest-path "$PROJECT_ROOT/Cargo.toml" \
|
||||
--summary-only \
|
||||
2>&1) || true
|
||||
echo "$RUST_REPORT"
|
||||
|
||||
# Parse the TOTAL line: columns are space-separated with % on coverage cols.
|
||||
# Format: TOTAL <regions> <missed> <cover%> <funcs> <missed> <exec%> <lines> <missed> <cover%> ...
|
||||
# We want field 10 (lines cover %).
|
||||
RUST_RAW=$(echo "$RUST_REPORT" | awk '/^TOTAL/ { print $10 }' | tr -d '%')
|
||||
if [ -n "$RUST_RAW" ]; then
|
||||
RUST_LINE_COV="$RUST_RAW"
|
||||
fi
|
||||
else
|
||||
echo "cargo-llvm-cov not available; skipping Rust coverage"
|
||||
fi
|
||||
echo "Rust line coverage: ${RUST_LINE_COV}%"
|
||||
echo ""
|
||||
|
||||
# ── Frontend coverage ─────────────────────────────────────────────────────────
|
||||
echo "=== Running frontend tests with coverage ==="
|
||||
FRONTEND_DIR="$PROJECT_ROOT/frontend"
|
||||
FRONTEND_LINE_COV=0
|
||||
if [ -d "$FRONTEND_DIR" ]; then
|
||||
FRONTEND_REPORT=$(cd "$FRONTEND_DIR" && npm run test:coverage 2>&1) || true
|
||||
echo "$FRONTEND_REPORT"
|
||||
|
||||
# Parse "All files" line from vitest coverage text table.
|
||||
# Format: All files | % Stmts | % Branch | % Funcs | % Lines | ...
|
||||
FRONTEND_RAW=$(echo "$FRONTEND_REPORT" | awk -F'|' '/All files/ { gsub(/ /, "", $5); print $5 }' | head -1)
|
||||
if [ -n "$FRONTEND_RAW" ]; then
|
||||
FRONTEND_LINE_COV="$FRONTEND_RAW"
|
||||
fi
|
||||
else
|
||||
echo "No frontend/ directory found; skipping frontend coverage"
|
||||
fi
|
||||
echo "Frontend line coverage: ${FRONTEND_LINE_COV}%"
|
||||
echo ""
|
||||
|
||||
# ── Overall (average of available measurements) ───────────────────────────────
|
||||
if [ "$RUST_LINE_COV" != "0" ] && [ "$FRONTEND_LINE_COV" != "0" ]; then
|
||||
OVERALL=$(awk "BEGIN { printf \"%.1f\", ($RUST_LINE_COV + $FRONTEND_LINE_COV) / 2 }")
|
||||
elif [ "$RUST_LINE_COV" != "0" ]; then
|
||||
OVERALL="$RUST_LINE_COV"
|
||||
elif [ "$FRONTEND_LINE_COV" != "0" ]; then
|
||||
OVERALL="$FRONTEND_LINE_COV"
|
||||
else
|
||||
OVERALL=0
|
||||
fi
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────────────
|
||||
echo "=== Coverage Summary ==="
|
||||
echo " Rust: ${RUST_LINE_COV}%"
|
||||
echo " Frontend: ${FRONTEND_LINE_COV}%"
|
||||
echo " Overall: ${OVERALL}%"
|
||||
echo " Threshold: ${THRESHOLD}%"
|
||||
echo ""
|
||||
|
||||
# ── Threshold check ───────────────────────────────────────────────────────────
|
||||
if awk "BEGIN { exit (($OVERALL + 0) < ($THRESHOLD + 0)) ? 0 : 1 }"; then
|
||||
echo "FAIL: Coverage ${OVERALL}% is below threshold ${THRESHOLD}%"
|
||||
PASS=false
|
||||
else
|
||||
echo "PASS: Coverage ${OVERALL}% meets threshold ${THRESHOLD}%"
|
||||
fi
|
||||
|
||||
# ── Update baseline when coverage improves ────────────────────────────────────
|
||||
if [ "$PASS" = "true" ]; then
|
||||
STORED_BASELINE="${THRESHOLD}"
|
||||
if awk "BEGIN { exit (($OVERALL + 0) > ($STORED_BASELINE + 0)) ? 0 : 1 }"; then
|
||||
echo "${OVERALL}" > "$BASELINE_FILE"
|
||||
echo "Baseline updated: ${STORED_BASELINE}% → ${OVERALL}%"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PASS" = "false" ]; then
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user