rlsbl 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/package.json +3 -5
  2. package/rlsbl/__init__.py +0 -247
  3. package/rlsbl/__main__.py +0 -4
  4. package/rlsbl/commands/__init__.py +0 -0
  5. package/rlsbl/commands/check.py +0 -229
  6. package/rlsbl/commands/config.py +0 -67
  7. package/rlsbl/commands/discover.py +0 -198
  8. package/rlsbl/commands/init_cmd.py +0 -518
  9. package/rlsbl/commands/pre_push_check.py +0 -46
  10. package/rlsbl/commands/record_gif.py +0 -92
  11. package/rlsbl/commands/release.py +0 -287
  12. package/rlsbl/commands/status.py +0 -76
  13. package/rlsbl/commands/undo.py +0 -74
  14. package/rlsbl/commands/watch.py +0 -122
  15. package/rlsbl/config.py +0 -57
  16. package/rlsbl/registries/__init__.py +0 -5
  17. package/rlsbl/registries/go.py +0 -123
  18. package/rlsbl/registries/npm.py +0 -119
  19. package/rlsbl/registries/pypi.py +0 -171
  20. package/rlsbl/tagging.py +0 -207
  21. package/rlsbl/templates/go/VERSION.tpl +0 -1
  22. package/rlsbl/templates/go/ci.yml.tpl +0 -21
  23. package/rlsbl/templates/go/goreleaser.yml.tpl +0 -25
  24. package/rlsbl/templates/go/publish.yml.tpl +0 -25
  25. package/rlsbl/templates/merged/publish.yml.tpl +0 -30
  26. package/rlsbl/templates/npm/ci.yml.tpl +0 -22
  27. package/rlsbl/templates/npm/publish.yml.tpl +0 -22
  28. package/rlsbl/templates/pypi/ci.yml.tpl +0 -20
  29. package/rlsbl/templates/pypi/publish.yml.tpl +0 -18
  30. package/rlsbl/templates/shared/CHANGELOG.md.tpl +0 -5
  31. package/rlsbl/templates/shared/CLAUDE.md.tpl +0 -20
  32. package/rlsbl/templates/shared/LICENSE.tpl +0 -21
  33. package/rlsbl/templates/shared/claude-settings.json.tpl +0 -3
  34. package/rlsbl/templates/shared/gitignore.tpl +0 -14
  35. package/rlsbl/templates/shared/hooks/post-release.sh.tpl +0 -8
  36. package/rlsbl/templates/shared/hooks/pre-release.sh.tpl +0 -31
  37. package/rlsbl/utils.py +0 -131
@@ -1,123 +0,0 @@
1
- """Go registry adapter for rlsbl.
2
-
3
- Go projects use a VERSION file as the source of truth for rlsbl. GoReleaser
4
- handles the build/publish step triggered by the GitHub Release that rlsbl creates.
5
- """
6
-
7
- import os
8
- import re
9
-
10
- from ..utils import run
11
-
12
- NAME = "go"
13
-
14
- VERSION_FILE = "VERSION"
15
-
16
-
17
- def read_version(dir_path):
18
- """Read version from the VERSION file."""
19
- version_path = os.path.join(dir_path, VERSION_FILE)
20
- if not os.path.exists(version_path):
21
- raise FileNotFoundError(f"No {VERSION_FILE} file found. Run 'rlsbl scaffold' first.")
22
- with open(version_path, "r", encoding="utf-8") as f:
23
- return f.read().strip()
24
-
25
-
26
- def write_version(dir_path, version):
27
- """Write the new version to the VERSION file."""
28
- version_path = os.path.join(dir_path, VERSION_FILE)
29
- tmp_path = version_path + ".tmp"
30
- with open(tmp_path, "w", encoding="utf-8") as f:
31
- f.write(version + "\n")
32
- os.replace(tmp_path, version_path)
33
-
34
-
35
- def get_version_file():
36
- """Returns the filename that holds the version for this registry."""
37
- return VERSION_FILE
38
-
39
-
40
- def get_template_dir():
41
- """Returns path to the go-specific template directory."""
42
- return os.path.join(os.path.dirname(__file__), "..", "templates", "go")
43
-
44
-
45
- def get_shared_template_dir():
46
- """Returns path to the shared template directory."""
47
- return os.path.join(os.path.dirname(__file__), "..", "templates", "shared")
48
-
49
-
50
- def get_template_vars(dir_path):
51
- """Extract template variables from go.mod."""
52
- mod_path = os.path.join(dir_path, "go.mod")
53
- name = ""
54
- if os.path.exists(mod_path):
55
- with open(mod_path) as f:
56
- content = f.read()
57
- match = re.search(r"^module\s+(\S+)", content, re.MULTILINE)
58
- if match:
59
- name = match.group(1)
60
-
61
- # Derive short name from module path (last segment)
62
- short_name = name.rsplit("/", 1)[-1] if "/" in name else name
63
-
64
- # Derive repo name from module path (e.g. "github.com/user/repo")
65
- repo_name = ""
66
- repo_match = re.search(r"github\.com/([^/\s]+/[^/\s]+)", name)
67
- if repo_match:
68
- repo_name = repo_match.group(1)
69
-
70
- # Author from git config
71
- author = ""
72
- try:
73
- author = run("git", ["config", "user.name"])
74
- except Exception:
75
- pass
76
-
77
- try:
78
- version = read_version(dir_path)
79
- except FileNotFoundError:
80
- version = "0.0.0"
81
-
82
- return {
83
- "name": short_name,
84
- "modulePath": name,
85
- "version": version,
86
- "author": author,
87
- "repoName": repo_name,
88
- "binCommand": short_name,
89
- "publishSetup": "GoReleaser handles binary publishing via GitHub Actions (no secrets needed)",
90
- }
91
-
92
-
93
- def get_template_mappings():
94
- """Returns go-specific template mappings (template file -> target path)."""
95
- return [
96
- {"template": "VERSION.tpl", "target": "VERSION"},
97
- {"template": "ci.yml.tpl", "target": ".github/workflows/ci.yml"},
98
- {"template": "publish.yml.tpl", "target": ".github/workflows/publish.yml"},
99
- {"template": "goreleaser.yml.tpl", "target": ".goreleaser.yml"},
100
- ]
101
-
102
-
103
- def get_shared_template_mappings():
104
- """Returns shared template mappings."""
105
- return [
106
- {"template": "CHANGELOG.md.tpl", "target": "CHANGELOG.md"},
107
- {"template": "gitignore.tpl", "target": ".gitignore"},
108
- {"template": "LICENSE.tpl", "target": "LICENSE"},
109
- {"template": "CLAUDE.md.tpl", "target": "CLAUDE.md"},
110
- {"template": "claude-settings.json.tpl", "target": ".claude/settings.json"},
111
- {"template": "hooks/pre-release.sh.tpl", "target": ".rlsbl/hooks/pre-release.sh"},
112
- {"template": "hooks/post-release.sh.tpl", "target": ".rlsbl/hooks/post-release.sh"},
113
- ]
114
-
115
-
116
- def check_project_exists(dir_path):
117
- """Returns True if a go.mod exists in the given directory."""
118
- return os.path.exists(os.path.join(dir_path, "go.mod"))
119
-
120
-
121
- def get_project_init_hint():
122
- """Hint for users who haven't initialized their project yet."""
123
- return 'Run "go mod init <module-path>" first'
@@ -1,119 +0,0 @@
1
- """npm registry adapter for rlsbl."""
2
-
3
- import json
4
- import os
5
- import re
6
-
7
- NAME = "npm"
8
-
9
-
10
- def read_version(dir_path):
11
- """Read the version from package.json in the given directory."""
12
- pkg_path = os.path.join(dir_path, "package.json")
13
- with open(pkg_path, "r", encoding="utf-8") as f:
14
- pkg = json.load(f)
15
- if "version" not in pkg:
16
- raise ValueError(f"No 'version' field in {pkg_path}")
17
- return pkg["version"]
18
-
19
-
20
- def write_version(dir_path, version):
21
- """Write a new version to package.json, preserving formatting."""
22
- pkg_path = os.path.join(dir_path, "package.json")
23
- with open(pkg_path, "r", encoding="utf-8") as f:
24
- raw = f.read()
25
-
26
- # Detect indent: look for the first indented line
27
- indent_match = re.search(r'^( +|\t+)"', raw, re.MULTILINE)
28
- indent = indent_match.group(1) if indent_match else " "
29
-
30
- pkg = json.loads(raw)
31
- pkg["version"] = version
32
-
33
- # Preserve trailing newline if present
34
- trailing_newline = "\n" if raw.endswith("\n") else ""
35
- output = json.dumps(pkg, indent=indent, ensure_ascii=False) + trailing_newline
36
- # Atomic write: write to temp file, then rename
37
- tmp_path = pkg_path + ".tmp"
38
- with open(tmp_path, "w", encoding="utf-8") as f:
39
- f.write(output)
40
- os.replace(tmp_path, pkg_path)
41
-
42
-
43
- def get_version_file():
44
- """Returns the filename that holds the version for this registry."""
45
- return "package.json"
46
-
47
-
48
- def get_template_dir():
49
- """Returns path to the npm-specific template directory."""
50
- return os.path.join(os.path.dirname(__file__), "..", "templates", "npm")
51
-
52
-
53
- def get_shared_template_dir():
54
- """Returns path to the shared template directory."""
55
- return os.path.join(os.path.dirname(__file__), "..", "templates", "shared")
56
-
57
-
58
- def get_template_vars(dir_path):
59
- """Extract template variables from the target project's package.json."""
60
- pkg_path = os.path.join(dir_path, "package.json")
61
- with open(pkg_path, "r", encoding="utf-8") as f:
62
- pkg = json.load(f)
63
-
64
- # Derive binCommand from the bin field (first key if object, or package name)
65
- bin_command = pkg.get("name", "")
66
- bin_field = pkg.get("bin")
67
- if isinstance(bin_field, dict) and bin_field:
68
- bin_command = next(iter(bin_field))
69
- elif isinstance(bin_field, str):
70
- bin_command = pkg.get("name", "")
71
-
72
- # Derive repoName from repository field
73
- repo_name = ""
74
- repository = pkg.get("repository")
75
- if repository:
76
- url = repository if isinstance(repository, str) else (repository.get("url") or "")
77
- match = re.search(r"github\.com[/:]([^/]+/[^/.]+)", url)
78
- if match:
79
- repo_name = match.group(1)
80
-
81
- return {
82
- "name": pkg.get("name", ""),
83
- "version": pkg.get("version", "0.1.0"),
84
- "binCommand": bin_command,
85
- "author": pkg.get("author", ""),
86
- "repoName": repo_name,
87
- "publishSetup": "Requires NPM_TOKEN secret on GitHub (Settings > Secrets > Actions)",
88
- }
89
-
90
-
91
- def get_template_mappings():
92
- """Returns npm-specific template mappings (template file -> target path)."""
93
- return [
94
- {"template": "ci.yml.tpl", "target": ".github/workflows/ci.yml"},
95
- {"template": "publish.yml.tpl", "target": ".github/workflows/publish.yml"},
96
- ]
97
-
98
-
99
- def get_shared_template_mappings():
100
- """Returns shared template mappings."""
101
- return [
102
- {"template": "CHANGELOG.md.tpl", "target": "CHANGELOG.md"},
103
- {"template": "gitignore.tpl", "target": ".gitignore"},
104
- {"template": "LICENSE.tpl", "target": "LICENSE"},
105
- {"template": "CLAUDE.md.tpl", "target": "CLAUDE.md"},
106
- {"template": "hooks/pre-release.sh.tpl", "target": ".rlsbl/hooks/pre-release.sh"},
107
- {"template": "hooks/post-release.sh.tpl", "target": ".rlsbl/hooks/post-release.sh"},
108
- {"template": "claude-settings.json.tpl", "target": ".claude/settings.json"},
109
- ]
110
-
111
-
112
- def check_project_exists(dir_path):
113
- """Returns True if a package.json exists in the given directory."""
114
- return os.path.exists(os.path.join(dir_path, "package.json"))
115
-
116
-
117
- def get_project_init_hint():
118
- """Hint for users who haven't initialized their project yet."""
119
- return 'Run "npm init" first'
@@ -1,171 +0,0 @@
1
- """PyPI registry adapter for rlsbl."""
2
-
3
- import os
4
- import re
5
- import tomllib
6
-
7
- from ..utils import run
8
-
9
- NAME = "pypi"
10
-
11
-
12
- def read_version(dir_path):
13
- """Read the version from pyproject.toml in the given directory."""
14
- toml_path = os.path.join(dir_path, "pyproject.toml")
15
- with open(toml_path, "rb") as f:
16
- data = tomllib.load(f)
17
- try:
18
- return data["project"]["version"]
19
- except KeyError:
20
- raise ValueError(f"No [project].version in {toml_path}")
21
-
22
-
23
- def write_version(dir_path, version):
24
- """Write a new version to pyproject.toml using regex replacement.
25
-
26
- tomllib is read-only (no stdlib TOML writer), so we use a regex to
27
- replace the version string within the [project] section only,
28
- preserving all other formatting.
29
- """
30
- toml_path = os.path.join(dir_path, "pyproject.toml")
31
- with open(toml_path, "r", encoding="utf-8") as f:
32
- content = f.read()
33
-
34
- # Find [project] section boundaries to avoid matching version keys
35
- # in other sections (e.g. [tool.something])
36
- project_match = re.search(r"^\[project\]\s*$", content, re.MULTILINE)
37
- if not project_match:
38
- raise ValueError("No [project] section found in pyproject.toml")
39
-
40
- section_start = project_match.end()
41
- # Find next top-level section header or EOF
42
- next_section = re.search(r"^\[", content[section_start:], re.MULTILINE)
43
- section_end = section_start + next_section.start() if next_section else len(content)
44
-
45
- # Replace version only within [project] section
46
- section = content[section_start:section_end]
47
- updated_section = re.sub(
48
- r'^(version\s*=\s*)"[^"]+"',
49
- rf'\g<1>"{version}"',
50
- section,
51
- count=1,
52
- flags=re.MULTILINE,
53
- )
54
- updated = content[:section_start] + updated_section + content[section_end:]
55
-
56
- # Atomic write: write to temp file, then rename
57
- tmp_path = toml_path + ".tmp"
58
- with open(tmp_path, "w", encoding="utf-8") as f:
59
- f.write(updated)
60
- os.replace(tmp_path, toml_path)
61
-
62
-
63
- def get_version_file():
64
- """Returns the filename that holds the version for this registry."""
65
- return "pyproject.toml"
66
-
67
-
68
- def get_template_dir():
69
- """Returns path to the pypi-specific template directory."""
70
- return os.path.join(os.path.dirname(__file__), "..", "templates", "pypi")
71
-
72
-
73
- def get_shared_template_dir():
74
- """Returns path to the shared template directory."""
75
- return os.path.join(os.path.dirname(__file__), "..", "templates", "shared")
76
-
77
-
78
- def get_template_vars(dir_path):
79
- """Extract template variables from the target project's pyproject.toml."""
80
- toml_path = os.path.join(dir_path, "pyproject.toml")
81
- with open(toml_path, "rb") as f:
82
- data = tomllib.load(f)
83
-
84
- project = data.get("project", {})
85
- name = project.get("name", "")
86
- version = project.get("version", "0.1.0")
87
-
88
- # Extract author -- fall back to git config
89
- author = ""
90
- try:
91
- author = run("git", ["config", "user.name"])
92
- except Exception:
93
- pass
94
-
95
- # Extract repo name from project.urls
96
- repo_name = ""
97
- urls = project.get("urls", {})
98
- for url in urls.values():
99
- match = re.search(r"github\.com/([^/\s\"]+/[^/\s\"]+)", url)
100
- if match:
101
- repo_name = match.group(1).removesuffix(".git")
102
- break
103
-
104
- # Derive binCommand from project.scripts (CLI entry points)
105
- bin_command = ""
106
- scripts = project.get("scripts", {})
107
- if scripts:
108
- bin_command = next(iter(scripts)) # first script entry
109
-
110
- # Derive the actual Python import name.
111
- # 1) Check hatch build config for an explicit packages list.
112
- import_name = None
113
- hatch = data.get("tool", {}).get("hatch", {})
114
- packages = (
115
- hatch.get("build", {}).get("targets", {}).get("wheel", {}).get("packages")
116
- )
117
- if packages and isinstance(packages, list) and len(packages) > 0:
118
- # Strip src/ prefix (common hatch src layout) and convert path to module name
119
- first_pkg = packages[0]
120
- import_name = first_pkg.removeprefix("src/").replace("/", ".")
121
-
122
- # 2) Fall back to filesystem detection, then underscore convention.
123
- if not import_name:
124
- underscored = name.replace("-", "_")
125
- if os.path.isdir(os.path.join(dir_path, underscored)):
126
- import_name = underscored
127
- elif os.path.isdir(os.path.join(dir_path, name)):
128
- import_name = name
129
- else:
130
- import_name = underscored # fallback to convention
131
-
132
- return {
133
- "name": name,
134
- "version": version,
135
- "binCommand": bin_command,
136
- "author": author,
137
- "repoName": repo_name,
138
- "importName": import_name,
139
- "publishSetup": "Configure Trusted Publishing on pypi.org for automated PyPI releases",
140
- }
141
-
142
-
143
- def get_template_mappings():
144
- """Returns pypi-specific template mappings (template file -> target path)."""
145
- return [
146
- {"template": "ci.yml.tpl", "target": ".github/workflows/ci.yml"},
147
- {"template": "publish.yml.tpl", "target": ".github/workflows/publish.yml"},
148
- ]
149
-
150
-
151
- def get_shared_template_mappings():
152
- """Returns shared template mappings."""
153
- return [
154
- {"template": "CHANGELOG.md.tpl", "target": "CHANGELOG.md"},
155
- {"template": "gitignore.tpl", "target": ".gitignore"},
156
- {"template": "LICENSE.tpl", "target": "LICENSE"},
157
- {"template": "CLAUDE.md.tpl", "target": "CLAUDE.md"},
158
- {"template": "hooks/pre-release.sh.tpl", "target": ".rlsbl/hooks/pre-release.sh"},
159
- {"template": "hooks/post-release.sh.tpl", "target": ".rlsbl/hooks/post-release.sh"},
160
- {"template": "claude-settings.json.tpl", "target": ".claude/settings.json"},
161
- ]
162
-
163
-
164
- def check_project_exists(dir_path):
165
- """Returns True if a pyproject.toml exists in the given directory."""
166
- return os.path.exists(os.path.join(dir_path, "pyproject.toml"))
167
-
168
-
169
- def get_project_init_hint():
170
- """Hint for users who haven't initialized their project yet."""
171
- return 'Run "uv init" first'
package/rlsbl/tagging.py DELETED
@@ -1,207 +0,0 @@
1
- """Tagging module: inject "rlsbl" keywords into manifests and GitHub topics."""
2
-
3
- import json
4
- import os
5
- import re
6
- import subprocess
7
- import tomllib
8
- import urllib.request
9
- import urllib.error
10
-
11
- from .utils import run
12
-
13
-
14
- def ensure_npm_keyword(dir_path=".", quiet=False):
15
- """Add "rlsbl" to the keywords array in package.json if not already present."""
16
- pkg_path = os.path.join(dir_path, "package.json")
17
- with open(pkg_path, "r", encoding="utf-8") as f:
18
- raw = f.read()
19
-
20
- # Detect indent: look for the first indented line
21
- indent_match = re.search(r'^( +|\t+)"', raw, re.MULTILINE)
22
- indent = indent_match.group(1) if indent_match else " "
23
-
24
- pkg = json.loads(raw)
25
- keywords = pkg.get("keywords", [])
26
-
27
- if "rlsbl" in keywords:
28
- return False
29
-
30
- keywords.append("rlsbl")
31
- pkg["keywords"] = keywords
32
-
33
- # Preserve trailing newline if present
34
- trailing_newline = "\n" if raw.endswith("\n") else ""
35
- output = json.dumps(pkg, indent=indent, ensure_ascii=False) + trailing_newline
36
-
37
- # Atomic write: write to temp file, then rename
38
- tmp_path = pkg_path + ".tmp"
39
- with open(tmp_path, "w", encoding="utf-8") as f:
40
- f.write(output)
41
- os.replace(tmp_path, pkg_path)
42
-
43
- if not quiet:
44
- print('Tagged package.json with "rlsbl" keyword')
45
- return True
46
-
47
-
48
- def ensure_pypi_keyword(dir_path=".", quiet=False):
49
- """Add "rlsbl" to the keywords array in pyproject.toml if not already present."""
50
- toml_path = os.path.join(dir_path, "pyproject.toml")
51
- with open(toml_path, "rb") as f:
52
- data = tomllib.load(f)
53
-
54
- # Check if already tagged
55
- existing = data.get("project", {}).get("keywords", [])
56
- if "rlsbl" in existing:
57
- return False
58
-
59
- # Read as text for regex-based editing
60
- with open(toml_path, "r", encoding="utf-8") as f:
61
- content = f.read()
62
-
63
- # Find [project] section boundaries
64
- project_match = re.search(r"^\[project\]\s*$", content, re.MULTILINE)
65
- if not project_match:
66
- raise ValueError("No [project] section found in pyproject.toml")
67
-
68
- section_start = project_match.end()
69
- next_section = re.search(r"^\[", content[section_start:], re.MULTILINE)
70
- section_end = section_start + next_section.start() if next_section else len(content)
71
- section = content[section_start:section_end]
72
-
73
- # Case 1: keywords field already exists -- add "rlsbl" to the array
74
- # Use DOTALL to handle multi-line arrays (e.g. keywords = [\n "foo",\n])
75
- keywords_match = re.search(r'^(keywords\s*=\s*\[)(.*?)\]', section, re.MULTILINE | re.DOTALL)
76
- if keywords_match:
77
- prefix = keywords_match.group(1)
78
- array_content = keywords_match.group(2)
79
- # Detect if multi-line (contains newline between brackets)
80
- if "\n" in array_content:
81
- # Multi-line: insert before the closing bracket on its own line
82
- # Find the indent used for existing items
83
- item_indent_match = re.search(r'\n( +)"', array_content)
84
- item_indent = item_indent_match.group(1) if item_indent_match else " "
85
- # Strip trailing comma to avoid double comma when the list
86
- # already has a trailing comma before the closing bracket
87
- stripped = array_content.rstrip()
88
- stripped = stripped.rstrip(",")
89
- new_array_content = stripped + f',\n{item_indent}"rlsbl"\n'
90
- else:
91
- # Single-line
92
- if array_content.strip():
93
- stripped_sl = array_content.rstrip().rstrip(",")
94
- new_array_content = stripped_sl + ', "rlsbl"'
95
- else:
96
- new_array_content = '"rlsbl"'
97
- new_field = prefix + new_array_content + "]"
98
- updated_section = section[:keywords_match.start()] + new_field + section[keywords_match.end():]
99
- else:
100
- # Case 2: keywords field missing -- insert after the version line
101
- version_match = re.search(r'^version\s*=\s*"[^"]*"\s*$', section, re.MULTILINE)
102
- if version_match:
103
- insert_pos = version_match.end()
104
- else:
105
- # Fallback: insert at the beginning of the section
106
- insert_pos = 0
107
- updated_section = (
108
- section[:insert_pos] + '\nkeywords = ["rlsbl"]' + section[insert_pos:]
109
- )
110
-
111
- updated = content[:section_start] + updated_section + content[section_end:]
112
-
113
- # Atomic write: write to temp file, then rename
114
- tmp_path = toml_path + ".tmp"
115
- with open(tmp_path, "w", encoding="utf-8") as f:
116
- f.write(updated)
117
- os.replace(tmp_path, toml_path)
118
-
119
- if not quiet:
120
- print('Tagged pyproject.toml with "rlsbl" keyword')
121
- return True
122
-
123
-
124
- def ensure_github_topic(quiet=False):
125
- """Add "rlsbl" topic to the GitHub repository if not already present."""
126
- # Try to get a GitHub token (env var first, then gh CLI)
127
- token = os.environ.get("GITHUB_TOKEN")
128
- if not token:
129
- try:
130
- token = run("gh", ["auth", "token"])
131
- except (subprocess.CalledProcessError, FileNotFoundError):
132
- pass
133
-
134
- if not token:
135
- if not quiet:
136
- print("No GitHub token available. Run 'gh auth login' or set GITHUB_TOKEN.")
137
- return False
138
-
139
- # Detect repo name
140
- repo_name = None
141
- try:
142
- repo_name = run("gh", ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"])
143
- except (subprocess.CalledProcessError, FileNotFoundError):
144
- pass
145
-
146
- if not repo_name:
147
- # Fallback: parse from git remote
148
- try:
149
- remote_url = run("git", ["remote", "get-url", "origin"])
150
- match = re.search(r"github\.com[/:]([^/]+/[^/.]+)", remote_url)
151
- if match:
152
- repo_name = match.group(1).removesuffix(".git")
153
- except (subprocess.CalledProcessError, FileNotFoundError):
154
- pass
155
-
156
- if not repo_name:
157
- if not quiet:
158
- print("Warning: could not detect GitHub repository name.")
159
- return False
160
-
161
- owner, repo = repo_name.split("/", 1)
162
- api_url = f"https://api.github.com/repos/{owner}/{repo}/topics"
163
- headers = {
164
- "Authorization": f"token {token}",
165
- "Accept": "application/vnd.github+json",
166
- "User-Agent": "rlsbl-cli",
167
- }
168
-
169
- # GET existing topics
170
- try:
171
- req = urllib.request.Request(api_url, headers=headers)
172
- with urllib.request.urlopen(req, timeout=15) as resp:
173
- data = json.loads(resp.read().decode("utf-8"))
174
- except (urllib.error.URLError, OSError, json.JSONDecodeError) as e:
175
- if not quiet:
176
- print(f"Warning: failed to fetch GitHub topics: {e}")
177
- return False
178
-
179
- topics = data.get("names", [])
180
- if "rlsbl" in topics:
181
- return False
182
-
183
- # PUT with merged topics list
184
- topics.append("rlsbl")
185
- payload = json.dumps({"names": topics}).encode("utf-8")
186
- try:
187
- req = urllib.request.Request(api_url, data=payload, headers=headers, method="PUT")
188
- req.add_header("Content-Type", "application/json")
189
- with urllib.request.urlopen(req, timeout=15) as resp:
190
- resp.read() # consume response
191
- except (urllib.error.URLError, OSError) as e:
192
- if not quiet:
193
- print(f"Warning: failed to set GitHub topics: {e}")
194
- return False
195
-
196
- if not quiet:
197
- print('Added "rlsbl" topic to GitHub repository')
198
- return True
199
-
200
-
201
- def ensure_tags(registries, dir_path=".", quiet=False):
202
- """Tag manifests and GitHub repo based on detected registries."""
203
- if "npm" in registries:
204
- ensure_npm_keyword(dir_path, quiet=quiet)
205
- if "pypi" in registries:
206
- ensure_pypi_keyword(dir_path, quiet=quiet)
207
- ensure_github_topic(quiet=quiet)
@@ -1 +0,0 @@
1
- {{version}}
@@ -1,21 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- go-version: ["1.22", "1.23", "1.24"]
15
- steps:
16
- - uses: actions/checkout@v6
17
- - uses: actions/setup-go@v6
18
- with:
19
- go-version: ${{ matrix.go-version }}
20
- - run: go test ./...
21
- - run: go vet ./...
@@ -1,25 +0,0 @@
1
- version: 2
2
-
3
- builds:
4
- - env:
5
- - CGO_ENABLED=0
6
- goos:
7
- - linux
8
- - darwin
9
- - windows
10
- goarch:
11
- - amd64
12
- - arm64
13
-
14
- archives:
15
- - format: tar.gz
16
- name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
17
- format_overrides:
18
- - goos: windows
19
- format: zip
20
-
21
- checksum:
22
- name_template: checksums.txt
23
-
24
- changelog:
25
- use: github-native