rlsbl 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # rlsbl
2
2
 
3
- Release orchestration and project scaffolding CLI for npm and PyPI.
3
+ Release orchestration and project scaffolding CLI for npm, PyPI, and Go.
4
4
 
5
5
  ## Install
6
6
 
@@ -25,7 +25,7 @@ rlsbl release minor
25
25
 
26
26
  ## Commands
27
27
 
28
- All commands work at the top level -- registries are auto-detected from project files (`package.json`, `pyproject.toml`). Use `--registry <npm|pypi>` when you need to target a specific registry.
28
+ All commands work at the top level -- registries are auto-detected from project files (`package.json`, `pyproject.toml`, `go.mod`). Use `--registry <npm|pypi|go>` when you need to target a specific registry.
29
29
 
30
30
  ### scaffold [--force] [--update]
31
31
 
@@ -35,6 +35,7 @@ Scaffolds CI/CD infrastructure and release tooling for all detected registries.
35
35
  rlsbl scaffold
36
36
  rlsbl scaffold --registry npm # target npm only
37
37
  rlsbl scaffold --registry pypi --force # overwrite existing files
38
+ rlsbl scaffold --registry go # target Go only
38
39
  ```
39
40
 
40
41
  Context-aware behavior when files already exist (without `--force`):
@@ -55,7 +56,7 @@ rlsbl release minor
55
56
  rlsbl release major --dry-run --registry npm
56
57
  ```
57
58
 
58
- The version is synced across all detected project files (`package.json`, `pyproject.toml`) regardless of which registry is primary.
59
+ The version is synced across all detected project files (`package.json`, `pyproject.toml`, `VERSION`) regardless of which registry is primary. Go projects use a plain `VERSION` file as the version source.
59
60
 
60
61
  If `scripts/pre-release.sh` exists, it runs before any changes are made. A non-zero exit aborts the release.
61
62
 
@@ -122,7 +123,7 @@ The scaffolded `scripts/pre-push-hook.sh` is installed as a git pre-push hook du
122
123
 
123
124
  How it works:
124
125
 
125
- 1. Detects project type (`package.json` or `pyproject.toml`)
126
+ 1. Detects project type (`package.json`, `pyproject.toml`, or `VERSION`)
126
127
  2. Extracts the current version
127
128
  3. Checks that `CHANGELOG.md` contains a heading `## <version>`
128
129
  4. Blocks the push with an error if the entry is missing
@@ -141,8 +142,9 @@ The first version must be published manually before CI can take over:
141
142
  |---|---|---|
142
143
  | npm | Add an `NPM_TOKEN` secret to your GitHub repo (Settings > Secrets > Actions), then push a release | CI handles subsequent publishes |
143
144
  | PyPI | Run `uv publish` | Set up [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) on pypi.org |
145
+ | Go | Push to GitHub and create a release -- Go modules are published by the tag itself | No secrets needed; `pkg.go.dev` indexes automatically |
144
146
 
145
- After configuration, all subsequent releases are handled by CI when `rlsbl release` creates a GitHub Release.
147
+ After configuration, all subsequent releases are handled by CI when `rlsbl release` creates a GitHub Release. Go projects use GoReleaser in CI (via GitHub Actions) to build cross-platform binaries.
146
148
 
147
149
  ## Requirements
148
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlsbl",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Release orchestration and project scaffolding for npm and PyPI",
5
5
  "license": "MIT",
6
6
  "bin": {
package/rlsbl/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- """rlsbl: Release orchestration and project scaffolding for npm and PyPI."""
1
+ """rlsbl: Release orchestration and project scaffolding for npm, PyPI, and Go."""
2
2
 
3
3
  import os
4
4
  import sys
@@ -9,12 +9,12 @@ try:
9
9
  except Exception:
10
10
  __version__ = "unknown"
11
11
 
12
- REGISTRIES = ("npm", "pypi")
12
+ REGISTRIES = ("npm", "pypi", "go")
13
13
  COMMANDS = ("release", "status", "scaffold", "check", "config", "undo")
14
14
  COMMAND_ALIASES = {"init": "scaffold"}
15
15
 
16
16
  HELP = f"""\
17
- rlsbl v{__version__} -- Release orchestration and project scaffolding for npm and PyPI
17
+ rlsbl v{__version__} -- Release orchestration and project scaffolding for npm, PyPI, and Go
18
18
 
19
19
  Usage:
20
20
  rlsbl release [patch|minor|major] [--dry-run] [--yes] [--quiet] Orchestrate a release
@@ -25,7 +25,7 @@ Usage:
25
25
  rlsbl undo [--yes] Revert the last release
26
26
 
27
27
  Options:
28
- --registry <npm|pypi> Target a specific registry (auto-detected if omitted)
28
+ --registry <npm|pypi|go> Target a specific registry (auto-detected if omitted)
29
29
  --help, -h Show this help
30
30
  --version, -v Show version"""
31
31
 
@@ -40,6 +40,8 @@ def detect_registries():
40
40
  found.append("npm")
41
41
  if os.path.exists("pyproject.toml"):
42
42
  found.append("pypi")
43
+ if os.path.exists("go.mod"):
44
+ found.append("go")
43
45
  return found
44
46
 
45
47
 
@@ -144,9 +146,10 @@ def main():
144
146
  if registry:
145
147
  handler.run_cmd(registry, args, flags)
146
148
  else:
147
- for i, r in enumerate(["npm", "pypi"]):
149
+ all_registries = ["npm", "pypi", "go"]
150
+ for i, r in enumerate(all_registries):
148
151
  handler.run_cmd(r, args, flags)
149
- if i < 1:
152
+ if i < len(all_registries) - 1:
150
153
  print("")
151
154
  elif command == "scaffold":
152
155
  if registry:
@@ -154,7 +157,7 @@ def main():
154
157
  else:
155
158
  regs = detect_registries()
156
159
  if not regs:
157
- print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
160
+ print("Error: no package.json, pyproject.toml, or go.mod found.", file=sys.stderr)
158
161
  sys.exit(1)
159
162
  if len(regs) > 1:
160
163
  handler.run_cmd_multi(regs, args, flags)
@@ -169,7 +172,7 @@ def main():
169
172
  if not registry:
170
173
  regs = detect_registries()
171
174
  if not regs:
172
- print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
175
+ print("Error: no package.json, pyproject.toml, or go.mod found.", file=sys.stderr)
173
176
  sys.exit(1)
174
177
  registry = regs[0]
175
178
  handler.run_cmd(registry, args, flags)
@@ -178,7 +181,7 @@ def main():
178
181
  if not registry:
179
182
  regs = detect_registries()
180
183
  if not regs:
181
- print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
184
+ print("Error: no package.json, pyproject.toml, or go.mod found.", file=sys.stderr)
182
185
  sys.exit(1)
183
186
  registry = regs[0]
184
187
  handler.run_cmd(registry, args, flags)
@@ -1,4 +1,4 @@
1
- """Check command: check package name availability on npm or PyPI."""
1
+ """Check command: check package name availability on npm, PyPI, or Go (pkg.go.dev)."""
2
2
 
3
3
  import re
4
4
  import subprocess
@@ -165,10 +165,44 @@ def _check_name_pypi(name):
165
165
  )
166
166
 
167
167
 
168
+ def check_go_availability(name):
169
+ """Check if a Go module path exists on pkg.go.dev.
170
+
171
+ Returns {"status": "available"|"taken"|"error", "message"?: str}.
172
+ """
173
+ url = f"https://pkg.go.dev/{name}"
174
+ try:
175
+ req = urllib.request.Request(url, method="GET")
176
+ with urllib.request.urlopen(req, timeout=5) as resp:
177
+ if resp.status == 200:
178
+ return {"status": "taken"}
179
+ return {"status": "error", "message": f"Unexpected status {resp.status}"}
180
+ except urllib.error.HTTPError as e:
181
+ if e.code == 404:
182
+ return {"status": "available"}
183
+ return {"status": "error", "message": f"Unexpected status {e.code}"}
184
+ except Exception as e:
185
+ return {"status": "error", "message": str(e) or "Network error"}
186
+
187
+
188
+ def _check_name_go(name):
189
+ """Check Go module path availability on pkg.go.dev."""
190
+ print(f'Checking pkg.go.dev for "{name}"...')
191
+
192
+ result = check_go_availability(name)
193
+ if result["status"] == "error":
194
+ print(f"Error checking pkg.go.dev: {result['message']}", file=sys.stderr)
195
+ sys.exit(1)
196
+ if result["status"] == "available":
197
+ print(f'"{name}" is available on pkg.go.dev.')
198
+ else:
199
+ print(f'"{name}" already exists on pkg.go.dev.')
200
+
201
+
168
202
  def run_cmd(registry, args, flags):
169
203
  """Check command handler.
170
204
 
171
- Checks package name availability on npm or PyPI, and warns about similar names.
205
+ Checks package name availability on npm, PyPI, or Go, and warns about similar names.
172
206
  """
173
207
  name = args[0] if args else None
174
208
  if not name:
@@ -180,5 +214,7 @@ def run_cmd(registry, args, flags):
180
214
 
181
215
  if registry == "npm":
182
216
  _check_name_npm(name)
183
- else:
217
+ elif registry == "pypi":
184
218
  _check_name_pypi(name)
219
+ elif registry == "go":
220
+ _check_name_go(name)
@@ -9,7 +9,8 @@ def run_cmd(registry, args, flags):
9
9
  for name, reg in REGISTRIES.items():
10
10
  if reg.check_project_exists("."):
11
11
  version = reg.read_version(".")
12
- print(f" {name}: {reg.get_version_file()} (v{version})")
12
+ vfile = reg.get_version_file() or "git tag"
13
+ print(f" {name}: {vfile} (v{version})")
13
14
  else:
14
15
  print(f" {name}: not found")
15
16
 
@@ -60,6 +60,11 @@ NEXT_STEPS = {
60
60
  "Configure Trusted Publishing on pypi.org",
61
61
  "Run rlsbl release [patch|minor|major]",
62
62
  ],
63
+ "go": [
64
+ "GoReleaser runs in CI via GitHub Actions (no local install needed)",
65
+ "Push to GitHub to activate the CI workflow",
66
+ "Run rlsbl release [patch|minor|major]",
67
+ ],
63
68
  }
64
69
 
65
70
 
@@ -55,19 +55,30 @@ def run_cmd(registry, args, flags):
55
55
  current_version = reg.read_version(".")
56
56
  log(f"Current version: {current_version}")
57
57
 
58
- # Bump type
59
- bump_type = args[0] if args else "patch"
60
- if bump_type not in VALID_BUMP_TYPES:
61
- print(
62
- f'Error: invalid bump type "{bump_type}". Use: {", ".join(VALID_BUMP_TYPES)}',
63
- file=sys.stderr,
64
- )
65
- sys.exit(1)
58
+ # If the current version has never been tagged, release it as-is (bootstrap)
59
+ current_tag = f"v{current_version}"
60
+ current_tag_exists = len(run("git", ["tag", "-l", current_tag])) > 0
61
+
62
+ if not current_tag_exists:
63
+ new_version = current_version
64
+ bump_type = None
65
+ tag = current_tag
66
+ if args:
67
+ log(f"First release: releasing {new_version} as-is (bump type ignored)")
68
+ else:
69
+ log(f"First release: {new_version}")
70
+ else:
71
+ bump_type = args[0] if args else "patch"
72
+ if bump_type not in VALID_BUMP_TYPES:
73
+ print(
74
+ f'Error: invalid bump type "{bump_type}". Use: {", ".join(VALID_BUMP_TYPES)}',
75
+ file=sys.stderr,
76
+ )
77
+ sys.exit(1)
66
78
 
67
- # Compute new version
68
- new_version = bump_version(current_version, bump_type)
69
- tag = f"v{new_version}"
70
- log(f"New version: {new_version} ({bump_type})")
79
+ new_version = bump_version(current_version, bump_type)
80
+ tag = f"v{new_version}"
81
+ log(f"New version: {new_version} ({bump_type})")
71
82
 
72
83
  # Check tag doesn't already exist
73
84
  tag_output = run("git", ["tag", "-l", tag])
@@ -111,7 +122,10 @@ def run_cmd(registry, args, flags):
111
122
  if flags.get("dry-run", False):
112
123
  log("\n--- Dry run summary ---")
113
124
  log(f"Registry: {registry}")
114
- log(f"Bump: {current_version} -> {new_version} ({bump_type})")
125
+ if bump_type:
126
+ log(f"Bump: {current_version} -> {new_version} ({bump_type})")
127
+ else:
128
+ log(f"Version: {new_version} (first release)")
115
129
  log(f"Tag: {tag}")
116
130
  log(f"Branch: {branch}")
117
131
  # Show other version files that would be synced
@@ -120,7 +134,9 @@ def run_cmd(registry, args, flags):
120
134
  if name == registry:
121
135
  continue
122
136
  if other_reg.check_project_exists("."):
123
- other_files.append(other_reg.get_version_file())
137
+ other_file = other_reg.get_version_file()
138
+ if other_file:
139
+ other_files.append(other_file)
124
140
  if other_files:
125
141
  log(f"Sync to: {', '.join(other_files)}")
126
142
  log(f"Changelog:\n{changelog_entry}")
@@ -129,18 +145,26 @@ def run_cmd(registry, args, flags):
129
145
 
130
146
  # Pre-compute which files will be modified
131
147
  version_file = reg.get_version_file()
132
- files_to_commit = [version_file]
148
+ files_to_commit = []
149
+ if version_file:
150
+ files_to_commit.append(version_file)
133
151
  for name, other_reg in REGISTRIES.items():
134
152
  if name == registry:
135
153
  continue
136
154
  if other_reg.check_project_exists("."):
137
- files_to_commit.append(other_reg.get_version_file())
155
+ other_file = other_reg.get_version_file()
156
+ if other_file:
157
+ files_to_commit.append(other_file)
138
158
 
139
159
  # Confirmation prompt (skip with --yes)
140
160
  if not flags.get("yes"):
141
- print(f"\nAbout to release {new_version} ({bump_type}) on {branch}")
161
+ bump_label = f" ({bump_type})" if bump_type else ""
162
+ print(f"\nAbout to release {new_version}{bump_label} on {branch}")
142
163
  print(f" Tag: {tag}")
143
- print(f" Files: {', '.join(files_to_commit)}")
164
+ if files_to_commit:
165
+ print(f" Files: {', '.join(files_to_commit)}")
166
+ else:
167
+ print(" Files: (none -- version is the git tag)")
144
168
  try:
145
169
  answer = input("Proceed? [y/N] ").strip().lower()
146
170
  except (EOFError, KeyboardInterrupt):
@@ -150,26 +174,33 @@ def run_cmd(registry, args, flags):
150
174
  print("Aborted.")
151
175
  sys.exit(0)
152
176
 
153
- # Write new version to the primary registry file
154
- reg.write_version(".", new_version)
155
- log(f"Updated version in {version_file}")
177
+ # Write new version to version files (skip if version didn't change, e.g. first release)
178
+ if new_version != current_version:
179
+ if version_file:
180
+ reg.write_version(".", new_version)
181
+ log(f"Updated version in {version_file}")
156
182
 
157
- # Sync version to all other recognized version files
158
- for name, other_reg in REGISTRIES.items():
159
- if name == registry:
160
- continue
161
- if other_reg.check_project_exists("."):
162
- other_reg.write_version(".", new_version)
163
- log(f"Synced version to {other_reg.get_version_file()}")
164
-
165
- # Commit all bumped version files together
166
- commit_tool = find_commit_tool()
167
- if commit_tool == "safegit":
168
- run(commit_tool, ["commit", "-m", tag, "--", *files_to_commit])
183
+ # Sync version to all other recognized version files
184
+ for name, other_reg in REGISTRIES.items():
185
+ if name == registry:
186
+ continue
187
+ if other_reg.check_project_exists("."):
188
+ other_file = other_reg.get_version_file()
189
+ if other_file:
190
+ other_reg.write_version(".", new_version)
191
+ log(f"Synced version to {other_file}")
192
+
193
+ # Commit version file changes (skip if version didn't change, e.g. first release)
194
+ if files_to_commit and new_version != current_version:
195
+ commit_tool = find_commit_tool()
196
+ if commit_tool == "safegit":
197
+ run(commit_tool, ["commit", "-m", tag, "--", *files_to_commit])
198
+ else:
199
+ run("git", ["add", *files_to_commit])
200
+ run("git", ["commit", "-m", tag])
201
+ log(f"Committed: {tag}")
169
202
  else:
170
- run("git", ["add", *files_to_commit])
171
- run("git", ["commit", "-m", tag])
172
- log(f"Committed: {tag}")
203
+ log("No version bump to commit")
173
204
 
174
205
  # Create local git tag
175
206
  run("git", ["tag", tag])
@@ -33,7 +33,7 @@ def run_cmd(registry, args, flags):
33
33
  for r_name, r_mod in REGISTRIES.items():
34
34
  if r_mod.check_project_exists("."):
35
35
  ver = r_mod.read_version(".")
36
- file = r_mod.get_version_file()
36
+ file = r_mod.get_version_file() or "git tag"
37
37
  print(f"Version: {ver} ({r_name}, {file})")
38
38
 
39
39
  # Git info
@@ -1,5 +1,5 @@
1
1
  """Registry lookup for rlsbl."""
2
2
 
3
- from . import npm, pypi
3
+ from . import go, npm, pypi
4
4
 
5
- REGISTRIES = {"npm": npm, "pypi": pypi}
5
+ REGISTRIES = {"npm": npm, "pypi": pypi, "go": go}
@@ -0,0 +1,124 @@
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
+ }
90
+
91
+
92
+ def get_template_mappings():
93
+ """Returns go-specific template mappings (template file -> target path)."""
94
+ return [
95
+ {"template": "VERSION.tpl", "target": "VERSION"},
96
+ {"template": "ci.yml.tpl", "target": ".github/workflows/ci.yml"},
97
+ {"template": "publish.yml.tpl", "target": ".github/workflows/publish.yml"},
98
+ {"template": "goreleaser.yml.tpl", "target": ".goreleaser.yml"},
99
+ ]
100
+
101
+
102
+ def get_shared_template_mappings():
103
+ """Returns shared template mappings."""
104
+ return [
105
+ {"template": "CHANGELOG.md.tpl", "target": "CHANGELOG.md"},
106
+ {"template": "gitignore.tpl", "target": ".gitignore"},
107
+ {"template": "LICENSE.tpl", "target": "LICENSE"},
108
+ {"template": "CLAUDE.md.tpl", "target": "CLAUDE.md"},
109
+ {"template": "check-prs.sh.tpl", "target": "scripts/check-prs.sh"},
110
+ {"template": "claude-settings.json.tpl", "target": ".claude/settings.json"},
111
+ {"template": "record-gif.sh.tpl", "target": "scripts/record-gif.sh"},
112
+ {"template": "pre-release.sh.tpl", "target": "scripts/pre-release.sh"},
113
+ {"template": "pre-push-hook.sh.tpl", "target": "scripts/pre-push-hook.sh"},
114
+ ]
115
+
116
+
117
+ def check_project_exists(dir_path):
118
+ """Returns True if a go.mod exists in the given directory."""
119
+ return os.path.exists(os.path.join(dir_path, "go.mod"))
120
+
121
+
122
+ def get_project_init_hint():
123
+ """Hint for users who haven't initialized their project yet."""
124
+ return 'Run "go mod init <module-path>" first'
package/rlsbl/utils.py CHANGED
@@ -93,11 +93,10 @@ def check_gh_auth():
93
93
  def find_commit_tool():
94
94
  """Detect safegit or fall back to git for committing.
95
95
 
96
- Returns the path/name of the commit tool.
96
+ Returns "safegit" if available on PATH, otherwise "git".
97
97
  """
98
- safegit_path = shutil.which("safegit")
99
- if safegit_path:
100
- return safegit_path
98
+ if shutil.which("safegit"):
99
+ return "safegit"
101
100
  return "git"
102
101
 
103
102
 
@@ -0,0 +1 @@
1
+ {{version}}
@@ -0,0 +1,21 @@
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@v5
17
+ - uses: actions/setup-go@v5
18
+ with:
19
+ go-version: ${{ matrix.go-version }}
20
+ - run: go test ./...
21
+ - run: go vet ./...
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,25 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ goreleaser:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+ with:
16
+ fetch-depth: 0
17
+ - uses: actions/setup-go@v5
18
+ with:
19
+ go-version-file: go.mod
20
+ - uses: goreleaser/goreleaser-action@v6
21
+ with:
22
+ version: latest
23
+ args: release --clean
24
+ env:
25
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -9,6 +9,8 @@ if [ -f package.json ]; then
9
9
  VERSION=$(node -e "console.log(require('./package.json').version)" 2>/dev/null) || exit 0
10
10
  elif [ -f pyproject.toml ]; then
11
11
  VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/.*"\(.*\)".*/\1/') || exit 0
12
+ elif [ -f VERSION ]; then
13
+ VERSION=$(tr -d '[:space:]' < VERSION) || exit 0
12
14
  else
13
15
  exit 0
14
16
  fi
@@ -1,13 +1,27 @@
1
1
  #!/usr/bin/env bash
2
2
  # Pre-release validation hook.
3
3
  # Runs before rlsbl creates a release. Exit non-zero to abort.
4
- # Add your project-specific checks here (e.g., tests, linting, audit).
4
+ # Detects project type and runs appropriate checks automatically.
5
5
 
6
6
  set -euo pipefail
7
7
 
8
8
  echo "Running pre-release checks..."
9
9
 
10
- # Example: run tests if available
11
- # npm test 2>/dev/null || true
10
+ if [ -f go.mod ]; then
11
+ echo "Detected Go project"
12
+ go vet ./...
13
+ go build ./...
14
+ go test ./... -race -short -count=1
15
+ elif [ -f package.json ]; then
16
+ echo "Detected npm project"
17
+ npm test
18
+ elif [ -f pyproject.toml ]; then
19
+ echo "Detected Python project"
20
+ if command -v uv &>/dev/null; then
21
+ uv run pytest
22
+ elif command -v pytest &>/dev/null; then
23
+ pytest
24
+ fi
25
+ fi
12
26
 
13
27
  echo "Pre-release checks passed."