Files
pnpm/.github/scripts/copy-docs-artifact.test.mjs
T
Zoltan Kochan 15441b1e75 fix: skip documentation push when content is unchanged (#16389)
Gate documentation publication on creation of a generated commit.

An unchanged import exits the commit step successfully, but must also skip
credential lookup and pushing. Emit changed=true after a successful commit
and use it as the condition for the push step.
2026-09-29 16:36:47 +02:00

87 lines
4.6 KiB
JavaScript

import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { test } from 'node:test'
import { copyDocsArtifact } from './copy-docs-artifact.mjs'
function fixture (t) {
const root = mkdtempSync(path.join(os.tmpdir(), 'docs-artifact-'))
t.after(() => rmSync(root, { recursive: true, force: true }))
const source = path.join(root, 'source')
const destination = path.join(root, 'website')
const write = (base, file, text) => {
mkdirSync(path.dirname(path.join(base, file)), { recursive: true })
writeFileSync(path.join(base, file), text)
}
for (const file of ['docs/index.md', 'pnpr-docs/index.md', 'versioned_docs/version-11.x/index.md', 'sidebars.json', 'sidebars-pnpr.json', 'versioned_sidebars/version-11.x-sidebars.json', 'docs-sync.json']) write(source, file, 'generated')
write(destination, 'docs/removed.md', 'old')
write(destination, 'blog/post.md', 'blog')
write(destination, '.git/config', 'trusted config')
return { source, destination, write }
}
test('copies generated docs, removes stale files and preserves website and Git files', t => {
const f = fixture(t)
f.write(f.source, 'static/docs-assets/12.x/img/logo.png', 'image')
copyDocsArtifact(f.source, f.destination)
assert.equal(readFileSync(path.join(f.destination, 'docs/index.md'), 'utf8'), 'generated')
assert.equal(readFileSync(path.join(f.destination, 'static/docs-assets/12.x/img/logo.png'), 'utf8'), 'image')
assert.equal(existsSync(path.join(f.destination, 'docs/removed.md')), false)
assert.equal(readFileSync(path.join(f.destination, 'blog/post.md'), 'utf8'), 'blog')
assert.equal(readFileSync(path.join(f.destination, '.git/config'), 'utf8'), 'trusted config')
})
test('rejects executable Git metadata and files outside documentation before changing the checkout', t => {
const f = fixture(t)
for (const file of ['.git/hooks/pre-commit', 'docs/.gitattributes', 'docs/.git/config', 'package.json', '.npmrc']) {
f.write(f.source, file, 'malicious')
assert.throws(() => copyDocsArtifact(f.source, f.destination), /Unexpected documentation artifact/)
assert.equal(readFileSync(path.join(f.destination, 'docs/removed.md'), 'utf8'), 'old')
rmSync(path.join(f.source, file))
}
})
test('rejects symlinks and incomplete artifacts before deleting published docs', t => {
const f = fixture(t)
const link = path.join(f.source, 'docs/link.md')
symlinkSync(path.join(f.destination, '.git/config'), link)
assert.throws(() => copyDocsArtifact(f.source, f.destination), /Unexpected documentation artifact/)
rmSync(link)
rmSync(path.join(f.source, 'sidebars.json'))
assert.throws(() => copyDocsArtifact(f.source, f.destination), /Missing documentation artifact/)
assert.equal(readFileSync(path.join(f.destination, 'docs/removed.md'), 'utf8'), 'old')
})
test('the workflow enables pushing only after creating a documentation commit', t => {
const f = fixture(t)
f.write(f.source, 'static/docs-assets/12.x/img/logo.svg', '<svg/>')
const workflow = readFileSync(new URL('../workflows/sync-docs.yml', import.meta.url), 'utf8')
const commitStep = workflow.split(' - name: Commit the generated documentation\n')[1].split(' - name: Push the verified documentation commit\n')[0]
assert.match(commitStep, / id: commit\n/)
assert.match(workflow, / - name: Push the verified documentation commit\n if: steps\.commit\.outputs\.changed == 'true'/)
const script = commitStep.split(' run: |\n')[1].trimEnd().split('\n').map(line => line.slice(10)).join('\n')
const git = (...args) => execFileSync('git', args, { cwd: f.source, encoding: 'utf8', stdio: 'pipe' }).trim()
git('init', '--quiet')
git('config', 'user.name', 'Test')
git('config', 'user.email', 'test@example.invalid')
git('config', 'commit.gpgsign', 'false')
git('add', '.')
git('commit', '--quiet', '-m', 'test: initial documentation')
const original = git('rev-parse', 'HEAD')
const output = path.join(f.source, 'workflow-output')
const run = () => execFileSync('bash', ['-e', '-c', script], {
cwd: f.source,
env: { ...process.env, GITHUB_OUTPUT: output, MAIN_SYNC: 'true', DOCS_COMMIT: original, LINE: '', VERSION: '', GH_TOKEN: '' },
stdio: 'pipe',
})
run()
assert.equal(git('rev-parse', 'HEAD'), original)
assert.equal(existsSync(output), false)
f.write(f.source, 'docs/index.md', 'updated')
run()
assert.notEqual(git('rev-parse', 'HEAD'), original)
assert.equal(readFileSync(output, 'utf8'), 'changed=true\n')
})