Skip to content

Commit 003f449

Browse files
committed
update README and metadata, add release task, and automate deploy previews and release using CI
1 parent 04c22eb commit 003f449

File tree

7 files changed

+291
-45
lines changed

7 files changed

+291
-45
lines changed

.github/workflows/release.yml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Release
2+
on:
3+
push:
4+
branches:
5+
- main
6+
jobs:
7+
activate:
8+
runs-on: ubuntu-latest
9+
if: |
10+
github.repository == 'asciidoctor/asciidoctor-docs-ui' &&
11+
!startsWith(github.event.head_commit.message, 'Release ') &&
12+
!endsWith(github.event.head_commit.message, ' [skip ci]')
13+
steps:
14+
- run: echo ok go
15+
build:
16+
needs: activate
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v2
21+
with:
22+
fetch-depth: 2
23+
- name: Install Node.js
24+
uses: actions/setup-node@v2-beta
25+
with:
26+
node-version: '10'
27+
- name: Install dependencies
28+
run: npm i
29+
- name: Tag and release
30+
env:
31+
GITHUB_API_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
32+
run: |
33+
$(npm bin)/gulp release

README.adoc

+114-38
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
= Antora Default UI
1+
= Asciidoctor Docs UI
2+
// Variables:
3+
:current-release: prod-0
24
// Settings:
35
:experimental:
46
:hide-uri-scheme:
7+
:toc: macro
8+
ifdef::env-github[]
9+
:important-caption: :exclamation:
10+
:!toc-title:
11+
:badges:
12+
endif::[]
513
// Project URLs:
6-
:url-project: https://gitlab.com/antora/antora-ui-default
7-
:url-preview: https://antora.gitlab.io/antora-ui-default
8-
:url-ci-pipelines: {url-project}/pipelines
9-
:img-ci-status: {url-project}/badges/master/pipeline.svg
14+
:project-repo-name: asciidoctor/asciidoctor-docs-ui
15+
:url-project: https://github.com/{project-repo-name}
16+
:url-preview: https://asciidoctor-docs-ui.netlify.app
17+
:url-ci: {project-repo-name}/actions
1018
// External URLs:
1119
:url-antora: https://antora.org
12-
:url-antora-docs: https://docs.antora.org
20+
:url-asciidoctor: https://asciidoctor.org
1321
:url-git: https://git-scm.com
1422
:url-git-dl: {url-git}/downloads
1523
:url-gulp: http://gulpjs.com
@@ -19,40 +27,39 @@
1927
:url-nvm-install: {url-nvm}#installation
2028
:url-source-maps: https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map
2129

22-
image:{img-ci-status}[CI Status (GitLab CI), link={url-ci-pipelines}]
30+
ifdef::badges[]
31+
image:https://img.shields.io/github/release/{project-repo-name}.svg[Latest Release,link={url-project}/releases/download/{current-release}/ui-bundle.zip]
32+
endif::[]
2333

24-
This project is an archetype that demonstrates how to produce a UI bundle that can be used by {url-antora}[Antora] to generated a documentation site.
25-
You can see a preview of the default UI at {url-preview}.
34+
toc::[]
2635

27-
While the default UI is ready to be used with Antora, the intent is that you'll fork it and customize it for your own needs.
28-
It's intentionally minimalistic so as to give you a good starting point without requiring too much effort to customize.
36+
This project provides the UI for the Asciidoctor docs site.
37+
It provides both the visual theme and user interactions for the docs site.
38+
The UI bundle that this project produces is designed to be used with Antora.
39+
It bundles the HTML templates (layouts, partials, and helpers), CSS, JavaScript, fonts, and site-wide images.
40+
The rest of the material for the docs site comes from the content repositories.
2941

30-
== Use the Default UI
42+
== Usage
3143

32-
If you want to simply use the default UI for your Antora-generated site, add the following UI configuration to your playbook:
44+
To use this UI with Antora, add the following configuration to the playbook file for your site:
3345

34-
[source,yaml]
46+
[source,yaml,subs=attributes+]
3547
----
3648
ui:
3749
bundle:
38-
url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/master/raw/build/ui-bundle.zip?job=bundle-stable
39-
snapshot: true
50+
url: {url-project}/releases/download/{current-release}/ui-bundle.zip
4051
----
4152

42-
NOTE: The `snapshot` flag tells Antora to fetch the UI when the `--fetch` command-line flag is present.
43-
This setting is required because updates to the UI bundle are pushed to the same URL.
44-
If the URL were to be unique, this setting would not be required.
45-
46-
Read on to learn how to customize the default UI for your own documentation.
53+
Read on to learn how to develop the UI.
4754

4855
== Development Quickstart
4956

50-
This section offers a basic tutorial to teach you how to set up the default UI project, preview it locally, and bundle it for use with Antora.
57+
This section offers a basic tutorial to teach you how to set up the UI project, preview it locally, and bundle it for use with Antora.
5158
A more comprehensive tutorial can be found in the documentation at {url-antora-docs}.
5259

5360
=== Prerequisites
5461

55-
To preview and bundle the default UI, you need the following software on your computer:
62+
To preview and bundle the UI, you need the following software on your computer:
5663

5764
* {url-git}[git] (command: `git`)
5865
* {url-nodejs}[Node.js] (commands: `node` and `npm`)
@@ -118,13 +125,13 @@ Now that you have the prerequisites installed, you can fetch and build the UI pr
118125

119126
=== Clone and Initialize the UI Project
120127

121-
Clone the default UI project using git:
128+
Clone the UI project using git:
122129

123130
[subs=attributes+]
124131
$ git clone {url-project} &&
125132
cd "`basename $_`"
126133

127-
The example above clones Antora's default UI project and then switches to the project folder on your filesystem.
134+
The example above clones the UI project and then switches to the project folder on your filesystem.
128135
Stay in this project folder when executing all subsequent commands.
129136

130137
Use npm to install the project's dependencies inside the project.
@@ -135,17 +142,10 @@ In your terminal, execute the following command:
135142
This command installs the dependencies listed in [.path]_package.json_ into the [.path]_node_modules/_ folder inside the project.
136143
This folder does not get included in the UI bundle and should _not_ be committed to the source control repository.
137144

138-
[TIP]
139-
====
140-
If you prefer to install packages using Yarn, run this command instead:
141-
142-
$ yarn
143-
====
144-
145145
=== Preview the UI
146146

147-
The default UI project is configured to preview offline.
148-
The files in the [.path]_preview-src/_ folder provide the sample content that allow you to see the UI in action.
147+
The UI project is configured to preview offline.
148+
The files in the [.path]_preview-site-src/_ folder provide the sample content that allow you to see the UI in action.
149149
In this folder, you'll primarily find pages written in AsciiDoc.
150150
These pages provide a representative sample and kitchen sink of content from the real site.
151151

@@ -200,14 +200,90 @@ If you need to include source maps in the bundle, you can do so by setting the `
200200
$ SOURCEMAPS=true gulp bundle
201201

202202
In this case, the bundle will include the source maps, which can be used for debuggging your production site.
203+
=== Create an Online Preview
204+
205+
You can share a preview of the UI online by submitting a pull request to GitHub.
206+
The repository is configured to create a deploy preview on Netlify for every pull request.
207+
Here's how that process works:
208+
209+
. Fork the repository on GitHub (only has to be done once).
210+
. Create a local branch.
211+
. Make changes to the UI.
212+
. Commit your changes to that branch.
213+
. Push that branch to your fork (on GitHub).
214+
. Submit a pull request from the branch you pushed to your fork.
215+
. Wait for deploy/netlify check to say "`Deploy preview ready!`" on the pull request page.
216+
. Click on the "`Details`" link under "`Show all checks`" on the pull request page to get the preview URL.
217+
. Visit the preview URL to view your changes or share the preview URL with others.
218+
219+
The deploy preview works because there is a webhook on the repository that pings \https://api.netlify.com/hooks/github for the following events: push, pull_request, delete_branch.
220+
Netlify then runs the command specified in netlify.toml, deploys the site, and allocates a temporary preview URL for it.
221+
222+
Included in that temporary preview URL is the UI bundle itself.
223+
That means you can test it directly with Antora.
224+
Simply append `dist/ui-bundle.zip` to the end of the preview URL and pass it to Antora as follows:
225+
226+
$ antora --ui-bundle-url=<preview URL>/dist/ui-bundle.zip antora-playbook.yml
227+
228+
The temporary preview URL will automatically be decommissioned once the PR is closed.
229+
230+
== Releases
231+
232+
Releases are handled by the `gulp release` task, which is automated by a CI job.
233+
The release process boils down to the following steps:
234+
235+
. Pack the UI bundle.
236+
. Tag the git repository using the next version number in the sequence (e.g., v100 after v99)
237+
. Create a GitHub release from that git tag.
238+
. Attach the UI bundle to that release as an asset in zip format.
239+
. Update the README to reference the URL of the lastest bundle and commit that update to the repository.
240+
241+
Fortunately, you don't have to do any of these steps yourself.
242+
These steps are fully automated by the `gulp release` task.
243+
In fact, you don't even have to run this task.
244+
Whenever a commit is pushed to the master branch of the repository, it triggers the CI job on master, which executes the `gulp release` task using pre-configured credentials.
245+
246+
IMPORTANT: A release will only be made if the project validates (i.e., all lint tasks pass).
247+
To validate the project, run `gulp lint` before pushing your changes to GitHub.
248+
249+
The CI job is already configured, so there's nothing you need to do to make automated release work.
250+
All you have to do is commit your changes and push those commits to the master branch of the git repository.
251+
A few seconds later, a new bundle will be available for use with Antora.
252+
Run `git pull` to retrieve the updated README that includes the new URL.
253+
254+
If you want to commit a change to master without making a release, add the string `[skip ci]` to the end of the commit message.
255+
256+
The next two sections document how the CI job is set up an configured.
257+
258+
=== Release Task Prerequisites
259+
260+
In addition to the <<Prerequisites>> covered above, you'll need a personal access token for the automated GitHub account, asciidoctor-docbot, so it has permission to make changes to the repository on GitHub.
261+
The asciidoctor-docbot account will need at least write access to the {url-project} repository, though admin access is recommended.
262+
263+
Start by creating a https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/[personal access token] for the asciidoctor-docbot user.
264+
The `release` task relies on this token to interact with the GitHub API to create the tag, create the release, and attach the UI bundle.
265+
The token must have the `public_repo` scope.
266+
No other scopes are required (as long as the asciidoctor-docbot user has write access to the repository).
267+
268+
=== CI Job
269+
270+
The {url-ci}[CI job] is executed by GitHub Actions and is defined in the file [.path]_.github/workflows/release.yml_.
271+
It boils down to running the `gulp release` task on the main branch.
272+
The GITHUB_API_TOKEN environment variable is defined in the job configuration.
273+
274+
Once the CI job runs and a new UI bundle is available, you can update the URL of the UI bundle in the Antora playbook file.
275+
See <<Usage>> for details.
203276

204277
== Copyright and License
205278

206-
Copyright (C) 2017-2019 OpenDevise Inc. and the Antora Project.
279+
=== Software
207280

208-
Use of this software is granted under the terms of the https://www.mozilla.org/en-US/MPL/2.0/[Mozilla Public License Version 2.0] (MPL-2.0).
281+
The software assets in this repository (Gulp build script and tasks, web JavaScript files, Handlebars templates and JavaScript helpers, common CSS, utility icons, etc.) are part of the {url-antora}[Antora project].
282+
As such, use of the software is granted under the terms of the https://www.mozilla.org/en-US/MPL/2.0/[Mozilla Public License Version 2.0] (MPL-2.0).
209283
See link:LICENSE[] to find the full license text.
210284

211-
== Authors
285+
=== Branding and Design
212286

213-
Development of Antora is led and sponsored by {url-opendevise}[OpenDevise Inc].
287+
Copyright (C) {url-asciidoctor}[Asciidoctor] 2018-2020.
288+
This includes any CSS that provides colors or iconography that depict the Asciidoctor brand.
289+
All rights reserved (until further notice).

gulp.d/tasks/release.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict'
2+
3+
const File = require('vinyl')
4+
const fs = require('fs-extra')
5+
const { Octokit } = require('@octokit/rest')
6+
const path = require('path')
7+
const { Transform } = require('stream')
8+
const map = (transform, flush = undefined) => new Transform({ objectMode: true, transform, flush })
9+
const vfs = require('vinyl-fs')
10+
const zip = require('gulp-vinyl-zip')
11+
12+
function getNextReleaseNumber ({ octokit, owner, repo, variant }) {
13+
const prefix = `${variant}-`
14+
const filter = (entry) => entry.name.startsWith(prefix)
15+
return collectReleases({ octokit, owner, repo, filter }).then((releases) => {
16+
if (releases.length) {
17+
releases.sort((a, b) => -1 * a.name.localeCompare(b.name, 'en', { numeric: true }))
18+
const latestName = releases[0].name
19+
return Number(latestName.slice(prefix.length)) + 1
20+
} else {
21+
return 1
22+
}
23+
})
24+
}
25+
26+
function collectReleases ({ octokit, owner, repo, filter, page = 1, accum = [] }) {
27+
return octokit.repos.listReleases({ owner, repo, page, per_page: 100 }).then((result) => {
28+
const releases = result.data.filter(filter)
29+
const links = result.headers.link
30+
if (links && links.includes('; rel="next"')) {
31+
return collectReleases({ octokit, owner, repo, filter, page: page + 1, accum: accum.concat(releases) })
32+
} else {
33+
return accum.concat(releases)
34+
}
35+
})
36+
}
37+
38+
function versionBundle (bundleFile, tagName) {
39+
return new Promise((resolve, reject) =>
40+
vfs
41+
.src(bundleFile)
42+
.pipe(zip.src().on('error', reject))
43+
.pipe(
44+
map(
45+
(file, enc, next) => next(null, file),
46+
function (done) {
47+
this.push(new File({ path: 'ui.yml', contents: Buffer.from(`version: ${tagName}\n`) }))
48+
done()
49+
}
50+
)
51+
)
52+
.pipe(zip.dest(bundleFile))
53+
.on('finish', () => resolve(bundleFile))
54+
)
55+
}
56+
57+
module.exports = (dest, bundleName, owner, repo, ref, token, updateBranch) => async () => {
58+
const octokit = new Octokit({ auth: `token ${token}` })
59+
let variant = ref.replace(/^refs\/heads\//, '')
60+
if (variant === 'main') variant = 'prod'
61+
ref = ref.replace(/^refs\//, '')
62+
const tagName = `${variant}-${await getNextReleaseNumber({ octokit, owner, repo, variant })}`
63+
const message = `Release ${tagName}`
64+
const bundleFileBasename = `${bundleName}-bundle.zip`
65+
const bundleFile = await versionBundle(path.join(dest, bundleFileBasename), tagName)
66+
let commit = await octokit.git.getRef({ owner, repo, ref }).then((result) => result.data.object.sha)
67+
const readmeContent = await fs
68+
.readFile('README.adoc', 'utf-8')
69+
.then((contents) => contents.replace(/^(?:\/\/)?(:current-release: ).+$/m, `$1${tagName}`))
70+
const readmeBlob = await octokit.git
71+
.createBlob({ owner, repo, content: readmeContent, encoding: 'utf-8' })
72+
.then((result) => result.data.sha)
73+
let tree = await octokit.git.getCommit({ owner, repo, commit_sha: commit }).then((result) => result.data.tree.sha)
74+
tree = await octokit.git
75+
.createTree({
76+
owner,
77+
repo,
78+
tree: [{ path: 'README.adoc', mode: '100644', type: 'blob', sha: readmeBlob }],
79+
base_tree: tree,
80+
})
81+
.then((result) => result.data.sha)
82+
commit = await octokit.git
83+
.createCommit({ owner, repo, message, tree, parents: [commit] })
84+
.then((result) => result.data.sha)
85+
if (updateBranch) await octokit.git.updateRef({ owner, repo, ref, sha: commit })
86+
const uploadUrl = await octokit.repos
87+
.createRelease({
88+
owner,
89+
repo,
90+
tag_name: tagName,
91+
target_commitish: commit,
92+
name: tagName,
93+
})
94+
.then((result) => result.data.upload_url)
95+
await octokit.repos.uploadReleaseAsset({
96+
url: uploadUrl,
97+
data: fs.createReadStream(bundleFile),
98+
name: bundleFileBasename,
99+
headers: {
100+
'content-length': (await fs.stat(bundleFile)).size,
101+
'content-type': 'application/zip',
102+
},
103+
})
104+
}

gulpfile.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
'use strict'
22

3+
const pkg = require('./package.json')
4+
const [owner, repo] = new URL(pkg.repository.url).pathname.slice(1).split('/')
5+
36
const { parallel, series, watch } = require('gulp')
47
const createTask = require('./gulp.d/lib/create-task')
58
const exportTasks = require('./gulp.d/lib/export-tasks')
69
const log = require('fancy-log')
710

811
const bundleName = 'ui'
9-
const buildDir = 'build'
12+
const buildDir = ['deploy-preview', 'branch-deploy'].includes(process.env.CONTEXT) ? 'public/dist' : 'build'
1013
const previewSrcDir = 'preview-src'
1114
const previewDestDir = 'public'
1215
const srcDir = 'src'
@@ -89,6 +92,17 @@ const packTask = createTask({
8992
call: series(bundleTask),
9093
})
9194

95+
const releasePublishTask = createTask({
96+
name: 'release:publish',
97+
call: task.release(buildDir, bundleName, owner, repo, process.env.GITHUB_REF, process.env.GITHUB_API_TOKEN, true),
98+
})
99+
100+
const releaseTask = createTask({
101+
name: 'release',
102+
desc: 'Bundle the UI and publish it to GitHub by attaching it to a new tag',
103+
call: series(bundleTask, releasePublishTask),
104+
})
105+
92106
const buildPreviewPagesTask = createTask({
93107
name: 'preview:build-pages',
94108
call: task.buildPreviewPages(srcDir, previewSrcDir, previewDestDir, livereload),
@@ -119,6 +133,7 @@ module.exports = exportTasks(
119133
buildTask,
120134
bundleTask,
121135
bundlePackTask,
136+
releaseTask,
122137
previewTask,
123138
previewBuildTask,
124139
packTask

0 commit comments

Comments
 (0)