Files
lai_hong 7a506114a2
Build, Push and Deploy / build (push) Failing after 6m44s
Build, Push and Deploy / deploy (push) Skipped
feat(export): add server-side AI export
2026-09-13 13:01:34 +08:00

181 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# 重新生成 release/ 与 release.tar.gz,供 Ubuntu Docker Compose 一键部署。
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
RELEASE_DIR="$ROOT_DIR/release"
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/wordcloud-release.XXXXXX")"
ARCHIVE="$ROOT_DIR/release.tar.gz"
PKG_NAME="wordcloud"
cleanup() {
rm -rf "$STAGING_DIR"
}
trap cleanup EXIT
log() { echo "[release] $*"; }
log "staging at $STAGING_DIR"
mkdir -p "$STAGING_DIR/$PKG_NAME"
# ── root deploy files ───────────────────────────────────────
cp -a "$ROOT_DIR/docker-compose.yml" "$STAGING_DIR/$PKG_NAME/"
cp -a "$ROOT_DIR/Makefile" "$STAGING_DIR/$PKG_NAME/"
cp -a "$ROOT_DIR/DOCKER.md" "$STAGING_DIR/$PKG_NAME/"
cp -a "$ROOT_DIR/install-ubuntu.sh" "$STAGING_DIR/$PKG_NAME/"
cp -a "$ROOT_DIR/README.md" "$STAGING_DIR/$PKG_NAME/" 2>/dev/null || true
cp -a "$ROOT_DIR/CHANGELOG.md" "$STAGING_DIR/$PKG_NAME/" 2>/dev/null || true
cat > "$STAGING_DIR/$PKG_NAME/.gitignore" <<'GI'
.DS_Store
.idea/
.claude/
__pycache__/
*.py[cod]
.venv/
node_modules/
frontend/dist/
backend/service_workspace/
backend/service_assets/
backend/service_projects/
backend/service_fonts/
backend/EfficientWordCloud/build/
backend/EfficientWordCloud/dist/
backend/EfficientWordCloud/**/*.so
*.log
*.db
GI
# ── backend (source only) ───────────────────────────────────
mkdir -p "$STAGING_DIR/$PKG_NAME/backend"
rsync -a \
--exclude '.venv/' \
--exclude '.runtime/' \
--exclude '.matplotlib/' \
--exclude '.idea/' \
--exclude '.claude/' \
--exclude '.DS_Store' \
--exclude '__pycache__/' \
--exclude '*.py[cod]' \
--exclude '*.so' \
--exclude '*.o' \
--exclude '*.egg-info/' \
--exclude 'service_workspace/' \
--exclude 'service_assets/' \
--exclude 'service_projects/' \
--exclude 'service_fonts/' \
--exclude 'service_design_templates/' \
--exclude 'output/' \
--exclude 'output_cli_test/' \
--exclude 'ref/' \
--exclude 'docs/' \
--exclude 'EfficientWordCloud/build/' \
--exclude 'EfficientWordCloud/dist/' \
--exclude 'EfficientWordCloud/efficient_wordcloud.egg-info/' \
--exclude 'EfficientWordCloud/**/__pycache__/' \
--exclude 'EfficientWordCloud/**/*.so' \
--exclude 'EfficientWordCloud/**/*.o' \
"$ROOT_DIR/backend/" "$STAGING_DIR/$PKG_NAME/backend/"
# design template seeds (json only)
if [[ -d "$ROOT_DIR/backend/service_design_templates" ]]; then
mkdir -p "$STAGING_DIR/$PKG_NAME/backend/service_design_templates"
rsync -a \
--include '*/' \
--include 'template.json' \
--exclude '*' \
"$ROOT_DIR/backend/service_design_templates/" \
"$STAGING_DIR/$PKG_NAME/backend/service_design_templates/" || true
fi
# ensure runtime dirs exist as empty placeholders (volumes will mount over them)
mkdir -p \
"$STAGING_DIR/$PKG_NAME/backend/service_workspace" \
"$STAGING_DIR/$PKG_NAME/backend/service_assets" \
"$STAGING_DIR/$PKG_NAME/backend/service_projects" \
"$STAGING_DIR/$PKG_NAME/backend/service_fonts"
# keep design templates if seeded; otherwise empty
mkdir -p "$STAGING_DIR/$PKG_NAME/backend/service_design_templates"
# ── frontend ────────────────────────────────────────────────
mkdir -p "$STAGING_DIR/$PKG_NAME/frontend"
rsync -a \
--exclude 'node_modules/' \
--exclude 'dist/' \
--exclude '.DS_Store' \
--exclude '.claude/' \
--exclude '.idea/' \
"$ROOT_DIR/frontend/" "$STAGING_DIR/$PKG_NAME/frontend/"
# ── private AI converter ────────────────────────────────────
mkdir -p "$STAGING_DIR/$PKG_NAME/ai-converter"
rsync -a \
--exclude '__pycache__/' \
--exclude '*.py[cod]' \
"$ROOT_DIR/ai-converter/" "$STAGING_DIR/$PKG_NAME/ai-converter/"
# ── docs ────────────────────────────────────────────────────
if [[ -d "$ROOT_DIR/docs" ]]; then
mkdir -p "$STAGING_DIR/$PKG_NAME/docs"
rsync -a \
--exclude '.DS_Store' \
"$ROOT_DIR/docs/" "$STAGING_DIR/$PKG_NAME/docs/" || true
fi
chmod +x "$STAGING_DIR/$PKG_NAME/install-ubuntu.sh"
chmod +x "$STAGING_DIR/$PKG_NAME/backend/start-dev.sh" 2>/dev/null || true
# sanity: refuse if venv/workspace leaked in
if [[ -e "$STAGING_DIR/$PKG_NAME/backend/.venv" ]]; then
echo "[release] ERROR: .venv leaked into staging" >&2
exit 1
fi
if find "$STAGING_DIR/$PKG_NAME/backend/service_workspace" -mindepth 1 | read -r _; then
echo "[release] ERROR: service_workspace not empty in staging" >&2
exit 1
fi
# ── overwrite release/ ──────────────────────────────────────
log "overwrite $RELEASE_DIR"
rm -rf "$RELEASE_DIR"
mkdir -p "$RELEASE_DIR"
rsync -a "$STAGING_DIR/$PKG_NAME/" "$RELEASE_DIR/"
# ── overwrite release.tar.gz ────────────────────────────────
log "write $ARCHIVE"
rm -f "$ARCHIVE"
# write to temp then atomic move to avoid truncated archive on interrupt
TMP_ARCHIVE="$(mktemp "${TMPDIR:-/tmp}/wordcloud-release-tar.XXXXXX").tar.gz"
tar -C "$STAGING_DIR" -czf "$TMP_ARCHIVE" "$PKG_NAME"
mv -f "$TMP_ARCHIVE" "$ARCHIVE"
# verify archive
tar -tzf "$ARCHIVE" >/dev/null
python3 - <<PY
import tarfile
tf = tarfile.open("$ARCHIVE", "r:gz")
names = tf.getnames()
assert "wordcloud/docker-compose.yml" in names
assert "wordcloud/install-ubuntu.sh" in names
assert "wordcloud/backend/wordcloud_generate_hybrid.py" in names
assert "wordcloud/backend/service/app.py" in names
assert "wordcloud/backend/service/line_spacing.py" in names
assert "wordcloud/frontend/src/pages/CanvasStudio.tsx" in names
assert "wordcloud/ai-converter/Dockerfile" in names
assert "wordcloud/ai-converter/app.py" in names
assert not any(".venv" in n for n in names), "venv leaked into archive"
assert not any("/service_workspace/" in n and not n.endswith("/service_workspace") for n in names), "workspace leaked"
print("archive entries:", len(names))
print("archive ok")
PY
log "done"
echo "---- release root ----"
ls -la "$RELEASE_DIR"
echo "---- sizes ----"
du -sh "$RELEASE_DIR" "$ARCHIVE"
echo "---- backend top ----"
ls -la "$RELEASE_DIR/backend"
echo "---- frontend src files ----"
find "$RELEASE_DIR/frontend/src" -type f | wc -l