Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
bf8fcf1
[work-enter] [work-enter] [codex] docs: add publish plugin extension …
wk1995 Jul 6, 2026
2bebe42
[work-enter] [work-enter] [codex] 完善发布 demo 模块
wk1995 Jul 6, 2026
0b0f8cb
[work-enter] [work-enter] [codex] 优化 PublishInfo 文档说明
wk1995 Jul 6, 2026
723df21
[work-enter] [work-enter] [codex] 增加 PublishInfo 默认值和 SCM 推导
wk1995 Jul 6, 2026
e39d601
[work-enter] [work-enter] [codex] 补充 Central 接入注意事项
wk1995 Jul 6, 2026
158a114
[work-enter] [work-enter] [codex] docs: add Vanniktech comparison
wk1995 Jul 7, 2026
7818e1c
[work-enter] [work-enter] [codex] add central publish one click confi…
wk1995 Jul 7, 2026
50b8b28
[work-enter] [work-enter] [codex] update one-click config plan local …
wk1995 Jul 7, 2026
f197527
[work-enter] [work-enter] [codex] implement central one-click config
wk1995 Jul 7, 2026
60e8372
[work-enter] [codex] Bump publish plugin version to 1.2.2
github-actions[bot] Jul 7, 2026
efc1fb5
[work-enter] [work-enter] [codex] fix central publish one-click revie…
wk1995 Jul 7, 2026
15eba2d
[work-enter] [work-enter] [codex] add central publish one-click skill
wk1995 Jul 7, 2026
11b2ee9
[work-enter] [work-enter] [codex] narrow publish skill trigger
wk1995 Jul 7, 2026
99fcdc1
[work-enter] [work-enter] [codex] append local suffix for local publish
wk1995 Jul 7, 2026
4a9ad30
[work-enter] [work-enter] [codex] fix central publish config comment …
wk1995 Jul 7, 2026
75872a0
[work-enter] [work-enter] [codex] prefer project git remote for scm i…
wk1995 Jul 7, 2026
d8a7774
[work-enter] [work-enter] [codex] Add Codex skill sync installer
wk1995 Jul 7, 2026
5168879
[work-enter] [work-enter] [codex] 支持发布到 GitHub Packages
wk1995 Jul 22, 2026
d328e28
[work-enter] [work-enter] [codex] 支持按 variant 自定义坐标
wk1995 Jul 22, 2026
27e0e9c
[work-enter] [work-enter] [codex] 默认发布到 GitHub Packages
wk1995 Jul 22, 2026
5a6f933
[work-enter] [work-enter] [codex] 增加 demo 文件跳转链接
wk1995 Jul 22, 2026
862f2a7
[work-enter] [work-enter] [codex] 修正文档示例表达
wk1995 Jul 22, 2026
1044f73
[work-enter] [work-enter] [codex] 增加 Central 一键配置离线脚本
wk1995 Jul 22, 2026
3374734
[work-enter] [work-enter] [codex] 调整快速开始发布入口
wk1995 Jul 22, 2026
8970d77
[work-enter] [work-enter] [codex] 优化快速开始配置结构
wk1995 Jul 22, 2026
f05efce
[work-enter] [work-enter] [codex] document one-click publish configur…
wk1995 Jul 22, 2026
10fd7bb
[work-enter] [work-enter] [codex] document GitHub Packages publishing
wk1995 Jul 22, 2026
357c591
[work-enter] [work-enter] [codex] 优化 PublishPlugin README 快速开始
wk1995 Jul 22, 2026
50fcccf
[work-enter] [work-enter] [codex] 修复发布版本 CLI 覆盖
wk1995 Jul 22, 2026
cf153f7
[work-enter] [work-enter] [codex] 支持 workflow 多发布目标
wk1995 Jul 22, 2026
1b82d2a
[work-enter] [work-enter] [codex] 替换 signing key id 测试邮箱占位
wk1995 Jul 22, 2026
74c030e
[work-enter] [work-enter] [codex] Rename one-click publish workflow
wk1995 Jul 22, 2026
41b3bfe
[work-enter] [work-enter] [codex] docs: update publish plugin roadmap
wk1995 Jul 22, 2026
624cdea
[work-enter] [work-enter] [codex] 更新一键发布 skill 规则
wk1995 Jul 22, 2026
5e763d6
[work-enter] [work-enter] [codex] configure demo publish workflows
wk1995 Jul 22, 2026
4e0f9d1
[work-enter] [work-enter] [codex] fail ambiguous GitHub Packages remotes
wk1995 Jul 22, 2026
cd94531
[work-enter] [work-enter] [codex] fix duplicate flavor publication co…
wk1995 Jul 22, 2026
6ab15a4
[work-enter] [work-enter] [codex] keep base publication name for sing…
wk1995 Jul 22, 2026
9a10b89
[work-enter] [work-enter] [codex] docs: describe recommended publish …
wk1995 Jul 22, 2026
70b6d27
[work-enter] [work-enter] [codex] clarify GitHub Packages publish URLs
wk1995 Jul 22, 2026
bf94f5d
[work-enter] [work-enter] [codex] print GitHub Packages web URL
wk1995 Jul 22, 2026
4d7d772
[work-enter] [work-enter] update config
wk1995 Jul 22, 2026
da6a19c
[work-enter] [work-enter] [codex] unify publish target selection
wk1995 Jul 22, 2026
f479d2c
[work-enter] [work-enter] [codex] Sync README for GitHub Packages pub…
wk1995 Jul 22, 2026
4c2832f
[work-enter] [work-enter] [codex] Support CI snapshot publish workflow
wk1995 Jul 22, 2026
627509e
[work-enter] [work-enter] [codex] Configure demo-lib CI Central snaps…
wk1995 Jul 22, 2026
637e81e
[work-enter] [codex] Sync root build publish plugin version after rel…
wk1995 Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/normalize_signing_key_id_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_converts_fingerprint_to_long_key_id(self):
)

def test_rejects_non_hex_values(self):
self.assertEqual(normalize_signing_key_id("wk1995 <1226426603@qq.com>"), "")
self.assertEqual(normalize_signing_key_id("Developer <dev@example.com>"), "")
self.assertEqual(normalize_signing_key_id("not-a-key-id"), "")

def test_shell_exports_clear_all_gradle_key_id_aliases_for_invalid_value(self):
Expand Down
24 changes: 24 additions & 0 deletions .github/scripts/publish_plugin_central_workflow_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,30 @@ def test_central_publish_is_gated_by_pre_publish_vs_main_version(self):
self.assertIn("== \"true\"", merge)
self.assertIn("does not require Central publish; merging directly", merge)

def test_release_sync_updates_readme_and_root_build_after_publish(self):
sync = step_block("Sync README release version")

self.assertIn("--root-build-file build.gradle.kts", sync)
self.assertIn("README.md build.gradle.kts", sync)
self.assertIn("git add plugin_base/build.gradle.kts README.md build.gradle.kts", sync)
self.assertIn("Update publish plugin usage version", sync)

def test_ci_mode_publishes_plugin_snapshot_without_release_steps(self):
text = workflow_text()
snapshot = step_block("Publish snapshot to Central snapshots")
resolve = step_block("Resolve snapshot version")

self.assertIn("publish_mode:", text)
self.assertIn("github.event.inputs.publish_mode != 'ci'", text)
self.assertIn("ci_snapshot:", text)
self.assertIn("github.event.inputs.publish_mode == 'ci'", text)
self.assertIn('snapshot_version="${base_version}-SNAPSHOT"', resolve)
self.assertIn("plugin_base/build.gradle.kts", resolve)
self.assertIn("publishAllPublicationsToCentralSnapshotsRepository", snapshot)
self.assertIn("-PcentralReleaseType=snapshot", snapshot)
self.assertNotIn("sync_readme_publish_version.py", snapshot)
self.assertNotIn("git tag", snapshot)


class PublishPluginPrCheckWorkflowTest(unittest.TestCase):
def test_version_bump_only_runs_when_plugin_base_changed(self):
Expand Down
99 changes: 99 additions & 0 deletions .github/scripts/reusable_publish_workflow_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import re
import unittest
from pathlib import Path


WORKFLOW = Path(__file__).resolve().parents[1] / "workflows" / "publish.yml"


def workflow_text():
return WORKFLOW.read_text(encoding="utf-8")


def step_block(name):
text = workflow_text()
match = re.search(
rf"(?ms)^ - name: {re.escape(name)}\n(?P<body>.*?)(?=^ - name: |\Z)",
text,
)
if match is None:
raise AssertionError(f"Missing workflow step: {name}")
return match.group("body")


class ReusablePublishWorkflowTest(unittest.TestCase):
def test_publish_target_input_supports_three_modes(self):
text = workflow_text()
validation = step_block("Validate publish inputs")

self.assertIn("publish_target:", text)
self.assertIn('default: "github_packages"', text)
self.assertIn("publish_mode:", text)
self.assertIn("sync_readme:", text)
self.assertIn("contents: write", text)
self.assertIn("central|github_packages|all", validation)
self.assertIn("publish_target must be central, github_packages, or all", validation)
self.assertIn("publish_mode must be release or ci", validation)
self.assertIn("publish_mode=ci only supports publish_target=central", validation)

def test_publish_version_resolution_supports_ci_snapshots(self):
resolve = step_block("Resolve publish version")

self.assertIn('effective_version="${effective_version}-SNAPSHOT"', resolve)
self.assertIn('central_release_type="snapshot"', resolve)
self.assertIn('sync_readme_effective=false', resolve)
self.assertIn("publish_mode=release must not use a -SNAPSHOT version", resolve)
self.assertIn("EFFECTIVE_PUBLISH_VERSION", resolve)
self.assertIn("CENTRAL_RELEASE_TYPE", resolve)

def test_github_packages_publish_step_is_target_gated(self):
publish = step_block("Publish to GitHub Packages")

self.assertIn("inputs.publish_target == 'github_packages'", publish)
self.assertIn("inputs.publish_target == 'all'", publish)
self.assertIn("-PpublishTarget=github_packages", publish)
self.assertIn("-PgithubPackagesRepository=${GITHUB_PACKAGES_REPOSITORY}", publish)
self.assertIn("-PgithubPackagesUrl=${GITHUB_PACKAGES_URL}", publish)
self.assertIn("-PpublishVersion=${EFFECTIVE_PUBLISH_VERSION}", publish)

def test_release_publish_can_sync_readme(self):
sync = step_block("Sync README for release")
commit = step_block("Commit README sync")

self.assertIn("inputs.sync_readme", sync)
self.assertIn("inputs.publish_mode == 'release'", sync)
self.assertIn("inputs.version != ''", sync)
self.assertIn("README_GITHUB_PACKAGES_URL", sync)
self.assertIn("maven.pkg.github.com", sync)
self.assertIn("cn.entertech.android:publish:{version}", sync)
self.assertIn("buildscript", sync)
self.assertIn("mavenCentral", sync)
self.assertIn("mavenLocal", sync)
self.assertIn("git commit -m \"[codex] Sync README publish config to ${EFFECTIVE_PUBLISH_VERSION} [skip ci]\"", commit)
self.assertIn("git push", commit)

def test_central_publish_step_is_target_gated(self):
publish = step_block("Publish to Central Portal")

self.assertIn("inputs.publish_target == 'central'", publish)
self.assertIn("inputs.publish_target == 'all'", publish)
self.assertIn("-PpublishTarget=central", publish)
self.assertIn("-PcentralReleaseType=${CENTRAL_RELEASE_TYPE}", publish)
self.assertIn("-PcentralNamespace=${CENTRAL_NAMESPACE}", publish)
self.assertIn("-PcentralPublishingType=${CENTRAL_PUBLISHING_TYPE}", publish)
self.assertIn("-PpublishVersion=${EFFECTIVE_PUBLISH_VERSION}", publish)

def test_central_secrets_are_conditionally_required(self):
text = workflow_text()
validation = step_block("Validate publish inputs")

self.assertIn("MAVEN_CENTRAL_USERNAME:\n required: false", text)
self.assertIn("GPG_KEY_CONTENTS:\n required: false", text)
self.assertIn("PUBLISH_TARGET\" == \"central\"", validation)
self.assertIn("PUBLISH_TARGET\" == \"all\"", validation)
self.assertIn("CENTRAL_USERNAME", validation)
self.assertIn("SIGNING_PASSWORD", validation)


if __name__ == "__main__":
unittest.main()
107 changes: 101 additions & 6 deletions .github/scripts/sync_readme_publish_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
BUILD_VERSION_RE = re.compile(r"(?m)^\s*version\s*=\s*['\"]([^'\"]+)['\"]")
PLUGIN_REPOSITORY_BLOCK_RE = re.compile(
r"(?ms)(buildscript\s*\{\s*repositories\s*\{\n)(?P<body>.*?)(\n\s*\}\s*\n\s*\n\s*dependencies\s*\{\s*classpath\(\"cn\.entertech\.android:publish:)"
)
PUBLISH_CLASSPATH_RE = re.compile(r'classpath\("cn\.entertech\.android:publish:[^"]+"\)')


def read_plugin_version(build_file: Path) -> str:
Expand All @@ -16,7 +20,61 @@ def read_plugin_version(build_file: Path) -> str:
return match.group(1)


def sync_readme_publish_version(content: str, version: str) -> str:
def github_packages_url_from_repository(repository: str) -> str:
if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository):
raise ValueError("GitHub Packages repository must use owner/repo format")
return f"https://maven.pkg.github.com/{repository}"


def normalize_publish_target(publish_target: str) -> str:
normalized = (publish_target or "central").strip().lower().replace("-", "_")
if normalized == "githubpackages":
normalized = "github_packages"
if normalized not in {"central", "github_packages", "all"}:
raise ValueError("publish_target must be central, github_packages, or all")
return normalized


def sync_readme_code_repository(content: str, publish_target: str, github_packages_url: str = "") -> str:
normalized_target = normalize_publish_target(publish_target)
if normalized_target == "central":
repository_lines = [
" google()",
" mavenCentral()",
" mavenLocal()",
]
else:
if not github_packages_url:
raise ValueError("github_packages_url is required when publish_target includes github_packages")
repository_lines = [
" google()",
" mavenLocal()",
" maven {",
f" url = uri(\"{github_packages_url}\")",
" credentials {",
" username = providers.gradleProperty(\"gpr.user\").orNull",
" ?: System.getenv(\"GITHUB_ACTOR\")",
" password = providers.gradleProperty(\"gpr.key\").orNull",
" ?: System.getenv(\"GITHUB_TOKEN\")",
" }",
" }",
]

def replace(match: re.Match) -> str:
return match.group(1) + "\n".join(repository_lines) + match.group(3)

updated, count = PLUGIN_REPOSITORY_BLOCK_RE.subn(replace, content, count=1)
if count != 1:
raise ValueError("Cannot find README code configuration buildscript repository block")
return updated


def sync_readme_publish_version(
content: str,
version: str,
publish_target: str = "central",
github_packages_url: str = "",
) -> str:
if not SEMVER_RE.fullmatch(version):
raise ValueError(f"Publish plugin version '{version}' must use digits.digits.digits format")

Expand All @@ -42,33 +100,70 @@ def sync_readme_publish_version(content: str, version: str) -> str:
next_content = content
for pattern, replacement in replacements:
next_content = re.sub(pattern, replacement, next_content)
next_content = sync_readme_code_repository(next_content, publish_target, github_packages_url)
return next_content


def sync_root_build_publish_version(content: str, version: str) -> str:
if not SEMVER_RE.fullmatch(version):
raise ValueError(f"Publish plugin version '{version}' must use digits.digits.digits format")

updated, count = PUBLISH_CLASSPATH_RE.subn(
f'classpath("cn.entertech.android:publish:{version}")',
content,
count=1,
)
if count != 1:
raise ValueError("Cannot find root build publish plugin classpath")
return updated


def main() -> None:
parser = argparse.ArgumentParser(description="Synchronize README publish plugin version.")
parser.add_argument("--readme", default="README.md")
parser.add_argument("--build-file", default="plugin_base/build.gradle.kts")
parser.add_argument("--root-build-file", default="")
parser.add_argument("--version")
parser.add_argument("--publish-target", default="central", choices=("central", "github_packages", "all"))
parser.add_argument("--github-packages-repository", default="")
parser.add_argument("--github-packages-url", default="")
parser.add_argument("--check", action="store_true")
args = parser.parse_args()

readme_path = Path(args.readme)
version = args.version or read_plugin_version(Path(args.build_file))
github_packages_url = args.github_packages_url
if not github_packages_url and args.github_packages_repository:
github_packages_url = github_packages_url_from_repository(args.github_packages_repository)
current = readme_path.read_text(encoding="utf-8")
updated = sync_readme_publish_version(current, version)
updated = sync_readme_publish_version(
current,
version,
publish_target=args.publish_target,
github_packages_url=github_packages_url,
)

if args.check:
if updated != current:
raise SystemExit(f"{readme_path} publish plugin version is not synchronized to {version}")
print(f"{readme_path} publish plugin version is synchronized to {version}")
raise SystemExit(f"{readme_path} publish plugin docs are not synchronized to {version}")
print(f"{readme_path} publish plugin docs are synchronized to {version}")
return

if updated != current:
readme_path.write_text(updated, encoding="utf-8")
print(f"Updated {readme_path} publish plugin version to {version}")
print(f"Updated {readme_path} publish plugin docs to {version}")
else:
print(f"{readme_path} publish plugin version already {version}")
print(f"{readme_path} publish plugin docs already {version}")

if args.root_build_file:
root_build_path = Path(args.root_build_file)
root_current = root_build_path.read_text(encoding="utf-8")
root_updated = sync_root_build_publish_version(root_current, version)
if root_updated != root_current:
root_build_path.write_text(root_updated, encoding="utf-8")
print(f"Updated {root_build_path} publish plugin classpath to {version}")
else:
print(f"{root_build_path} publish plugin classpath already {version}")


if __name__ == "__main__":
Expand Down
86 changes: 82 additions & 4 deletions .github/scripts/sync_readme_publish_version_test.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import unittest

from sync_readme_publish_version import sync_readme_publish_version
from sync_readme_publish_version import (
github_packages_url_from_repository,
sync_readme_publish_version,
sync_root_build_publish_version,
)


README_CODE_CONFIG = """
buildscript {
repositories {
google()
mavenCentral()
mavenLocal()
}

dependencies {
classpath("cn.entertech.android:publish:1.2.0")
}
}
"""


class SyncReadmePublishVersionTest(unittest.TestCase):
def test_updates_publish_plugin_versions_only(self):
readme = """
classpath("cn.entertech.android:publish:1.2.0")
readme = README_CODE_CONFIG + """
cn.entertech.android:publish:<version>
cn.entertech.publish:cn.entertech.publish.gradle.plugin:<version>
id("cn.entertech.publish") version "<version>"
Expand All @@ -23,9 +41,69 @@ def test_updates_publish_plugin_versions_only(self):
self.assertIn('id("cn.entertech.publish") version "1.2.3"', result)
self.assertIn('version = "1.0.0"', result)

def test_keeps_maven_central_code_config_for_central_publish(self):
result = sync_readme_publish_version(README_CODE_CONFIG, "1.2.3", publish_target="central")

self.assertIn(" mavenCentral()", result)
self.assertNotIn("maven.pkg.github.com", result)

def test_updates_code_config_for_github_packages_publish(self):
result = sync_readme_publish_version(
README_CODE_CONFIG,
"1.2.3",
publish_target="github_packages",
github_packages_url="https://maven.pkg.github.com/owner/repo",
)

self.assertNotIn(" mavenCentral()", result)
self.assertIn('url = uri("https://maven.pkg.github.com/owner/repo")', result)
self.assertIn('username = providers.gradleProperty("gpr.user").orNull', result)
self.assertIn('password = providers.gradleProperty("gpr.key").orNull', result)
self.assertIn('classpath("cn.entertech.android:publish:1.2.3")', result)

def test_updates_code_config_for_all_publish(self):
result = sync_readme_publish_version(
README_CODE_CONFIG,
"1.2.3",
publish_target="all",
github_packages_url="https://maven.pkg.github.com/owner/repo",
)

self.assertNotIn(" mavenCentral()", result)
self.assertIn('url = uri("https://maven.pkg.github.com/owner/repo")', result)

def test_requires_github_packages_url_when_target_includes_github_packages(self):
with self.assertRaisesRegex(ValueError, "github_packages_url is required"):
sync_readme_publish_version(README_CODE_CONFIG, "1.2.3", publish_target="github_packages")

def test_builds_github_packages_url_from_repository(self):
self.assertEqual(
"https://maven.pkg.github.com/owner/repo",
github_packages_url_from_repository("owner/repo"),
)

def test_rejects_non_semver(self):
with self.assertRaisesRegex(ValueError, "digits.digits.digits"):
sync_readme_publish_version("README", "1.2.3-rc1")
sync_readme_publish_version(README_CODE_CONFIG, "1.2.3-rc1")

def test_updates_root_build_publish_plugin_classpath(self):
build_file = """
buildscript {
dependencies {
classpath("com.android.tools.build:gradle:8.1.3")
classpath("cn.entertech.android:publish:1.2.1")
}
}
"""

result = sync_root_build_publish_version(build_file, "1.2.3")

self.assertIn('classpath("cn.entertech.android:publish:1.2.3")', result)
self.assertIn('classpath("com.android.tools.build:gradle:8.1.3")', result)

def test_rejects_missing_root_build_publish_plugin_classpath(self):
with self.assertRaisesRegex(ValueError, "Cannot find root build publish plugin classpath"):
sync_root_build_publish_version("buildscript {}\n", "1.2.3")


if __name__ == "__main__":
Expand Down
Loading
Loading