mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-11-03 19:20:16 +08:00
fixed # 修复工作流BUG
This commit is contained in:
@@ -6,13 +6,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
|
||||||
description: Preview version
|
|
||||||
default: 0.1.0-preview.1
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
publish:
|
publish:
|
||||||
description: Sync fixed farm-preview branch in zhenxun repository
|
description: Sync fixed main branch in zhenxun repository
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
@@ -54,8 +49,6 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
needs: test
|
needs: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
PREVIEW_VERSION: ${{ inputs.version || '0.1.0-preview.1' }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
@@ -66,7 +59,7 @@ jobs:
|
|||||||
- name: Build selected target only
|
- name: Build selected target only
|
||||||
run: >-
|
run: >-
|
||||||
python scripts/build_zhenxun.py
|
python scripts/build_zhenxun.py
|
||||||
--output dist/zhenxun-preview --version "$PREVIEW_VERSION"
|
--output dist/zhenxun-preview
|
||||||
- name: Copy standalone helper scripts
|
- name: Copy standalone helper scripts
|
||||||
run: |
|
run: |
|
||||||
cp scripts/install_zhenxun.py dist/zhenxun-preview/
|
cp scripts/install_zhenxun.py dist/zhenxun-preview/
|
||||||
|
|||||||
+4
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "farm-core"
|
name = "farm-core"
|
||||||
version = "0.1.0"
|
dynamic = ["version"]
|
||||||
description = "Framework-independent farm foundation"
|
description = "Framework-independent farm foundation"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
@@ -17,6 +17,9 @@ render = ["Pillow>=10.4,<13"]
|
|||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
where = ["src"]
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {file = "version.txt"}
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
farm_core = ["data/*.json"]
|
farm_core = ["data/*.json"]
|
||||||
farm_render = ["data/*.json", "data/*.zip", "data/*.md"]
|
farm_render = ["data/*.json", "data/*.zip", "data/*.md"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: Verify generated farm preview
|
name: Verify generated farm preview
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [farm-preview]
|
branches: [main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -24,10 +24,16 @@ def source_identity(root):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build(root, output, version):
|
def read_version(root):
|
||||||
|
version = (Path(root) / "version.txt").read_text(encoding="utf-8").strip()
|
||||||
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9.-]+)?", version):
|
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9.-]+)?", version):
|
||||||
raise ValueError("Invalid preview version")
|
raise ValueError("Invalid version in root version.txt file")
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def build(root, output):
|
||||||
root, output = Path(root).resolve(), Path(output).resolve()
|
root, output = Path(root).resolve(), Path(output).resolve()
|
||||||
|
version = read_version(root)
|
||||||
if output.exists():
|
if output.exists():
|
||||||
raise FileExistsError("Use a new output directory")
|
raise FileExistsError("Use a new output directory")
|
||||||
if not output.is_relative_to(root / "dist") and output.is_relative_to(root):
|
if not output.is_relative_to(root / "dist") and output.is_relative_to(root):
|
||||||
@@ -63,6 +69,7 @@ def build(root, output, version):
|
|||||||
for name, source in {
|
for name, source in {
|
||||||
"LICENSE": root / "LICENSE",
|
"LICENSE": root / "LICENSE",
|
||||||
"README.md": root / "docs/11-zhenxun-preview.md",
|
"README.md": root / "docs/11-zhenxun-preview.md",
|
||||||
|
"version.txt": root / "version.txt",
|
||||||
"images/farm-preview.png": root / "images/farm-preview.png",
|
"images/farm-preview.png": root / "images/farm-preview.png",
|
||||||
"verify_package.py": root / "scripts/verify_zhenxun.py",
|
"verify_package.py": root / "scripts/verify_zhenxun.py",
|
||||||
".github/workflows/verify.yml": root / "release/zhenxun/verify.yml",
|
".github/workflows/verify.yml": root / "release/zhenxun/verify.yml",
|
||||||
@@ -107,10 +114,9 @@ def build(root, output, version):
|
|||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--output", type=Path, required=True)
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
parser.add_argument("--version", default="0.1.0-preview.1")
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
root = Path(__file__).resolve().parents[1]
|
root = Path(__file__).resolve().parents[1]
|
||||||
print(build(root, args.output, args.version))
|
print(build(root, args.output))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ def main():
|
|||||||
if set(trigger) != {"push", "pull_request", "workflow_dispatch"}:
|
if set(trigger) != {"push", "pull_request", "workflow_dispatch"}:
|
||||||
raise ValueError("Unsafe or missing workflow trigger")
|
raise ValueError("Unsafe or missing workflow trigger")
|
||||||
inputs = trigger["workflow_dispatch"]["inputs"]
|
inputs = trigger["workflow_dispatch"]["inputs"]
|
||||||
|
if set(inputs) != {"publish"}:
|
||||||
|
raise ValueError("Version must come from the root version.txt file")
|
||||||
if inputs["publish"]["default"] != "false":
|
if inputs["publish"]["default"] != "false":
|
||||||
raise ValueError("Publish must be opt-in")
|
raise ValueError("Publish must be opt-in")
|
||||||
sync = source["jobs"]["sync-preview"]
|
sync = source["jobs"]["sync-preview"]
|
||||||
@@ -35,7 +37,7 @@ def main():
|
|||||||
raise ValueError("Publish boundary differs")
|
raise ValueError("Publish boundary differs")
|
||||||
if generated["permissions"] != {"contents":"read"}:
|
if generated["permissions"] != {"contents":"read"}:
|
||||||
raise ValueError("Generated workflow permissions differ")
|
raise ValueError("Generated workflow permissions differ")
|
||||||
if generated["on"]["push"]["branches"] != ["farm-preview"]:
|
if generated["on"]["push"]["branches"] != ["main"]:
|
||||||
raise ValueError("Generated branch differs")
|
raise ValueError("Generated branch differs")
|
||||||
print("Workflow YAML and publication guards checked")
|
print("Workflow YAML and publication guards checked")
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ def publish(package, confirmed=False, request=None):
|
|||||||
repository = call("GET", "", None)
|
repository = call("GET", "", None)
|
||||||
if not repository:
|
if not repository:
|
||||||
raise ValueError(f"Target repository is not accessible: {REPOSITORY}")
|
raise ValueError(f"Target repository is not accessible: {REPOSITORY}")
|
||||||
if repository.get("default_branch") == BRANCH:
|
|
||||||
raise ValueError("Preview must not be the repository default branch")
|
|
||||||
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:
|
||||||
|
|||||||
@@ -60,14 +60,15 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
self.addCleanup(self.temporary.cleanup)
|
self.addCleanup(self.temporary.cleanup)
|
||||||
self.root = Path(self.temporary.name)
|
self.root = Path(self.temporary.name)
|
||||||
self.output = self.root / "bundle"
|
self.output = self.root / "bundle"
|
||||||
self.bundle = build(ROOT, self.output, "0.1.0-preview.1")
|
self.bundle = build(ROOT, self.output)
|
||||||
|
|
||||||
def test_reproducible_private_complete_package(self):
|
def test_reproducible_private_complete_package(self):
|
||||||
other = build(ROOT, self.root / "second", "0.1.0-preview.1")
|
other = build(ROOT, self.root / "second")
|
||||||
self.assertEqual(self.bundle.read_bytes(), other.read_bytes())
|
self.assertEqual(self.bundle.read_bytes(), other.read_bytes())
|
||||||
manifest = verify_package(self.bundle)
|
manifest = verify_package(self.bundle)
|
||||||
self.assertEqual(manifest["target"], "zhenxun")
|
self.assertEqual(manifest["target"], "zhenxun")
|
||||||
self.assertEqual(manifest["version"], "0.1.0-preview.1")
|
self.assertEqual(manifest["version"],
|
||||||
|
(ROOT / "version.txt").read_text().strip())
|
||||||
self.assertIn("dirty", manifest["source"])
|
self.assertIn("dirty", manifest["source"])
|
||||||
with ZipFile(self.bundle) as archive:
|
with ZipFile(self.bundle) as archive:
|
||||||
paths = archive.namelist()
|
paths = archive.namelist()
|
||||||
@@ -80,7 +81,7 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
self.assertIn("from ..farm_core.domain", source)
|
self.assertIn("from ..farm_core.domain", source)
|
||||||
self.assertNotIn("from farm_core.domain", source)
|
self.assertNotIn("from farm_core.domain", source)
|
||||||
with self.assertRaises(FileExistsError):
|
with self.assertRaises(FileExistsError):
|
||||||
build(ROOT, self.output, "0.1.0-preview.1")
|
build(ROOT, self.output)
|
||||||
|
|
||||||
def test_install_and_existing_target_are_safe(self):
|
def test_install_and_existing_target_are_safe(self):
|
||||||
plugins = self.root / "plugins"
|
plugins = self.root / "plugins"
|
||||||
@@ -131,11 +132,29 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
from publish_zhenxun import publish
|
from publish_zhenxun import publish
|
||||||
report = publish(self.bundle)
|
report = publish(self.bundle)
|
||||||
self.assertFalse(report["published"])
|
self.assertFalse(report["published"])
|
||||||
self.assertEqual(report["branch"], "farm-preview")
|
self.assertEqual(report["branch"], "main")
|
||||||
self.assertEqual(report["repository"], "Shu-Ying/zhenxun_plugin_farm")
|
self.assertEqual(report["repository"], "Shu-Ying/zhenxun_plugin_farm")
|
||||||
self.assertNotIn("token", json.dumps(report))
|
self.assertNotIn("token", json.dumps(report))
|
||||||
with self.assertRaises(ValueError):
|
from build_zhenxun import read_version
|
||||||
build(ROOT, self.root / "bad", "bad; echo secret")
|
for value in ("bad; echo secret", "", "1.0.0\n2.0.0"):
|
||||||
|
with self.subTest(value=value):
|
||||||
|
(self.root / "version.txt").write_text(value)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
read_version(self.root)
|
||||||
|
|
||||||
|
def test_version_file_controls_package_version(self):
|
||||||
|
from build_zhenxun import read_version
|
||||||
|
(self.root / "version.txt").write_text("1.2.3-preview.4\n")
|
||||||
|
self.assertEqual(read_version(self.root), "1.2.3-preview.4")
|
||||||
|
with patch("build_zhenxun.read_version",
|
||||||
|
return_value="1.2.3-preview.4"):
|
||||||
|
bundle = build(ROOT, self.root / "versioned")
|
||||||
|
self.assertTrue(bundle.name.endswith("-1.2.3-preview.4.zip"))
|
||||||
|
self.assertEqual(verify_package(bundle)["version"], "1.2.3-preview.4")
|
||||||
|
with ZipFile(bundle) as archive:
|
||||||
|
self.assertEqual(archive.read(
|
||||||
|
"zhenxun_plugin_farm_preview/_version.py",
|
||||||
|
), b'__version__ = "1.2.3-preview.4"\n')
|
||||||
|
|
||||||
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
|
||||||
@@ -180,7 +199,7 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
self.assertTrue(publish(
|
self.assertTrue(publish(
|
||||||
package, True, request,
|
package, True, request,
|
||||||
)["published"])
|
)["published"])
|
||||||
self.assertIn(("PATCH", "git/refs/heads/farm-preview", {
|
self.assertIn(("PATCH", "git/refs/heads/main", {
|
||||||
"sha":"created", "force":False,
|
"sha":"created", "force":False,
|
||||||
}), calls)
|
}), calls)
|
||||||
|
|
||||||
@@ -200,7 +219,7 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
with patch.dict("os.environ", {"FARM_RELEASE_TOKEN": "unit-test"}):
|
with patch.dict("os.environ", {"FARM_RELEASE_TOKEN": "unit-test"}):
|
||||||
self.assertTrue(publish(package, True, request)["published"])
|
self.assertTrue(publish(package, True, request)["published"])
|
||||||
self.assertIn(("POST", "git/refs", {
|
self.assertIn(("POST", "git/refs", {
|
||||||
"ref": "refs/heads/farm-preview", "sha": "created",
|
"ref": "refs/heads/main", "sha": "created",
|
||||||
}), calls)
|
}), calls)
|
||||||
self.assertFalse(any(c[0] == "PATCH" for c in calls))
|
self.assertFalse(any(c[0] == "PATCH" for c in calls))
|
||||||
|
|
||||||
@@ -211,15 +230,20 @@ class ZhenxunPackageTests(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError, "not accessible"):
|
with self.assertRaisesRegex(ValueError, "not accessible"):
|
||||||
publish(package, True, lambda *args: None)
|
publish(package, True, lambda *args: None)
|
||||||
|
|
||||||
def test_symlink_duplicate_and_default_branch_rejected(self):
|
def test_symlink_duplicate_and_unmanaged_branch_rejected(self):
|
||||||
from publish_zhenxun import publish
|
from publish_zhenxun import publish
|
||||||
from zipfile import ZipInfo
|
from zipfile import ZipInfo
|
||||||
package, _ = self.clean_package()
|
package, _ = self.clean_package()
|
||||||
with patch.dict("os.environ", {"FARM_RELEASE_TOKEN":"unit-test"}):
|
with patch.dict("os.environ", {"FARM_RELEASE_TOKEN":"unit-test"}):
|
||||||
with self.assertRaises(ValueError):
|
def unmanaged(method, path, data):
|
||||||
publish(package, True, lambda *args: {
|
if path == "":
|
||||||
"default_branch":"farm-preview",
|
return {"default_branch": "main"}
|
||||||
})
|
if path.startswith("git/ref/"):
|
||||||
|
return {"object": {"sha": "existing"}}
|
||||||
|
return None
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "not generated by Farm"):
|
||||||
|
publish(package, True, unmanaged)
|
||||||
for kind in ("duplicate", "link"):
|
for kind in ("duplicate", "link"):
|
||||||
bad = self.root / (kind + ".zip")
|
bad = self.root / (kind + ".zip")
|
||||||
bad.write_bytes(self.bundle.read_bytes())
|
bad.write_bytes(self.bundle.read_bytes())
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ZhenxunRuntimeTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
self.temporary = tempfile.TemporaryDirectory()
|
self.temporary = tempfile.TemporaryDirectory()
|
||||||
self.root = Path(self.temporary.name)
|
self.root = Path(self.temporary.name)
|
||||||
build(ROOT, self.root / "built", "0.1.0-preview.1")
|
build(ROOT, self.root / "built")
|
||||||
self.package = "farm_fixture"
|
self.package = "farm_fixture"
|
||||||
module = ModuleType(self.package)
|
module = ModuleType(self.package)
|
||||||
module.__path__ = [str(self.root / "built/repository")]
|
module.__path__ = [str(self.root / "built/repository")]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
1.5.3
|
||||||
Reference in New Issue
Block a user