diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99dd0a4e992..c13c41e549e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,59 @@ jobs: - name: Test run: npm test --ignore-scripts + test-cli-content: + name: Test CLI Content - ${{ matrix.platform.name }} - ${{ matrix.node-version }} + if: github.repository_owner == 'npm' + strategy: + fail-fast: false + matrix: + platform: + - name: Linux + os: ubuntu-latest + shell: bash + node-version: + - 18.x + runs-on: ${{ matrix.platform.os }} + defaults: + run: + shell: ${{ matrix.platform.shell }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Git User + run: | + git config --global user.email "npm-cli+bot@github.com" + git config --global user.name "npm CLI robot" + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: npm + - name: Update Windows npm + # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows + if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12.') || startsWith(matrix.node-version, '14.')) + run: | + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz + tar xf npm-7.5.4.tgz + cd package + node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz + cd .. + rmdir /s /q package + - name: Install npm@7 + if: startsWith(matrix.node-version, '10.') + run: npm i --prefer-online --no-fund --no-audit -g npm@7 + - name: Install npm@latest + if: ${{ !startsWith(matrix.node-version, '10.') }} + run: npm i --prefer-online --no-fund --no-audit -g npm@latest + - name: npm Version + run: npm -v + - name: Install Dependencies + run: npm i --no-audit --no-fund + - name: Check CLI Documentation + run: npm run build -w cli -- --check-only + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + licenses: name: REUSE Compliance Check runs-on: ubuntu-latest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b7660b64a83..c40307f04aa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,7 +6,7 @@ on: push: branches: - main - pull_request_target: + pull_request: workflow_dispatch: workflow_call: @@ -23,19 +23,12 @@ jobs: run: shell: bash steps: - - name: Checkout PR - if: ${{ github.event_name == 'pull_request_target' }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Checkout - if: ${{ github.event_name != 'pull_request_target' }} uses: actions/checkout@v3 - with: - ref: main - - name: Setup Pages - uses: actions/configure-pages@v1 + - name: Setup Git User + run: | + git config --global user.email "npm-cli+bot@github.com" + git config --global user.name "npm CLI robot" - name: Setup Node uses: actions/setup-node@v3 with: @@ -47,6 +40,8 @@ jobs: run: npm -v - name: Install Dependencies run: npm i --no-audit --no-fund + - name: Setup Pages + uses: actions/configure-pages@v1 - name: Build documentation run: npm run build env: @@ -70,4 +65,4 @@ jobs: id: deployment uses: actions/deploy-pages@v1 with: - preview: ${{ github.event_name == 'pull_request_target' }} + preview: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/update-cli.yml b/.github/workflows/update-cli.yml index e8f3b0d80fa..326ac6c0639 100644 --- a/.github/workflows/update-cli.yml +++ b/.github/workflows/update-cli.yml @@ -38,6 +38,8 @@ jobs: - name: Build documentation run: npm run build -w cli env: + # token is used to get files from `npm/cli` that + # are not present in the published tarball GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check for changes id: status diff --git a/cli/bin/build.js b/cli/bin/build.js index 796b9ec9c8f..b055ad41ec4 100644 --- a/cli/bin/build.js +++ b/cli/bin/build.js @@ -1,21 +1,43 @@ -const { resolve } = require('path') +const { resolve, relative, join } = require('path') +const { spawnSync } = require('child_process') const build = require('../lib/build.js') +const { nwo } = require('../lib/gh') + +// check only build with the current versions instead of checking the registry +// and also fails if any changes are detected. this is used in CI to make sure +// edits to the CLI content are made in the CLI repo +const checkOnly = process.argv.includes('--check-only') + +const ROOT = resolve(__dirname, '../..') +const contentPath = join(ROOT, 'content/cli') +const navPath = join(ROOT, 'src/theme/nav.yml') + +const checkContent = () => { + const status = spawnSync('git', ['status', '--porcelain', contentPath], { encoding: 'utf-8' }) + if (status.stdout) { + const msg = [ + `The following untracked changes to ${relative(process.cwd(), contentPath)} were found:`, + status.stdout, + `These files are generated and changes might need to be made in the ${nwo} repository.`, + ] + throw new Error(msg.join('\n')) + } +} build({ + releases: require('../releases.json'), loglevel: process.argv.includes('--debug') || process.env.CI ? 'verbose' : 'info', prerelease: false, - contentPath: resolve(__dirname, '..', '..', 'content', 'cli'), - releasesPath: resolve(__dirname, '..', 'releases.json'), - navPath: resolve( - __dirname, - '..', - '..', - 'src', - 'theme', - 'nav.yml' - ), + useCurrent: checkOnly, + contentPath, + navPath, }) - .then(() => console.log('DONE')) + .then(() => { + if (checkOnly) { + checkContent() + } + return console.log('DONE') + }) .catch((e) => { console.error(e) process.exit(1) diff --git a/cli/lib/build.js b/cli/lib/build.js index e9321f7f22b..05c22748b61 100644 --- a/cli/lib/build.js +++ b/cli/lib/build.js @@ -38,9 +38,25 @@ const updateNav = async (updates, { nav, path }) => { return fs.writeFile(path, nav.toString(), 'utf-8') } +const getCurrentVersions = (nav) => { + // the only place the current versions are stored is in the nav + const currentSections = nav.find(s => s.url === `/${DOCS_PATH}`).variants + + const currentVersions = currentSections.map((v) => { + const version = v.title?.match(/^Version\s(.*?)\s/)[1] + return version + }).sort(semver.compare) + + return { + versions: currentVersions, + latest: currentVersions[currentVersions.length - 1], + } +} + const main = async ({ loglevel, - releasesPath, + releases: rawReleases, + useCurrent, navPath, contentPath, prerelease, @@ -50,12 +66,18 @@ const main = async ({ log.on(loglevel) } - const pack = await pacote.packument('npm', { preferOnline: true }).then(p => ({ - versions: Object.keys(p.versions), - latest: p['dist-tags'].latest, - })) + const baseNav = await fs.readFile(navPath, 'utf-8') + const navData = yaml.parse(baseNav) + const navDoc = yaml.parseDocument(baseNav) + + const pack = useCurrent + ? getCurrentVersions(navData) + : await pacote.packument('npm', { preferOnline: true }).then(p => ({ + versions: Object.keys(p.versions), + latest: p['dist-tags'].latest, + })) - const releaseVersions = require(releasesPath).map(release => { + const releaseVersions = rawReleases.map(release => { const major = Number(release.id.replace(/^v/, '')) const range = `>=${major}.0.0-a <${major + 1}.0.0` // include all prereleases const version = semver.parse(semver.maxSatisfying(pack.versions, range)) @@ -86,15 +108,13 @@ const main = async ({ } }) - const baseNav = await fs.readFile(navPath, 'utf-8') - const updates = await Promise.all( releases.map((r) => - extractRelease(r, { contentPath, baseNav: yaml.parse(baseNav), prerelease }) + extractRelease(r, { contentPath, baseNav: navData, prerelease }) ) ).then((r) => r.filter(Boolean)) - await updateNav(updates, { nav: yaml.parseDocument(baseNav), path: navPath }) + await updateNav(updates, { nav: navDoc, path: navPath }) } module.exports = main diff --git a/cli/lib/extract.js b/cli/lib/extract.js index d236f67dd96..85802f55b55 100644 --- a/cli/lib/extract.js +++ b/cli/lib/extract.js @@ -96,9 +96,6 @@ const unpackRelease = async ( log.info(release.id, release) const cwd = join(contentPath, release.id) - await fs - .rm(cwd, { force: true, recursive: true }) - .then(() => fs.mkdir(cwd, { recursive: true })) const builtPath = join('docs', 'content') const srcPath = join('docs', 'lib', 'content') @@ -119,10 +116,10 @@ const unpackRelease = async ( ?? await gh.pathExists(release.branch, join('docs', 'nav.yml')), }) - // If we are using the release's GitHub ref, then we fetch - // the tree of the doc directory's sha which has all the docs - // we need in it. Note that this requires the docs to all be - // built in source, which is true for v6 but not for v9 and later. + await fs + .rm(cwd, { force: true, recursive: true }) + .then(() => fs.mkdir(cwd, { recursive: true })) + const files = await unpackTarball({ release, cwd, diff --git a/cli/lib/gh.js b/cli/lib/gh.js index 77ef696bdfc..04623ca8887 100644 --- a/cli/lib/gh.js +++ b/cli/lib/gh.js @@ -1,6 +1,10 @@ const { Octokit } = require('@octokit/rest') const { posix, sep } = require('path') +if (!process.env.GITHUB_TOKEN) { + throw new Error('GITHUB_TOKEN env var is required to build CLI docs') +} + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }) const owner = 'npm' const repo = 'cli' diff --git a/cli/scripts/template-oss/update-cli.yml b/cli/scripts/template-oss/update-cli.yml index 2072cf79e95..79b07764bfa 100644 --- a/cli/scripts/template-oss/update-cli.yml +++ b/cli/scripts/template-oss/update-cli.yml @@ -13,6 +13,8 @@ jobs: - name: Build documentation run: npm run build -w cli env: + # token is used to get files from `npm/cli` that + # are not present in the published tarball GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }} - name: Check for changes id: status diff --git a/cli/test/index.js b/cli/test/index.js index c6e33ae40e1..3e3de02f4c8 100644 --- a/cli/test/index.js +++ b/cli/test/index.js @@ -38,7 +38,6 @@ const mockBuild = async ({ releases, packument = {}, testdir: testdirOpts }) => const nav = yaml.parse(rawNav) const testdir = t.testdir({ - 'releases.json': JSON.stringify(releases), 'nav.yml': rawNav, content: {}, ...testdirOpts, @@ -102,8 +101,8 @@ const mockBuild = async ({ releases, packument = {}, testdir: testdirOpts }) => return { testdir, build: (opts) => build({ + releases, contentPath: join(testdir, 'content'), - releasesPath: join(testdir, 'releases.json'), navPath: join(testdir, 'nav.yml'), ...opts, }), @@ -158,6 +157,15 @@ t.test('earlier release is latest', async (t) => { await build() }) +t.test('can skip fetching latest', async (t) => { + const releases = getReleases() + const { build } = await mockBuild({ + releases, + }) + + await build({ useCurrent: true }) +}) + t.test('add variant to nav', async (t) => { const releases = getReleases() const { build } = await mockBuild({ diff --git a/scripts/template-oss/ci.yml b/scripts/template-oss/ci.yml index 6a3ab738e2e..b4ff082ab70 100644 --- a/scripts/template-oss/ci.yml +++ b/scripts/template-oss/ci.yml @@ -1,5 +1,12 @@ {{> ci }} + test-cli-content: + {{> jobMatrix jobName="Test CLI Content" }} + - name: Check CLI Documentation + run: npm run build -w cli -- --check-only + env: + GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }} + licenses: name: REUSE Compliance Check runs-on: ubuntu-latest diff --git a/scripts/template-oss/publish.yml b/scripts/template-oss/publish.yml index 881b37904ec..75f5389ecf2 100644 --- a/scripts/template-oss/publish.yml +++ b/scripts/template-oss/publish.yml @@ -4,7 +4,7 @@ on: push: branches: - {{ defaultBranch }} - pull_request_target: + pull_request: workflow_dispatch: workflow_call: @@ -13,22 +13,9 @@ jobs: permissions: contents: read pages: read - {{> job jobName="Build and Upload" jobSkipSetup=true }} - - name: Checkout PR - if: $\{{ github.event_name == 'pull_request_target' }} - uses: actions/checkout@v3 - with: - ref: $\{{ github.event.pull_request.head.ref }} - repository: $\{{ github.event.pull_request.head.repo.full_name }} - - name: Checkout - if: $\{{ github.event_name != 'pull_request_target' }} - uses: actions/checkout@v3 - with: - ref: {{ defaultBranch }} + {{> job jobName="Build and Upload" }} - name: Setup Pages uses: actions/configure-pages@v1 - {{> stepNode }} - {{> stepDeps }} - name: Build documentation run: npm run build env: @@ -52,4 +39,4 @@ jobs: id: deployment uses: actions/deploy-pages@v1 with: - preview: $\{{ github.event_name == 'pull_request_target' }} + preview: $\{{ github.event_name == 'pull_request' }}