@@ -21,6 +21,7 @@ class Publisher:
2121 TOOL_CONVENTIONAL_CHANGELOG = "conventional-changelog"
2222 TOOL_POETRY = "poetry"
2323 TOOL_GIT = "git"
24+ TOOL_HUB = "hub"
2425 TOOL_TWINE = "twine"
2526 TOOL_CONVENTIONAL_GITHUB_RELEASER = "conventional-github-releaser"
2627
@@ -32,6 +33,7 @@ class Publisher:
3233 ),
3334 TOOL_POETRY : "Install from https://github.com/sdispater/poetry#installation" ,
3435 TOOL_GIT : "Install using your OS package tools" ,
36+ TOOL_HUB : "Install from https://github.com/github/hub#installation" ,
3537 TOOL_TWINE : "Install from https://github.com/pypa/twine#installation" ,
3638 TOOL_CONVENTIONAL_GITHUB_RELEASER : (
3739 "Install from https://github.com/conventional-changelog/releaser-tools/tree"
@@ -47,36 +49,35 @@ class Publisher:
4749 }
4850
4951 # https://github.com/peritus/bumpversion
50- BUMP_VERSION = TOOL_BUMPVERSION + " {allow_dirty} {part}"
51- BUMP_VERSION_SIMPLE_CHECK = f"{ BUMP_VERSION } --dry-run"
52- BUMP_VERSION_VERBOSE = f"{ BUMP_VERSION_SIMPLE_CHECK } --verbose 2>&1"
53- BUMP_VERSION_VERBOSE_FILES = f"{ BUMP_VERSION_VERBOSE } | grep -i -E -e '^would'"
54- BUMP_VERSION_GREP = (
55- f'{ BUMP_VERSION_VERBOSE } | grep -i -E -e "would commit to git.+bump" -e "^new version" | grep -E -o "\' (.+)\' "'
56- )
52+ CMD_BUMP_VERSION = TOOL_BUMPVERSION + " {allow_dirty} {part}"
53+ CMD_BUMP_VERSION_SIMPLE_CHECK = f"{ CMD_BUMP_VERSION } --dry-run"
54+ CMD_BUMP_VERSION_VERBOSE = f"{ CMD_BUMP_VERSION_SIMPLE_CHECK } --verbose 2>&1"
55+ CMD_BUMP_VERSION_VERBOSE_FILES = f"{ CMD_BUMP_VERSION_VERBOSE } | grep -i -E -e '^would'"
56+ CMD_BUMP_VERSION_GREP = f'{ CMD_BUMP_VERSION_VERBOSE } | grep -i -E -e "would commit to git.+bump" -e "^new version" | grep -E -o "\' (.+)\' "'
5757
5858 # https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli
59- CHANGELOG = f"{ TOOL_CONVENTIONAL_CHANGELOG } -i CHANGELOG.md -p angular"
59+ CMD_CHANGELOG = f"{ TOOL_CONVENTIONAL_CHANGELOG } -i CHANGELOG.md -p angular"
6060
61- BUILD_SETUP_PY = "python setup.py sdist bdist_wheel --universal"
61+ CMD_BUILD_SETUP_PY = "python setup.py sdist bdist_wheel --universal"
6262
6363 # https://poetry.eustace.io/
64- POETRY_BUILD = f"{ TOOL_POETRY } build"
64+ CMD_POETRY_BUILD = f"{ TOOL_POETRY } build"
6565
66- GIT_ADD_AND_COMMIT = TOOL_GIT + " add . && git commit -m'{}' --no-verify"
67- GIT_PUSH = f"{ TOOL_GIT } push"
68- GIT_TAG = TOOL_GIT + " tag v{} "
66+ CMD_GIT_ADD_AND_COMMIT = TOOL_GIT + " add . && git commit -m'{}' --no-verify"
67+ CMD_GIT_PUSH = f"{ TOOL_GIT } push"
68+ CMD_GIT_CHECKOUT_MASTER = f"echo { TOOL_GIT } checkout master && echo { TOOL_GIT } pull "
6969
7070 # https://github.com/pypa/twine
7171 # I tried using "poetry publish -u $TWINE_USERNAME -p $TWINE_PASSWORD"; the command didn't fail,
7272 # but nothing was uploaded
7373 # I also tried setting $TWINE_USERNAME and $TWINE_PASSWORD on the environment,
7474 # but then "twine upload" didn't work for some reason.
75- TWINE_UPLOAD = TOOL_TWINE + " upload {repo} dist/*"
75+ CMD_TWINE_UPLOAD = TOOL_TWINE + " upload {repo} dist/*"
7676
7777 # https://www.npmjs.com/package/conventional-github-releaser
78- GITHUB_RELEASE = TOOL_CONVENTIONAL_GITHUB_RELEASER + " -p angular -v --token {}"
79- GITHUB_RELEASE_ENVVAR = "CONVENTIONAL_GITHUB_RELEASER_TOKEN"
78+ CMD_GITHUB_RELEASE = TOOL_CONVENTIONAL_GITHUB_RELEASER + " -p angular -v --token {}"
79+ CMD_MANUAL_GITHUB_RELEASE = f"echo { TOOL_HUB } browse"
80+ CMD_GITHUB_RELEASE_ENVVAR = "CONVENTIONAL_GITHUB_RELEASER_TOKEN"
8081
8182 def __init__ (self , dry_run : bool ):
8283 self .dry_run = dry_run
@@ -113,7 +114,7 @@ def github_access_token_option(cls):
113114 "-t" ,
114115 help = (
115116 f"GitHub access token used by { cls .TOOL_CONVENTIONAL_GITHUB_RELEASER } . If not defined, will use the value"
116- + f" from the ${ cls .GITHUB_RELEASE_ENVVAR } environment variable"
117+ + f" from the ${ cls .CMD_GITHUB_RELEASE_ENVVAR } environment variable"
117118 ),
118119 )
119120
@@ -136,8 +137,8 @@ def check_tools(self, github_access_token: str = None) -> None:
136137 self .github_access_token = github_access_token
137138 else :
138139 error_message = "Missing access token"
139- if self .GITHUB_RELEASE_ENVVAR in os .environ :
140- variable = self .GITHUB_RELEASE_ENVVAR
140+ if self .CMD_GITHUB_RELEASE_ENVVAR in os .environ :
141+ variable = self .CMD_GITHUB_RELEASE_ENVVAR
141142 else :
142143 token_keys = {k for k in os .environ .keys () if "github_access_token" .casefold () in k .casefold ()}
143144 if len (token_keys ) == 1 :
@@ -152,7 +153,7 @@ def check_tools(self, github_access_token: str = None) -> None:
152153 else :
153154 click .secho (f"{ error_message } . " , fg = "bright_red" , nl = False )
154155 click .echo (
155- f"Set the variable ${ self .GITHUB_RELEASE_ENVVAR } or use"
156+ f"Set the variable ${ self .CMD_GITHUB_RELEASE_ENVVAR } or use"
156157 + " --github-access-token to define a GitHub access token"
157158 )
158159 all_ok = False
@@ -174,27 +175,27 @@ def _bump(cls, base_command: str, part: str, allow_dirty: bool):
174175 def check_bumped_version (self , part : str , allow_dirty : bool ) -> Tuple [str , str ]:
175176 """Check the version that will be bumped."""
176177 shell (
177- self ._bump (self .BUMP_VERSION_SIMPLE_CHECK , part , allow_dirty ),
178+ self ._bump (self .CMD_BUMP_VERSION_SIMPLE_CHECK , part , allow_dirty ),
178179 exit_on_failure = True ,
179180 header = "Check the version that will be bumped" ,
180181 )
181182
182- bump_cmd = self ._bump (self .BUMP_VERSION_VERBOSE_FILES , part , allow_dirty )
183+ bump_cmd = self ._bump (self .CMD_BUMP_VERSION_VERBOSE_FILES , part , allow_dirty )
183184 shell (bump_cmd , dry_run = self .dry_run , header = f"Display what files would be changed" , exit_on_failure = True )
184185 if not self .dry_run :
185- chosen_lines = shell (self ._bump (self .BUMP_VERSION_GREP , part , allow_dirty ), return_lines = True )
186+ chosen_lines = shell (self ._bump (self .CMD_BUMP_VERSION_GREP , part , allow_dirty ), return_lines = True )
186187 new_version = chosen_lines [0 ].strip ("'" )
187- commit_message = chosen_lines [1 ].strip ("'" )
188+ commit_message = chosen_lines [1 ].strip ("'" ). lower ()
188189 click .echo (f"New version: { new_version } \n Commit message: { commit_message } " )
189190 prompt ("Were all versions correctly displayed?" )
190191 else :
191- commit_message = "Bump version from X to Y"
192+ commit_message = "bump version from X to Y"
192193 new_version = "<new version here>"
193- return commit_message , new_version
194+ return f"build: { commit_message } " , new_version
194195
195196 def actually_bump_version (self , part : str , allow_dirty : bool ) -> None :
196197 """Actually bump the version."""
197- shell (self ._bump (self .BUMP_VERSION , part , allow_dirty ), dry_run = self .dry_run , header = f"Bump versions" )
198+ shell (self ._bump (self .CMD_BUMP_VERSION , part , allow_dirty ), dry_run = self .dry_run , header = f"Bump versions" )
198199
199200 def recreate_setup_py (self , ctx ) -> None :
200201 """Recreate the setup.py if it exists."""
@@ -206,14 +207,16 @@ def recreate_setup_py(self, ctx) -> None:
206207
207208 def generate_changelog (self ) -> None :
208209 """Generate the changelog."""
209- shell (f"{ Publisher .CHANGELOG } -s" , dry_run = self .dry_run , header = "Generate the changelog" )
210+ shell (f"{ Publisher .CMD_CHANGELOG } -s" , dry_run = self .dry_run , header = "Generate the changelog" )
210211
211212 def build_with_poetry (self ) -> None :
212213 """Build the project with poetry."""
213214 if not self .dry_run :
214215 remove_previous_builds ()
215216
216- shell (Publisher .POETRY_BUILD , dry_run = self .dry_run , header = f"Build the project with { Publisher .TOOL_POETRY } " )
217+ shell (
218+ Publisher .CMD_POETRY_BUILD , dry_run = self .dry_run , header = f"Build the project with { Publisher .TOOL_POETRY } "
219+ )
217220
218221 if not self .dry_run :
219222 shell ("ls -l dist" )
@@ -235,28 +238,60 @@ def show_diff(self) -> None:
235238 )
236239
237240 @classmethod
238- def commit_push_tag (cls , commit_message : str , new_version : str ) -> List [HeaderCommand ]:
241+ def commit_push_tag (cls , commit_message : str , new_version : str , manual_release : bool ) -> List [HeaderCommand ]:
239242 """Prepare the commands to commit, push and tag."""
240- return [
241- ("Add all files and commit (skipping hooks)" , Publisher .GIT_ADD_AND_COMMIT .format (commit_message )),
242- ("Push" , Publisher .GIT_PUSH ),
243- (
244- f"Create the tag but don't push it yet ({ Publisher .TOOL_CONVENTIONAL_GITHUB_RELEASER } will do that)" ,
245- Publisher .GIT_TAG .format (new_version ),
246- ),
243+ commands = [
244+ ("Add all files and commit (skipping hooks)" , Publisher .CMD_GIT_ADD_AND_COMMIT .format (commit_message )),
245+ ("Push" , Publisher .CMD_GIT_PUSH ),
247246 ]
247+ if manual_release :
248+ commands .extend (
249+ [
250+ (
251+ "Approve the pull request on GitHub, then return here and run the following commands" ,
252+ Publisher .CMD_GIT_CHECKOUT_MASTER ,
253+ ),
254+ ("Create the tag manually" , cls .cmd_tag (new_version , echo = True )),
255+ ("Push the tags manually" , cls .cmd_push_tags ()),
256+ ]
257+ )
258+ else :
259+ commands .append (
260+ (
261+ f"Create the tag but don't push it yet ({ Publisher .TOOL_CONVENTIONAL_GITHUB_RELEASER } will do that)" ,
262+ cls .cmd_tag (new_version ),
263+ )
264+ )
265+ return commands
266+
267+ @classmethod
268+ def cmd_tag (cls , version : str , echo = False ) -> str :
269+ """Command to create a Git tag."""
270+ return f"{ 'echo ' if echo else '' } { cls .TOOL_GIT } tag v{ version } "
271+
272+ @classmethod
273+ def cmd_push_tags (cls ) -> str :
274+ """Command to push tags."""
275+ return f"echo { cls .TOOL_GIT } push --tags"
248276
249277 @classmethod
250278 def upload_pypi (cls ) -> List [HeaderCommand ]:
251279 """Prepare commands to upload to PyPI."""
252280 return [
253- ("Test upload the files to TestPyPI via Twine" , Publisher .TWINE_UPLOAD .format (repo = "-r testpypi" )),
254- ("Upload the files to PyPI via Twine" , Publisher .TWINE_UPLOAD .format (repo = "" )),
281+ ("Test upload the files to TestPyPI via Twine" , Publisher .CMD_TWINE_UPLOAD .format (repo = "-r testpypi" )),
282+ ("Upload the files to PyPI via Twine" , Publisher .CMD_TWINE_UPLOAD .format (repo = "" )),
255283 ]
256284
257- def release (self ) -> List [HeaderCommand ]:
285+ def release (self , manual_release ) -> List [HeaderCommand ]:
258286 """Prepare release commands."""
259- return [("Create a GitHub release" , Publisher .GITHUB_RELEASE .format (self .github_access_token ))]
287+ if manual_release :
288+ return [
289+ (
290+ "Open GitHub and create a GitHub release manually, copying the content from CHANGELOG.md" ,
291+ Publisher .CMD_MANUAL_GITHUB_RELEASE ,
292+ )
293+ ]
294+ return [("Create a GitHub release" , Publisher .CMD_GITHUB_RELEASE .format (self .github_access_token ))]
260295
261296 def run_commands (self , commands : List [HeaderCommand ]):
262297 """Run a list of commands."""
@@ -273,7 +308,15 @@ def success(self, new_version: str, upload_destination: str):
273308 return
274309 click .secho (f"The new version { new_version } was uploaded to { upload_destination } ! ✨ 🍰 ✨" , fg = "bright_white" )
275310
276- def publish (self , pypi : bool , ctx , part : str , allow_dirty : bool , github_access_token : str = None ):
311+ def publish (
312+ self ,
313+ pypi : bool ,
314+ ctx ,
315+ part : str ,
316+ allow_dirty : bool ,
317+ github_access_token : str = None ,
318+ manual_release : bool = False ,
319+ ):
277320 """Publish a package."""
278321 self .check_tools (github_access_token )
279322 commit_message , new_version = self .check_bumped_version (part , allow_dirty )
@@ -283,10 +326,10 @@ def publish(self, pypi: bool, ctx, part: str, allow_dirty: bool, github_access_t
283326 self .build_with_poetry ()
284327 self .show_diff ()
285328
286- commands = self .commit_push_tag (commit_message , new_version )
329+ commands = self .commit_push_tag (commit_message , new_version , manual_release )
287330 if pypi :
288331 commands .extend (self .upload_pypi ())
289- commands .extend (self .release ())
332+ commands .extend (self .release (manual_release ))
290333
291334 self .run_commands (commands )
292335 self .success (new_version , "PyPI" if pypi else "GitHub" )
@@ -313,9 +356,10 @@ def pypub():
313356
314357
315358@pypub .command ()
316- def check ():
359+ @Publisher .github_access_token_option ()
360+ def check (github_access_token : str = None ):
317361 """Check if all needed tools and files are present."""
318- Publisher (False ).check_tools ()
362+ Publisher (False ).check_tools (github_access_token )
319363
320364
321365@pypub .command ()
@@ -349,16 +393,26 @@ def pypi(ctx, dry_run: bool, part: str, allow_dirty: bool, github_access_token:
349393@Publisher .part_option ()
350394@Publisher .allow_dirty_option ()
351395@Publisher .github_access_token_option ()
396+ @click .option (
397+ "--manual-release" ,
398+ "-r" ,
399+ default = False ,
400+ is_flag = True ,
401+ type = bool ,
402+ help = f"Run commands up until tagging. Tag, merge, create the release: all have to be done manually" ,
403+ )
352404@click .pass_context
353- def github (ctx , dry_run : bool , part : str , allow_dirty : bool , github_access_token : str = None ):
405+ def github (
406+ ctx , dry_run : bool , part : str , allow_dirty : bool , github_access_token : str = None , manual_release : bool = False
407+ ):
354408 """Release to GitHub only (bump version, changelog, package, upload)."""
355- Publisher (dry_run ).publish (False , ctx , part , allow_dirty , github_access_token )
409+ Publisher (dry_run ).publish (False , ctx , part , allow_dirty , github_access_token , manual_release )
356410
357411
358412@pypub .command ()
359413def changelog ():
360414 """Preview the changelog."""
361- shell (f"{ Publisher .CHANGELOG } -u | less" )
415+ shell (f"{ Publisher .CMD_CHANGELOG } -u | less" )
362416
363417
364418@click .group ()
@@ -381,8 +435,15 @@ def setup_py():
381435 1 ,
382436 dedent (
383437 '''
384- """NOTICE: This file was generated automatically by the command: xpoetry setup-py."""
385- '''
438+ """
439+ Setup for this package.
440+
441+ .. note::
442+
443+ This file was generated automatically by ``xpoetry setup-py``.
444+ A ``setup.py`` file is needed to install this project in editable mode (``pip install -e /path/to/project``).
445+ """
446+ '''
386447 ).strip (),
387448 )
388449
0 commit comments