Skip to content

Commit 127e10e

Browse files
committed
Antora Playbook
1 parent 538541b commit 127e10e

13 files changed

+424
-5
lines changed

.github/actions/algolia-config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"index_name": "security-docs",
3+
"start_urls": [
4+
"https://docs.spring.io/spring-security/reference/"
5+
],
6+
"selectors": {
7+
"lvl0": {
8+
"selector": "//nav[@class='crumbs']//li[@class='crumb'][last()-1]",
9+
"type": "xpath",
10+
"global": true,
11+
"default_value": "Home"
12+
},
13+
"lvl1": ".doc h1",
14+
"lvl2": ".doc h2",
15+
"lvl3": ".doc h3",
16+
"lvl4": ".doc h4",
17+
"text": ".doc p, .doc td.content, .doc th.tableblock"
18+
}
19+
}
20+

.github/actions/algolia-deploy.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
HOST="$1"
4+
HOST_PATH="$2"
5+
SSH_PRIVATE_KEY="$3"
6+
SSH_KNOWN_HOST="$4"
7+
8+
9+
if [ "$#" -ne 4 ]; then
10+
echo -e "not enough arguments USAGE:\n\n$0 \$HOST \$HOST_PATH \$SSH_PRIVATE_KEY \$SSH_KNOWN_HOSTS \n\n" >&2
11+
exit 1
12+
fi
13+
14+
# Use a non-default path to avoid overriding when testing locally
15+
SSH_PRIVATE_KEY_PATH=~/.ssh/github-actions-docs
16+
install -m 600 -D /dev/null "$SSH_PRIVATE_KEY_PATH"
17+
echo "$SSH_PRIVATE_KEY" > "$SSH_PRIVATE_KEY_PATH"
18+
echo "$SSH_KNOWN_HOST" > ~/.ssh/known_hosts
19+
rsync --delete -avze "ssh -i $SSH_PRIVATE_KEY_PATH" docs/build/site/ "$HOST:$HOST_PATH"
20+
rm -f "$SSH_PRIVATE_KEY_PATH"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
3+
###
4+
# Docs
5+
# config.json https://docsearch.algolia.com/docs/config-file
6+
# Run the crawler https://docsearch.algolia.com/docs/run-your-own/#run-the-crawl-from-the-docker-image
7+
8+
### USAGE
9+
if [ "$#" -ne 3 ]; then
10+
echo -e "not enough arguments USAGE:\n\n$0 \$ALGOLIA_APPLICATION_ID \$ALGOLIA_API_KEY \$CONFIG_FILE\n\n" >&2
11+
exit 1
12+
fi
13+
14+
# Script Parameters
15+
APPLICATION_ID=$1
16+
API_KEY=$2
17+
CONFIG_FILE=$3
18+
19+
#### Script
20+
script_dir=$(dirname $0)
21+
docker run -e "APPLICATION_ID=$APPLICATION_ID" -e "API_KEY=$API_KEY" -e "CONFIG=$(cat $CONFIG_FILE | jq -r tostring)" algolia/docsearch-scraper

.github/actions/dispatch.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
REPOSITORY_REF="$1"
22
TOKEN="$2"
33

4-
curl -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${TOKEN}" --request POST --data '{"event_type": "request-build"}' https://api.github.com/repos/${REPOSITORY_REF}/dispatches
5-
echo "Requested Build for $REPOSITORY_REF"
4+
curl -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${TOKEN}" --request POST --data '{"event_type": "request-build-reference"}' https://api.github.com/repos/${REPOSITORY_REF}/dispatches
5+
echo "Requested Build for $REPOSITORY_REF"

.github/workflows/algolia-index.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Update Algolia Index
2+
3+
on:
4+
schedule:
5+
- cron: '0 10 * * *' # Once per day at 10am UTC
6+
workflow_dispatch: # Manual trigger
7+
8+
jobs:
9+
update:
10+
name: Update Algolia Index
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout Source
14+
uses: actions/checkout@v2
15+
- name: Update Index
16+
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-docsearch-scraper.sh "${{ secrets.ALGOLIA_APPLICATION_ID }}" "${{ secrets.ALGOLIA_WRITE_API_KEY }}" "${GITHUB_WORKSPACE}/.github/actions/algolia-config.json"

.github/workflows/build-reference.yml renamed to .github/workflows/antora-generate.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: reference
1+
name: Generate Antora Files and Request Build
22

33
on:
44
push:
@@ -27,4 +27,4 @@ jobs:
2727
repository-name: "spring-io/spring-generated-docs"
2828
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
2929
- name: Dispatch Build Request
30-
run: ${GITHUB_WORKSPACE}/.github/actions/dispatch.sh 'spring-io/spring-reference' "$GH_ACTIONS_REPO_TOKEN"
30+
run: ${GITHUB_WORKSPACE}/.github/actions/dispatch.sh 'spring-projects/spring-security' "$GH_ACTIONS_REPO_TOKEN"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Build & Deploy Reference
2+
3+
on:
4+
repository_dispatch:
5+
types: request-build-reference
6+
schedule:
7+
- cron: '0 10 * * *' # Once per day at 10am UTC
8+
workflow_dispatch: # Manual trigger
9+
10+
jobs:
11+
deploy:
12+
name: deploy
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Set up JDK 11
17+
uses: actions/setup-java@v2
18+
with:
19+
java-version: '11'
20+
distribution: 'adopt'
21+
cache: gradle
22+
- name: Validate Gradle wrapper
23+
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
24+
- name: Build with Gradle
25+
run: ./gradlew :spring-security-docs:antora --stacktrace
26+
- name: Cleanup Gradle Cache
27+
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
28+
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
29+
run: |
30+
rm -f ~/.gradle/caches/modules-2/modules-2.lock
31+
rm -f ~/.gradle/caches/modules-2/gc.properties
32+
- name: Deploy
33+
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-deploy.sh "${{ secrets.DOCS_USERNAME }}@${{ secrets.DOCS_HOST }}" "/opt/www/domains/spring.io/docs/htdocs/spring-security/reference/" "${{ secrets.DOCS_SSH_KEY }}" "${{ secrets.DOCS_SSH_HOST_KEY }}"

docs/antora-playbook.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
site:
2+
title: Spring Security
3+
url: https://docs.spring.io/spring-security/reference/
4+
asciidoc:
5+
attributes:
6+
page-pagination: true
7+
content:
8+
sources:
9+
- url: https://github.com/spring-io/spring-generated-docs
10+
branches: [spring-projects/spring-security/*]
11+
- url: https://github.com/spring-projects/spring-security
12+
branches: [main,5.6.x]
13+
start_path: docs
14+
urls:
15+
latest_version_segment_strategy: redirect:to
16+
latest_version_segment: ''
17+
redirect_facility: httpd
18+
ui:
19+
bundle:
20+
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
21+
snapshot: true
22+
23+
pipeline:
24+
extensions:
25+
- require: ./antora/extensions/major-minor-segment.js
26+
- require: ./antora/extensions/root-component-name.js

docs/antora.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
name: ROOT
2-
version: 5.6
2+
version: '5.6'
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// https://gitlab.com/antora/antora/-/issues/132#note_712132072
2+
'use strict'
3+
4+
const { posix: path } = require('path')
5+
6+
module.exports.register = (pipeline, { config }) => {
7+
pipeline.on('contentClassified', ({ contentCatalog }) => {
8+
contentCatalog.getComponents().forEach(component => {
9+
const componentName = component.name;
10+
const generationToVersion = new Map();
11+
component.versions.forEach(version => {
12+
const generation = getGeneration(version.version);
13+
const original = generationToVersion.get(generation);
14+
if (original === undefined || (original.prerelease && !version.prerelease)) {
15+
generationToVersion.set(generation, version);
16+
}
17+
});
18+
19+
const versionToGeneration = Array.from(generationToVersion.entries()).reduce((acc, entry) => {
20+
const [ generation, version ] = entry;
21+
acc.set(version.version, generation);
22+
return acc;
23+
}, new Map());
24+
25+
contentCatalog.findBy({ component: componentName }).forEach((file) => {
26+
const candidateVersion = file.src.version;
27+
if (versionToGeneration.has(candidateVersion)) {
28+
const generation = versionToGeneration.get(candidateVersion);
29+
if (file.out) {
30+
if (file.out) {
31+
file.out.dirname = file.out.dirname.replace(candidateVersion, generation)
32+
file.out.path = file.out.path.replace(candidateVersion, generation);
33+
}
34+
}
35+
if (file.pub) {
36+
file.pub.url = file.pub.url.replace(candidateVersion, generation)
37+
}
38+
}
39+
});
40+
versionToGeneration.forEach((generation, mappedVersion) => {
41+
contentCatalog.getComponent(componentName).versions.filter(version => version.version === mappedVersion).forEach((version) => {
42+
version.url = version.url.replace(mappedVersion, generation);
43+
})
44+
const symbolicVersionAlias = createSymbolicVersionAlias(
45+
componentName,
46+
mappedVersion,
47+
generation,
48+
'redirect:to'
49+
)
50+
symbolicVersionAlias.src.version = generation;
51+
contentCatalog.addFile(symbolicVersionAlias);
52+
});
53+
})
54+
})
55+
}
56+
57+
function createSymbolicVersionAlias (component, version, symbolicVersionSegment, strategy) {
58+
if (symbolicVersionSegment == null || symbolicVersionSegment === version) return
59+
const family = 'alias'
60+
const baseVersionAliasSrc = { component, module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
61+
const symbolicVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version: symbolicVersionSegment })
62+
const symbolicVersionAlias = {
63+
src: symbolicVersionAliasSrc,
64+
pub: computePub(
65+
symbolicVersionAliasSrc,
66+
computeOut(symbolicVersionAliasSrc, family, symbolicVersionSegment),
67+
family
68+
),
69+
}
70+
const originalVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version })
71+
const originalVersionSegment = computeVersionSegment(component, version, 'original')
72+
const originalVersionAlias = {
73+
src: originalVersionAliasSrc,
74+
pub: computePub(
75+
originalVersionAliasSrc,
76+
computeOut(originalVersionAliasSrc, family, originalVersionSegment),
77+
family
78+
),
79+
}
80+
if (strategy === 'redirect:to') {
81+
originalVersionAlias.out = undefined
82+
originalVersionAlias.rel = symbolicVersionAlias
83+
return originalVersionAlias
84+
} else {
85+
symbolicVersionAlias.out = undefined
86+
symbolicVersionAlias.rel = originalVersionAlias
87+
return symbolicVersionAlias
88+
}
89+
}
90+
91+
92+
function computeOut (src, family, version, htmlUrlExtensionStyle) {
93+
let { component, module: module_, basename, extname, relative, stem } = src
94+
if (module_ === 'ROOT') module_ = ''
95+
let indexifyPathSegment = ''
96+
let familyPathSegment = ''
97+
98+
if (family === 'page') {
99+
if (stem !== 'index' && htmlUrlExtensionStyle === 'indexify') {
100+
basename = 'index.html'
101+
indexifyPathSegment = stem
102+
} else if (extname === '.adoc') {
103+
basename = stem + '.html'
104+
}
105+
} else if (family === 'image') {
106+
familyPathSegment = '_images'
107+
} else if (family === 'attachment') {
108+
familyPathSegment = '_attachments'
109+
}
110+
const modulePath = path.join(component, version, module_)
111+
const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment)
112+
const path_ = path.join(dirname, basename)
113+
const moduleRootPath = path.relative(dirname, modulePath) || '.'
114+
const rootPath = path.relative(dirname, '') || '.'
115+
116+
return { dirname, basename, path: path_, moduleRootPath, rootPath }
117+
}
118+
119+
function computePub (src, out, family, version, htmlUrlExtensionStyle) {
120+
const pub = {}
121+
let url
122+
if (family === 'nav') {
123+
const urlSegments = version ? [src.component, version] : [src.component]
124+
if (src.module && src.module !== 'ROOT') urlSegments.push(src.module)
125+
// an artificial URL used for resolving page references in navigation model
126+
url = '/' + urlSegments.join('/') + '/'
127+
pub.moduleRootPath = '.'
128+
} else if (family === 'page') {
129+
const urlSegments = out.path.split('/')
130+
const lastUrlSegmentIdx = urlSegments.length - 1
131+
if (htmlUrlExtensionStyle === 'drop') {
132+
// drop just the .html extension or, if the filename is index.html, the whole segment
133+
const lastUrlSegment = urlSegments[lastUrlSegmentIdx]
134+
urlSegments[lastUrlSegmentIdx] =
135+
lastUrlSegment === 'index.html' ? '' : lastUrlSegment.substr(0, lastUrlSegment.length - 5)
136+
} else if (htmlUrlExtensionStyle === 'indexify') {
137+
urlSegments[lastUrlSegmentIdx] = ''
138+
}
139+
url = '/' + urlSegments.join('/')
140+
} else {
141+
url = '/' + out.path
142+
if (family === 'alias' && !src.relative.length) pub.splat = true
143+
}
144+
145+
pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
146+
147+
if (out) {
148+
pub.moduleRootPath = out.moduleRootPath
149+
pub.rootPath = out.rootPath
150+
}
151+
152+
return pub
153+
}
154+
155+
function computeVersionSegment (name, version, mode) {
156+
if (mode === 'original') return !version || version === 'master' ? '' : version
157+
const strategy = this.latestVersionUrlSegmentStrategy
158+
// NOTE: special exception; revisit in Antora 3
159+
if (!version || version === 'master') {
160+
if (mode !== 'alias') return ''
161+
if (strategy === 'redirect:to') return
162+
}
163+
if (strategy === 'redirect:to' || strategy === (mode === 'alias' ? 'redirect:from' : 'replace')) {
164+
const component = this.getComponent(name)
165+
const componentVersion = component && this.getComponentVersion(component, version)
166+
if (componentVersion) {
167+
const segment =
168+
componentVersion === component.latest
169+
? this.latestVersionUrlSegment
170+
: componentVersion === component.latestPrerelease
171+
? this.latestPrereleaseVersionUrlSegment
172+
: undefined
173+
return segment == null ? version : segment
174+
}
175+
}
176+
return version
177+
}
178+
179+
function getGeneration(version) {
180+
if (!version) return version;
181+
const firstIndex = version.indexOf('.')
182+
if (firstIndex < 0) {
183+
return version;
184+
}
185+
const secondIndex = version.indexOf('.', firstIndex + 1);
186+
const result = version.substr(0, secondIndex);
187+
return result;
188+
}
189+
190+
function out(args) {
191+
console.log(JSON.stringify(args, no_data, 2));
192+
}
193+
194+
195+
function no_data(key, value) {
196+
if (key == "data" || key == "files") {
197+
return value ? "__data__" : value;
198+
}
199+
return value;
200+
}

0 commit comments

Comments
 (0)