Skip to content

Commit

Permalink
feat: add alternative versioning schemes
Browse files Browse the repository at this point in the history
This adds a OSGI centric versioning scheme as well as a plain text versioning scheme with SemVer still being the recommended default.

feat: pass through given `edit_url`

fix: either pass through `edit_url` or suppress it
  • Loading branch information
jekkel committed Feb 11, 2022
1 parent 127680c commit f4064b4
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 116 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ antora:
- groupId: "com.example" # required
artifactId: "antora-module" # required
version: "1.x.x" # defaults to '*'
versionScheme: "SemVer" # defaults to 'SemVer'
limit: 3 # defaults to 1
limitBy: minor # defaults to 'major', one of 'major', 'minor', 'patch', 'any'
includeSnapshots: true # defaults to false, true has no effect if includePrerelease is false as SNAPSHOTS are SemVer pre releases
Expand All @@ -45,6 +46,7 @@ antora:
extension: "tgz" # defaults to 'zip'
startPath: ~ # defaults to null
startPaths: "docs/*" # defaults to null
edit_url: "https://git.example.com/repos/myRepo/browse/{path}" # defaults to false
# ...
```

Expand All @@ -71,6 +73,15 @@ For each picked version a corresponding playbook content source entry is created

* points to a local transient cached on-demand git repository the artifact has been extracted to
* is configured with the same [start path(s)](https://docs.antora.org/antora/3.0/playbook/content-source-start-paths/)
* is configured with an [edit url](https://docs.antora.org/antora/latest/playbook/content-edit-url/)

### Supported Versioning Schemes

| Scheme | Structure | version format | notes
| --- | ---- | ---- | ---
| [`SemVer`](https://semver.org/) | `<major>.<minor>.<patch>+<metadata>-<prerelease>` | any valid [SemVer Range](https://www.npmjs.com/package/semver#user-content-ranges) | recommended
| [`OSGI`](https://www.eclipse.org/virgo/documentation/virgo-documentation-3.7.0.M01/docs/virgo-user-guide/html/ch02s02.html#d0e341) | `<major>.<minor>.<micro>.<qualifier>` | any valid [OSGI range](https://www.eclipse.org/virgo/documentation/virgo-documentation-3.7.0.M01/docs/virgo-user-guide/html/ch02s02.html#d0e404) | `micro` is exposed as `patch`, there is no order between qualifiers
| `Lexicographically` | any | any valid regular expression | `minor` and `patch` are always `0`, the complete version is the `major` part

### Maven `settings.xml`

Expand Down
11 changes: 5 additions & 6 deletions lib/maven-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ const {
MavenRepository,
MavenArtifact
} = require('./maven-types')
const semver = require('semver')
const {MavenSettingsFile, MavenMetaDataFile} = require("./maven-files");
const Process = require("process");


const META_DATA_FILE_NAME = 'maven-metadata.xml';

class MavenClient {
Expand Down Expand Up @@ -75,22 +73,23 @@ class MavenClient {
* @param {MavenRepository[]} repositories
* @param {string} groupId
* @param {string} artifactId
* @param {object} versionScheme The versioning scheme from versioning.js
* @param {string} version optional, try to find this if metadata discovery fails for a reason
* @param {string} classifier optional, try to find this if metadata discovery fails for a reason
* @param {string} extension optional, try to find this if metadata discovery fails for a reason
*
* @returns {Promise<{version, repository}[]>}
*/
async retrieveAvailableVersions(repositories, {groupId, artifactId, fallback: {version, classifier, extension}}) {
async retrieveAvailableVersions(repositories, versionScheme, {groupId, artifactId, fallback: {version, classifier, extension}}) {
const versionToRepoMap = new Map();
await Promise.all(repositories.map(async (repository) => {
const metaDataUrl = MavenClient.#buildMetaDataUrlForArtifact(repository.baseUrl, {groupId, artifactId});
this.logger.debug('Downloading metadata to extract version from ' + metaDataUrl);
try {
const metaDataFile = new MavenMetaDataFile(await MavenClient.#downloadXml(metaDataUrl, repository.fetchOptions), this.logger);
metaDataFile.getVersions().forEach(discoveredVersion => {
if (!semver.valid(discoveredVersion)) {
this.logger.info('Found invalid SemVer version ' + discoveredVersion + ' for ' + groupId + ':' + artifactId + ', skipping.');
if (!versionScheme.valid(discoveredVersion)) {
this.logger.info('Found invalid version ' + discoveredVersion + ' for ' + groupId + ':' + artifactId + ', skipping.');
} else if (!versionToRepoMap.has(discoveredVersion)) {
versionToRepoMap.set(discoveredVersion, repository);
}
Expand All @@ -113,7 +112,7 @@ class MavenClient {
if (!versionToRepoMap.size) {
throw new MavenClientError('Unable to find any version for ' + groupId + ':' + artifactId);
}
const versionList = semver.sort([...versionToRepoMap.keys()]);
const versionList = versionScheme.sort([...versionToRepoMap.keys()]);
versionList.reverse();
this.logger.info('Found ' + versionList.length + ' versions for ' + groupId + ':' + artifactId);
return versionList.map(version => {
Expand Down
11 changes: 9 additions & 2 deletions lib/maven-content-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class MavenContentSource {
#startPath;
#startPaths;
#logger;
#edit_url;

toString() {
return this.#mavenArtifact
Expand All @@ -32,7 +33,8 @@ class MavenContentSource {
cacheFolder,
logger,
startPath,
startPaths
startPaths,
edit_url
}) {
this.#mavenArtifact = mavenArtifact;
this.#mavenClient = mavenClient;
Expand All @@ -42,6 +44,7 @@ class MavenContentSource {
this.#logger = logger;
this.#startPath = startPath;
this.#startPaths = startPaths;
this.#edit_url = edit_url;

if (!["zip", "jar", "tgz"].includes(this.#mavenArtifact.extension)) {
throw new MavenContentSourceError('Unsupported extension "' + this.#mavenArtifact.extension + '", use one of zip, jar, tgz');
Expand Down Expand Up @@ -104,14 +107,18 @@ class MavenContentSource {
if (!playbook.content.sources) playbook.content.sources = [];
let antoraContentSource = {
url: folder,
branches: 'HEAD'
branches: 'HEAD',
edit_url: false
};
if (this.#startPath) {
antoraContentSource.start_path = this.#startPath;
}
if (this.#startPaths) {
antoraContentSource.start_paths = this.#startPaths;
}
if (this.#edit_url) {
antoraContentSource.edit_url = this.#edit_url;
}
playbook.content.sources.push(antoraContentSource);
}

Expand Down
57 changes: 14 additions & 43 deletions lib/maven-types.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const semver = require('semver')
const MavenContentSource = require('./maven-content-source')
const selectVersion = require('./version-selector');
const versioning = require('./versioning')

class MavenRepository {
baseUrl;
Expand Down Expand Up @@ -51,34 +51,42 @@ class MavenContentCoordinate {
includeSnapshots;
startPath;
startPaths;
edit_url;

constructor({
groupId,
artifactId,
version = "*",
versionScheme = 'SemVer',
classifier = 'docs',
extension = 'zip',
limit = 1,
limitBy = 'major',
includeSnapshots = false,
startPath = null,
startPaths = null,
includePrerelease = true
includePrerelease = true,
edit_url = false
}) {
this.groupId = groupId;
this.artifactId = artifactId;
this.includeSnapshots = includeSnapshots;
this.startPath = startPath;
this.startPaths = startPaths;
this.version = version;
this.versionRange = new semver.Range(version, {loose: false, includePrerelease});
this.versionScheme = versioning[versionScheme];
if (!this.versionScheme) {
throw new TypeError(`versionScheme must be one of "${Object.keys(versioning).join('", "')}"`);
}
this.versionRange = this.versionScheme.range(version, includePrerelease);
this.classifier = classifier;
this.extension = extension;
this.limit = limit;
this.limitBy = limitBy;
if (!['major', 'minor', 'patch', 'any'].includes(this.limitBy)) {
throw new TypeError("limitBy must be one of 'major', 'minor', 'patch', 'any'");
}
this.edit_url = edit_url;
}

toString() {
Expand All @@ -102,7 +110,7 @@ class MavenContentCoordinate {
async resolveToContentSources(mavenClient, repositories, git, cacheFolder, logger) {
let versionRepoTupleList = [];
try {
versionRepoTupleList = await mavenClient.retrieveAvailableVersions(repositories, {
versionRepoTupleList = await mavenClient.retrieveAvailableVersions(repositories, this.versionScheme,{
groupId: this.groupId,
artifactId: this.artifactId,
fallback: {
Expand All @@ -125,7 +133,7 @@ class MavenContentCoordinate {

createSourcesForMatchingVersions(versionRepoTupleList, mavenClient, git, cacheFolder, logger) {
let results = [];
selectVersion(this.versionRange, this.limit, this.limitBy, versionRepoTupleList, versionRepoTuple => {
selectVersion(this.versionScheme, this.versionRange, this.limit, this.limitBy, versionRepoTupleList, versionRepoTuple => {
results.push(new MavenContentSource({
mavenClient: mavenClient,
logger: mavenClient.logger,
Expand All @@ -134,6 +142,7 @@ class MavenContentCoordinate {
cacheFolder: cacheFolder,
startPath: this.startPath,
startPaths: this.startPaths,
edit_url: this.edit_url,
mavenArtifact: new MavenArtifact({
groupId: this.groupId,
artifactId: this.artifactId,
Expand All @@ -146,44 +155,6 @@ class MavenContentCoordinate {
}, logger);
return results;
}

/**
* @param {{repository: MavenRepository, version: string}[]} versionRepoTupleList
* @param {function({repository: MavenRepository, version: string})} consumer
* @param logger
*/
forEachSelectedTuple(versionRepoTupleList, consumer, logger) {
let usedVersionFragments = new Set();
let idx = 0;
do {
let candidate = versionRepoTupleList[idx];
if (this.versionRange.test(candidate.version)) {
const candidateFragment = this.#extractVersionFragment(candidate.version);
if (!usedVersionFragments.has(candidateFragment)) {
consumer(candidate);
usedVersionFragments.add(candidateFragment);
} else {
logger.debug('Skipping version ' + candidate.version + ' of ' + this + ': version fragment already present');
}
} else {
logger.debug('Skipping version ' + candidate.version + ' of ' + this + ': does not match ' + this.versionRange);
}
} while (++idx < versionRepoTupleList.length && usedVersionFragments.length < this.limit)
}

#extractVersionFragment(version) {
let semanticVersion = semver.parse(version);
switch (this.limitBy) {
case 'major':
return semanticVersion.major;
case 'minor':
return semanticVersion.major + '.' + semanticVersion.minor;
case 'patch':
return semanticVersion.major + '.' + semanticVersion.minor + '.' + semanticVersion.patch;
default:
return version;
}
}
}

module.exports = {
Expand Down
18 changes: 8 additions & 10 deletions lib/version-selector.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
const semver = require("semver");

function extractVersionFragment(version, fragment) {
let semanticVersion = semver.parse(version);
function extractVersionFragment(versionScheme, version, fragment) {
let parsedVersion = versionScheme.parse(version);
switch (fragment) {
case 'major': return semanticVersion.major;
case 'minor': return semanticVersion.major + '.' + semanticVersion.minor;
case 'patch': return semanticVersion.major + '.' + semanticVersion.minor + '.' + semanticVersion.patch;
case 'major': return parsedVersion.major;
case 'minor': return parsedVersion.major + '.' + parsedVersion.minor;
case 'patch': return parsedVersion.major + '.' + parsedVersion.minor + '.' + parsedVersion.patch;
default: return version;
}
}

function selectVersions(versionRange, limit, limitBy, orderedVersionRepoTupleList, consumer, logger) {
function selectVersions(versionScheme, versionRange, limit, limitBy, orderedVersionRepoTupleList, consumer, logger) {
if (!orderedVersionRepoTupleList || !orderedVersionRepoTupleList.length) {
logger.debug('No version given.');
return;
}
let usedVersionFragments = new Set();
if (typeof versionRange === "string") {
versionRange = new semver.Range(versionRange);
versionRange = versionScheme.range(versionRange);
}
let idx = 0;
do {
let candidate = orderedVersionRepoTupleList[idx];
if (versionRange.test(candidate.version)) {
const candidateFragment = extractVersionFragment(candidate.version, limitBy);
const candidateFragment = extractVersionFragment(versionScheme, candidate.version, limitBy);
if (!usedVersionFragments.has(candidateFragment)) {
consumer(candidate);
usedVersionFragments.add(candidateFragment);
Expand Down
Loading

0 comments on commit f4064b4

Please sign in to comment.