rlsbl 0.8.3 → 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 -125
  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 -18
  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,198 +0,0 @@
1
- """Discover command: list projects in the rlsbl ecosystem."""
2
-
3
- import json
4
- import os
5
- import subprocess
6
- import sys
7
- import urllib.error
8
- import urllib.request
9
-
10
-
11
- SEARCH_URL = "https://api.github.com/search/repositories?q=topic:rlsbl&sort=updated&per_page=100"
12
- MAX_RESULTS = 1000
13
-
14
-
15
- def _get_github_token():
16
- """Get a GitHub token from GITHUB_TOKEN env or `gh auth token`."""
17
- token = os.environ.get("GITHUB_TOKEN")
18
- if token:
19
- return token
20
- try:
21
- result = subprocess.run(
22
- ["gh", "auth", "token"],
23
- capture_output=True, text=True, check=True, timeout=10,
24
- )
25
- return result.stdout.strip() or None
26
- except Exception:
27
- return None
28
-
29
-
30
- def _make_request(url, token):
31
- """Make a GET request to the GitHub API, return parsed JSON and response headers."""
32
- req = urllib.request.Request(url, method="GET")
33
- req.add_header("Accept", "application/vnd.github+json")
34
- req.add_header("User-Agent", "rlsbl-cli")
35
- if token:
36
- req.add_header("Authorization", f"token {token}")
37
- with urllib.request.urlopen(req, timeout=15) as resp:
38
- data = json.loads(resp.read().decode("utf-8"))
39
- headers = dict(resp.headers)
40
- return data, headers
41
-
42
-
43
- def _parse_next_link(headers):
44
- """Extract the 'next' URL from the Link header, or None."""
45
- link = headers.get("Link") or headers.get("link")
46
- if not link:
47
- return None
48
- for part in link.split(","):
49
- if 'rel="next"' in part:
50
- start = part.index("<") + 1
51
- end = part.index(">")
52
- url = part[start:end]
53
- if not url.startswith("https://api.github.com/"):
54
- return None
55
- return url
56
- return None
57
-
58
-
59
- def _relative_time(iso_timestamp):
60
- """Convert an ISO 8601 timestamp to a relative time string like '2d ago'."""
61
- from datetime import datetime, timezone
62
-
63
- if not iso_timestamp:
64
- return ""
65
-
66
- # Parse ISO timestamp (GitHub uses Z suffix)
67
- ts = iso_timestamp.replace("Z", "+00:00")
68
- dt = datetime.fromisoformat(ts)
69
- now = datetime.now(timezone.utc)
70
- delta = now - dt
71
-
72
- seconds = int(delta.total_seconds())
73
- if seconds < 60:
74
- return "just now"
75
- minutes = seconds // 60
76
- if minutes < 60:
77
- return f"{minutes}m ago"
78
- hours = minutes // 60
79
- if hours < 24:
80
- return f"{hours}h ago"
81
- days = hours // 24
82
- if days < 7:
83
- return f"{days}d ago"
84
- weeks = days // 7
85
- if weeks < 5:
86
- return f"{weeks}w ago"
87
- months = days // 30
88
- if months < 12:
89
- return f"{months}mo ago"
90
- years = days // 365
91
- return f"{years}y ago"
92
-
93
-
94
- def _get_authenticated_user(token):
95
- """Get the authenticated user's login name."""
96
- if not token:
97
- return None
98
- try:
99
- req = urllib.request.Request("https://api.github.com/user", method="GET")
100
- req.add_header("Accept", "application/vnd.github+json")
101
- req.add_header("User-Agent", "rlsbl-cli")
102
- req.add_header("Authorization", f"token {token}")
103
- with urllib.request.urlopen(req, timeout=10) as resp:
104
- data = json.loads(resp.read().decode("utf-8"))
105
- return data.get("login")
106
- except Exception:
107
- return None
108
-
109
-
110
- def _fetch_all_repos(token):
111
- """Fetch all repos with the rlsbl topic, handling pagination."""
112
- repos = []
113
- url = SEARCH_URL
114
-
115
- while url and len(repos) < MAX_RESULTS:
116
- data, headers = _make_request(url, token)
117
- items = data.get("items", [])
118
- repos.extend(items)
119
- url = _parse_next_link(headers)
120
-
121
- return repos
122
-
123
-
124
- def run_cmd(registry, args, flags):
125
- """Discover command: list projects in the rlsbl ecosystem."""
126
- token = _get_github_token()
127
- mine_only = flags.get("mine", False)
128
-
129
- if mine_only and not token:
130
- print("Error: --mine requires authentication (set GITHUB_TOKEN or install gh CLI).", file=sys.stderr)
131
- sys.exit(1)
132
-
133
- # Fetch repos
134
- try:
135
- repos = _fetch_all_repos(token)
136
- except urllib.error.HTTPError as e:
137
- print(f"Error: GitHub API returned {e.code}: {e.reason}", file=sys.stderr)
138
- if e.code == 403:
139
- print("Hint: run 'gh auth login' to increase API rate limits (60/hr unauthenticated → 5000/hr).", file=sys.stderr)
140
- sys.exit(1)
141
- except urllib.error.URLError as e:
142
- print(f"Error: could not reach GitHub API: {e.reason}", file=sys.stderr)
143
- sys.exit(1)
144
- except Exception as e:
145
- print(f"Error: {e}", file=sys.stderr)
146
- sys.exit(1)
147
-
148
- # Filter to --mine if requested
149
- if mine_only:
150
- username = _get_authenticated_user(token)
151
- if not username:
152
- print("Error: could not determine authenticated user.", file=sys.stderr)
153
- sys.exit(1)
154
- repos = [r for r in repos if r.get("owner", {}).get("login") == username]
155
-
156
- if not repos:
157
- if mine_only:
158
- print("No rlsbl-tagged repositories found for your account.")
159
- else:
160
- print("No rlsbl-tagged repositories found.")
161
- return
162
-
163
- # Build table rows
164
- rows = []
165
- for repo in repos:
166
- full_name = repo.get("full_name", "")
167
- description = repo.get("description") or ""
168
- updated = _relative_time(repo.get("updated_at", ""))
169
- rows.append((full_name, description, updated))
170
-
171
- # Calculate column widths
172
- name_width = max(len(r[0]) for r in rows)
173
- desc_width = max(len(r[1]) for r in rows)
174
- time_width = max(len(r[2]) for r in rows)
175
-
176
- # Cap description width to keep output readable
177
- max_desc = 40
178
- if desc_width > max_desc:
179
- desc_width = max_desc
180
-
181
- # Ensure minimum widths match headers
182
- name_width = max(name_width, len("owner/repo"))
183
- desc_width = max(desc_width, len("description"))
184
- time_width = max(time_width, len("updated"))
185
-
186
- # Print header
187
- print(f"\nrlsbl ecosystem ({len(repos)} projects)\n")
188
- header = f" {'owner/repo':<{name_width}} {'description':<{desc_width}} {'updated':<{time_width}}"
189
- print(header)
190
- separator_len = name_width + desc_width + time_width + 6
191
- print(f" {'─' * separator_len}")
192
-
193
- # Print rows
194
- for full_name, description, updated in rows:
195
- # Truncate long descriptions
196
- if len(description) > max_desc:
197
- description = description[:max_desc - 1] + "…"
198
- print(f" {full_name:<{name_width}} {description:<{desc_width}} {updated}")