diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..d6ec28c86e4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# EditorConfig for Spring Security +# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.adoc#mind-the-whitespace + +root = true + +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 120 + +[*.{java,xml}] +indent_style = tab +indent_size = 4 +charset = utf-8 +continuation_indent_size = 8 + +ij_smart_tabs = false +ij_java_align_multiline_parameters = false + +[*.gradle] +indent_style = tab diff --git a/.github/actions/algolia-config.json b/.github/actions/algolia-config.json new file mode 100644 index 00000000000..09d30d486ea --- /dev/null +++ b/.github/actions/algolia-config.json @@ -0,0 +1,20 @@ +{ + "index_name": "security-docs", + "start_urls": [ + "https://docs.spring.io/spring-security/reference/" + ], + "selectors": { + "lvl0": { + "selector": "//nav[@class='crumbs']//li[@class='crumb'][last()-1]", + "type": "xpath", + "global": true, + "default_value": "Home" + }, + "lvl1": ".doc h1", + "lvl2": ".doc h2", + "lvl3": ".doc h3", + "lvl4": ".doc h4", + "text": ".doc p, .doc td.content, .doc th.tableblock" + } +} + diff --git a/.github/actions/algolia-docsearch-scraper.sh b/.github/actions/algolia-docsearch-scraper.sh new file mode 100755 index 00000000000..2bb9ce178ac --- /dev/null +++ b/.github/actions/algolia-docsearch-scraper.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +### +# Docs +# config.json https://docsearch.algolia.com/docs/config-file +# Run the crawler https://docsearch.algolia.com/docs/run-your-own/#run-the-crawl-from-the-docker-image + +### USAGE +if [ "$#" -ne 3 ]; then + echo -e "not enough arguments USAGE:\n\n$0 \$ALGOLIA_APPLICATION_ID \$ALGOLIA_API_KEY \$CONFIG_FILE\n\n" >&2 + exit 1 +fi + +# Script Parameters +APPLICATION_ID=$1 +API_KEY=$2 +CONFIG_FILE=$3 + +#### Script +script_dir=$(dirname $0) +docker run -e "APPLICATION_ID=$APPLICATION_ID" -e "API_KEY=$API_KEY" -e "CONFIG=$(cat $CONFIG_FILE | jq -r tostring)" algolia/docsearch-scraper diff --git a/.github/actions/publish-docs.sh b/.github/actions/publish-docs.sh new file mode 100644 index 00000000000..ce60e1793f2 --- /dev/null +++ b/.github/actions/publish-docs.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +HOST="$1" +HOST_PATH="$2" +SSH_PRIVATE_KEY="$3" +SSH_KNOWN_HOST="$4" +SSH_PRIVATE_KEY_PATH="$HOME/.ssh-ci/${GITHUB_REPOSITORY:-publish-docs}" + +if [ "$#" -ne 4 ]; then + echo -e "not enough arguments USAGE:\n\n$0 \$HOST \$HOST_PATH \$SSH_PRIVATE_KEY \$SSH_KNOWN_HOSTS\n\n" >&2 + exit 1 +fi + +install -m 600 -D /dev/null "$SSH_PRIVATE_KEY_PATH" +echo "$SSH_PRIVATE_KEY" > "$SSH_PRIVATE_KEY_PATH" +echo "$SSH_KNOWN_HOST" > ~/.ssh/known_hosts +rsync --delete -avze "ssh -i $SSH_PRIVATE_KEY_PATH" docs/build/site/ "$HOST:$HOST_PATH" +rm -f "$SSH_PRIVATE_KEY_PATH" diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000000..080b5b0060f --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,28 @@ +name: Deploy Docs +on: + workflow_dispatch: +permissions: read-all +jobs: + build: + if: github.repository_owner == 'spring-projects' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 5 + - name: Set Up Gradle + uses: spring-io/spring-gradle-build-action@v1 + with: + java-version: '17' + distribution: temurin + - name: Scrub Gradle Cache + # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. + # Restoring these files from a GitHub Actions cache might cause problems for future builds. + run: | + rm -f /home/runner/.gradle/caches/modules-2/modules-2.lock + rm -f /home/runner/.gradle/caches/modules-2/gc.properties + - name: Run Antora + run: ./gradlew antora + - name: Publish Docs + run: ${GITHUB_WORKSPACE}/.github/actions/publish-docs.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 }}" diff --git a/.github/workflows/rebuild-search-index.yml b/.github/workflows/rebuild-search-index.yml new file mode 100644 index 00000000000..2e21ef9ea63 --- /dev/null +++ b/.github/workflows/rebuild-search-index.yml @@ -0,0 +1,15 @@ +name: Rebuild Search Index +on: + workflow_dispatch: +permissions: read-all +jobs: + build: + if: github.repository_owner == 'spring-projects' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 5 + - name: Run Docsearch Scraper + 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..60bfae78748 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/.gradle/ +/.idea/* +/.settings/ +/.classpath +/.project +/build/ +/node_modules/ +/package-lock.json +/*.iml +/*.ipr +/*.iws +!/.idea/checkstyle-idea.xml +!/.idea/externalDependencies.xml diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml new file mode 100644 index 00000000000..bf41a5a9de3 --- /dev/null +++ b/.idea/checkstyle-idea.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml new file mode 100644 index 00000000000..1e8bdd64928 --- /dev/null +++ b/.idea/externalDependencies.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..c4aece09060 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +lockfile-version=3 +omit=optional diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 00000000000..64345a4be3a --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,6 @@ +# Use sdkman to run "sdk env" to initialize with correct JDK version +# Enable auto-env through the sdkman_auto_env config +# See https://sdkman.io/usage#config +# A summary is to add the following to ~/.sdkman/etc/config +# sdkman_auto_env=true +java=17.0.3-tem diff --git a/antora-playbook-with-worktrees.yml b/antora-playbook-with-worktrees.yml new file mode 100644 index 00000000000..5cbd1436d42 --- /dev/null +++ b/antora-playbook-with-worktrees.yml @@ -0,0 +1,35 @@ +antora: + extensions: + - ./lib/antora/extensions/antora-linked-worktree-patch.js + - ./lib/antora/extensions/version-fix.js + - ./lib/antora/extensions/major-minor-segment.js +runtime: + log: + format: pretty +site: + title: Spring Security + url: https://docs.spring.io/spring-security/reference + robots: allow +git: + ensure_git_suffix: false +content: + sources: + - url: https://github.com/spring-io/spring-generated-docs + branches: 'spring-projects/spring-security/{main,5.{{6..9},{1..9}+({0..9})}.{x,+({0..9})}}' + - url: . + branches: '{main,5.{{6..9},{1..9}+({0..9})}.x}' + worktrees: true # will automatically discover worktrees if they are set up; otherwise, will use git tree + tags: '5.{{6..9},{1..9}+({0..9})}.+({0..9})' + start_path: docs +asciidoc: + attributes: + page-pagination: '' + hide-uri-scheme: '@' +urls: + latest_version_segment_strategy: redirect:to + latest_version_segment: '' + redirect_facility: httpd +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip + snapshot: true diff --git a/antora-playbook.yml b/antora-playbook.yml new file mode 100644 index 00000000000..51a1f2843f7 --- /dev/null +++ b/antora-playbook.yml @@ -0,0 +1,33 @@ +antora: + extensions: + - ./lib/antora/extensions/version-fix.js + - ./lib/antora/extensions/major-minor-segment.js +runtime: + log: + format: pretty +site: + title: Spring Security + url: https://docs.spring.io/spring-security/reference + robots: allow +git: + ensure_git_suffix: false +content: + sources: + - url: https://github.com/spring-io/spring-generated-docs + branches: 'spring-projects/spring-security/{main,5.{{6..9},{1..9}+({0..9})}.{x,+({0..9})}}' + - url: https://github.com/spring-projects/spring-security + branches: '{main,5.{{6..9},{1..9}+({0..9})}.x}' + tags: '5.{{6..9},{1..9}+({0..9})}.+({0..9})' + start_path: docs +asciidoc: + attributes: + page-pagination: '' + hide-uri-scheme: '@' +urls: + latest_version_segment_strategy: redirect:to + latest_version_segment: '' + redirect_facility: httpd +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip + snapshot: true diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000000..b854b68865c --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "io.github.rwinch.antora" version "0.0.2" +} + +group = 'org.springframework.security' +description = 'Spring Security' + +node { + version = "16.17.0" +} + +antora { + antoraVersion = "3.1.0" + arguments = ["--clean", "--fetch", "--stacktrace"] +} + +tasks.antora { + environment = [ + "ALGOLIA_API_KEY" : "82c7ead946afbac3cf98c32446154691", + "ALGOLIA_APP_ID" : "244V8V9FGG", + "ALGOLIA_INDEX_NAME" : "security-docs" + ] +} + +repositories { + mavenCentral() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..41d9927a4d4 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..aa991fceae6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000000..1b6c787337f --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000000..ac1b06f9382 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/antora/extensions/antora-linked-worktree-patch.js b/lib/antora/extensions/antora-linked-worktree-patch.js new file mode 100644 index 00000000000..3ef07ed14c8 --- /dev/null +++ b/lib/antora/extensions/antora-linked-worktree-patch.js @@ -0,0 +1,53 @@ +'use strict' + +/* Copyright (c) 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const { promises: fsp } = require('fs') +const ospath = require('path') + +/** + * Rewrites local content sources to support the use of linked worktrees. + * + * @author Dan Allen + */ +module.exports.register = function () { + this.once('playbookBuilt', async ({ playbook }) => { + const expandPath = this.require('@antora/expand-path-helper') + for (const contentSource of playbook.content.sources) { + const { url, branches } = contentSource + if (url.charAt() !== '.') continue + const absdir = expandPath(url, { dot: playbook.dir }) + const gitfile = ospath.join(absdir, '.git') + if (await fsp.stat(gitfile).then((stat) => !stat.isDirectory(), () => false)) { + const worktreeGitdir = await fsp.readFile(gitfile, 'utf8') + .then((contents) => contents.trimRight().substr(8)) + const worktreeBranch = await fsp.readFile(ospath.join(worktreeGitdir, 'HEAD'), 'utf8') + .then((contents) => contents.trimRight().replace(/^ref: (?:refs\/heads\/)?/, '')) + const reldir = ospath.relative( + playbook.dir, + await fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8') + .then((contents) => { + const gitdir = ospath.join(worktreeGitdir, contents.trimRight()) + return ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir + }) + ) + contentSource.url = reldir ? `.${ospath.sep}${reldir}` : '.' + if (!branches) continue + contentSource.branches = (branches.constructor === Array ? branches : [branches]) + .map((pattern) => pattern.replaceAll('HEAD', worktreeBranch)) + } + } + }) +} diff --git a/lib/antora/extensions/major-minor-segment.js b/lib/antora/extensions/major-minor-segment.js new file mode 100644 index 00000000000..6cf7152b2c7 --- /dev/null +++ b/lib/antora/extensions/major-minor-segment.js @@ -0,0 +1,204 @@ +// https://gitlab.com/antora/antora/-/issues/132#note_712132072 +'use strict' + +const { posix: path } = require('path') + +module.exports.register = function({ config }) { + this.on('contentClassified', ({ contentCatalog }) => { + contentCatalog.getComponents().forEach(component => { + const componentName = component.name; + const generationToVersion = new Map(); + component.versions.forEach(version => { + const generation = getGeneration(version.version); + const original = generationToVersion.get(generation); + if (original === undefined || (original.prerelease && !version.prerelease)) { + generationToVersion.set(generation, version); + } + }); + + const versionToGeneration = Array.from(generationToVersion.entries()).reduce((acc, entry) => { + const [ generation, version ] = entry; + acc.set(version.version, generation); + return acc; + }, new Map()); + + contentCatalog.findBy({ component: componentName }).forEach((file) => { + const candidateVersion = file.src.version; + if (versionToGeneration.has(candidateVersion)) { + const generation = versionToGeneration.get(candidateVersion); + if (file.out) { + if (file.out) { + file.out.dirname = file.out.dirname.replace(candidateVersion, generation) + file.out.path = file.out.path.replace(candidateVersion, generation); + } + } + if (file.pub) { + file.pub.url = file.pub.url.replace(candidateVersion, generation) + } + } + }); + versionToGeneration.forEach((generation, mappedVersion) => { + contentCatalog.getComponent(componentName).versions.filter(version => version.version === mappedVersion).forEach((version) => { + version.url = version.url.replace(mappedVersion, generation); + }) + const symbolicVersionAlias = createSymbolicVersionAlias( + componentName, + mappedVersion, + generation, + 'redirect:to' + ) + symbolicVersionAlias.src.version = generation; + contentCatalog.addFile(symbolicVersionAlias); + }); + }) + }) +} + +function createSymbolicVersionAlias (component, version, symbolicVersionSegment, strategy) { + if (symbolicVersionSegment == null || symbolicVersionSegment === version) return + const family = 'alias' + const baseVersionAliasSrc = { component, module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' } + const symbolicVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version: symbolicVersionSegment }) + const symbolicVersionAlias = { + src: symbolicVersionAliasSrc, + pub: computePub( + symbolicVersionAliasSrc, + computeOut(symbolicVersionAliasSrc, family, symbolicVersionSegment), + family + ), + } + const originalVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version }) + const originalVersionSegment = computeVersionSegment(component, version, 'original') + const originalVersionAlias = { + src: originalVersionAliasSrc, + pub: computePub( + originalVersionAliasSrc, + computeOut(originalVersionAliasSrc, family, originalVersionSegment), + family + ), + } + if (strategy === 'redirect:to') { + originalVersionAlias.out = undefined + originalVersionAlias.rel = symbolicVersionAlias + return originalVersionAlias + } else { + symbolicVersionAlias.out = undefined + symbolicVersionAlias.rel = originalVersionAlias + return symbolicVersionAlias + } +} + + +function computeOut (src, family, version, htmlUrlExtensionStyle) { + let { component, module: module_, basename, extname, relative, stem } = src + if (component === 'ROOT') component = '' + if (module_ === 'ROOT') module_ = '' + let indexifyPathSegment = '' + let familyPathSegment = '' + + if (family === 'page') { + if (stem !== 'index' && htmlUrlExtensionStyle === 'indexify') { + basename = 'index.html' + indexifyPathSegment = stem + } else if (extname === '.adoc') { + basename = stem + '.html' + } + } else if (family === 'image') { + familyPathSegment = '_images' + } else if (family === 'attachment') { + familyPathSegment = '_attachments' + } + const modulePath = path.join(component, version, module_) + const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment) + const path_ = path.join(dirname, basename) + const moduleRootPath = path.relative(dirname, modulePath) || '.' + const rootPath = path.relative(dirname, '') || '.' + + return { dirname, basename, path: path_, moduleRootPath, rootPath } +} + +function computePub (src, out, family, version, htmlUrlExtensionStyle) { + const pub = {} + let url + if (family === 'nav') { + const component = src.component || 'ROOT' + const urlSegments = component === 'ROOT' ? [] : [component] + if (version) urlSegments.push(version) + const module_ = src.module || 'ROOT' + if (module_ !== 'ROOT') urlSegments.push(module_) + // an artificial URL used for resolving page references in navigation model + url = '/' + urlSegments.join('/') + '/' + pub.moduleRootPath = '.' + } else if (family === 'page') { + const urlSegments = out.path.split('/') + const lastUrlSegmentIdx = urlSegments.length - 1 + if (htmlUrlExtensionStyle === 'drop') { + // drop just the .html extension or, if the filename is index.html, the whole segment + const lastUrlSegment = urlSegments[lastUrlSegmentIdx] + urlSegments[lastUrlSegmentIdx] = + lastUrlSegment === 'index.html' ? '' : lastUrlSegment.substr(0, lastUrlSegment.length - 5) + } else if (htmlUrlExtensionStyle === 'indexify') { + urlSegments[lastUrlSegmentIdx] = '' + } + url = '/' + urlSegments.join('/') + } else { + url = '/' + out.path + if (family === 'alias' && !src.relative.length) pub.splat = true + } + + pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url + + if (out) { + pub.moduleRootPath = out.moduleRootPath + pub.rootPath = out.rootPath + } + + return pub +} + +function computeVersionSegment (name, version, mode) { + if (mode === 'original') return !version || version === 'master' ? '' : version + const strategy = this.latestVersionUrlSegmentStrategy + // NOTE: special exception; revisit in Antora 3 + if (!version || version === 'master') { + if (mode !== 'alias') return '' + if (strategy === 'redirect:to') return + } + if (strategy === 'redirect:to' || strategy === (mode === 'alias' ? 'redirect:from' : 'replace')) { + const component = this.getComponent(name) + const componentVersion = component && this.getComponentVersion(component, version) + if (componentVersion) { + const segment = + componentVersion === component.latest + ? this.latestVersionUrlSegment + : componentVersion === component.latestPrerelease + ? this.latestPrereleaseVersionUrlSegment + : undefined + return segment == null ? version : segment + } + } + return version +} + +function getGeneration(version) { + if (!version) return version; + const firstIndex = version.indexOf('.') + if (firstIndex < 0) { + return version; + } + const secondIndex = version.indexOf('.', firstIndex + 1); + const result = version.substr(0, secondIndex); + return result; +} + +function out(args) { + console.log(JSON.stringify(args, no_data, 2)); +} + + +function no_data(key, value) { + if (key == "data" || key == "files") { + return value ? "__data__" : value; + } + return value; +} diff --git a/lib/antora/extensions/version-fix.js b/lib/antora/extensions/version-fix.js new file mode 100644 index 00000000000..b0208d25d75 --- /dev/null +++ b/lib/antora/extensions/version-fix.js @@ -0,0 +1,35 @@ +// https://gitlab.com/antora/antora/-/issues/132#note_712132072 +'use strict' + + +module.exports.register = function({ config }) { + this.on('contentAggregated', ({ contentAggregate }) => { + contentAggregate.forEach(aggregate => { + if (aggregate.name === "" && aggregate.displayVersion === 5.6) { + aggregate.name = "ROOT"; + aggregate.version = "5.6.0-RC1" + aggregate.startPage = "ROOT:index.adoc" + aggregate.displayVersion = `${aggregate.version}` + delete aggregate.prerelease + } + if (aggregate.version === "5.6.1" && + aggregate.prerelease == "-SNAPSHOT") { + aggregate.version = "5.6.1" + aggregate.displayVersion = `${aggregate.version}` + delete aggregate.prerelease + } + }) + }) +} + +function out(args) { + console.log(JSON.stringify(args, no_data, 2)); +} + + +function no_data(key, value) { + if (key == "data" || key == "files") { + return value ? "__data__" : value; + } + return value; +} diff --git a/package.json b/package.json new file mode 100644 index 00000000000..e79a9923ee1 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "spring-security-docs-site", + "description": "Spring Security Docs", + "private": true, + "dependencies": { + "@antora/cli": "^3.1.0", + "@antora/site-generator-default": "^3.1.0" + }, + "overrides": { + "vinyl-fs": { + "glob-stream": "~7.0" + } + } +}