fix: label latest dist-tag release as latest and anything greater current (#264)

Luke Karrys committed Oct 19, 2022 at 13:26 UTC 40929f2f1ef94090b581db9012bdec0ec8407729
5 files changed +126 -45
cli/lib/build.js
+43 -10
@@ -1,6 +1,9 @@
1 const { posix, join, sep } = require('path')
2 const fs = require('fs').promises
3 const yaml = require('yaml')
4 +const semver = require('semver')
5 +const pacote = require('pacote')
6 +const gh = require('./gh')
7 const extractRelease = require('./extract')
8 const log = require('./log')
9
@@ -64,18 +67,48 @@ const main = async ({
67 // can be used to write files later
68 const defaultBuiltDir = join('docs', 'content')
69
67 - const releases = require(releasesPath).map((release) => ({
68 - ...release,
69 - // dir of the built docs that should be copied
70 - built: defaultBuiltDir,
71 - // dir of the source for the docs that should
72 - // be linked to for editing on github
73 - src: release.src?.split(posix.sep).join(sep) || defaultBuiltDir,
74 - url: `/${DOCS_PATH}/${release.id}`,
75 - urlPrefix: DOCS_PATH,
76 - urlPrefixes: [DOCS_PATH, `${DOCS_PATH}-documentation`],
70 + const rawReleases = require(releasesPath)
71 +
72 + const releaseManifests = await Promise.all(rawReleases.map(async release => {
73 + const manifest = await pacote.manifest(`${gh.owner}@${release.spec}`, {
74 + preferOnline: true,
75 + })
76 + // the default release is always controlled by the latest dist-tag
77 + release.default = release.spec === 'latest'
78 + release.manifest = manifest
79 + release.version = manifest.version
80 + const sVersion = semver.parse(release.version)
81 + release.semver = sVersion
82 + release.prerelease = sVersion.prerelease.length > 0
83 + return release
84 }))
85
86 + const latestRelease = releaseManifests.find(r => r.spec === 'latest')
87 +
88 + if (!latestRelease) {
89 + throw new Error(`One of the CLI releases must have \`spec: 'latest'\``)
90 + }
91 +
92 + const releases = releaseManifests.map((release) => {
93 + const type = release.default ? 'Latest Release'
94 + : release.prerelease ? 'Prerelease'
95 + : semver.gt(release.version, latestRelease.version) ? 'Current Release'
96 + : 'Legacy Release'
97 +
98 + return {
99 + ...release,
100 + title: `Version ${release.version} (${type})`,
101 + // dir of the built docs that should be copied
102 + built: defaultBuiltDir,
103 + // dir of the source for the docs that should
104 + // be linked to for editing on github
105 + src: release.src?.split(posix.sep).join(sep) || defaultBuiltDir,
106 + url: `/${DOCS_PATH}/${release.id}`,
107 + urlPrefix: DOCS_PATH,
108 + urlPrefixes: [DOCS_PATH, `${DOCS_PATH}-documentation`],
109 + }
110 + })
111 +
112 const baseNav = await fs.readFile(navPath, 'utf-8')
113
114 const updates = await Promise.all(
cli/lib/extract.js
+3 -18
@@ -2,7 +2,6 @@ const pacote = require('pacote')
2 const tar = require('tar')
3 const { join, sep, dirname, posix } = require('path')
4 const fs = require('fs/promises')
5 -const semver = require('semver')
5 const yaml = require('yaml')
6 const Transform = require('./transform')
7 const gh = require('./gh')
@@ -99,25 +98,11 @@ const resolveRelease = async (
98 { resolved: current, ...release },
99 { force, prerelease }
100 ) => {
102 - const manifest = await pacote.manifest(`${gh.owner}@${release.spec}`, {
103 - preferOnline: true,
104 - })
105 -
106 - release.version = manifest.version
107 - const isPre = semver.parse(release.version).prerelease.length > 0
108 -
109 - if (isPre && !prerelease) {
101 + if (release.prerelease && !prerelease) {
102 log.info(`Skipping ${release.id} due to prerelease ${release.version}`)
103 return null
104 }
105
114 - const versionType = release.default
115 - ? 'Current Release'
116 - : isPre
117 - ? 'Prerelease'
118 - : 'Legacy Release'
119 - release.title = `Version ${release.version} (${versionType})`
120 -
106 // The legacy v6 release has updated docs in GitHub that were never
107 // published. So in this case we skip cloning the repo with pacote
108 // and get the latest commit on the branch. Later we will use the
@@ -126,8 +111,8 @@ const resolveRelease = async (
111 if (release.useBranch) {
112 release.resolved = await gh.getLatestSha(release.branch)
113 } else {
129 - release.resolved = manifest._resolved
130 - release.spec = manifest._from
114 + release.resolved = release.manifest._resolved
115 + release.spec = release.manifest._from
116 }
117
118 log.info(release.id, release.version, release.resolved)
cli/releases.json
+1 -2
@@ -16,7 +16,6 @@
16 "id": "v8",
17 "branch": "v8",
18 "spec": "latest",
19 - "default": true,
19 "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.2.tgz"
20 },
21 {
@@ -24,6 +23,6 @@
23 "branch": "latest",
24 "spec": "next-9",
25 "src": "docs/lib/content",
27 - "resolved": "https://registry.npmjs.org/npm/-/npm-9.0.0-pre.4.tgz"
26 + "resolved": "https://registry.npmjs.org/npm/-/npm-9.0.0-pre.5.tgz"
27 }
28 ]
cli/test/index.js
+76 -14
@@ -1,9 +1,8 @@
1 const t = require('tap')
2 const { resolve, join } = require('path')
3 const fs = require('fs/promises')
4 +const pacote = require('pacote')
5
5 -const build = require('../lib/build')
6 -const releases = require('../releases.json')
6 const navPath = resolve(
7 __dirname,
8 '..',
@@ -13,32 +12,95 @@ const navPath = resolve(
12 'nav.yml'
13 )
14
16 -t.test('builds successfully', async (t) => {
15 +const getReleases = () => {
16 + return JSON.parse(JSON.stringify(require('../releases.json')))
17 +}
18 +
19 +const mockBuild = async ({ releases, testdir: testdirOpts }) => {
20 const testdir = t.testdir({
21 'releases.json': JSON.stringify(releases),
22 'nav.yml': await fs.readFile(navPath, 'utf-8'),
23 content: {},
24 + ...testdirOpts,
25 })
26
23 - await build({
24 - force: true,
25 - prerelease: true,
27 + const build = t.mock('../lib/build', {
28 + pacote: {
29 + ...pacote,
30 + manifest: async (spec) => {
31 + let version = spec.split('@')[1]
32 + const release = releases.find(r => r.spec === version)
33 +
34 + if (version.match(/^\^\d+$/)) {
35 + version = version.slice(1) + '.0.0'
36 + } else if (version === 'latest') {
37 + version = release.id.slice(1) + '.0.0'
38 + } else if (version.startsWith('next-')) {
39 + version = version.replace('next-', '') + '.0.0-pre.4'
40 + }
41 +
42 + return {
43 + version,
44 + _resolved: release.resolved,
45 + _from: spec,
46 + }
47 + },
48 + },
49 + })
50 +
51 + return (opts) => build({
52 contentPath: join(testdir, 'content'),
53 releasesPath: join(testdir, 'releases.json'),
54 navPath: join(testdir, 'nav.yml'),
55 + ...opts,
56 + })
57 +}
58 +
59 +t.test('builds successfully', async (t) => {
60 + const releases = getReleases()
61 + const build = await mockBuild({ releases })
62 +
63 + await build({
64 + force: true,
65 + prerelease: true,
66 })
67 })
68
69 t.test('no force', async (t) => {
33 - const testdir = t.testdir({
34 - 'releases.json': JSON.stringify(releases),
35 - 'nav.yml': '- title: cli\n url: /cli',
36 - content: {},
70 + const releases = getReleases()
71 + const build = await mockBuild({
72 + releases,
73 + testdir: {
74 + 'nav.yml': '- title: cli\n url: /cli',
75 + },
76 })
77
39 - await build({
40 - contentPath: join(testdir, 'content'),
41 - releasesPath: join(testdir, 'releases.json'),
42 - navPath: join(testdir, 'nav.yml'),
78 + await build()
79 +})
80 +
81 +t.test('no default release', async (t) => {
82 + const releases = getReleases().filter(r => r.spec !== 'latest')
83 + const build = await mockBuild({
84 + releases,
85 + testdir: {
86 + 'nav.yml': '- title: cli\n url: /cli',
87 + },
88 })
89 +
90 + await t.rejects(() => build())
91 +})
92 +
93 +t.test('earlier release is latest', async (t) => {
94 + const releases = getReleases()
95 + releases[1].spec = 'latest'
96 + releases[2].spec = '^8'
97 +
98 + const build = await mockBuild({
99 + releases,
100 + testdir: {
101 + 'nav.yml': '- title: cli\n url: /cli',
102 + },
103 + })
104 +
105 + await build()
106 })
src/theme/nav.yml
+3 -1
@@ -288,6 +288,7 @@
288 - title: Version 6.14.17 (Legacy Release)
289 shortName: v6
290 url: /cli/v6
291 + default: false
292 children:
293 - title: CLI Commands
294 shortName: Commands
@@ -526,6 +527,7 @@
527 - title: Version 7.24.2 (Legacy Release)
528 shortName: v7
529 url: /cli/v7
530 + default: false
531 children:
532 - title: CLI Commands
533 shortName: Commands
@@ -776,7 +778,7 @@
778 - title: Removal
779 url: /cli/v7/using-npm/removal
780 description: Cleaning the slate
779 - - title: Version 8.19.2 (Current Release)
781 + - title: Version 8.19.2 (Latest Release)
782 shortName: v8
783 url: /cli/v8
784 default: true