rlsbl 0.5.1 → 0.5.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlsbl",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Release orchestration and project scaffolding for npm and PyPI",
5
5
  "license": "MIT",
6
6
  "bin": {
package/rlsbl/__init__.py CHANGED
@@ -3,11 +3,40 @@
3
3
  import os
4
4
  import sys
5
5
 
6
- try:
7
- from importlib.metadata import version as _get_version
8
- __version__ = _get_version("rlsbl")
9
- except Exception:
10
- __version__ = "unknown"
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()
11
40
 
12
41
  REGISTRIES = ("npm", "pypi", "go")
13
42
  COMMANDS = ("release", "status", "scaffold", "check", "config", "undo", "discover")
@@ -145,14 +174,17 @@ def main():
145
174
  sys.exit(1)
146
175
 
147
176
  if command == "check":
148
- # check: if registry given, check that one; otherwise check all
177
+ # check: if registry given, check that one; otherwise check npm and pypi.
178
+ # Go is excluded from the default set because Go modules use repository
179
+ # paths (e.g. github.com/user/repo), not a flat claimable namespace, so
180
+ # "available" would be misleading. Pass --registry go explicitly to check.
149
181
  if registry:
150
182
  handler.run_cmd(registry, args, flags)
151
183
  else:
152
- all_registries = ["npm", "pypi", "go"]
153
- for i, r in enumerate(all_registries):
184
+ default_registries = ["npm", "pypi"]
185
+ for i, r in enumerate(default_registries):
154
186
  handler.run_cmd(r, args, flags)
155
- if i < len(all_registries) - 1:
187
+ if i < len(default_registries) - 1:
156
188
  print("")
157
189
  elif command == "scaffold":
158
190
  if registry:
@@ -168,35 +168,44 @@ def _check_name_pypi(name):
168
168
  def check_go_availability(name):
169
169
  """Check if a Go module path exists on pkg.go.dev.
170
170
 
171
- Returns {"status": "available"|"taken"|"error", "message"?: str}.
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.
172
176
  """
173
177
  url = f"https://pkg.go.dev/{name}"
174
178
  try:
175
179
  req = urllib.request.Request(url, method="GET")
176
180
  with urllib.request.urlopen(req, timeout=5) as resp:
177
181
  if resp.status == 200:
178
- return {"status": "taken"}
182
+ return {"status": "exists"}
179
183
  return {"status": "error", "message": f"Unexpected status {resp.status}"}
180
184
  except urllib.error.HTTPError as e:
181
185
  if e.code == 404:
182
- return {"status": "available"}
186
+ return {
187
+ "status": "not_found",
188
+ "note": "Go modules use repository paths, not a central registry.",
189
+ }
183
190
  return {"status": "error", "message": f"Unexpected status {e.code}"}
184
191
  except Exception as e:
185
192
  return {"status": "error", "message": str(e) or "Network error"}
186
193
 
187
194
 
188
195
  def _check_name_go(name):
189
- """Check Go module path availability on pkg.go.dev."""
196
+ """Check Go module path on pkg.go.dev."""
190
197
  print(f'Checking pkg.go.dev for "{name}"...')
191
198
 
192
199
  result = check_go_availability(name)
193
200
  if result["status"] == "error":
194
201
  print(f"Error checking pkg.go.dev: {result['message']}", file=sys.stderr)
195
202
  sys.exit(1)
196
- if result["status"] == "available":
197
- print(f'"{name}" is available on pkg.go.dev.')
203
+ if result["status"] == "not_found":
204
+ print(f'"{name}" not found on pkg.go.dev.')
198
205
  else:
199
- print(f'"{name}" already exists on pkg.go.dev.')
206
+ print(f'"{name}" exists on pkg.go.dev.')
207
+ if result.get("note"):
208
+ print(f" Note: {result['note']}")
200
209
 
201
210
 
202
211
  def run_cmd(registry, args, flags):
@@ -133,6 +133,8 @@ def run_cmd(registry, args, flags):
133
133
  repos = _fetch_all_repos(token)
134
134
  except urllib.error.HTTPError as e:
135
135
  print(f"Error: GitHub API returned {e.code}: {e.reason}", file=sys.stderr)
136
+ if e.code == 403:
137
+ print("Hint: run 'gh auth login' to increase API rate limits (60/hr unauthenticated → 5000/hr).", file=sys.stderr)
136
138
  sys.exit(1)
137
139
  except urllib.error.URLError as e:
138
140
  print(f"Error: could not reach GitHub API: {e.reason}", file=sys.stderr)
@@ -177,13 +177,19 @@ def process_mappings(template_dir, mappings, vars_dict, force, update=False,
177
177
  existing_lines = {
178
178
  line.strip() for line in existing.split("\n") if line.strip()
179
179
  }
180
+ # Normalize by stripping trailing slashes so e.g.
181
+ # "*.egg-info/" matches "*.egg-info" and vice versa.
182
+ existing_normalized = {
183
+ line.rstrip("/") for line in existing_lines
184
+ }
180
185
  new_lines = [
181
186
  line.strip() for line in content.split("\n") if line.strip()
182
187
  ]
183
188
  # Only merge non-comment entries that are missing from the existing file
184
189
  missing = [
185
190
  line for line in new_lines
186
- if line not in existing_lines and not line.startswith("#")
191
+ if line.rstrip("/") not in existing_normalized
192
+ and not line.startswith("#")
187
193
  ]
188
194
  if missing:
189
195
  with open(target, "a", encoding="utf-8") as f:
@@ -86,6 +86,7 @@ def get_template_vars(dir_path):
86
86
  "author": author,
87
87
  "repoName": repo_name,
88
88
  "binCommand": short_name,
89
+ "publishSetup": "GoReleaser handles binary publishing via GitHub Actions (no secrets needed)",
89
90
  }
90
91
 
91
92
 
@@ -84,6 +84,7 @@ def get_template_vars(dir_path):
84
84
  "binCommand": bin_command,
85
85
  "author": pkg.get("author", ""),
86
86
  "repoName": repo_name,
87
+ "publishSetup": "Requires NPM_TOKEN secret on GitHub (Settings > Secrets > Actions)",
87
88
  }
88
89
 
89
90
 
@@ -136,6 +136,7 @@ def get_template_vars(dir_path):
136
136
  "author": author,
137
137
  "repoName": repo_name,
138
138
  "importName": import_name,
139
+ "publishSetup": "Configure Trusted Publishing on pypi.org for automated PyPI releases",
139
140
  }
140
141
 
141
142
 
@@ -13,8 +13,8 @@ jobs:
13
13
  matrix:
14
14
  go-version: ["1.22", "1.23", "1.24"]
15
15
  steps:
16
- - uses: actions/checkout@v5
17
- - uses: actions/setup-go@v5
16
+ - uses: actions/checkout@v6
17
+ - uses: actions/setup-go@v6
18
18
  with:
19
19
  go-version: ${{ matrix.go-version }}
20
20
  - run: go test ./...
@@ -11,15 +11,15 @@ jobs:
11
11
  goreleaser:
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
- - uses: actions/checkout@v5
14
+ - uses: actions/checkout@v6
15
15
  with:
16
16
  fetch-depth: 0
17
- - uses: actions/setup-go@v5
17
+ - uses: actions/setup-go@v6
18
18
  with:
19
19
  go-version-file: go.mod
20
- - uses: goreleaser/goreleaser-action@v6
20
+ - uses: goreleaser/goreleaser-action@v7
21
21
  with:
22
- version: latest
22
+ version: "~> v2"
23
23
  args: release --clean
24
24
  env:
25
25
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -12,8 +12,8 @@ jobs:
12
12
  npm:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v5
16
- - uses: actions/setup-node@v5
15
+ - uses: actions/checkout@v6
16
+ - uses: actions/setup-node@v6
17
17
  with:
18
18
  node-version: 22
19
19
  registry-url: https://registry.npmjs.org
@@ -24,7 +24,7 @@ jobs:
24
24
  pypi:
25
25
  runs-on: ubuntu-latest
26
26
  steps:
27
- - uses: actions/checkout@v5
27
+ - uses: actions/checkout@v6
28
28
  - uses: astral-sh/setup-uv@v7
29
29
  - run: uv build
30
30
  - uses: pypa/gh-action-pypi-publish@release/v1
@@ -13,8 +13,8 @@ jobs:
13
13
  matrix:
14
14
  node-version: [18, 20, 22]
15
15
  steps:
16
- - uses: actions/checkout@v5
17
- - uses: actions/setup-node@v5
16
+ - uses: actions/checkout@v6
17
+ - uses: actions/setup-node@v6
18
18
  with:
19
19
  node-version: ${{ matrix.node-version }}
20
20
  - run: node -e "require('./package.json')"
@@ -12,8 +12,8 @@ jobs:
12
12
  publish:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v5
16
- - uses: actions/setup-node@v5
15
+ - uses: actions/checkout@v6
16
+ - uses: actions/setup-node@v6
17
17
  with:
18
18
  node-version: 22
19
19
  registry-url: https://registry.npmjs.org
@@ -13,7 +13,7 @@ jobs:
13
13
  matrix:
14
14
  python-version: ["3.12", "3.13", "3.14"]
15
15
  steps:
16
- - uses: actions/checkout@v5
16
+ - uses: actions/checkout@v6
17
17
  - uses: astral-sh/setup-uv@v7
18
18
  - run: uv python install ${{ matrix.python-version }}
19
19
  - run: uv sync
@@ -12,7 +12,7 @@ jobs:
12
12
  publish:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v5
15
+ - uses: actions/checkout@v6
16
16
  - uses: astral-sh/setup-uv@v7
17
17
  - run: uv build
18
18
  - uses: pypa/gh-action-pypi-publish@release/v1
@@ -8,7 +8,7 @@ This project uses [rlsbl](https://github.com/smm-h/rlsbl) for release orchestrat
8
8
  - Run `rlsbl release [patch|minor|major]` to bump version and create a GitHub Release
9
9
  - CI handles publishing automatically via the publish workflow
10
10
  - Never publish manually — always use `rlsbl release`
11
- - Requires `NPM_TOKEN` secret on GitHub (for npm projects)
11
+ - {{publishSetup}}
12
12
  - Use `rlsbl release --dry-run` to preview a release without making changes
13
13
 
14
14
  ## Conventions
@@ -14,7 +14,7 @@ if ! command -v vhs &>/dev/null; then
14
14
  exit 1
15
15
  fi
16
16
 
17
- TAPE=$(mktemp /tmp/record-XXXX.tape)
17
+ TAPE=$(mktemp)
18
18
  cat > "$TAPE" <<EOF
19
19
  Set FontFamily "monospace"
20
20
  Set FontSize 24