diff --git a/.gitea/workflows/backend-image.yml b/.gitea/workflows/backend-image.yml index 96fe9b6..184498a 100644 --- a/.gitea/workflows/backend-image.yml +++ b/.gitea/workflows/backend-image.yml @@ -41,3 +41,7 @@ jobs: --tag "$image:$IMAGE_TAG" \ backend docker push "$image:$IMAGE_TAG" + if [ "${GITHUB_REF_NAME:-}" = "main" ]; then + docker tag "$image:$IMAGE_TAG" "$image:latest" + docker push "$image:latest" + fi diff --git a/.gitignore b/.gitignore index ffde631..24d054e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .codex/ .codegraph/ +.mcp.json +__pycache__/ # Environment files .env diff --git a/AGENTS.md b/AGENTS.md index bed289c..0d29cdf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,8 @@ # 项目约定 - 修改 `backend/queries/*.sql` 后必须执行 `sqlc generate`,不得手改 `backend/internal/db/*.sql.go`。 + +## windows 构建机器连接 +- 优先使用 Windows MCP 操作 Windows 主机。 +- 如果 MCP 无法满足需求,或通过 MCP 操作明显不便,则改用 SSH 连接该主机执行操作。 +- 如果问题调试或定位需要相关应用、工具或依赖,直接安装,无需事先确认。 diff --git a/Makefile b/Makefile index 6c09f76..a1dcdcf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,9 @@ -IMAGE_NAME ?= rogeecn/weixin-guahao +# Load local secrets/config when present; .env is ignored by Git. +-include .env + +REGISTRY ?= git.ipao.vip +IMAGE_REPOSITORY ?= rogee/wxapp-guantong +IMAGE_NAME ?= $(REGISTRY)/$(IMAGE_REPOSITORY) IMAGE_TAG ?= latest IMAGE ?= $(IMAGE_NAME):$(IMAGE_TAG) @@ -16,7 +21,22 @@ ALPINE_MIRROR ?= mirrors.aliyun.com GOPROXY ?= https://goproxy.cn,direct GOSUMDB ?= sum.golang.google.cn -.PHONY: docker-build docker-push docker-release deploy compose-up compose-down +MINIPROGRAM_CI ?= miniprogram-ci +MINIPROGRAM_PATH ?= uniapp/unpackage/dist/build/mp-weixin +MINIPROGRAM_NORMALIZER ?= scripts/normalize-miniprogram-paths.py +WECHAT_APPID ?= $(or $(APP_ID),wx0a0e45ada30f01d4) +WECHAT_PRIVATE_KEY ?= $(HOME)/.secrets/wechat/private.$(WECHAT_APPID).key +WECHAT_VERSION ?= auto +WECHAT_VERSION_FALLBACK ?= 1.0.2 +# ponytail: local state; override WECHAT_VERSION when another machine uploads. +WECHAT_VERSION_STATE ?= .cache/wechat-version +WECHAT_VERSION_SCRIPT ?= scripts/wechat-next-version.mjs +WECHAT_ROBOT ?= 1 + +export WECHAT_APPID WECHAT_VERSION_FALLBACK +WECHAT_UPLOAD_DESCRIPTION ?= 骨安通生产版本 + +.PHONY: docker-build docker-push docker-release miniapp-normalize miniapp-upload deploy compose-up compose-down docker-build: docker build \ @@ -34,6 +54,34 @@ docker-push: docker-release: docker-build docker-push +miniapp-normalize: + python3 "$(MINIPROGRAM_NORMALIZER)" "$(MINIPROGRAM_PATH)" + +miniapp-upload: miniapp-normalize + test -f "$(WECHAT_PRIVATE_KEY)" + test -f "$(MINIPROGRAM_PATH)/project.config.json" + @set -eu; \ + if [ "$(WECHAT_VERSION)" = "auto" ]; then \ + if [ -s "$(WECHAT_VERSION_STATE)" ]; then \ + current="$$(cat "$(WECHAT_VERSION_STATE)")"; \ + version="$$(node "$(WECHAT_VERSION_SCRIPT)" "$$current")"; \ + else \ + version="$(WECHAT_VERSION_FALLBACK)"; \ + fi; \ + else \ + version="$(WECHAT_VERSION)"; \ + fi; \ + printf '上传小程序版本: %s\n' "$$version"; \ + $(MINIPROGRAM_CI) upload \ + --pp "$(MINIPROGRAM_PATH)" \ + --pkp "$(WECHAT_PRIVATE_KEY)" \ + --appid "$(WECHAT_APPID)" \ + --uv "$$version" \ + --ud "$(WECHAT_UPLOAD_DESCRIPTION)" \ + -r "$(WECHAT_ROBOT)"; \ + mkdir -p "$$(dirname "$(WECHAT_VERSION_STATE)")"; \ + printf '%s\n' "$$version" > "$(WECHAT_VERSION_STATE)" + deploy: docker-build mkdir -p $(DEPLOY_DIR) docker save $(IMAGE) | gzip > $(DEPLOY_ARCHIVE) @@ -41,7 +89,7 @@ deploy: docker-build ssh $(DEPLOY_HOST) 'docker load -i $(DEPLOY_REMOTE_ARCHIVE) && rm -f $(DEPLOY_REMOTE_ARCHIVE)' compose-up: - docker compose up -d + GUAHAO_IMAGE="$(IMAGE)" docker compose up -d compose-down: docker compose down diff --git a/docker-compose.yml b/docker-compose.yml index 5988321..0d46e35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: smilefirst-api: - image: rogeecn/weixin-guahao:latest + image: "${GUAHAO_IMAGE:-git.ipao.vip/rogee/wxapp-guantong:latest}" container_name: smilefirst-api environment: GUAHAO_ADDR: ":9800" diff --git a/scripts/normalize-miniprogram-paths.py b/scripts/normalize-miniprogram-paths.py new file mode 100644 index 0000000..8f591c0 --- /dev/null +++ b/scripts/normalize-miniprogram-paths.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Normalize Windows-style ZIP entries in a Mini Program build directory.""" + +from pathlib import Path +import os +import sys + + +def normalize(root: Path) -> int: + moved = 0 + entries = sorted(root.rglob("*"), key=lambda path: len(path.parts), reverse=True) + for entry in entries: + if "\\" not in entry.name: + continue + target = entry.parent.joinpath(*entry.name.split("\\")) + target.parent.mkdir(parents=True, exist_ok=True) + if target.exists(): + raise FileExistsError(f"normalization target already exists: {target}") + os.replace(entry, target) + moved += 1 + return moved + + +if __name__ == "__main__": + if len(sys.argv) != 2: + raise SystemExit(f"usage: {Path(sys.argv[0]).name} BUILD_DIR") + build_dir = Path(sys.argv[1]) + if not build_dir.is_dir(): + raise SystemExit(f"build directory not found: {build_dir}") + print(f"normalized {normalize(build_dir)} Windows-style paths") diff --git a/scripts/wechat-next-version.mjs b/scripts/wechat-next-version.mjs new file mode 100644 index 0000000..a8d85f2 --- /dev/null +++ b/scripts/wechat-next-version.mjs @@ -0,0 +1,23 @@ +#!/usr/bin/env node + +function parseVersion(value) { + const match = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?$/i.exec(String(value ?? '').trim()) + return match ? [Number(match[1]), Number(match[2] ?? 0), Number(match[3] ?? 0)] : null +} + +function nextVersion(value) { + const version = parseVersion(value) + if (!version) throw new Error(`版本号格式无效: ${value}`) + version[2] += 1 + return version.join('.') +} + +if (process.argv.includes('--self-test')) { + if (nextVersion('1.0.9') !== '1.0.10' || nextVersion('v2.3') !== '2.3.1') { + throw new Error('wechat-next-version self-test failed') + } + console.log('wechat-next-version self-test passed') +} else { + const current = process.argv[2] + console.log(current ? nextVersion(current) : (process.env.WECHAT_VERSION_FALLBACK || '1.0.2')) +}