Skip to content

Commit ae42433

Browse files
committed
Add release workflow
1 parent 559707b commit ae42433

File tree

2 files changed

+160
-147
lines changed

2 files changed

+160
-147
lines changed

.github/workflows/release.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Publish release
2+
3+
on:
4+
pull_request:
5+
types:
6+
- closed
7+
8+
jobs:
9+
release:
10+
name: Release
11+
if: ${{ github.event.pull_request.merged && startsWith(github.head_ref, 'release/') }}
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
with:
16+
token: ${{ secrets.GH_ACCESS_TOKEN_TOM }}
17+
- id: get-version
18+
run: echo "::set-output name=version::$(echo ${{ github.head_ref }} | sed 's|release/||')"
19+
- name: Use Node.js 12
20+
uses: actions/setup-node@v1
21+
with:
22+
node-version: 12.x
23+
- name: Install dependencies
24+
run: npm install
25+
- name: Set up Python 3.8
26+
uses: actions/setup-python@v1
27+
with:
28+
python-version: 3.8
29+
- name: Install Python dependencies
30+
run: python -m pip install dash[dev] flit invoke semver termcolor
31+
- name: Publish release to npm
32+
run: npm publish
33+
env:
34+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
35+
- name: Publish release to PyPI
36+
run: flit publish
37+
env:
38+
FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }}
39+
FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
40+
- name: Commit updated package-lock.json
41+
uses: stefanzweifel/git-auto-commit-action@v4
42+
with:
43+
commit_message: Release ${{ steps.get-version.outputs.version }}
44+
branch: master
45+
push_options: --force
46+
file_pattern: package-lock.json
47+
- name: Create GitHub release
48+
id: create-release
49+
uses: actions/create-release@v1
50+
env:
51+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52+
with:
53+
tag_name: ${{ steps.get-version.outputs.version }}
54+
release_name: Release ${{ steps.get-version.outputs.version }}
55+
body: ${{ github.event.pull_request.body }}
56+
prerelease: false
57+
- name: Post-release cleanup
58+
run: invoke postrelease ${{ steps.get-version.outputs.version }}
59+
- uses: stefanzweifel/git-auto-commit-action@v4
60+
with:
61+
commit_message: Back to dev
62+
branch: master
63+
push_options: --force
64+
file_pattern: package.json tests/test_version.py dash_bootstrap_components/__init__.py
65+
66+
prerelease:
67+
name: Prerelease
68+
if: ${{ github.event.pull_request.merged && startsWith(github.head_ref, 'prerelease/') }}
69+
runs-on: ubuntu-latest
70+
steps:
71+
- uses: actions/checkout@v2
72+
with:
73+
token: ${{ secrets.GH_ACCESS_TOKEN_TOM }}
74+
- id: get-version
75+
run: echo "::set-output name=version::$(echo ${{ github.head_ref }} | sed 's|prerelease/||')"
76+
- name: Use Node.js 12
77+
uses: actions/setup-node@v1
78+
with:
79+
node-version: 12.x
80+
- name: Install dependencies
81+
run: npm install
82+
- name: Set up Python 3.8
83+
uses: actions/setup-python@v1
84+
with:
85+
python-version: 3.8
86+
- name: Install Python dependencies
87+
run: python -m pip install dash[dev] flit invoke semver termcolor
88+
- name: Publish prerelease to npm
89+
run: npm publish
90+
env:
91+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
92+
- name: Publish prerelease to PyPI
93+
run: flit publish
94+
env:
95+
FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }}
96+
FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
97+
- name: Commit updated package-lock.json
98+
uses: stefanzweifel/git-auto-commit-action@v4
99+
with:
100+
commit_message: Release ${{ steps.get-version.outputs.version }}
101+
branch: master
102+
push_options: --force
103+
file_pattern: package-lock.json
104+
- name: Create GitHub prerelease
105+
id: create-prerelease
106+
uses: actions/create-release@v1
107+
env:
108+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
109+
with:
110+
tag_name: ${{ steps.get-version.outputs.version }}
111+
release_name: Prerelease ${{ steps.get-version.outputs.version }}
112+
body: ${{ github.event.pull_request.body }}
113+
prerelease: true
114+
- name: Post-release cleanup
115+
run: invoke postrelease ${{ steps.get-version.outputs.version }}
116+
- uses: stefanzweifel/git-auto-commit-action@v4
117+
with:
118+
commit_message: Back to dev
119+
branch: master
120+
push_options: --force
121+
file_pattern: package.json tests/test_version.py dash_bootstrap_components/__init__.py

tasks.py

Lines changed: 39 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import os
2-
import tempfile
31
from pathlib import Path
4-
from shutil import which
5-
from subprocess import call
62

73
import semver
84
from invoke import run as invoke_run
@@ -19,16 +15,9 @@ def test_version():
1915
assert __version__ == "{version_string}"
2016
"""
2117

22-
RELEASE_NOTES_TEMPLATE = """# Write the release notes here
23-
# Delete the version title to cancel
24-
Version {version_string}
25-
{underline}
26-
"""
27-
2818
HERE = Path(__file__).parent
2919

3020
DASH_BOOTSTRAP_DIR = HERE / "dash_bootstrap_components"
31-
JS_DIR = HERE
3221

3322

3423
@task(help={"version": "Version number to release"})
@@ -39,51 +28,45 @@ def prerelease(ctx, version):
3928
- Bump the version number
4029
- Push a release to pypi
4130
"""
42-
check_prerequisites()
43-
info(f"Releasing version {version} as prerelease")
44-
build_publish(version)
31+
info(f"Creating prerelease branch for {version}")
32+
set_source_version(version)
33+
run(f"git checkout -b prerelease/{version}")
34+
run(
35+
"git add package.json package-lock.json "
36+
"dash_bootstrap_components/__init__.py "
37+
"tests/test_version.py"
38+
)
39+
run(f'git commit -m "Set version to {version}"')
40+
run(f"git push origin prerelease/{version}")
4541

4642

4743
@task(help={"version": "Version number to release"})
4844
def release(ctx, version):
4945
"""
5046
Release a new version
5147
Running this task will:
52-
- Prompt the user for a changelog and write it to
53-
the release notes
54-
- Commit the release notes
48+
- Create a release branch
5549
- Bump the version number
56-
- Push a release to pypi
57-
- commit the version changes to source control
58-
- tag the commit
50+
Release notes should be written in the body of the pull request. When
51+
changes are merged GitHub actions will
52+
- Build the package
53+
- Push a release to PyPI
54+
- Create a release
55+
- Revert to a dev version change.
5956
"""
60-
check_prerequisites()
61-
info(f"Releasing version {version} as full release")
57+
info(f"Creating release branch for {version}")
58+
set_source_version(version)
6259
set_documentation_version(version)
63-
release_notes_lines = get_release_notes(version)
6460

65-
if release_notes_lines is None:
66-
error("No release notes: exiting")
67-
exit()
68-
69-
info("Writing release notes to changelog.tmp")
70-
with open("changelog.tmp", "w") as f:
71-
f.writelines(release_notes_lines)
72-
73-
# TODO when we have release notes, these should be amended here
74-
75-
build_publish(version)
76-
77-
info("Committing version changes")
78-
run(f"git checkout -b release-{version}")
61+
run(f"git checkout -b release/{version}")
7962
run(
80-
"git add package.json package-lock.json tests/test_version.py"
81-
"docs/requirements.txt dash_bootstrap_components/_version.py"
63+
"git add package.json package-lock.json "
64+
"docs/requirements.txt "
65+
"dash_bootstrap_components/__init__.py "
66+
"tests/test_version.py"
8267
)
8368
run(f'git commit -m "Bump version to {version}"')
84-
info(f"Tagging version {version} and pushing to GitHub")
85-
run(f'git tag -a "{version}" -F changelog.tmp')
86-
run(f"git push origin release-{version} --tags")
69+
run(f"git push origin release/{version}")
8770

8871

8972
@task
@@ -104,23 +87,7 @@ def copy_examples(ctx):
10487
)
10588

10689

107-
@task(copy_examples)
108-
def documentation(ctx):
109-
"""
110-
Push documentation to Heroku
111-
"""
112-
info("Pushing documentation to Heroku")
113-
run("git checkout -b inv-push-docs")
114-
run("git add docs/examples/vendor/*.py -f")
115-
run('git commit -m "Add examples" --allow-empty')
116-
run("git subtree split --prefix docs -b inv-push-docs-subtree")
117-
run("git push -f heroku inv-push-docs-subtree:master")
118-
run("git checkout master")
119-
run("git branch -D inv-push-docs inv-push-docs-subtree")
120-
121-
12290
@task(
123-
documentation,
12491
help={
12592
"version": "Version number to finalize. Must be "
12693
"the same version number that was used in the release."
@@ -133,54 +100,24 @@ def postrelease(ctx, version):
133100
- bump the version to the next dev version
134101
- push changes to master
135102
"""
136-
new_version = semver.bump_patch(version) + "-dev"
137-
info(f"Bumping version numbers to {new_version} and committing")
138-
set_pyversion(new_version)
139-
set_jsversion(new_version)
140-
run(f"git checkout -b postrelease-{version}")
141-
run(
142-
"git add package.json package-lock.json tests/test_version.py "
143-
"dash_bootstrap_components/_version.py"
144-
)
145-
run('git commit -m "Back to dev"')
146-
run(f"git push origin postrelease-{version}")
147-
148-
149-
def build_publish(version):
150-
info("Cleaning")
151-
clean()
152-
info("Updating versions")
153-
set_pyversion(version)
154-
set_jsversion(version)
155-
info("Building JavaScript components")
156-
build_js()
157-
info("Building and uploading Python source distribution")
158-
info("PyPI credentials:")
159-
release_python_sdist()
160-
161-
162-
def clean():
163-
paths_to_clean = ["dash_bootstrap_components/_components", "dist/", "lib/"]
164-
for path in paths_to_clean:
165-
run(f"rm -rf {path}")
166-
103+
clean_version = semver.finalize_version(version)
104+
if clean_version == version:
105+
# last release was full release, bump patch
106+
new_version = semver.bump_patch(version) + "-dev"
107+
else:
108+
# last release was prerelease, revert to dev version
109+
new_version = clean_version + "-dev"
167110

168-
def build_js():
169-
os.chdir(JS_DIR)
170-
try:
171-
run("npm install")
172-
run("npm publish")
173-
finally:
174-
os.chdir(HERE)
111+
info(f"Bumping version numbers to {new_version} and committing")
112+
set_source_version(new_version)
175113

176114

177-
def release_python_sdist():
178-
run("rm -f dist/*")
179-
run("python setup.py sdist")
180-
invoke_run("twine upload dist/*")
115+
def set_source_version(version):
116+
set_js_version(version)
117+
set_py_version(version)
181118

182119

183-
def set_pyversion(version):
120+
def set_py_version(version):
184121
version = normalize_version(version)
185122
init_path = DASH_BOOTSTRAP_DIR / "__init__.py"
186123
with init_path.open("r") as f:
@@ -197,7 +134,7 @@ def set_pyversion(version):
197134
f.write(TEST_VERSION_TEMPLATE.format(version_string=version))
198135

199136

200-
def set_jsversion(version):
137+
def set_js_version(version):
201138
version = normalize_version(version)
202139
package_json_path = HERE / "package.json"
203140
with package_json_path.open() as f:
@@ -222,51 +159,6 @@ def set_documentation_version(version):
222159
f.writelines(docs_requirements)
223160

224161

225-
def get_release_notes(version):
226-
version = normalize_version(version)
227-
underline = "=" * len(f"Version {version}")
228-
initial_message = RELEASE_NOTES_TEMPLATE.format(
229-
version_string=version, underline=underline
230-
)
231-
lines = open_editor(initial_message)
232-
non_commented_lines = [line for line in lines if not line.startswith("#")]
233-
changelog = "".join(non_commented_lines)
234-
if version in changelog:
235-
if not non_commented_lines[-1].isspace():
236-
non_commented_lines.append("\n")
237-
return non_commented_lines
238-
else:
239-
return None
240-
241-
242-
def open_editor(initial_message):
243-
editor = os.environ.get("EDITOR", "vim")
244-
tmp = tempfile.NamedTemporaryFile(suffix=".tmp")
245-
fname = tmp.name
246-
247-
with open(fname, "w") as f:
248-
f.write(initial_message)
249-
f.flush()
250-
251-
call([editor, fname], close_fds=True)
252-
253-
with open(fname, "r") as f:
254-
lines = f.readlines()
255-
256-
return lines
257-
258-
259-
def check_prerequisites():
260-
for executable in ["twine", "npm", "dash-generate-components"]:
261-
if which(executable) is None:
262-
error(
263-
f"{executable} executable not found. "
264-
f"You must have {executable} to release "
265-
"dash-bootstrap-components."
266-
)
267-
exit(127)
268-
269-
270162
def normalize_version(version):
271163
version_info = semver.parse_version_info(version)
272164
version_string = str(version_info)

0 commit comments

Comments
 (0)