fix: change publish workflow to pull_request

This also adds a check to fail if CLI content is updated in this repo instead of `npm/cli`

Luke Karrys committed Jan 1, 2023 at 21:20 UTC 2c54fe219fba05371b4889464813fb066510b032
11 files changed +157 -60
.github/workflows/ci.yml
+53
@@ -103,6 +103,59 @@ jobs:
103 - name: Test
104 run: npm test --ignore-scripts
105
106 + test-cli-content:
107 + name: Test CLI Content - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
108 + if: github.repository_owner == 'npm'
109 + strategy:
110 + fail-fast: false
111 + matrix:
112 + platform:
113 + - name: Linux
114 + os: ubuntu-latest
115 + shell: bash
116 + node-version:
117 + - 18.x
118 + runs-on: ${{ matrix.platform.os }}
119 + defaults:
120 + run:
121 + shell: ${{ matrix.platform.shell }}
122 + steps:
123 + - name: Checkout
124 + uses: actions/checkout@v3
125 + - name: Setup Git User
126 + run: |
127 + git config --global user.email "npm-cli+bot@github.com"
128 + git config --global user.name "npm CLI robot"
129 + - name: Setup Node
130 + uses: actions/setup-node@v3
131 + with:
132 + node-version: ${{ matrix.node-version }}
133 + cache: npm
134 + - name: Update Windows npm
135 + # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
136 + if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12.') || startsWith(matrix.node-version, '14.'))
137 + run: |
138 + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
139 + tar xf npm-7.5.4.tgz
140 + cd package
141 + node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
142 + cd ..
143 + rmdir /s /q package
144 + - name: Install npm@7
145 + if: startsWith(matrix.node-version, '10.')
146 + run: npm i --prefer-online --no-fund --no-audit -g npm@7
147 + - name: Install npm@latest
148 + if: ${{ !startsWith(matrix.node-version, '10.') }}
149 + run: npm i --prefer-online --no-fund --no-audit -g npm@latest
150 + - name: npm Version
151 + run: npm -v
152 + - name: Install Dependencies
153 + run: npm i --no-audit --no-fund
154 + - name: Check CLI Documentation
155 + run: npm run build -w cli -- --check-only
156 + env:
157 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158 +
159 licenses:
160 name: REUSE Compliance Check
161 runs-on: ubuntu-latest
.github/workflows/publish.yml
+8 -13
@@ -6,7 +6,7 @@ on:
6 push:
7 branches:
8 - main
9 - pull_request_target:
9 + pull_request:
10 workflow_dispatch:
11 workflow_call:
12
@@ -23,19 +23,12 @@ jobs:
23 run:
24 shell: bash
25 steps:
26 - - name: Checkout PR
27 - if: ${{ github.event_name == 'pull_request_target' }}
28 - uses: actions/checkout@v3
29 - with:
30 - ref: ${{ github.event.pull_request.head.ref }}
31 - repository: ${{ github.event.pull_request.head.repo.full_name }}
26 - name: Checkout
33 - if: ${{ github.event_name != 'pull_request_target' }}
27 uses: actions/checkout@v3
35 - with:
36 - ref: main
37 - - name: Setup Pages
38 - uses: actions/configure-pages@v1
28 + - name: Setup Git User
29 + run: |
30 + git config --global user.email "npm-cli+bot@github.com"
31 + git config --global user.name "npm CLI robot"
32 - name: Setup Node
33 uses: actions/setup-node@v3
34 with:
@@ -47,6 +40,8 @@ jobs:
40 run: npm -v
41 - name: Install Dependencies
42 run: npm i --no-audit --no-fund
43 + - name: Setup Pages
44 + uses: actions/configure-pages@v1
45 - name: Build documentation
46 run: npm run build
47 env:
@@ -70,4 +65,4 @@ jobs:
65 id: deployment
66 uses: actions/deploy-pages@v1
67 with:
73 - preview: ${{ github.event_name == 'pull_request_target' }}
68 + preview: ${{ github.event_name == 'pull_request' }}
.github/workflows/update-cli.yml
+2
@@ -38,6 +38,8 @@ jobs:
38 - name: Build documentation
39 run: npm run build -w cli
40 env:
41 + # token is used to get files from `npm/cli` that
42 + # are not present in the published tarball
43 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44 - name: Check for changes
45 id: status
cli/bin/build.js
+34 -12
@@ -1,21 +1,43 @@
1 -const { resolve } = require('path')
1 +const { resolve, relative, join } = require('path')
2 +const { spawnSync } = require('child_process')
3 const build = require('../lib/build.js')
4 +const { nwo } = require('../lib/gh')
5 +
6 +// check only build with the current versions instead of checking the registry
7 +// and also fails if any changes are detected. this is used in CI to make sure
8 +// edits to the CLI content are made in the CLI repo
9 +const checkOnly = process.argv.includes('--check-only')
10 +
11 +const ROOT = resolve(__dirname, '../..')
12 +const contentPath = join(ROOT, 'content/cli')
13 +const navPath = join(ROOT, 'src/theme/nav.yml')
14 +
15 +const checkContent = () => {
16 + const status = spawnSync('git', ['status', '--porcelain', contentPath], { encoding: 'utf-8' })
17 + if (status.stdout) {
18 + const msg = [
19 + `The following untracked changes to ${relative(process.cwd(), contentPath)} were found:`,
20 + status.stdout,
21 + `These files are generated and changes might need to be made in the ${nwo} repository.`,
22 + ]
23 + throw new Error(msg.join('\n'))
24 + }
25 +}
26
27 build({
28 + releases: require('../releases.json'),
29 loglevel: process.argv.includes('--debug') || process.env.CI ? 'verbose' : 'info',
30 prerelease: false,
7 - contentPath: resolve(__dirname, '..', '..', 'content', 'cli'),
8 - releasesPath: resolve(__dirname, '..', 'releases.json'),
9 - navPath: resolve(
10 - __dirname,
11 - '..',
12 - '..',
13 - 'src',
14 - 'theme',
15 - 'nav.yml'
16 - ),
31 + useCurrent: checkOnly,
32 + contentPath,
33 + navPath,
34 })
18 - .then(() => console.log('DONE'))
35 + .then(() => {
36 + if (checkOnly) {
37 + checkContent()
38 + }
39 + return console.log('DONE')
40 + })
41 .catch((e) => {
42 console.error(e)
43 process.exit(1)
cli/lib/build.js
+30 -10
@@ -38,9 +38,25 @@ const updateNav = async (updates, { nav, path }) => {
38 return fs.writeFile(path, nav.toString(), 'utf-8')
39 }
40
41 +const getCurrentVersions = (nav) => {
42 + // the only place the current versions are stored is in the nav
43 + const currentSections = nav.find(s => s.url === `/${DOCS_PATH}`).variants
44 +
45 + const currentVersions = currentSections.map((v) => {
46 + const version = v.title?.match(/^Version\s(.*?)\s/)[1]
47 + return version
48 + }).sort(semver.compare)
49 +
50 + return {
51 + versions: currentVersions,
52 + latest: currentVersions[currentVersions.length - 1],
53 + }
54 +}
55 +
56 const main = async ({
57 loglevel,
43 - releasesPath,
58 + releases: rawReleases,
59 + useCurrent,
60 navPath,
61 contentPath,
62 prerelease,
@@ -50,12 +66,18 @@ const main = async ({
66 log.on(loglevel)
67 }
68
53 - const pack = await pacote.packument('npm', { preferOnline: true }).then(p => ({
54 - versions: Object.keys(p.versions),
55 - latest: p['dist-tags'].latest,
56 - }))
69 + const baseNav = await fs.readFile(navPath, 'utf-8')
70 + const navData = yaml.parse(baseNav)
71 + const navDoc = yaml.parseDocument(baseNav)
72 +
73 + const pack = useCurrent
74 + ? getCurrentVersions(navData)
75 + : await pacote.packument('npm', { preferOnline: true }).then(p => ({
76 + versions: Object.keys(p.versions),
77 + latest: p['dist-tags'].latest,
78 + }))
79
58 - const releaseVersions = require(releasesPath).map(release => {
80 + const releaseVersions = rawReleases.map(release => {
81 const major = Number(release.id.replace(/^v/, ''))
82 const range = `>=${major}.0.0-a <${major + 1}.0.0` // include all prereleases
83 const version = semver.parse(semver.maxSatisfying(pack.versions, range))
@@ -86,15 +108,13 @@ const main = async ({
108 }
109 })
110
89 - const baseNav = await fs.readFile(navPath, 'utf-8')
90 -
111 const updates = await Promise.all(
112 releases.map((r) =>
93 - extractRelease(r, { contentPath, baseNav: yaml.parse(baseNav), prerelease })
113 + extractRelease(r, { contentPath, baseNav: navData, prerelease })
114 )
115 ).then((r) => r.filter(Boolean))
116
97 - await updateNav(updates, { nav: yaml.parseDocument(baseNav), path: navPath })
117 + await updateNav(updates, { nav: navDoc, path: navPath })
118 }
119
120 module.exports = main
cli/lib/extract.js
+4 -7
@@ -96,9 +96,6 @@ const unpackRelease = async (
96 log.info(release.id, release)
97
98 const cwd = join(contentPath, release.id)
99 - await fs
100 - .rm(cwd, { force: true, recursive: true })
101 - .then(() => fs.mkdir(cwd, { recursive: true }))
99
100 const builtPath = join('docs', 'content')
101 const srcPath = join('docs', 'lib', 'content')
@@ -119,10 +116,10 @@ const unpackRelease = async (
116 ?? await gh.pathExists(release.branch, join('docs', 'nav.yml')),
117 })
118
122 - // If we are using the release's GitHub ref, then we fetch
123 - // the tree of the doc directory's sha which has all the docs
124 - // we need in it. Note that this requires the docs to all be
125 - // built in source, which is true for v6 but not for v9 and later.
119 + await fs
120 + .rm(cwd, { force: true, recursive: true })
121 + .then(() => fs.mkdir(cwd, { recursive: true }))
122 +
123 const files = await unpackTarball({
124 release,
125 cwd,
cli/lib/gh.js
+4
@@ -1,6 +1,10 @@
1 const { Octokit } = require('@octokit/rest')
2 const { posix, sep } = require('path')
3
4 +if (!process.env.GITHUB_TOKEN) {
5 + throw new Error('GITHUB_TOKEN env var is required to build CLI docs')
6 +}
7 +
8 const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
9 const owner = 'npm'
10 const repo = 'cli'
cli/scripts/template-oss/update-cli.yml
+2
@@ -13,6 +13,8 @@ jobs:
13 - name: Build documentation
14 run: npm run build -w cli
15 env:
16 + # token is used to get files from `npm/cli` that
17 + # are not present in the published tarball
18 GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
19 - name: Check for changes
20 id: status
cli/test/index.js
+10 -2
@@ -38,7 +38,6 @@ const mockBuild = async ({ releases, packument = {}, testdir: testdirOpts }) =>
38 const nav = yaml.parse(rawNav)
39
40 const testdir = t.testdir({
41 - 'releases.json': JSON.stringify(releases),
41 'nav.yml': rawNav,
42 content: {},
43 ...testdirOpts,
@@ -102,8 +101,8 @@ const mockBuild = async ({ releases, packument = {}, testdir: testdirOpts }) =>
101 return {
102 testdir,
103 build: (opts) => build({
104 + releases,
105 contentPath: join(testdir, 'content'),
106 - releasesPath: join(testdir, 'releases.json'),
106 navPath: join(testdir, 'nav.yml'),
107 ...opts,
108 }),
@@ -158,6 +157,15 @@ t.test('earlier release is latest', async (t) => {
157 await build()
158 })
159
160 +t.test('can skip fetching latest', async (t) => {
161 + const releases = getReleases()
162 + const { build } = await mockBuild({
163 + releases,
164 + })
165 +
166 + await build({ useCurrent: true })
167 +})
168 +
169 t.test('add variant to nav', async (t) => {
170 const releases = getReleases()
171 const { build } = await mockBuild({
scripts/template-oss/ci.yml
+7
@@ -1,5 +1,12 @@
1 {{> ci }}
2
3 + test-cli-content:
4 + {{> jobMatrix jobName="Test CLI Content" }}
5 + - name: Check CLI Documentation
6 + run: npm run build -w cli -- --check-only
7 + env:
8 + GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
9 +
10 licenses:
11 name: REUSE Compliance Check
12 runs-on: ubuntu-latest
scripts/template-oss/publish.yml
+3 -16
@@ -4,7 +4,7 @@ on:
4 push:
5 branches:
6 - {{ defaultBranch }}
7 - pull_request_target:
7 + pull_request:
8 workflow_dispatch:
9 workflow_call:
10
@@ -13,22 +13,9 @@ jobs:
13 permissions:
14 contents: read
15 pages: read
16 - {{> job jobName="Build and Upload" jobSkipSetup=true }}
17 - - name: Checkout PR
18 - if: $\{{ github.event_name == 'pull_request_target' }}
19 - uses: actions/checkout@v3
20 - with:
21 - ref: $\{{ github.event.pull_request.head.ref }}
22 - repository: $\{{ github.event.pull_request.head.repo.full_name }}
23 - - name: Checkout
24 - if: $\{{ github.event_name != 'pull_request_target' }}
25 - uses: actions/checkout@v3
26 - with:
27 - ref: {{ defaultBranch }}
16 + {{> job jobName="Build and Upload" }}
17 - name: Setup Pages
18 uses: actions/configure-pages@v1
30 - {{> stepNode }}
31 - {{> stepDeps }}
19 - name: Build documentation
20 run: npm run build
21 env:
@@ -52,4 +39,4 @@ jobs:
39 id: deployment
40 uses: actions/deploy-pages@v1
41 with:
55 - preview: $\{{ github.event_name == 'pull_request_target' }}
42 + preview: $\{{ github.event_name == 'pull_request' }}