diff --git a/.github/workflows/appstore-publish.yml b/.github/workflows/appstore-publish.yml index 978028bfb..0fc853e3e 100644 --- a/.github/workflows/appstore-publish.yml +++ b/.github/workflows/appstore-publish.yml @@ -67,6 +67,45 @@ jobs: --channel "${{ steps.resolve_channel.outputs.channel }}" \ --output-dir dist/catalog-source + # ── legacy media assets (logos + screenshots, same as old media.yml) ── + + - name: Download legacy logos from R2 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} + AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com + run: | + mkdir -p dist/catalog-source/logos + aws s3 sync \ + s3://libs/Websoft9/logo/product \ + dist/catalog-source/logos \ + --endpoint-url "$AWS_ENDPOINT_URL" || echo "Warning: logo sync failed (non-blocking)" + + - name: Download legacy screenshots from Contentful + run: | + mkdir -p dist/catalog-source/screenshots + for f in dist/catalog-source/product_*.json; do + [ -f "$f" ] && python3 -c " +import json, sys, urllib.request, os +with open('$f') as fh: + data = json.load(fh) +urls = set() +for item in data: + for s in item.get('screenshots', []): + u = s.get('value') or s.get('imageurl') + if u: urls.add(u) +for u in sorted(urls): + try: + name = u.rsplit('/', 1)[-1].split('?')[0] + if '.' not in name: name = f'{hash(u) & 0xFFFFFFFFFFFF:012x}.png' + path = os.path.join('dist/catalog-source/screenshots', name) + if not os.path.exists(path): + urllib.request.urlretrieve(u, path) + except Exception as e: + print(f'Warning: screenshot {u}: {e}', file=sys.stderr) +" + done + - name: Check if first v2 publish (R2 apps/ prefix empty) id: check_seed env: @@ -74,7 +113,7 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com run: | - count=$(aws s3 ls "s3://artifact/websoft9/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/library/apps/" --recursive --endpoint-url "$AWS_ENDPOINT_URL" 2>/dev/null | wc -l) + count=$(aws s3 ls "s3://artifact/appstore/${{ steps.resolve_channel.outputs.channel }}/library/apps/" --recursive --endpoint-url "$AWS_ENDPOINT_URL" 2>/dev/null | wc -l) if [ "$count" -eq 0 ]; then echo "R2 apps/ prefix is empty — first publish, will seed all app packages" echo "seed=true" >> "$GITHUB_OUTPUT" @@ -111,8 +150,8 @@ jobs: AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com run: | aws s3 sync \ - dist/appstore-publish/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/catalog \ - s3://artifact/websoft9/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/catalog \ + dist/appstore-publish/appstore/${{ steps.resolve_channel.outputs.channel }}/catalog \ + s3://artifact/appstore/${{ steps.resolve_channel.outputs.channel }}/catalog \ --endpoint-url "$AWS_ENDPOINT_URL" - name: Upload v2 library metadata to Cloudflare R2 @@ -122,8 +161,8 @@ jobs: AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com run: | aws s3 sync \ - dist/appstore-publish/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/library \ - s3://artifact/websoft9/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/library \ + dist/appstore-publish/appstore/${{ steps.resolve_channel.outputs.channel }}/library \ + s3://artifact/appstore/${{ steps.resolve_channel.outputs.channel }}/library \ --exclude "apps/*" \ --endpoint-url "$AWS_ENDPOINT_URL" @@ -133,10 +172,10 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com run: | - if [ -d "dist/appstore-publish/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/library/apps" ]; then + if [ -d "dist/appstore-publish/appstore/${{ steps.resolve_channel.outputs.channel }}/library/apps" ]; then aws s3 cp --recursive \ - dist/appstore-publish/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/library/apps \ - s3://artifact/websoft9/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/library/apps \ + dist/appstore-publish/appstore/${{ steps.resolve_channel.outputs.channel }}/library/apps \ + s3://artifact/appstore/${{ steps.resolve_channel.outputs.channel }}/library/apps \ --endpoint-url "$AWS_ENDPOINT_URL" fi @@ -147,6 +186,86 @@ jobs: AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com run: | aws s3 sync \ - dist/appstore-publish/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/manifests \ - s3://artifact/websoft9/v2/${{ steps.resolve_channel.outputs.channel }}/appstore/manifests \ - --endpoint-url "$AWS_ENDPOINT_URL" \ No newline at end of file + dist/appstore-publish/appstore/${{ steps.resolve_channel.outputs.channel }}/manifests \ + s3://artifact/appstore/${{ steps.resolve_channel.outputs.channel }}/manifests \ + --endpoint-url "$AWS_ENDPOINT_URL" + + # ── legacy uploads (old R2 paths, kept for backward compatibility) ── + + - name: Upload legacy library to Cloudflare R2 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} + AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com + run: | + aws s3 sync \ + dist/appstore-publish/legacy/${{ steps.resolve_channel.outputs.channel }}/library \ + s3://artifact/${{ steps.resolve_channel.outputs.channel }}/websoft9/plugin/library \ + --endpoint-url "$AWS_ENDPOINT_URL" + + - name: Upload legacy media to Cloudflare R2 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} + AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com + run: | + aws s3 sync \ + dist/appstore-publish/legacy/${{ steps.resolve_channel.outputs.channel }}/media \ + s3://artifact/${{ steps.resolve_channel.outputs.channel }}/websoft9/plugin/media \ + --endpoint-url "$AWS_ENDPOINT_URL" + + - name: Resolve legacy purge URLs + id: purge_urls + run: | + if [[ "${{ steps.resolve_channel.outputs.channel }}" == "dev" ]]; then + echo "urls=[\"https://artifact.websoft9.com/dev/websoft9/plugin/library/library-dev.zip\",\"https://artifact.websoft9.com/dev/websoft9/plugin/media/media-dev.zip\"]" >> "$GITHUB_OUTPUT" + else + echo "urls=[\"https://artifact.websoft9.com/release/websoft9/plugin/library/library-latest.zip\",\"https://artifact.websoft9.com/release/websoft9/plugin/media/media.zip\"]" >> "$GITHUB_OUTPUT" + fi + + - name: Purge legacy Cloudflare Cache + uses: jakejarvis/cloudflare-purge-action@v2 + env: + CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE_ID }} + CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + with: + PURGE_URLS: ${{ steps.purge_urls.outputs.urls }} + + - name: Create GitHub Release (release channel only) + if: steps.resolve_channel.outputs.channel == 'release' + uses: softprops/action-gh-release@v1 + with: + files: | + dist/appstore-publish/legacy/release/library/library.json + tag_name: release-${{ github.run_number }} + name: Release ${{ github.run_number }} + draft: false + prerelease: false + + pages: + name: Build Github Pages (release only) + if: github.ref_name == 'main' + needs: publish + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '.' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml deleted file mode 100644 index 309f41978..000000000 --- a/.github/workflows/release-dev.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Release library and media to artifact(dev) - -on: - workflow_dispatch: - -jobs: - release: - name: Release to github and artifact - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - name: Check out code - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install requests - - - name: Fetch catalog inputs - env: - CONTENTFUL_GRAPHQL_TOKEN: ${{ secrets.CONTENTFUL_GRAPHQLTOKEN }} - run: | - python build/fetch_catalog.py \ - --channel dev \ - --output-dir dist/catalog-source - - - name: Build legacy and v2 artifacts - run: | - python build/library_publish.py \ - --channel dev \ - --output-dir dist/publish \ - --catalog-source-dir dist/catalog-source - - - name: Upload workflow artifact - uses: actions/upload-artifact@v4 - with: - name: publish-dev - path: dist/publish - - - name: Upload legacy library to Cloudflare R2 - uses: ryand56/r2-upload-action@latest - with: - r2-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - r2-access-key-id: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} - r2-secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} - r2-bucket: artifact - source-dir: dist/publish/legacy/dev/library - destination-dir: ./dev/websoft9/plugin/library - - - name: Upload legacy media to Cloudflare R2 - uses: ryand56/r2-upload-action@latest - with: - r2-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - r2-access-key-id: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} - r2-secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} - r2-bucket: artifact - source-dir: dist/publish/legacy/dev/media - destination-dir: ./dev/websoft9/plugin/media - - - name: Purge Cloudflare Cache - uses: jakejarvis/cloudflare-purge-action@master - env: - CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE_ID }} - CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PURGE_URLS: '["https://artifact.websoft9.com/dev/websoft9/plugin/library/library-dev.zip","https://artifact.websoft9.com/dev/websoft9/plugin/media/media-dev.zip"]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 524386ac0..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Release to Github and artifact - -on: - workflow_dispatch: - inputs: - library_version: - description: Optional legacy release version tag used for the versioned library zip and GitHub Release - required: false - type: string - from_ref: - description: Optional git ref used to calculate apps-delta - required: false - type: string - repository_dispatch: - types: [custom_event] - -jobs: - release: - name: Release to github and artifact - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - name: Check out code - - - name: Resolve release inputs - id: resolve_inputs - run: | - version="${{ inputs.library_version }}" - if [[ -z "$version" ]]; then - version="${{ github.event.client_payload.library_version }}" - fi - if [[ -z "$version" ]]; then - version="$(date -u +%Y.%m.%d.%H%M%S)" - fi - from_ref="${{ inputs.from_ref }}" - if [[ -z "$from_ref" ]]; then - from_ref="${{ github.event.client_payload.from_ref }}" - fi - echo "library_version=$version" >> "$GITHUB_OUTPUT" - echo "from_ref=$from_ref" >> "$GITHUB_OUTPUT" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install requests - - - name: Fetch catalog inputs - env: - CONTENTFUL_GRAPHQL_TOKEN: ${{ secrets.CONTENTFUL_GRAPHQLTOKEN }} - run: | - python build/fetch_catalog.py \ - --channel release \ - --output-dir dist/catalog-source - - - name: Build legacy and v2 artifacts - run: | - args=( - --channel release - --output-dir dist/publish - --catalog-source-dir dist/catalog-source - --library-version "${{ steps.resolve_inputs.outputs.library_version }}" - ) - if [[ -n "${{ steps.resolve_inputs.outputs.from_ref }}" ]]; then - args+=(--from-ref "${{ steps.resolve_inputs.outputs.from_ref }}") - fi - python build/library_publish.py "${args[@]}" - - - name: Upload workflow artifact - uses: actions/upload-artifact@v4 - with: - name: publish-release - path: dist/publish - - - name: Upload legacy library to Cloudflare R2 - uses: ryand56/r2-upload-action@latest - with: - r2-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - r2-access-key-id: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} - r2-secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} - r2-bucket: artifact - source-dir: dist/publish/legacy/release/library - destination-dir: ./release/websoft9/plugin/library - - - name: Upload legacy media to Cloudflare R2 - uses: ryand56/r2-upload-action@latest - with: - r2-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - r2-access-key-id: ${{ secrets.CLOUDFLARE_R2_SECRET_ID }} - r2-secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_KEY }} - r2-bucket: artifact - source-dir: dist/publish/legacy/release/media - destination-dir: ./release/websoft9/plugin/media - - - name: Purge Cloudflare Cache - uses: jakejarvis/cloudflare-purge-action@master - env: - CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE_ID }} - CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PURGE_URLS: '["https://artifact.websoft9.com/release/websoft9/plugin/library/library-latest.zip","https://artifact.websoft9.com/release/websoft9/plugin/media/media.zip"]' - - - name: Create Github Release from code - uses: softprops/action-gh-release@v1 - with: - files: | - dist/publish/legacy/release/library/library.json - tag_name: ${{ steps.resolve_inputs.outputs.library_version }} - name: ${{ steps.resolve_inputs.outputs.library_version }} - draft: false - prerelease: false - - pages: - name: Build Github Pages - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Pages - uses: actions/configure-pages@v4 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: '.' - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/build/__pycache__/library_publish.cpython-39.pyc b/build/__pycache__/library_publish.cpython-39.pyc index ccf92ae7b..ee6e453bb 100644 Binary files a/build/__pycache__/library_publish.cpython-39.pyc and b/build/__pycache__/library_publish.cpython-39.pyc differ diff --git a/build/library_publish.py b/build/library_publish.py index 98012317a..1ff649d74 100644 --- a/build/library_publish.py +++ b/build/library_publish.py @@ -458,7 +458,7 @@ def validate_appstore_artifacts(appstore_dir: Path) -> None: def build_output_paths(base_output_dir: Path, channel: str) -> tuple[Path, Path]: legacy_library_dir = base_output_dir / "legacy" / channel / "library" - v2_appstore_dir = base_output_dir / "v2" / channel / "appstore" + v2_appstore_dir = base_output_dir / "appstore" / channel return legacy_library_dir, v2_appstore_dir @@ -502,6 +502,7 @@ def build_legacy_media_artifacts(output_dir: Path, catalog_source_dir: Path, cha json_dir.mkdir(parents=True, exist_ok=True) catalog_sources = ensure_catalog_source(catalog_source_dir) + for file_name, source_path in catalog_sources.items(): destination = json_dir / file_name if file_name.startswith("product_") and distribution_map: @@ -510,6 +511,12 @@ def build_legacy_media_artifacts(output_dir: Path, catalog_source_dir: Path, cha else: shutil.copy2(source_path, destination) + # Include logos and screenshots if workflow downloaded them + for asset_dir in ("logos", "screenshots"): + src = catalog_source_dir / asset_dir + if src.is_dir(): + shutil.copytree(src, media_root / asset_dir) + create_zip_from_directory(media_root, output_dir / archive_name) checksum_name = write_checksum_file(output_dir / archive_name) @@ -558,16 +565,18 @@ def build_v2_appstore_artifacts( catalog_dsv = _hash_content(catalog_checksum_values) # ── catalog full package ───────────────────────────────── + catalog_full_dir = catalog_dir / "full" + catalog_full_dir.mkdir(parents=True, exist_ok=True) catalog_zip_name = f"catalog-{catalog_dsv}.zip" with tempfile.TemporaryDirectory() as tmp_dir_name: tmp_dir = Path(tmp_dir_name) for file_name in CATALOG_FILE_NAMES: shutil.copy2(catalog_dir / file_name, tmp_dir / file_name) - create_zip_from_directory(tmp_dir, catalog_dir / catalog_zip_name) - catalog_zip_checksum = write_checksum_file(catalog_dir / catalog_zip_name) + create_zip_from_directory(tmp_dir, catalog_full_dir / catalog_zip_name) + catalog_zip_checksum = write_checksum_file(catalog_full_dir / catalog_zip_name) catalog_checksums["fullPackage"] = catalog_zip_checksum - catalog_manifest = build_catalog_manifest(catalog_dsv, catalog_checksums, generated_at, catalog_zip_name) + catalog_manifest = build_catalog_manifest(catalog_dsv, catalog_checksums, generated_at, f"full/{catalog_zip_name}") write_json(catalog_dir / "manifest.json", catalog_manifest) write_checksum_file(catalog_dir / "manifest.json") validate_catalog_artifacts(catalog_dir, catalog_manifest) diff --git a/docs/appstore-release-spec.md b/docs/appstore-release-spec.md index 6f42711a4..22ea606c9 100644 --- a/docs/appstore-release-spec.md +++ b/docs/appstore-release-spec.md @@ -102,28 +102,29 @@ else: ### 4.1 R2 目录结构 ``` -artifact/websoft9/v2//appstore/ +artifact/appstore// ├── catalog/ -│ ├── manifest.json # catalog 清单 -│ ├── catalog-.zip # 全量包(4 个 JSON) +│ ├── manifest.json +│ ├── full/ +│ │ └── catalog-.zip │ ├── catalog_en.json │ ├── catalog_zh.json │ ├── product_en.json │ ├── product_zh.json │ └── *.sha256 ├── library/ -│ ├── manifest.json # library 清单 +│ ├── manifest.json │ ├── apps-index-.json │ ├── apps-delta--to-.json │ ├── full/ -│ │ ├── library-.zip # 不可变全量包 -│ │ └── library-.zip # 可变全量别名 +│ │ ├── library-.zip +│ │ └── library-.zip │ └── apps/ │ └── / -│ ├── latest.zip # 唯一安装包 +│ ├── latest.zip │ └── latest.zip.sha256 └── manifests/ - └── appstore-manifest.json # 根清单 + └── appstore-manifest.json ``` ### 4.2 设计要点 @@ -169,7 +170,7 @@ artifact/websoft9/v2//appstore/ "schemaVersion": "1", "datasetVersion": "7ee7a10b7da49f38", "source": "contentful", - "fullPackage": "catalog-7ee7a10b7da49f38.zip", + "fullPackage": "full/catalog-7ee7a10b7da49f38.zip", "files": { "catalogEn": "catalog_en.json", "catalogZh": "catalog_zh.json", @@ -181,7 +182,7 @@ artifact/websoft9/v2//appstore/ "catalogZh": "catalog_zh.json.sha256", "productEn": "product_en.json.sha256", "productZh": "product_zh.json.sha256", - "fullPackage": "catalog-7ee7a10b7da49f38.zip.sha256" + "fullPackage": "full/catalog-7ee7a10b7da49f38.zip.sha256" }, "generatedAt": "2026-06-09T12:00:00Z" } @@ -247,30 +248,40 @@ artifact/websoft9/v2//appstore/ 2. 解析 channel 3. 检查 R2 apps/ 是否为空 → 空则标记首次发布(--all-apps) 4. 从 Contentful 拉取 catalog 源数据 -5. 运行 library_publish.py 构建所有制品 -6. 分步上传到 R2(catalog / library 元数据 / apps / manifests) +5. 运行 library_publish.py 构建所有制品(v2 + legacy) +6. 上传 v2 制品到新 R2 路径 +7. 上传 legacy 制品到旧 R2 路径(兼容旧消费者) +8. 清除 Cloudflare 缓存 +9. release 通道额外创建 GitHub Release ``` ### 6.3 上传策略 -| 子目录 | 命令 | 说明 | -|--------|------|------| -| `catalog/` | `aws s3 sync` | 追加/覆盖 | -| `library/`(排除 `apps/`) | `aws s3 sync --exclude "apps/*"` | 追加/覆盖 | -| `library/apps/` | `aws s3 cp --recursive` | 纯增量,仅上传本次构建的 app | -| `manifests/` | `aws s3 sync` | 追加/覆盖 | +| 目标 | 命令 | R2 路径 | +|------|------|--------| +| v2 catalog | `aws s3 sync` | `appstore//catalog/` | +| v2 library 元数据 | `aws s3 sync --exclude "apps/*"` | `appstore//library/` | +| v2 单 app 包 | `aws s3 cp --recursive` | `appstore//library/apps/` | +| v2 manifests | `aws s3 sync` | `appstore//manifests/` | +| legacy library | `aws s3 sync` | `/websoft9/plugin/library/` | +| legacy media | `aws s3 sync` | `/websoft9/plugin/media/` | + +所有 `sync` 均为默认追加/覆盖模式,不删除目标已有文件。 --- ## 7. Legacy 兼容 -并行期内 legacy 与 v2 共存: +并行期内 legacy 与 v2 通过**同一 workflow、不同 R2 路径**共存: + +| 制品 | v2 路径 | legacy 路径 | +|------|--------|------------| +| catalog | `appstore//catalog/` | `/websoft9/plugin/media/`(打包为 `media.zip`) | +| library | `appstore//library/` | `/websoft9/plugin/library/`(打包为 `library-*.zip`) | + +**legacy `media.zip` 保持旧格式**:内含 `json/` + `logos/` + `screenshots/`,图片从 Contentful URL 下载后打包。v2 目录中不包含图片二进制,统一使用在线 URL。 -- Legacy 使用旧 R2 路径(`dev/websoft9/plugin/library/` 等) -- v2 使用新路径(`v2//appstore/`) -- 两套 workflow 独立运行,物理隔离 -- 旧跨仓库 workflow 依赖已迁回本项目内部实现 -- Legacy 在迁移窗口结束后下线 +每次 push 同时产出两套制品,迁移窗口结束后下线 legacy 上传步骤。 ---