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 the registry-specific form (`rlsbl <registry> <command>`) only when you need to target a single registry.
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 scaffold # target npm only
37
- rlsbl pypi scaffold --force # overwrite existing files
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 npm release major --dry-run
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 status
68
+ rlsbl status --registry pypi
69
69
  ```
70
70
 
71
- ### check-name \<name\>
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-name my-cool-lib
77
- rlsbl npm check-name my-cool-lib # npm only
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlsbl",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Release orchestration and project scaffolding for npm and PyPI",
5
5
  "license": "MIT",
6
6
  "bin": {
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-name")
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 Show project status
21
+ rlsbl status Show project status
22
22
  rlsbl scaffold [--force] [--update] Scaffold release infrastructure
23
- rlsbl check-name <name> Check name availability
23
+ rlsbl check <name> Check name availability
24
24
 
25
- Registry-specific (when you need to target one):
26
- rlsbl <registry> <command> [args...]
27
-
28
- Registries: {', '.join(REGISTRIES)}"""
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
- for arg in raw:
54
+ i = 0
55
+ while i < len(raw):
56
+ arg = raw[i]
51
57
  if arg.startswith("--"):
52
- flags[arg[2:]] = True
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-name": "check_name",
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
- first = positional[0] if positional else None
105
+ command = positional[0] if positional else None
92
106
 
93
107
  # Resolve command aliases (e.g. "init" -> "scaffold")
94
- if first in COMMAND_ALIASES:
95
- first = COMMAND_ALIASES[first]
108
+ if command in COMMAND_ALIASES:
109
+ command = COMMAND_ALIASES[command]
96
110
 
97
- if not first:
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
- registry = None
103
- command = None
104
- args = []
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
- if command not in COMMANDS:
123
- print(
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
- args = positional[2:]
131
- else:
132
- print(f'Error: unknown command or registry "{first}".\n', file=sys.stderr)
133
- print(HELP, file=sys.stderr)
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 registry:
143
- # Explicit registry -- single invocation
144
- handler.run_cmd(registry, args, flags)
145
- elif command == "check-name":
146
- # Top-level check-name: check ALL registries
147
- regs = ["npm", "pypi"]
148
- for i, r in enumerate(regs):
149
- handler.run_cmd(r, args, flags)
150
- if i < len(regs) - 1:
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
- # Top-level scaffold: scaffold for each detected registry
154
- regs = detect_registries()
155
- if not regs:
156
- print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
157
- sys.exit(1)
158
- if len(regs) > 1:
159
- # Multi-registry: only scaffold for the primary registry
160
- # (CI/publish workflows conflict when both registries target the same paths)
161
- print(f"Multiple registries detected: {', '.join(regs)}")
162
- print(f"Scaffolding for primary registry: {regs[0]}")
163
- print("For dual-registry projects, manually configure workflows with both jobs.")
164
- handler.run_cmd(regs[0], args, flags)
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
- # Top-level release/status: use primary detected registry
167
- regs = detect_registries()
168
- if not regs:
169
- print("Error: no package.json or pyproject.toml found.", file=sys.stderr)
170
- sys.exit(1)
171
- handler.run_cmd(regs[0], args, flags)
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-name command: check package name availability on npm or PyPI."""
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-name command handler.
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-name <name>",
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 npm release [patch|minor|major]",
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 pypi release [patch|minor|major]",
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 <registry> release [patch|minor|major]` to bump version and create a GitHub Release
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 <registry> release`
10
+ - Never publish manually — always use `rlsbl release`
11
11
  - Requires `NPM_TOKEN` secret on GitHub (for npm projects)
12
- - Use `rlsbl <registry> release --dry-run` to preview a release without making changes
12
+ - Use `rlsbl release --dry-run` to preview a release without making changes
13
13
 
14
14
  ## Conventions
15
15