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.
- package/package.json +3 -5
- package/rlsbl/__init__.py +0 -247
- package/rlsbl/__main__.py +0 -4
- package/rlsbl/commands/__init__.py +0 -0
- package/rlsbl/commands/check.py +0 -229
- package/rlsbl/commands/config.py +0 -67
- package/rlsbl/commands/discover.py +0 -198
- package/rlsbl/commands/init_cmd.py +0 -518
- package/rlsbl/commands/pre_push_check.py +0 -46
- package/rlsbl/commands/record_gif.py +0 -92
- package/rlsbl/commands/release.py +0 -287
- package/rlsbl/commands/status.py +0 -76
- package/rlsbl/commands/undo.py +0 -74
- package/rlsbl/commands/watch.py +0 -122
- package/rlsbl/config.py +0 -57
- package/rlsbl/registries/__init__.py +0 -5
- package/rlsbl/registries/go.py +0 -123
- package/rlsbl/registries/npm.py +0 -119
- package/rlsbl/registries/pypi.py +0 -171
- package/rlsbl/tagging.py +0 -207
- package/rlsbl/templates/go/VERSION.tpl +0 -1
- package/rlsbl/templates/go/ci.yml.tpl +0 -21
- package/rlsbl/templates/go/goreleaser.yml.tpl +0 -25
- package/rlsbl/templates/go/publish.yml.tpl +0 -25
- package/rlsbl/templates/merged/publish.yml.tpl +0 -30
- package/rlsbl/templates/npm/ci.yml.tpl +0 -22
- package/rlsbl/templates/npm/publish.yml.tpl +0 -22
- package/rlsbl/templates/pypi/ci.yml.tpl +0 -20
- package/rlsbl/templates/pypi/publish.yml.tpl +0 -18
- package/rlsbl/templates/shared/CHANGELOG.md.tpl +0 -5
- package/rlsbl/templates/shared/CLAUDE.md.tpl +0 -20
- package/rlsbl/templates/shared/LICENSE.tpl +0 -21
- package/rlsbl/templates/shared/claude-settings.json.tpl +0 -3
- package/rlsbl/templates/shared/gitignore.tpl +0 -14
- package/rlsbl/templates/shared/hooks/post-release.sh.tpl +0 -8
- package/rlsbl/templates/shared/hooks/pre-release.sh.tpl +0 -31
- package/rlsbl/utils.py +0 -131
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rlsbl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Release orchestration and project scaffolding for npm and
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Release orchestration and project scaffolding for npm, PyPI, and Go",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
7
7
|
"rlsbl": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"bin/"
|
|
11
|
-
"rlsbl/",
|
|
12
|
-
"templates/"
|
|
10
|
+
"bin/"
|
|
13
11
|
],
|
|
14
12
|
"repository": {
|
|
15
13
|
"type": "git",
|
package/rlsbl/__init__.py
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
"""rlsbl: Release orchestration and project scaffolding for npm, PyPI, and Go."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _detect_version():
|
|
8
|
-
"""Detect package version, preferring pyproject.toml over installed metadata.
|
|
9
|
-
|
|
10
|
-
Order: pyproject.toml in the source tree (accurate during editable installs)
|
|
11
|
-
-> importlib.metadata (works for regular installs) -> "unknown".
|
|
12
|
-
"""
|
|
13
|
-
# Try reading version from pyproject.toml next to the package source
|
|
14
|
-
try:
|
|
15
|
-
pyproject_path = os.path.realpath(
|
|
16
|
-
os.path.join(os.path.dirname(__file__), "..", "pyproject.toml")
|
|
17
|
-
)
|
|
18
|
-
if os.path.isfile(pyproject_path):
|
|
19
|
-
try:
|
|
20
|
-
import tomllib
|
|
21
|
-
except ModuleNotFoundError:
|
|
22
|
-
import tomli as tomllib # type: ignore[no-redef]
|
|
23
|
-
with open(pyproject_path, "rb") as f:
|
|
24
|
-
data = tomllib.load(f)
|
|
25
|
-
return data["project"]["version"]
|
|
26
|
-
except Exception:
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
# Fall back to installed dist-info metadata
|
|
30
|
-
try:
|
|
31
|
-
from importlib.metadata import version as _get_version
|
|
32
|
-
return _get_version("rlsbl")
|
|
33
|
-
except Exception:
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
return "unknown"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
__version__ = _detect_version()
|
|
40
|
-
|
|
41
|
-
REGISTRIES = ("npm", "pypi", "go")
|
|
42
|
-
COMMANDS = ("release", "status", "scaffold", "check", "config", "undo", "discover", "watch",
|
|
43
|
-
"pre-push-check", "record-gif")
|
|
44
|
-
COMMAND_ALIASES = {"init": "scaffold"}
|
|
45
|
-
|
|
46
|
-
HELP = f"""\
|
|
47
|
-
rlsbl v{__version__} -- Release orchestration and project scaffolding for npm, PyPI, and Go
|
|
48
|
-
|
|
49
|
-
Usage:
|
|
50
|
-
rlsbl release [patch|minor|major] [--dry-run] [--yes] [--quiet] Orchestrate a release
|
|
51
|
-
rlsbl status Show project status
|
|
52
|
-
rlsbl scaffold [--force] [--update] Scaffold release infrastructure
|
|
53
|
-
rlsbl check <name> Check name availability
|
|
54
|
-
rlsbl config Show project configuration
|
|
55
|
-
rlsbl undo [--yes] Revert the last release
|
|
56
|
-
rlsbl discover [--mine] List rlsbl ecosystem projects
|
|
57
|
-
rlsbl watch [<commit-sha>] Watch CI runs for a commit
|
|
58
|
-
rlsbl pre-push-check Verify CHANGELOG entry for current version
|
|
59
|
-
rlsbl record-gif [--width N] [--height N] [--font-size N] [--duration N]
|
|
60
|
-
Record a demo GIF with vhs
|
|
61
|
-
|
|
62
|
-
Options:
|
|
63
|
-
--registry <npm|pypi|go> Target a specific registry (auto-detected if omitted)
|
|
64
|
-
--no-tag Disable ecosystem tagging for this invocation
|
|
65
|
-
--help, -h Show this help
|
|
66
|
-
--version, -v Show version"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def detect_registries():
|
|
70
|
-
"""Detect all registries that have a project file in the current directory.
|
|
71
|
-
|
|
72
|
-
Returns a list, e.g. ["npm"], ["pypi"], or ["npm", "pypi"].
|
|
73
|
-
"""
|
|
74
|
-
found = []
|
|
75
|
-
if os.path.exists("package.json"):
|
|
76
|
-
found.append("npm")
|
|
77
|
-
if os.path.exists("pyproject.toml"):
|
|
78
|
-
found.append("pypi")
|
|
79
|
-
if os.path.exists("go.mod"):
|
|
80
|
-
found.append("go")
|
|
81
|
-
return found
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def parse_args(argv):
|
|
85
|
-
"""Parse sys.argv into positional args and flags.
|
|
86
|
-
|
|
87
|
-
Flags listed in VALUE_FLAGS consume the next token as their value
|
|
88
|
-
(e.g. --registry npm). All other --flags are boolean.
|
|
89
|
-
"""
|
|
90
|
-
VALUE_FLAGS = ("registry", "width", "height", "font-size", "duration")
|
|
91
|
-
raw = argv[1:]
|
|
92
|
-
positional = []
|
|
93
|
-
flags = {}
|
|
94
|
-
i = 0
|
|
95
|
-
while i < len(raw):
|
|
96
|
-
arg = raw[i]
|
|
97
|
-
if arg.startswith("--"):
|
|
98
|
-
key = arg[2:]
|
|
99
|
-
if "=" in key:
|
|
100
|
-
k, v = key.split("=", 1)
|
|
101
|
-
flags[k] = v
|
|
102
|
-
elif key in VALUE_FLAGS and i + 1 < len(raw) and not raw[i + 1].startswith("-"):
|
|
103
|
-
flags[key] = raw[i + 1]
|
|
104
|
-
i += 1
|
|
105
|
-
else:
|
|
106
|
-
flags[key] = True
|
|
107
|
-
elif arg.startswith("-") and len(arg) == 2:
|
|
108
|
-
flags[arg[1:]] = True
|
|
109
|
-
else:
|
|
110
|
-
positional.append(arg)
|
|
111
|
-
i += 1
|
|
112
|
-
return positional, flags
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _get_command_module(command):
|
|
116
|
-
"""Import and return the command module for the given command name."""
|
|
117
|
-
# Map CLI command names to Python module names
|
|
118
|
-
module_map = {
|
|
119
|
-
"release": "release",
|
|
120
|
-
"status": "status",
|
|
121
|
-
"scaffold": "init_cmd",
|
|
122
|
-
"check": "check",
|
|
123
|
-
"config": "config",
|
|
124
|
-
"undo": "undo",
|
|
125
|
-
"discover": "discover",
|
|
126
|
-
"watch": "watch",
|
|
127
|
-
"pre-push-check": "pre_push_check",
|
|
128
|
-
"record-gif": "record_gif",
|
|
129
|
-
}
|
|
130
|
-
module_name = module_map.get(command)
|
|
131
|
-
if not module_name:
|
|
132
|
-
return None
|
|
133
|
-
|
|
134
|
-
# Import the command module
|
|
135
|
-
from importlib import import_module
|
|
136
|
-
return import_module(f".commands.{module_name}", package="rlsbl")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def main():
|
|
140
|
-
positional, flags = parse_args(sys.argv)
|
|
141
|
-
|
|
142
|
-
# Top-level flags
|
|
143
|
-
if flags.get("help") or flags.get("h"):
|
|
144
|
-
print(HELP)
|
|
145
|
-
sys.exit(0)
|
|
146
|
-
|
|
147
|
-
if flags.get("version") or flags.get("v"):
|
|
148
|
-
print(__version__)
|
|
149
|
-
sys.exit(0)
|
|
150
|
-
|
|
151
|
-
command = positional[0] if positional else None
|
|
152
|
-
|
|
153
|
-
# Resolve command aliases (e.g. "init" -> "scaffold")
|
|
154
|
-
if command in COMMAND_ALIASES:
|
|
155
|
-
command = COMMAND_ALIASES[command]
|
|
156
|
-
|
|
157
|
-
if not command:
|
|
158
|
-
print("Error: missing command.\n", file=sys.stderr)
|
|
159
|
-
print(HELP, file=sys.stderr)
|
|
160
|
-
sys.exit(1)
|
|
161
|
-
|
|
162
|
-
if command not in COMMANDS:
|
|
163
|
-
print(f'Error: unknown command "{command}".\n', file=sys.stderr)
|
|
164
|
-
print(HELP, file=sys.stderr)
|
|
165
|
-
sys.exit(1)
|
|
166
|
-
|
|
167
|
-
args = positional[1:]
|
|
168
|
-
registry = flags.get("registry")
|
|
169
|
-
|
|
170
|
-
# --registry was the last arg with no value following it
|
|
171
|
-
if registry is True:
|
|
172
|
-
print("Error: --registry requires a value (npm, pypi, or go).", file=sys.stderr)
|
|
173
|
-
sys.exit(1)
|
|
174
|
-
|
|
175
|
-
# Validate --registry if provided
|
|
176
|
-
if registry and registry not in REGISTRIES:
|
|
177
|
-
print(
|
|
178
|
-
f"Error: unknown registry '{registry}'. Valid: {', '.join(REGISTRIES)}",
|
|
179
|
-
file=sys.stderr,
|
|
180
|
-
)
|
|
181
|
-
sys.exit(1)
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
handler = _get_command_module(command)
|
|
185
|
-
if handler is None:
|
|
186
|
-
print(f'Error: command "{command}" is not yet implemented.', file=sys.stderr)
|
|
187
|
-
sys.exit(1)
|
|
188
|
-
|
|
189
|
-
if command == "check":
|
|
190
|
-
# check: if registry given, check that one; otherwise check npm and pypi.
|
|
191
|
-
# Go is excluded from the default set because Go modules use repository
|
|
192
|
-
# paths (e.g. github.com/user/repo), not a flat claimable namespace, so
|
|
193
|
-
# "available" would be misleading. Pass --registry go explicitly to check.
|
|
194
|
-
if registry:
|
|
195
|
-
handler.run_cmd(registry, args, flags)
|
|
196
|
-
else:
|
|
197
|
-
default_registries = ["npm", "pypi"]
|
|
198
|
-
for i, r in enumerate(default_registries):
|
|
199
|
-
handler.run_cmd(r, args, flags)
|
|
200
|
-
if i < len(default_registries) - 1:
|
|
201
|
-
print("")
|
|
202
|
-
elif command == "scaffold":
|
|
203
|
-
if registry:
|
|
204
|
-
handler.run_cmd(registry, args, flags)
|
|
205
|
-
else:
|
|
206
|
-
regs = detect_registries()
|
|
207
|
-
if not regs:
|
|
208
|
-
print("Error: no package.json, pyproject.toml, or go.mod found.", file=sys.stderr)
|
|
209
|
-
sys.exit(1)
|
|
210
|
-
if len(regs) > 1:
|
|
211
|
-
handler.run_cmd_multi(regs, args, flags)
|
|
212
|
-
else:
|
|
213
|
-
handler.run_cmd(regs[0], args, flags)
|
|
214
|
-
elif command == "config":
|
|
215
|
-
# config: auto-detect, pass first registry or fallback
|
|
216
|
-
regs = detect_registries()
|
|
217
|
-
handler.run_cmd(registry or (regs[0] if regs else "npm"), args, flags)
|
|
218
|
-
elif command == "undo":
|
|
219
|
-
# undo: auto-detect like release
|
|
220
|
-
if not registry:
|
|
221
|
-
regs = detect_registries()
|
|
222
|
-
if not regs:
|
|
223
|
-
print("Error: no package.json, pyproject.toml, or go.mod found.", file=sys.stderr)
|
|
224
|
-
sys.exit(1)
|
|
225
|
-
registry = regs[0]
|
|
226
|
-
handler.run_cmd(registry, args, flags)
|
|
227
|
-
elif command == "discover":
|
|
228
|
-
# discover: global query, no registry needed
|
|
229
|
-
handler.run_cmd(registry, args, flags)
|
|
230
|
-
elif command == "watch":
|
|
231
|
-
# watch: monitors CI runs, no registry needed
|
|
232
|
-
handler.run_cmd(registry, args, flags)
|
|
233
|
-
elif command in ("pre-push-check", "record-gif"):
|
|
234
|
-
# Standalone commands, no registry needed
|
|
235
|
-
handler.run_cmd(registry, args, flags)
|
|
236
|
-
else:
|
|
237
|
-
# release, status: use explicit registry or auto-detect primary
|
|
238
|
-
if not registry:
|
|
239
|
-
regs = detect_registries()
|
|
240
|
-
if not regs:
|
|
241
|
-
print("Error: no package.json, pyproject.toml, or go.mod found.", file=sys.stderr)
|
|
242
|
-
sys.exit(1)
|
|
243
|
-
registry = regs[0]
|
|
244
|
-
handler.run_cmd(registry, args, flags)
|
|
245
|
-
except Exception as e:
|
|
246
|
-
print(f"Error: {e}", file=sys.stderr)
|
|
247
|
-
sys.exit(1)
|
package/rlsbl/__main__.py
DELETED
|
File without changes
|
package/rlsbl/commands/check.py
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
"""Check command: check package name availability on npm, PyPI, or Go (pkg.go.dev)."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
import subprocess
|
|
5
|
-
import sys
|
|
6
|
-
import urllib.request
|
|
7
|
-
import urllib.error
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def normalize_npm(name):
|
|
11
|
-
"""Normalize an npm package name for similarity comparison.
|
|
12
|
-
|
|
13
|
-
Strips hyphens, underscores, dots, and lowercases.
|
|
14
|
-
"""
|
|
15
|
-
return re.sub(r"[-_.]", "", name.lower())
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def normalize_pypi(name):
|
|
19
|
-
"""Normalize a PyPI package name per PEP 503.
|
|
20
|
-
|
|
21
|
-
Lowercases and replaces runs of [-_.] with a single hyphen.
|
|
22
|
-
"""
|
|
23
|
-
return re.sub(r"[-_.]+", "-", name.lower())
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def check_npm_availability(name):
|
|
27
|
-
"""Check if an npm package name is available.
|
|
28
|
-
|
|
29
|
-
Returns {"status": "available"|"taken"|"error", "message"?: str}.
|
|
30
|
-
Distinguishes 404 (truly available) from network/other errors.
|
|
31
|
-
"""
|
|
32
|
-
try:
|
|
33
|
-
subprocess.run(
|
|
34
|
-
["npm", "view", name, "name"],
|
|
35
|
-
capture_output=True, text=True, check=True,
|
|
36
|
-
)
|
|
37
|
-
return {"status": "taken"}
|
|
38
|
-
except subprocess.CalledProcessError as e:
|
|
39
|
-
stderr = e.stderr or ""
|
|
40
|
-
if "E404" in stderr or "404" in stderr:
|
|
41
|
-
return {"status": "available"}
|
|
42
|
-
return {"status": "error", "message": stderr.strip() or "Unknown error checking npm"}
|
|
43
|
-
except FileNotFoundError:
|
|
44
|
-
return {"status": "error", "message": "npm CLI not found"}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def get_npm_variants(name):
|
|
48
|
-
"""Generate common npm name variants for similarity checking."""
|
|
49
|
-
variants = set()
|
|
50
|
-
lower = name.lower()
|
|
51
|
-
variants.add(lower)
|
|
52
|
-
variants.add(lower.replace("_", "-"))
|
|
53
|
-
variants.add(lower.replace("-", "_"))
|
|
54
|
-
variants.add(re.sub(r"[-_]", "", lower))
|
|
55
|
-
variants.add(re.sub(r"[-_]", ".", lower))
|
|
56
|
-
|
|
57
|
-
# Remove the original name itself from the set
|
|
58
|
-
variants.discard(name)
|
|
59
|
-
|
|
60
|
-
return list(variants)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def check_pypi_availability(name):
|
|
64
|
-
"""Check if a PyPI package name is available.
|
|
65
|
-
|
|
66
|
-
Returns {"status": "available"|"taken"|"error", "message"?: str}.
|
|
67
|
-
Distinguishes 404 (truly available) from network/other errors.
|
|
68
|
-
"""
|
|
69
|
-
url = f"https://pypi.org/pypi/{name}/json"
|
|
70
|
-
try:
|
|
71
|
-
req = urllib.request.Request(url, method="GET")
|
|
72
|
-
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
73
|
-
if resp.status == 200:
|
|
74
|
-
return {"status": "taken"}
|
|
75
|
-
return {"status": "error", "message": f"Unexpected status {resp.status}"}
|
|
76
|
-
except urllib.error.HTTPError as e:
|
|
77
|
-
if e.code == 404:
|
|
78
|
-
return {"status": "available"}
|
|
79
|
-
return {"status": "error", "message": f"Unexpected status {e.code}"}
|
|
80
|
-
except Exception as e:
|
|
81
|
-
return {"status": "error", "message": str(e) or "Network error"}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def get_pypi_variants(name):
|
|
85
|
-
"""Generate common PyPI name variants for similarity checking."""
|
|
86
|
-
normalized = normalize_pypi(name)
|
|
87
|
-
lower = name.lower()
|
|
88
|
-
variants = set()
|
|
89
|
-
variants.add(normalized)
|
|
90
|
-
variants.add(re.sub(r"[-_.]+", "_", lower))
|
|
91
|
-
variants.add(re.sub(r"[-_.]+", "-", lower))
|
|
92
|
-
variants.add(re.sub(r"[-_.]+", "", lower))
|
|
93
|
-
|
|
94
|
-
# Remove the original name itself
|
|
95
|
-
variants.discard(name)
|
|
96
|
-
|
|
97
|
-
return list(variants)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _check_name_npm(name):
|
|
101
|
-
"""Check npm availability and report similar names."""
|
|
102
|
-
print(f'Checking npm for "{name}"...')
|
|
103
|
-
|
|
104
|
-
result = check_npm_availability(name)
|
|
105
|
-
if result["status"] == "error":
|
|
106
|
-
print(f"Error checking npm: {result['message']}", file=sys.stderr)
|
|
107
|
-
sys.exit(1)
|
|
108
|
-
available = result["status"] == "available"
|
|
109
|
-
if available:
|
|
110
|
-
print(f'"{name}" is available on npm.')
|
|
111
|
-
else:
|
|
112
|
-
print(f'"{name}" is taken on npm.')
|
|
113
|
-
|
|
114
|
-
# Check variants for similarity; skip variants that error
|
|
115
|
-
variants = get_npm_variants(name)
|
|
116
|
-
similar = []
|
|
117
|
-
for variant in variants:
|
|
118
|
-
var_result = check_npm_availability(variant)
|
|
119
|
-
if var_result["status"] == "taken":
|
|
120
|
-
similar.append(variant)
|
|
121
|
-
|
|
122
|
-
if similar:
|
|
123
|
-
print("\nSimilar names already taken:")
|
|
124
|
-
for s in similar:
|
|
125
|
-
print(f" {s}")
|
|
126
|
-
if available:
|
|
127
|
-
print(
|
|
128
|
-
"\nYour name is available but has similar existing packages. "
|
|
129
|
-
"Consider if this could cause confusion."
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _check_name_pypi(name):
|
|
134
|
-
"""Check PyPI availability and report similar names."""
|
|
135
|
-
print(f'Checking PyPI for "{name}"...')
|
|
136
|
-
|
|
137
|
-
result = check_pypi_availability(name)
|
|
138
|
-
if result["status"] == "error":
|
|
139
|
-
print(f"Error checking PyPI: {result['message']}", file=sys.stderr)
|
|
140
|
-
sys.exit(1)
|
|
141
|
-
available = result["status"] == "available"
|
|
142
|
-
if available:
|
|
143
|
-
print(f'"{name}" is available on PyPI.')
|
|
144
|
-
else:
|
|
145
|
-
print(f'"{name}" is taken on PyPI.')
|
|
146
|
-
|
|
147
|
-
# Check variants for similarity (PEP 503 normalization); skip variants that error
|
|
148
|
-
variants = get_pypi_variants(name)
|
|
149
|
-
similar = []
|
|
150
|
-
for variant in variants:
|
|
151
|
-
if variant == name:
|
|
152
|
-
continue
|
|
153
|
-
var_result = check_pypi_availability(variant)
|
|
154
|
-
if var_result["status"] == "taken":
|
|
155
|
-
similar.append(variant)
|
|
156
|
-
|
|
157
|
-
if similar:
|
|
158
|
-
print("\nSimilar names already taken:")
|
|
159
|
-
for s in similar:
|
|
160
|
-
print(f" {s}")
|
|
161
|
-
if available:
|
|
162
|
-
print(
|
|
163
|
-
"\nYour name is available but has similar existing packages. "
|
|
164
|
-
"Consider if this could cause confusion."
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def check_go_availability(name):
|
|
169
|
-
"""Check if a Go module path exists on pkg.go.dev.
|
|
170
|
-
|
|
171
|
-
Returns {"status": "not_found"|"exists"|"error", "message"?: str, "note"?: str}.
|
|
172
|
-
|
|
173
|
-
Go modules use repository paths (e.g. github.com/user/repo), not a flat
|
|
174
|
-
claimable namespace, so we report "not found" / "exists" rather than the
|
|
175
|
-
"available" / "taken" language used for npm and PyPI.
|
|
176
|
-
"""
|
|
177
|
-
url = f"https://pkg.go.dev/{name}"
|
|
178
|
-
try:
|
|
179
|
-
req = urllib.request.Request(url, method="GET")
|
|
180
|
-
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
181
|
-
if resp.status == 200:
|
|
182
|
-
return {"status": "exists"}
|
|
183
|
-
return {"status": "error", "message": f"Unexpected status {resp.status}"}
|
|
184
|
-
except urllib.error.HTTPError as e:
|
|
185
|
-
if e.code == 404:
|
|
186
|
-
return {
|
|
187
|
-
"status": "not_found",
|
|
188
|
-
"note": "Go modules use repository paths, not a central registry.",
|
|
189
|
-
}
|
|
190
|
-
return {"status": "error", "message": f"Unexpected status {e.code}"}
|
|
191
|
-
except Exception as e:
|
|
192
|
-
return {"status": "error", "message": str(e) or "Network error"}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def _check_name_go(name):
|
|
196
|
-
"""Check Go module path on pkg.go.dev."""
|
|
197
|
-
print(f'Checking pkg.go.dev for "{name}"...')
|
|
198
|
-
|
|
199
|
-
result = check_go_availability(name)
|
|
200
|
-
if result["status"] == "error":
|
|
201
|
-
print(f"Error checking pkg.go.dev: {result['message']}", file=sys.stderr)
|
|
202
|
-
sys.exit(1)
|
|
203
|
-
if result["status"] == "not_found":
|
|
204
|
-
print(f'"{name}" not found on pkg.go.dev.')
|
|
205
|
-
else:
|
|
206
|
-
print(f'"{name}" exists on pkg.go.dev.')
|
|
207
|
-
if result.get("note"):
|
|
208
|
-
print(f" Note: {result['note']}")
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def run_cmd(registry, args, flags):
|
|
212
|
-
"""Check command handler.
|
|
213
|
-
|
|
214
|
-
Checks package name availability on npm, PyPI, or Go, and warns about similar names.
|
|
215
|
-
"""
|
|
216
|
-
name = args[0] if args else None
|
|
217
|
-
if not name:
|
|
218
|
-
print(
|
|
219
|
-
"Error: missing package name. Usage: rlsbl check <name>",
|
|
220
|
-
file=sys.stderr,
|
|
221
|
-
)
|
|
222
|
-
sys.exit(1)
|
|
223
|
-
|
|
224
|
-
if registry == "npm":
|
|
225
|
-
_check_name_npm(name)
|
|
226
|
-
elif registry == "pypi":
|
|
227
|
-
_check_name_pypi(name)
|
|
228
|
-
elif registry == "go":
|
|
229
|
-
_check_name_go(name)
|
package/rlsbl/commands/config.py
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"""Config command: show resolved project configuration."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from ..config import _project_config, USER_CONFIG, read_json_config, should_tag
|
|
5
|
-
from ..registries import REGISTRIES
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run_cmd(registry, args, flags):
|
|
9
|
-
print("Detected registries:")
|
|
10
|
-
for name, reg in REGISTRIES.items():
|
|
11
|
-
if reg.check_project_exists("."):
|
|
12
|
-
version = reg.read_version(".")
|
|
13
|
-
vfile = reg.get_version_file() or "git tag"
|
|
14
|
-
print(f" {name}: {vfile} (v{version})")
|
|
15
|
-
else:
|
|
16
|
-
print(f" {name}: not found")
|
|
17
|
-
|
|
18
|
-
print("\nScaffolding:")
|
|
19
|
-
rlsbl_dir = os.path.join(".", ".rlsbl")
|
|
20
|
-
if os.path.isdir(rlsbl_dir):
|
|
21
|
-
version_file = os.path.join(rlsbl_dir, "version")
|
|
22
|
-
if os.path.exists(version_file):
|
|
23
|
-
with open(version_file) as f:
|
|
24
|
-
scaffold_ver = f.read().strip()
|
|
25
|
-
print(f" Version marker: {scaffold_ver}")
|
|
26
|
-
hashes_file = os.path.join(rlsbl_dir, "hashes.json")
|
|
27
|
-
if os.path.exists(hashes_file):
|
|
28
|
-
import json
|
|
29
|
-
with open(hashes_file) as f:
|
|
30
|
-
hashes = json.load(f)
|
|
31
|
-
print(f" Tracked files: {len(hashes)}")
|
|
32
|
-
for path in sorted(hashes):
|
|
33
|
-
print(f" {path}")
|
|
34
|
-
else:
|
|
35
|
-
print(" Not scaffolded (run 'rlsbl scaffold')")
|
|
36
|
-
|
|
37
|
-
print("\nWorkflows:")
|
|
38
|
-
for wf in ["ci.yml", "publish.yml", "workflow.yml"]:
|
|
39
|
-
path = os.path.join(".github", "workflows", wf)
|
|
40
|
-
print(f" {wf}: {'yes' if os.path.exists(path) else 'no'}")
|
|
41
|
-
|
|
42
|
-
print("\nHooks:")
|
|
43
|
-
pre_release = os.path.join(".rlsbl", "hooks", "pre-release.sh")
|
|
44
|
-
print(f" pre-release.sh: {'yes' if os.path.exists(pre_release) else 'no'}")
|
|
45
|
-
post_release = os.path.join(".rlsbl", "hooks", "post-release.sh")
|
|
46
|
-
print(f" post-release.sh: {'yes' if os.path.exists(post_release) else 'no'}")
|
|
47
|
-
pre_push = os.path.join(".git", "hooks", "pre-push")
|
|
48
|
-
print(f" pre-push hook: {'installed' if os.path.exists(pre_push) else 'not installed'}")
|
|
49
|
-
|
|
50
|
-
print("\nEcosystem tagging:")
|
|
51
|
-
enabled = should_tag(flags)
|
|
52
|
-
# Determine why it's enabled/disabled
|
|
53
|
-
project_cfg = read_json_config(_project_config())
|
|
54
|
-
user_cfg = read_json_config(USER_CONFIG)
|
|
55
|
-
if flags.get("no-tag"):
|
|
56
|
-
source = "CLI flag"
|
|
57
|
-
elif "tag" in project_cfg:
|
|
58
|
-
source = f"project config ({_project_config()})"
|
|
59
|
-
elif "tag" in user_cfg:
|
|
60
|
-
source = f"user config ({USER_CONFIG})"
|
|
61
|
-
else:
|
|
62
|
-
source = "default"
|
|
63
|
-
print(f" Status: {'enabled' if enabled else 'disabled'} ({source})")
|
|
64
|
-
|
|
65
|
-
print("\nFiles:")
|
|
66
|
-
for f in ["CHANGELOG.md", "LICENSE", ".gitignore", "CLAUDE.md"]:
|
|
67
|
-
print(f" {f}: {'yes' if os.path.exists(f) else 'no'}")
|