mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-28 21:19:58 +08:00
fixed # 修复部分工作流BUG
This commit is contained in:
@@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
publish:
|
publish:
|
||||||
description: Sync main branch in NoneBot repository
|
description: Sync NoneBot repository and trigger PyPI version release
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
permissions:
|
permissions:
|
||||||
@@ -102,9 +102,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: nonebot-preview
|
name: nonebot-preview
|
||||||
path: incoming
|
path: incoming
|
||||||
- name: Sync validated NoneBot repository
|
- name: Sync NoneBot repository and trigger version release
|
||||||
env:
|
env:
|
||||||
FARM_RELEASE_TOKEN: ${{ secrets.FARM_RELEASE_TOKEN }}
|
FARM_RELEASE_TOKEN: ${{ secrets.FARM_RELEASE_TOKEN }}
|
||||||
run: >-
|
run: >-
|
||||||
python scripts/publish_nonebot.py
|
python scripts/publish_nonebot.py
|
||||||
incoming/nonebot_plugin_farm-*.zip --publish
|
incoming/nonebot_plugin_farm-*.zip --publish --release-tag
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
| 文档 | 用途 |
|
| 文档 | 用途 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| [nonebot-release.md](nonebot-release.md) | NoneBot 同步、自动版本标签、PyPI发布及失败重跑 |
|
||||||
| [catalogue-updates.md](catalogue-updates.md) | Service 作物维护、发布、启动更新、缓存与兼容回滚 |
|
| [catalogue-updates.md](catalogue-updates.md) | Service 作物维护、发布、启动更新、缓存与兼容回滚 |
|
||||||
| [23-seasonal-events-and-rust-service.md](23-seasonal-events-and-rust-service.md) | 节日活动交互备选、Rust 发布中心、同步、资产事务、过期清理与上线规划(草案) |
|
| [23-seasonal-events-and-rust-service.md](23-seasonal-events-and-rust-service.md) | 节日活动交互备选、Rust 发布中心、同步、资产事务、过期清理与上线规划(草案) |
|
||||||
| [09-foundation.md](09-foundation.md) | 已实现的第一批核心、API、测试与限制 |
|
| [09-foundation.md](09-foundation.md) | 已实现的第一批核心、API、测试与限制 |
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# NoneBot 发布与版本更新
|
||||||
|
|
||||||
|
主仓库 `version.txt` 是版本来源。NoneBot preview 工作流通过测试并生成插件后,
|
||||||
|
在主仓库 `main` 推送或手动勾选 `publish` 时,执行以下操作:
|
||||||
|
|
||||||
|
1. 校验生成包及来源工作区状态。
|
||||||
|
2. 同步 `Shu-Ying/nonebot_plugin_farm` 的 `main` 分支。
|
||||||
|
3. 在本次同步提交上创建 `v<version.txt>` 标签。
|
||||||
|
4. 标签触发独立仓库的“🚀 发布到 PyPI”工作流,构建、校验并上传发行包。
|
||||||
|
|
||||||
|
PR及未勾选publish的手动运行只构建测试,不同步、不打标签。脚本未传
|
||||||
|
`--publish` 时仍是离线预演;`--release-tag` 明确开启自动标签步骤。
|
||||||
|
标签创建成功只表示触发条件已满足,不表示下游PyPI发布或商店检测已成功。
|
||||||
|
|
||||||
|
## 凭据与发布前提
|
||||||
|
|
||||||
|
- 主仓库受保护环境中继续使用 `FARM_RELEASE_TOKEN`,必须是可写目标仓库
|
||||||
|
内容及工作流的PAT或GitHub App token;不能替换为普通 `GITHUB_TOKEN`。
|
||||||
|
- NoneBot独立仓库需允许Actions运行,并已配置有效的 `PYPI_TOKEN`。
|
||||||
|
- 目标仓库标签规则也必须允许该发布身份创建 `v*` 标签。
|
||||||
|
- 发布新内容前递增主仓库 `version.txt`;不要在发布仓库手工改生成的版本文件。
|
||||||
|
|
||||||
|
## 重跑和失败处理
|
||||||
|
|
||||||
|
已有同版本标签时,比较标签中生成清单的版本、包身份和全部文件摘要;内容
|
||||||
|
一致则跳过同步和重复触发。只有来源提交信息变化而插件文件相同也会跳过。
|
||||||
|
若内容不同则拒绝,并提示升级 `version.txt`;脚本不删除或移动旧标签。
|
||||||
|
|
||||||
|
同步main成功但创建标签失败时,工作流会报错,保留已同步代码;修复权限等
|
||||||
|
原因后重跑。若标签已创建而下游PyPI上传失败,应在独立仓库重新运行失败的
|
||||||
|
发布任务;上游重跑不会再次创建同名标签。PyPI已占用的版本不能靠移动标签
|
||||||
|
覆盖,应使用新版本。
|
||||||
|
|
||||||
|
本地测试使用模拟GitHub请求,不会创建远端标签或上传PyPI。
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig
|
||||||
|
|
||||||
from ._version import __version__
|
|
||||||
from .runtime import COMMANDS, HELP
|
from .runtime import COMMANDS, HELP
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
@@ -12,7 +11,7 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
homepage="https://github.com/Shu-Ying/zhenxun_plugin_farm",
|
homepage="https://github.com/Shu-Ying/zhenxun_plugin_farm",
|
||||||
supported_adapters={"~onebot.v11", "~qq"},
|
supported_adapters={"~onebot.v11", "~qq"},
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="Shu-Ying", version=__version__, menu_type="群内小游戏",
|
author="Shu-Ying", version="__FARM_VERSION__", menu_type="群内小游戏",
|
||||||
commands=[Command(command=name) for name in COMMANDS],
|
commands=[Command(command=name) for name in COMMANDS],
|
||||||
configs=[RegisterConfig(
|
configs=[RegisterConfig(
|
||||||
key="农场图片质量", value=75, default_value=75, type=int,
|
key="农场图片质量", value=75, default_value=75, type=int,
|
||||||
|
|||||||
+2
-2
@@ -49,9 +49,9 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
## 如何安装
|
## 如何安装
|
||||||
|
在Astrbot的插件商店下载即可
|
||||||
|
|
||||||
方法一(推荐):在小真寻后台的插件商店下载即可<br>
|
`在安装后,每次启动均会向`[farm_service](https://github.com/Shu-Ying/farm_service)`仓库进行下载资源,默认使用Github进行下载,可以在设置中切换为Gitea镜像`
|
||||||
方法二:下载源码放在小真寻`plugin`目录下
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+3
-16
@@ -50,24 +50,11 @@
|
|||||||
|
|
||||||
## 如何安装
|
## 如何安装
|
||||||
|
|
||||||
使用真寻分发仓库的完整生成内容或Actions中的真寻预览包,不能直接
|
方法一(推荐):在小真寻后台的插件商店下载即可
|
||||||
复制主仓库的integrations目录。先停止机器人,备份代码与数据,停用
|
|
||||||
会注册同名命令的旧插件,再在宿主环境安装本插件requirements.txt。
|
|
||||||
Pillow要求>=10.4,<11,不强制升级宿主到12。安装后手工启动并检查日志。
|
|
||||||
|
|
||||||
默认数据目录为真寻DATA_PATH下的farm_db,新库为farm.sqlite3,日志为
|
方法二:下载源码放在小真寻`plugin`目录下
|
||||||
farm-preview.log;旧farm.db不得直接改名覆盖。资源源默认Github,可在
|
|
||||||
网页配置选择Gitea/farm_service;服务地址独立保留,修改资源源后重载。
|
|
||||||
图片质量默认75、整数1~95,仅“我的农场 高清”支持高清且质量至少85。
|
|
||||||
资源或字体下载失败时文字业务仍可用,详细原因见日志。
|
|
||||||
|
|
||||||
升级前停机备份完整代码与数据目录;安装器拒绝覆盖,先移走旧目录。
|
`在安装后,每次启动均会向`[farm_service](https://github.com/Shu-Ying/farm_service)`仓库进行下载资源,默认使用Github进行下载,可以在设置中切换为Gitea镜像`
|
||||||
回退需匹配代码和数据库备份,不让旧代码强行打开新schema;恢复旧数据
|
|
||||||
会丢失备份后进度。卸载时停止加载并移走插件代码,保留数据供恢复。
|
|
||||||
|
|
||||||
开发者构建、包验证、安全安装、配置、备份卸载与Secrets详见
|
|
||||||
[当前维护说明](https://github.com/Shu-Ying/main_plugin_farm/blob/main/docs/zhenxun-preview-current.md)。
|
|
||||||
本地离线验证不代表真实真寻宿主、公开资源或远端发布已验证。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ jobs:
|
|||||||
raise SystemExit('Tag differs from version.txt')
|
raise SystemExit('Tag differs from version.txt')
|
||||||
PY
|
PY
|
||||||
- run: python -m pip install build twine
|
- run: python -m pip install build twine
|
||||||
- run: python -m build --sdist --wheel --outdir ../packages
|
- run: python -m build --sdist --wheel --outdir dist
|
||||||
- run: python -m twine check ../packages/*
|
- run: python -m twine check dist/*
|
||||||
- uses: actions/upload-artifact@v7
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: pypi-distributions
|
name: pypi-distributions
|
||||||
path: ../packages/*
|
path: dist/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
publish:
|
publish:
|
||||||
needs: build
|
needs: build
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ jobs:
|
|||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
- run: python verify_package.py --directory .
|
- run: python verify_package.py --directory .
|
||||||
- run: python -m pip install build
|
- run: python -m pip install build
|
||||||
- run: python -m build --sdist --wheel --outdir ../packages
|
- run: python -m build --sdist --wheel --outdir dist
|
||||||
- uses: actions/upload-artifact@v7
|
- uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: nonebot-distributions
|
name: nonebot-distributions
|
||||||
path: ../packages/*
|
path: dist/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ def build(root, output, *, help_image=None):
|
|||||||
"requirements.txt", "config_migration.py",
|
"requirements.txt", "config_migration.py",
|
||||||
):
|
):
|
||||||
files[name] = (integration / name).read_bytes()
|
files[name] = (integration / name).read_bytes()
|
||||||
|
# 真寻市场静态读取版本字段,生成时写入字面量而非运行时变量。
|
||||||
|
marker = b'"__FARM_VERSION__"'
|
||||||
|
if files['__init__.py'].count(marker) != 1:
|
||||||
|
raise ValueError('Zhenxun entry requires one version placeholder')
|
||||||
|
files['__init__.py'] = files['__init__.py'].replace(
|
||||||
|
marker, json.dumps(version).encode('ascii'),
|
||||||
|
)
|
||||||
files["runtime.py"] = (
|
files["runtime.py"] = (
|
||||||
root / "integrations/shared/runtime.py"
|
root / "integrations/shared/runtime.py"
|
||||||
).read_bytes()
|
).read_bytes()
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ def main():
|
|||||||
token = sync["steps"][-1]["env"]["FARM_RELEASE_TOKEN"]
|
token = sync["steps"][-1]["env"]["FARM_RELEASE_TOKEN"]
|
||||||
if token != "${{ secrets.FARM_RELEASE_TOKEN }}":
|
if token != "${{ secrets.FARM_RELEASE_TOKEN }}":
|
||||||
raise ValueError("NoneBot must reuse FARM_RELEASE_TOKEN")
|
raise ValueError("NoneBot must reuse FARM_RELEASE_TOKEN")
|
||||||
|
if '--release-tag' not in sync['steps'][-1]['run'].split():
|
||||||
|
raise ValueError('NoneBot sync must create its version release tag')
|
||||||
generated = yaml.load((
|
generated = yaml.load((
|
||||||
root / "release/nonebot/verify.yml"
|
root / "release/nonebot/verify.yml"
|
||||||
).read_text(encoding="utf-8"), Loader=yaml.BaseLoader)
|
).read_text(encoding="utf-8"), Loader=yaml.BaseLoader)
|
||||||
@@ -113,6 +115,21 @@ def main():
|
|||||||
"github.repository == 'Shu-Ying/nonebot_plugin_farm'"
|
"github.repository == 'Shu-Ying/nonebot_plugin_farm'"
|
||||||
)):
|
)):
|
||||||
raise ValueError("NoneBot PyPI boundary differs")
|
raise ValueError("NoneBot PyPI boundary differs")
|
||||||
|
for workflow, job in ((generated, 'verify'), (publishing, 'build')):
|
||||||
|
steps = workflow['jobs'][job]['steps']
|
||||||
|
if not any(step.get('run') == (
|
||||||
|
'python -m build --sdist --wheel --outdir dist'
|
||||||
|
) for step in steps):
|
||||||
|
raise ValueError('NoneBot distributions must build inside dist')
|
||||||
|
upload = next(step for step in steps
|
||||||
|
if step.get('uses', '').startswith(
|
||||||
|
'actions/upload-artifact@'))
|
||||||
|
if (upload['with']['path'] != 'dist/*'
|
||||||
|
or upload['with'].get('if-no-files-found') != 'error'):
|
||||||
|
raise ValueError('NoneBot upload path must be dist/*')
|
||||||
|
if not any(step.get('run') == 'python -m twine check dist/*'
|
||||||
|
for step in publishing['jobs']['build']['steps']):
|
||||||
|
raise ValueError('NoneBot distribution check must use dist/*')
|
||||||
steps = publishing["jobs"]["publish"]["steps"]
|
steps = publishing["jobs"]["publish"]["steps"]
|
||||||
if steps[-1]["with"]["password"] != "${{ secrets.PYPI_TOKEN }}":
|
if steps[-1]["with"]["password"] != "${{ secrets.PYPI_TOKEN }}":
|
||||||
raise ValueError("Keep the existing PyPI secret")
|
raise ValueError("Keep the existing PyPI secret")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import argparse
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from urllib.error import HTTPError
|
from urllib.error import HTTPError
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
@@ -37,13 +38,19 @@ def github(method, path, payload, token, allow_missing=False):
|
|||||||
) from None
|
) from None
|
||||||
|
|
||||||
|
|
||||||
def publish(package, confirmed=False, request=None):
|
def publish(package, confirmed=False, request=None, *, release_tag=False):
|
||||||
files = read_package(package)
|
files = read_package(package)
|
||||||
manifest = verify_files(files)
|
manifest = verify_files(files)
|
||||||
|
if not re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9.-]+)?',
|
||||||
|
manifest['version']):
|
||||||
|
raise ValueError('Invalid release version in version.txt')
|
||||||
message = commit_message(manifest)
|
message = commit_message(manifest)
|
||||||
result = {"repository": REPOSITORY, "branch": BRANCH,
|
result = {"repository": REPOSITORY, "branch": BRANCH,
|
||||||
"version": manifest["version"], "message": message,
|
"version": manifest["version"], "message": message,
|
||||||
"published": False}
|
"published": False}
|
||||||
|
tag = 'v' + manifest['version']
|
||||||
|
if release_tag:
|
||||||
|
result.update(tag=tag, tag_created=False)
|
||||||
if not confirmed:
|
if not confirmed:
|
||||||
return result
|
return result
|
||||||
if manifest["source"]["dirty"]:
|
if manifest["source"]["dirty"]:
|
||||||
@@ -53,10 +60,23 @@ def publish(package, confirmed=False, request=None):
|
|||||||
raise ValueError("Configure FARM_RELEASE_TOKEN in a protected job")
|
raise ValueError("Configure FARM_RELEASE_TOKEN in a protected job")
|
||||||
call = request or (lambda method, path, data: github(
|
call = request or (lambda method, path, data: github(
|
||||||
method, path, data, token,
|
method, path, data, token,
|
||||||
allow_missing=path.startswith(("git/ref/heads/", "contents/")),
|
allow_missing=path.startswith((
|
||||||
|
"git/ref/heads/", "git/ref/tags/", "contents/",
|
||||||
|
)),
|
||||||
))
|
))
|
||||||
if not call("GET", "", None):
|
if not call("GET", "", None):
|
||||||
raise ValueError(f"Target repository is not accessible: {REPOSITORY}")
|
raise ValueError(f"Target repository is not accessible: {REPOSITORY}")
|
||||||
|
if release_tag and call('GET', f'git/ref/tags/{tag}', None):
|
||||||
|
owned = call('GET', f'contents/FARM-MANIFEST.json?ref={tag}', None)
|
||||||
|
if owned is None or owned.get('encoding') != 'base64':
|
||||||
|
raise ValueError('Existing release is not managed; bump version.txt')
|
||||||
|
previous = json.loads(base64.b64decode(owned['content']))
|
||||||
|
if (previous.get('target') != 'nonebot'
|
||||||
|
or previous.get('plugin') != manifest['plugin']
|
||||||
|
or previous.get('version') != manifest['version']
|
||||||
|
or previous.get('files') != manifest['files']):
|
||||||
|
raise ValueError('Release tag has different files; bump version.txt')
|
||||||
|
return {**result, 'tag_reused': True}
|
||||||
ref = call("GET", f"git/ref/heads/{BRANCH}", None)
|
ref = call("GET", f"git/ref/heads/{BRANCH}", None)
|
||||||
parent = ref["object"]["sha"] if ref else None
|
parent = ref["object"]["sha"] if ref else None
|
||||||
if parent:
|
if parent:
|
||||||
@@ -92,15 +112,25 @@ def publish(package, confirmed=False, request=None):
|
|||||||
call("POST", "git/refs", {
|
call("POST", "git/refs", {
|
||||||
"ref": f"refs/heads/{BRANCH}", "sha": commit["sha"],
|
"ref": f"refs/heads/{BRANCH}", "sha": commit["sha"],
|
||||||
})
|
})
|
||||||
return {**result, "published": True, "commit": commit["sha"]}
|
result.update(published=True, commit=commit['sha'])
|
||||||
|
if release_tag:
|
||||||
|
# 仅创建不可变标签;失败保留已同步main,重跑时重新检查,不移动旧标签。
|
||||||
|
call('POST', 'git/refs', {
|
||||||
|
'ref': f'refs/tags/{tag}', 'sha': commit['sha'],
|
||||||
|
})
|
||||||
|
result['tag_created'] = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("package")
|
parser.add_argument("package")
|
||||||
parser.add_argument("--publish", action="store_true")
|
parser.add_argument("--publish", action="store_true")
|
||||||
|
parser.add_argument('--release-tag', action='store_true',
|
||||||
|
help='同步后创建v版本标签,触发目标仓库PyPI发布')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print(json.dumps(publish(args.package, args.publish), ensure_ascii=False))
|
print(json.dumps(publish(args.package, args.publish,
|
||||||
|
release_tag=args.release_tag), ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""独立验证生成的真寻包;仅标准库,不导入或启动插件。"""
|
"""独立验证生成的真寻包;仅标准库,不导入或启动插件。"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import ast
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
@@ -45,6 +46,16 @@ def verify_files(files):
|
|||||||
raise ValueError('Resource ZIP must not be in plugin repository')
|
raise ValueError('Resource ZIP must not be in plugin repository')
|
||||||
if not required <= set(files):
|
if not required <= set(files):
|
||||||
raise ValueError("Incomplete plugin")
|
raise ValueError("Incomplete plugin")
|
||||||
|
tree = ast.parse(files['__init__.py'])
|
||||||
|
versions = [
|
||||||
|
keyword.value for node in ast.walk(tree)
|
||||||
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
||||||
|
and node.func.id == 'PluginExtraData'
|
||||||
|
for keyword in node.keywords if keyword.arg == 'version'
|
||||||
|
]
|
||||||
|
if (len(versions) != 1 or not isinstance(versions[0], ast.Constant)
|
||||||
|
or versions[0].value != manifest['version']):
|
||||||
|
raise ValueError('PluginExtraData requires matching literal version')
|
||||||
return manifest
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ class AstrBotPackageTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertEqual(plugin.runtime.data_dir,
|
self.assertEqual(plugin.runtime.data_dir,
|
||||||
(self.root / 'existing-custom-data').resolve())
|
(self.root / 'existing-custom-data').resolve())
|
||||||
self.assertIsNone(plugin.runtime.font_path)
|
self.assertIsNone(plugin.runtime.font_path)
|
||||||
|
self.assertEqual(plugin.runtime.resource_source, 'Github')
|
||||||
# 使用真实开发素材预置缓存,模拟宿主测试不访问网络。
|
# 使用真实开发素材预置缓存,模拟宿主测试不访问网络。
|
||||||
manifest = json.loads((ROOT /
|
manifest = json.loads((ROOT /
|
||||||
'src/farm_render/data/asset-manifest.json').read_bytes())
|
'src/farm_render/data/asset-manifest.json').read_bytes())
|
||||||
|
|||||||
@@ -165,3 +165,59 @@ class NoneBotPackageTests(unittest.TestCase):
|
|||||||
with self.assertRaises((ValueError, RuntimeError)):
|
with self.assertRaises((ValueError, RuntimeError)):
|
||||||
publish(bundle, True, request)
|
publish(bundle, True, request)
|
||||||
self.assertFalse(any(method == "PATCH" for method, _, _ in calls))
|
self.assertFalse(any(method == "PATCH" for method, _, _ in calls))
|
||||||
|
|
||||||
|
def test_release_tag_is_created_after_main_sync(self):
|
||||||
|
bundle, manifest = self.clean_package()
|
||||||
|
calls = []
|
||||||
|
def request(method, path, data):
|
||||||
|
calls.append((method, path, data))
|
||||||
|
if path == '':
|
||||||
|
return {'default_branch': 'main'}
|
||||||
|
if path.startswith('git/ref/tags/'):
|
||||||
|
return None
|
||||||
|
if path.startswith('git/ref/heads/'):
|
||||||
|
return {'object': {'sha': 'existing'}}
|
||||||
|
if path.startswith('contents/'):
|
||||||
|
return None
|
||||||
|
return {'sha': 'generated'}
|
||||||
|
with patch.dict('os.environ', {'FARM_RELEASE_TOKEN': 'test'}):
|
||||||
|
result = publish(bundle, True, request, release_tag=True)
|
||||||
|
self.assertTrue(result['tag_created'])
|
||||||
|
self.assertEqual(calls[-1], ('POST', 'git/refs', {
|
||||||
|
'ref': 'refs/tags/v' + manifest['version'], 'sha': 'generated',
|
||||||
|
}))
|
||||||
|
self.assertLess(next(i for i, call in enumerate(calls)
|
||||||
|
if call[0] == 'PATCH'), len(calls) - 1)
|
||||||
|
|
||||||
|
def test_existing_release_tag_is_reused_or_conflict_blocks_all_writes(self):
|
||||||
|
bundle, manifest = self.clean_package()
|
||||||
|
for matching in (True, False):
|
||||||
|
calls = []
|
||||||
|
previous = json.loads(json.dumps(manifest))
|
||||||
|
if not matching:
|
||||||
|
previous['files']['README.md'] = '0' * 64
|
||||||
|
def request(method, path, data):
|
||||||
|
calls.append((method, path, data))
|
||||||
|
if path == '':
|
||||||
|
return {'default_branch': 'main'}
|
||||||
|
if path.startswith('git/ref/tags/'):
|
||||||
|
return {'object': {'sha': 'released'}}
|
||||||
|
if path.startswith('contents/'):
|
||||||
|
return {'encoding': 'base64', 'content': base64.b64encode(
|
||||||
|
json.dumps(previous).encode(),
|
||||||
|
).decode()}
|
||||||
|
self.fail('Unexpected request: ' + path)
|
||||||
|
with patch.dict('os.environ', {'FARM_RELEASE_TOKEN': 'test'}):
|
||||||
|
if matching:
|
||||||
|
result = publish(bundle, True, request, release_tag=True)
|
||||||
|
self.assertFalse(result['tag_created'])
|
||||||
|
else:
|
||||||
|
with self.assertRaisesRegex(ValueError, 'version.txt'):
|
||||||
|
publish(bundle, True, request, release_tag=True)
|
||||||
|
self.assertTrue(all(method == 'GET' for method, _, _ in calls))
|
||||||
|
|
||||||
|
def test_release_tag_dry_run_does_not_access_remote(self):
|
||||||
|
result = publish(self.bundle, release_tag=True,
|
||||||
|
request=lambda *args: self.fail('Network in dry run'))
|
||||||
|
self.assertEqual(result['tag'], 'v' + result['version'])
|
||||||
|
self.assertFalse(result['tag_created'])
|
||||||
|
|||||||
@@ -157,6 +157,11 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
self.assertEqual(archive.read(
|
self.assertEqual(archive.read(
|
||||||
"zhenxun_plugin_farm_preview/_version.py",
|
"zhenxun_plugin_farm_preview/_version.py",
|
||||||
), b'__version__ = "1.2.3-preview.4"\n')
|
), b'__version__ = "1.2.3-preview.4"\n')
|
||||||
|
entry = archive.read(
|
||||||
|
'zhenxun_plugin_farm_preview/__init__.py',
|
||||||
|
).decode()
|
||||||
|
self.assertIn('version="1.2.3-preview.4"', entry)
|
||||||
|
self.assertNotIn('__version__', entry)
|
||||||
|
|
||||||
def clean_package(self):
|
def clean_package(self):
|
||||||
from verify_zhenxun import read_package, MANIFEST, ROOT_NAME
|
from verify_zhenxun import read_package, MANIFEST, ROOT_NAME
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
2.0.4
|
2.0.5
|
||||||
Reference in New Issue
Block a user