rlsbl 0.1.1 → 0.2.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/README.md
CHANGED
|
@@ -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
|
|
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.
|
|
29
29
|
|
|
30
30
|
### scaffold [--force] [--update]
|
|
31
31
|
|
|
@@ -33,8 +33,8 @@ Scaffolds CI/CD infrastructure and release tooling for all detected registries.
|
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
rlsbl scaffold
|
|
36
|
-
rlsbl npm
|
|
37
|
-
rlsbl
|
|
36
|
+
rlsbl scaffold --registry npm # target npm only
|
|
37
|
+
rlsbl scaffold --registry pypi --force # overwrite existing files
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Context-aware behavior when files already exist (without `--force`):
|
|
@@ -52,7 +52,7 @@ Bumps version, commits, pushes, and creates a GitHub Release. Defaults to `patch
|
|
|
52
52
|
|
|
53
53
|
```
|
|
54
54
|
rlsbl release minor
|
|
55
|
-
rlsbl
|
|
55
|
+
rlsbl release major --dry-run --registry npm
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
The version is synced across all detected project files (`package.json`, `pyproject.toml`) regardless of which registry is primary.
|
|
@@ -65,16 +65,16 @@ Shows project status: package name, version (per registry), git branch, last tag
|
|
|
65
65
|
|
|
66
66
|
```
|
|
67
67
|
rlsbl status
|
|
68
|
-
rlsbl pypi
|
|
68
|
+
rlsbl status --registry pypi
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
### check
|
|
71
|
+
### check \<name\>
|
|
72
72
|
|
|
73
73
|
Checks name availability on both npm and PyPI, and warns about confusingly similar names.
|
|
74
74
|
|
|
75
75
|
```
|
|
76
|
-
rlsbl check
|
|
77
|
-
rlsbl
|
|
76
|
+
rlsbl check my-cool-lib
|
|
77
|
+
rlsbl check my-cool-lib --registry npm # npm only
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
npm checks variant spellings (hyphens, underscores, dots, no separator). PyPI normalizes per PEP 503 and checks common alternatives.
|
package/package.json
CHANGED
package/rlsbl/__init__.py
CHANGED
|
@@ -10,7 +10,7 @@ except Exception:
|
|
|
10
10
|
__version__ = "unknown"
|
|
11
11
|
|
|
12
12
|
REGISTRIES = ("npm", "pypi")
|
|
13
|
-
COMMANDS = ("release", "status", "scaffold", "check
|
|
13
|
+
COMMANDS = ("release", "status", "scaffold", "check")
|
|
14
14
|
COMMAND_ALIASES = {"init": "scaffold"}
|
|
15
15
|
|
|
16
16
|
HELP = f"""\
|
|
@@ -18,14 +18,14 @@ rlsbl v{__version__} -- Release orchestration and project scaffolding for npm an
|
|
|
18
18
|
|
|
19
19
|
Usage:
|
|
20
20
|
rlsbl release [patch|minor|major] [--dry-run] [--quiet] Orchestrate a release
|
|
21
|
-
rlsbl status
|
|
21
|
+
rlsbl status Show project status
|
|
22
22
|
rlsbl scaffold [--force] [--update] Scaffold release infrastructure
|
|
23
|
-
rlsbl check
|
|
23
|
+
rlsbl check <name> Check name availability
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
Options:
|
|
26
|
+
--registry <npm|pypi> Target a specific registry (auto-detected if omitted)
|
|
27
|
+
--help, -h Show this help
|
|
28
|
+
--version, -v Show version"""
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def detect_registries():
|
|
@@ -42,19 +42,33 @@ def detect_registries():
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def parse_args(argv):
|
|
45
|
-
"""Parse sys.argv into positional args and flags.
|
|
45
|
+
"""Parse sys.argv into positional args and flags.
|
|
46
|
+
|
|
47
|
+
Flags listed in VALUE_FLAGS consume the next token as their value
|
|
48
|
+
(e.g. --registry npm). All other --flags are boolean.
|
|
49
|
+
"""
|
|
50
|
+
VALUE_FLAGS = ("registry",)
|
|
46
51
|
raw = argv[1:]
|
|
47
52
|
positional = []
|
|
48
53
|
flags = {}
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
i = 0
|
|
55
|
+
while i < len(raw):
|
|
56
|
+
arg = raw[i]
|
|
51
57
|
if arg.startswith("--"):
|
|
52
|
-
|
|
58
|
+
key = arg[2:]
|
|
59
|
+
if "=" in key:
|
|
60
|
+
k, v = key.split("=", 1)
|
|
61
|
+
flags[k] = v
|
|
62
|
+
elif key in VALUE_FLAGS and i + 1 < len(raw) and not raw[i + 1].startswith("-"):
|
|
63
|
+
flags[key] = raw[i + 1]
|
|
64
|
+
i += 1
|
|
65
|
+
else:
|
|
66
|
+
flags[key] = True
|
|
53
67
|
elif arg.startswith("-") and len(arg) == 2:
|
|
54
68
|
flags[arg[1:]] = True
|
|
55
69
|
else:
|
|
56
70
|
positional.append(arg)
|
|
57
|
-
|
|
71
|
+
i += 1
|
|
58
72
|
return positional, flags
|
|
59
73
|
|
|
60
74
|
|
|
@@ -65,7 +79,7 @@ def _get_command_module(command):
|
|
|
65
79
|
"release": "release",
|
|
66
80
|
"status": "status",
|
|
67
81
|
"scaffold": "init_cmd",
|
|
68
|
-
"check
|
|
82
|
+
"check": "check",
|
|
69
83
|
}
|
|
70
84
|
module_name = module_map.get(command)
|
|
71
85
|
if not module_name:
|
|
@@ -88,49 +102,31 @@ def main():
|
|
|
88
102
|
print(__version__)
|
|
89
103
|
sys.exit(0)
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
command = positional[0] if positional else None
|
|
92
106
|
|
|
93
107
|
# Resolve command aliases (e.g. "init" -> "scaffold")
|
|
94
|
-
if
|
|
95
|
-
|
|
108
|
+
if command in COMMAND_ALIASES:
|
|
109
|
+
command = COMMAND_ALIASES[command]
|
|
96
110
|
|
|
97
|
-
if not
|
|
111
|
+
if not command:
|
|
98
112
|
print("Error: missing command.\n", file=sys.stderr)
|
|
99
113
|
print(HELP, file=sys.stderr)
|
|
100
114
|
sys.exit(1)
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if first in COMMANDS:
|
|
107
|
-
# Top-level: rlsbl <command> ... -- auto-detect registry
|
|
108
|
-
command = first
|
|
109
|
-
args = positional[1:]
|
|
110
|
-
elif first in REGISTRIES:
|
|
111
|
-
# Registry-prefixed: rlsbl <registry> <command> ...
|
|
112
|
-
registry = first
|
|
113
|
-
command = positional[1] if len(positional) > 1 else None
|
|
114
|
-
if command and command in COMMAND_ALIASES:
|
|
115
|
-
command = COMMAND_ALIASES[command]
|
|
116
|
-
|
|
117
|
-
if not command:
|
|
118
|
-
print(f'Error: missing command for registry "{registry}".\n', file=sys.stderr)
|
|
119
|
-
print(HELP, file=sys.stderr)
|
|
120
|
-
sys.exit(1)
|
|
116
|
+
if command not in COMMANDS:
|
|
117
|
+
print(f'Error: unknown command "{command}".\n', file=sys.stderr)
|
|
118
|
+
print(HELP, file=sys.stderr)
|
|
119
|
+
sys.exit(1)
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
f'Error: unknown command "{command}". Valid commands: {", ".join(COMMANDS)}\n',
|
|
125
|
-
file=sys.stderr,
|
|
126
|
-
)
|
|
127
|
-
print(HELP, file=sys.stderr)
|
|
128
|
-
sys.exit(1)
|
|
121
|
+
args = positional[1:]
|
|
122
|
+
registry = flags.get("registry")
|
|
129
123
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
print(
|
|
133
|
-
|
|
124
|
+
# Validate --registry if provided
|
|
125
|
+
if registry and registry not in REGISTRIES:
|
|
126
|
+
print(
|
|
127
|
+
f"Error: unknown registry '{registry}'. Valid: {', '.join(REGISTRIES)}",
|
|
128
|
+
file=sys.stderr,
|
|
129
|
+
)
|
|
134
130
|
sys.exit(1)
|
|
135
131
|
|
|
136
132
|
try:
|
|
@@ -139,36 +135,37 @@ def main():
|
|
|
139
135
|
print(f'Error: command "{command}" is not yet implemented.', file=sys.stderr)
|
|
140
136
|
sys.exit(1)
|
|
141
137
|
|
|
142
|
-
if
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
print("")
|
|
138
|
+
if command == "check":
|
|
139
|
+
# check: if registry given, check that one; otherwise check all
|
|
140
|
+
if registry:
|
|
141
|
+
handler.run_cmd(registry, args, flags)
|
|
142
|
+
else:
|
|
143
|
+
for i, r in enumerate(["npm", "pypi"]):
|
|
144
|
+
handler.run_cmd(r, args, flags)
|
|
145
|
+
if i < 1:
|
|
146
|
+
print("")
|
|
152
147
|
elif command == "scaffold":
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
148
|
+
if registry:
|
|
149
|
+
handler.run_cmd(registry, args, flags)
|
|
150
|
+
else:
|
|
151
|
+
regs = detect_registries()
|
|
152
|
+
if not regs:
|
|
153
|
+
print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
if len(regs) > 1:
|
|
156
|
+
print(f"Multiple registries detected: {', '.join(regs)}")
|
|
157
|
+
print(f"Scaffolding for primary registry: {regs[0]}")
|
|
158
|
+
print("For dual-registry projects, manually configure workflows with both jobs.")
|
|
159
|
+
handler.run_cmd(regs[0], args, flags)
|
|
165
160
|
else:
|
|
166
|
-
#
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
161
|
+
# release, status: use explicit registry or auto-detect primary
|
|
162
|
+
if not registry:
|
|
163
|
+
regs = detect_registries()
|
|
164
|
+
if not regs:
|
|
165
|
+
print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
registry = regs[0]
|
|
168
|
+
handler.run_cmd(registry, args, flags)
|
|
172
169
|
except Exception as e:
|
|
173
170
|
print(f"Error: {e}", file=sys.stderr)
|
|
174
171
|
sys.exit(1)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Check
|
|
1
|
+
"""Check command: check package name availability on npm or PyPI."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import subprocess
|
|
@@ -166,14 +166,14 @@ def _check_name_pypi(name):
|
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
def run_cmd(registry, args, flags):
|
|
169
|
-
"""Check
|
|
169
|
+
"""Check command handler.
|
|
170
170
|
|
|
171
171
|
Checks package name availability on npm or PyPI, and warns about similar names.
|
|
172
172
|
"""
|
|
173
173
|
name = args[0] if args else None
|
|
174
174
|
if not name:
|
|
175
175
|
print(
|
|
176
|
-
"Error: missing package name. Usage: rlsbl check
|
|
176
|
+
"Error: missing package name. Usage: rlsbl check <name>",
|
|
177
177
|
file=sys.stderr,
|
|
178
178
|
)
|
|
179
179
|
sys.exit(1)
|
|
@@ -53,12 +53,12 @@ NEXT_STEPS = {
|
|
|
53
53
|
"npm": [
|
|
54
54
|
"Add an NPM_TOKEN secret to your GitHub repo (Settings > Secrets > Actions)",
|
|
55
55
|
"Push to GitHub to activate the CI workflow",
|
|
56
|
-
"Run rlsbl
|
|
56
|
+
"Run rlsbl release [patch|minor|major]",
|
|
57
57
|
],
|
|
58
58
|
"pypi": [
|
|
59
59
|
"Push to GitHub",
|
|
60
60
|
"Configure Trusted Publishing on pypi.org",
|
|
61
|
-
"Run rlsbl
|
|
61
|
+
"Run rlsbl release [patch|minor|major]",
|
|
62
62
|
],
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
This project uses [rlsbl](https://github.com/smm-h/rlsbl) for release orchestration.
|
|
6
6
|
|
|
7
7
|
- Update CHANGELOG.md with a `## X.Y.Z` entry describing changes
|
|
8
|
-
- Run `rlsbl
|
|
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
|
-
- Never publish manually — always use `rlsbl
|
|
10
|
+
- Never publish manually — always use `rlsbl release`
|
|
11
11
|
- Requires `NPM_TOKEN` secret on GitHub (for npm projects)
|
|
12
|
-
- Use `rlsbl
|
|
12
|
+
- Use `rlsbl release --dry-run` to preview a release without making changes
|
|
13
13
|
|
|
14
14
|
## Conventions
|
|
15
15
|
|