Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

streamline clone logix and intro cache option #51

Merged
merged 39 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0e88028
streamline clone logix and intro cache option
pirog Sep 30, 2024
6b43d98
first pass on cached builds
pirog Sep 30, 2024
9cc2a9d
fix gha build and log output
pirog Sep 30, 2024
ae2023c
clean up and cache again
pirog Oct 1, 2024
9b125fa
build again
pirog Oct 1, 2024
4ac9393
docs
pirog Oct 1, 2024
b32275b
disable cache
pirog Oct 1, 2024
e5db5c6
improve cache resolution
pirog Oct 1, 2024
7a6e0d4
fix annoying alias.dev bug causing ref to always report as highest ta…
pirog Oct 1, 2024
d35e9d4
debug 1
pirog Oct 1, 2024
e76597f
debug 2
pirog Oct 1, 2024
3a03577
debug 3
pirog Oct 1, 2024
5e916ee
debug 4
pirog Oct 1, 2024
94ae812
debug 5
pirog Oct 1, 2024
2c82cff
debug 6
pirog Oct 1, 2024
3ae0cbb
debug 7
pirog Oct 1, 2024
4348b8d
debug 8
pirog Oct 1, 2024
395c48f
debug 9
pirog Oct 1, 2024
5329850
debug 10
pirog Oct 1, 2024
ace309d
debug 11
pirog Oct 1, 2024
66511dd
debug 12
pirog Oct 1, 2024
59ec5a2
debug 13
pirog Oct 1, 2024
eb59de0
debug 14
pirog Oct 1, 2024
0520d96
debug 15
pirog Oct 1, 2024
821b9f8
debug 16
pirog Oct 1, 2024
a77d775
debug 17
pirog Oct 1, 2024
ffbf5fb
debug 18
pirog Oct 1, 2024
93b84bf
debug 19
pirog Oct 1, 2024
f124367
debug 20
pirog Oct 1, 2024
d77b83a
debug 21
pirog Oct 1, 2024
a9caeda
debug 22
pirog Oct 1, 2024
f9fe27b
debug 23
pirog Oct 1, 2024
83c0880
debug 23
pirog Oct 1, 2024
9470f44
debug 24
pirog Oct 1, 2024
4f4df07
debug 25
pirog Oct 1, 2024
0099378
debug 26
pirog Oct 1, 2024
8eee6a4
debug 27
pirog Oct 1, 2024
6b219a7
debug 28
pirog Oct 1, 2024
42b9e5a
debug 29
pirog Oct 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ jobs:
# Install deps and cache
- name: Checkout code
uses: actions/checkout@v4
- name: Cache version builds
uses: actions/cache@v4
with:
key: lando-mvb-docs
path: docs/.vitepress/cache/@lando/mvb
save-always: true
- name: Install node ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm clean-install --prefer-offline --frozen-lockfile

# Run tests
- name: Run linter
run: npm run lint
Expand All @@ -31,4 +38,5 @@ jobs:
- name: Test build
run: npm run build
- name: Test multiversion build
run: npm run mvb
run: npx mvb docs --no-cache --debug

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }})

* Added `caching` to `mvb` multiversion build
* Fixed bug causing `alias.dev` to always report highest built tag instead of actual dev version

## v1.1.0-beta.3 - [September 30, 2024](https://github.com/lando/vitepress-theme-default-plus/releases/tag/v1.1.0-beta.3)

* Improved `is-dev-release` to handle `dev` releases that are in front of a prerelease tag
Expand Down
126 changes: 81 additions & 45 deletions bin/mvb.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
import crypto from 'node:crypto';
import path from 'node:path';
import {format, inspect} from 'node:util';

Expand All @@ -10,16 +11,23 @@ import {resolveConfig} from 'vitepress';

import {default as getStdOut} from '../utils/parse-stdout.js';
import {default as createExec} from '../utils/create-exec.js';
import {default as getBranch} from '../utils/get-branch.js';
import {default as getTags} from '../utils/get-tags.js';
import {default as traverseUp} from '../utils/traverse-up.js';

import Debug from 'debug';

// debugger
const debug = Debug('@lando/mvb'); // eslint-disable-line
// helper to get remote git clone url
const getCloneUrl = () => getStdOut('git config --get remote.origin.url', {trim: true});
// env
const onNetlify = process.env?.NETLIFY === 'true';

// enable debug if applicable
if (process.argv.includes('--debug')) Debug.enable(process.env.DEBUG ?? '*');
if (process.argv.includes('--debug') || process.env.RUNNER_DEBUG === '1') {
Debug.enable(process.env.DEBUG ?? '*');
}

// kenny loggin utils
const log = (message = '', ...args) => {
Expand All @@ -30,13 +38,10 @@ const log = (message = '', ...args) => {

// lets start by getting argv
const argv = parser(process.argv.slice(2));

// source dir
const srcDir = argv._[0] ?? 'docs';

// orginal absolute path to source
const osource = path.resolve(process.cwd(), srcDir);

// help
const help = argv.h || argv.help;

Expand All @@ -49,6 +54,7 @@ const {site} = siteConfig;
const defaults = {
base: site?.base ?? '/',
build: site?.themeConfig?.multiVersionBuild?.build ?? 'stable',
cache: site?.themeConfig?.multiVersionBuild?.cache ?? true,
match: site?.themeConfig?.multiVersionBuild?.match ?? 'v[0-9].*',
outDir: path.relative(process.cwd(), siteConfig.outDir) ?? './.vitepress/dist',
satisfies: site?.themeConfig?.multiVersionBuild?.satisfies ?? '*',
Expand All @@ -58,12 +64,13 @@ const defaults = {
// show help/usage if requested
if (help) {
log(`
Usage: ${dim('[CI=1]')} ${bold(`${path.basename(process.argv[1])} <root>`)} ${dim('[--base <base>] [--build <alias>] [--match "<match>"] [--out-dir <dir>] [--satisifes "<satisfies>"] [--version-base <dir>] [--debug] [--help]')}
Usage: ${dim('[CI=1]')} ${bold(`${path.basename(process.argv[1])} <root>`)} ${dim('[--base <base>] [--build <alias>] [--match "<match>"] [--no-cache] [--out-dir <dir>] [--satisifes "<satisfies>"] [--version-base <dir>] [--debug] [--help]')}

${green('Options')}:
--base sets site base ${dim(`[default: ${defaults.base}`)}]
--build uses this version alias for main/root build ${dim(`[default: ${defaults.build}`)}]
--match filters versions from git tags ${dim(`[default: "${defaults.match}"`)}]
--no-cache builds all versions every build ${dim(`[default: "${!defaults.cache}"`)}]
--out-dir builds into this location ${dim(`[default: ${defaults.outDir}`)}]
--satisfies builds versioned docs in this semantic range ${dim(`[default: "${defaults.satisfies}"`)}]
--version-base builds versioned docs in this location ${dim(`[default: ${defaults.versionBase}`)}]
Expand All @@ -82,53 +89,55 @@ debug('received argv %o', argv);
debug('default options %o', defaults);
log('found site %s at %s', magenta(site.title), magenta(osource));

// determine cachebase
const cacheBase = onNetlify ? '/opt/build/cache' : siteConfig.cacheDir;

// resolve options with argv input
// @TODO: /tmp location option?
const options = {...defaults, ...argv, tmpDir: path.resolve(siteConfig.tempDir, nanoid())};
const options = {
...defaults,
...argv,
cacheDir: path.resolve(cacheBase, '@lando', 'mvb'),
tmpDir: path.resolve(siteConfig.tempDir, nanoid()),
};
debug('multiversion build from %o using resolved build options: %O', srcDir, options);

// determine gitdir
// @TODO: throw error if no git dir?
const gitDir = traverseUp(['.git'], osource).find(dir => fs.existsSync(dir));
debug('determined git-dir: %o', gitDir);

// destructure some helpful options
const {outDir, tmpDir} = options;

// do the initial setup
fs.removeSync(tmpDir, {force: true, maxRetries: 10, recursive: true});
fs.mkdirSync(tmpDir, {recursive: true});
fs.removeSync(options.tmpDir, {force: true, maxRetries: 10, recursive: true});
fs.mkdirSync(options.tmpDir, {recursive: true});

// create execer for source and tmp ops
// create execers for source and tmp opts
const oexec = createExec({cwd: process.cwd(), debug});
const exec = createExec({cwd: tmpDir, debug});
const exec = createExec({cwd: options.tmpDir, debug});

// start it up
log('collecting version information from %s...', magenta(gitDir));

// lets make sure the source repo at least has all the tag information it needs
const updateRefs = ['fetch', 'origin', '--tags', '--no-filter'];
// determine whether we have a shallow clone eg as on GHA
const shallow = getStdOut('git rev-parse --is-shallow-repository', {trim: true}) === 'true';
const updateArgs = ['fetch', 'origin', '--tags', '--no-filter'];
// if shallow then add to update refs
if (shallow) updateRefs.push('--unshallow');
if (getStdOut('git rev-parse --is-shallow-repository', {trim: true}) === 'true') updateArgs.push('--unshallow');
// update all refs
await oexec('git', updateRefs);
await oexec('git', updateArgs);

// make a copy of our repo
// @NOTE: netlify does weird shit that requires special special dispensation
if (process.env?.NETLIFY === 'true') {
// get git URL from git config
const gitUrl = getStdOut('git config --get remote.origin.url', {trim: true});
// reclone in tmp
await exec('git', ['clone', '--depth=2147483647', '--branch', process.env.HEAD, gitUrl, './']);
// if we are in detached head state then checkout best branch
if (getStdOut('git rev-parse --abbrev-ref HEAD', {trim: true}) === 'HEAD') await oexec('git', ['checkout', getBranch()]);

// everything else can just use this
} else await exec('git', ['clone', gitDir, './']);
// build clone args
const cloneArgs = ['clone', '--origin', 'origin', '--no-single-branch'];
// netlicf clone
if (onNetlify) cloneArgs.push('--depth', '2147483647', '--branch', getBranch(), getCloneUrl(), './');
// generic clone
else cloneArgs.push('--no-local', '--no-hardlinks', gitDir, './');
// do the vampire
await exec('git', cloneArgs);

// get extended version information
const {extended} = await getTags(gitDir, options);
const {extended} = await getTags(options.tmpDir, options);
debug('determined versions to build: %o', extended);

// if we cant find the base build then reset it to dev
Expand All @@ -144,26 +153,43 @@ debug('determined main/root build is %o %o', options.build, extended[0]);
// now loop through extended and construct the build metadata
const builds = extended.map((version, index) => {
if (index === 0) {
version.base = site.base;
version.outDir = outDir;
version.base = options.base;
version.outDir = options.outDir;
} else {
version.base = path.resolve(`/${site.base}/${options.versionBase}/${version.alias ?? version.version}`) + '/';
version.outDir = path.join(outDir, options.versionBase, version.alias ?? version.version);
version.base = path.resolve(`/${options.base}/${options.versionBase}/${version.alias ?? version.version}`) + '/';
version.outDir = path.join(options.outDir, options.versionBase, version.alias ?? version.version);
}

// if caching then also suggest a cache location
if (options.cache) {
version.cacheKey = path.join(options.base, options.versionBase, version.version, version.base);
version.cachePath = path.join(options.cacheDir, crypto.createHash('sha256').update(version.cacheKey).digest('hex'));
}

// return
return {...version, srcDir};
});

// report
log('found %s versions to build', magenta(builds.length - 1));
log('default/main/root build using alias %s, ref %s', magenta(builds[0]?.alias), magenta(builds[0]?.ref));
log('normal build at %s using alias %s, ref %s', magenta(options.base), magenta(builds[0]?.alias), magenta(builds[0]?.ref));
log('and found %s other versions to build', magenta(builds.length - 1));
log('');

// and now build them all
for (const build of builds) {
// @LOG: building?
// separate out our stuff
const {alias, ref, semantic, srcDir, version, ...config} = build;
const {alias, cachePath, ref, semantic, srcDir, version, ...config} = build;

// if we have cache then lets just copy it over
if (cachePath && fs.existsSync(cachePath)) {
log('restoring version %s from %s at %s...', magenta(alias ?? version), magenta('cache'), magenta(cachePath));
fs.removeSync(path.resolve(config.outDir), {force: true, maxRetries: 10, recursive: true});
fs.mkdirSync(config.outDir, {recursive: true});
fs.copySync(cachePath, path.resolve(config.outDir));
continue;
}

// if we get here we need to actually do a build
debug('building %o version %o with config %o', srcDir, `${alias ?? version}@${ref}`, config);
log('building version %s, ref %s, from %s to %s...', magenta(alias ?? version), magenta(ref), magenta(srcDir), magenta(config.outDir));

Expand All @@ -175,7 +201,7 @@ for (const build of builds) {
await exec('npm', ['clean-install']);

// update package.json if needed
const pjsonPath = path.join(tmpDir, 'package.json');
const pjsonPath = path.join(options.tmpDir, 'package.json');
const pjson = JSON.parse(fs.readFileSync(pjsonPath, {encoding: 'utf8'}));
if (pjson.version !== semantic) {
// update version
Expand All @@ -188,19 +214,29 @@ for (const build of builds) {

// build the version
try {
await exec('npx', ['vitepress', 'build', srcDir, '--outDir', config.outDir, '--base', config.base]);
await exec(
'npx',
['vitepress', 'build', srcDir, '--outDir', config.outDir, '--base', config.base],
{env: {LANDO_MVB_BUILD: 1, LANDO_MVB_BRANCH: getBranch(gitDir), LANDO_MVB_SOURCE: process.cwd()}},
);
} catch (error) {
error.message = red(`Build failed for version ${version} with error: ${error.message}`);
error.build = build;
throw error;
}
}

// clean original
fs.removeSync(siteConfig.outDir, {force: true, maxRetries: 10, recursive: true});
// move tmp to original
fs.moveSync(path.resolve(tmpDir, outDir), siteConfig.outDir);
// clean original
fs.removeSync(path.resolve(config.outDir), {force: true, maxRetries: 10, recursive: true});
// copy tmp to original
fs.copySync(path.join(options.tmpDir, config.outDir), path.resolve(config.outDir));

// save cache if its on
if (options.cache) {
debug('saving version %s to %s at %s...', version, 'cache', cachePath);
fs.copySync(path.resolve(config.outDir), cachePath);
}
}

// @LOG: finish info, where / and /v/ and how many versions built?
// FIN
log('');
log('%s %s builds at %s!', green('completed'), magenta(builds.length), magenta(siteConfig.outDir));
3 changes: 2 additions & 1 deletion config/landov3.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,10 @@ export default function({landoPlugin, version}) {
layouts: {},
logo: {src: '/images/icon.svg', width: 24, height: 24},
multiVersionBuild: {
base: '/v/',
build: 'stable',
cache: true,
match: 'v[0-9].*',
base: '/v/',
satisfies: '>=1.0.0',
},
nav: [],
Expand Down
3 changes: 2 additions & 1 deletion config/landov4.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,10 @@ export default function({landoPlugin, version}) {
layouts: {},
logo: {src: '/images/icon.svg', width: 24, height: 24},
multiVersionBuild: {
base: '/v/',
build: 'stable',
cache: true,
match: 'v[0-9].*',
base: '/v/',
satisfies: '>=1.0.0',
},
nav: [],
Expand Down
3 changes: 2 additions & 1 deletion docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ export default defineConfig({
},
logo: {src: '/images/vitepress-lando-logo-icon.png', width: 24, height: 24},
multiVersionBuild: {
base: '/v/',
build: 'edge',
cache: true,
match: 'v[0-9].*',
base: '/v/',
satisfies: '>=1.0.0-beta.42',
},
tags: {
Expand Down
10 changes: 7 additions & 3 deletions docs/build/multiversion-vitepress-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Usage: [CI=1] multiversion-vitepress-build <root> \
[--base <base>] \
[--build <alias>] \
[--match "<match>"] \
[--no-cache] \
[--out-dir <dir>] \
[--satisifes "<satisfies>"] \
[--version-base <dir>] \
Expand All @@ -35,10 +36,11 @@ Usage: [CI=1] multiversion-vitepress-build <root> \

Options:
--base sets site base [default: /]
--build uses this version alias for main/root build [default: dev]
--build uses this version alias for main/root build [default: stable]
--match filters versions from git tags [default: "v[0-9].*"]
--no-cache builds all versions every build [default: "false"]
--out-dir builds into this location [default: docs/.vitepress/dist]
--satisfies builds versioned docs in this semantic range [default: ">=1.0.0-beta.42"]
--satisfies builds versioned docs in this semantic range [default: "*"]
--version-base builds versioned docs in this location [default: /v/]
--debug shows debug messages
-h, --help displays this message
Expand All @@ -62,7 +64,9 @@ npx mvb old_docs --build stable --satisfies ">=1.0.0 <1.0.4"
# build all versions into a separate directory
npx mvb --satisfies "*" --version-base "/all-versions/"

# rebuild all versions from scratch
npx mvb --no-cache

# get usage/help
npx mvb --help

```
9 changes: 6 additions & 3 deletions docs/config/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,27 @@ Once you have you should be able to use all the things below.
```js
multiVersionBuild: [
{
base: '/v/',
build: 'stable',
cache: true,
match: 'v[0-9].*',
base: '/v/',
satisfies: '*',
},
],
```
* Details:

`base` is where to put all the versioned docs relative to `siteConfig.base`.

`build` will set the main or base version of your docs that is served at your `siteConfig.base`. It can take the values `stable`, `edge` or `dev`.

* `stable` will build from the last commit tagged with a non-prerelease semantically valid version eg `v1.0.0`.
* `edge` will use the commit for the last semantically valid tag which may be `v1.0.0-beta.1` but could also be `v1.0.0`.
* `dev` will build from `HEAD`.

`match` is a `REGEX` to match which `git` tags should be considered versions.
`cache` will speed up builds by using any cached versions you've already build.

`base` is where to put all the versioned docs relative to `siteConfig.base`.
`match` is a `REGEX` to match which `git` tags should be considered versions.

`satisifes` allows you to restrict which versions to build from the versions that `match` returned using a `semver` range as is documented [here](https://github.com/npm/node-semver?tab=readme-ov-file#ranges).

Expand Down
Loading
Loading