mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-17 10:01:20 -08:00
ci: [CI-01] add OS matrices, scoped caches and required result gate
This commit is contained in:
1 parent
2c73e2b105
commit
585e11c53c
15 files changed
+677
-32
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
export function ciContext(root, env = process.env) {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
|
||||
const node = fs.readFileSync(path.join(root, '.node-version'), 'utf8').trim()
|
||||
const yarn = manifest.packageManager?.replace(/^yarn@/, '')
|
||||
const playwright = manifest.devDependencies['@playwright/test']
|
||||
for (const version of [node, yarn, playwright]) assert(/^\d+\.\d+\.\d+$/.test(version), 'CI cache inputs must use exact versions')
|
||||
assert.equal(manifest.packageManager, `yarn@${yarn}`)
|
||||
const source = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim()
|
||||
const downloads = path.resolve(env.RUNNER_TEMP || path.join(root, 'refactor/.cache/downloads'))
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
source,
|
||||
workflowSource: env.GITHUB_SHA || null,
|
||||
ref: env.GITHUB_REF || null,
|
||||
event: env.GITHUB_EVENT_NAME || 'local',
|
||||
run: env.GITHUB_RUN_ID || null,
|
||||
attempt: env.GITHUB_RUN_ATTEMPT || null,
|
||||
runUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}` : null,
|
||||
os: process.platform,
|
||||
arch: process.arch,
|
||||
node,
|
||||
actualNode: process.versions.node,
|
||||
yarn,
|
||||
playwright,
|
||||
yarnCache: path.join(downloads, 'artplayer-yarn-cache'),
|
||||
browserCache: path.join(downloads, 'artplayer-playwright'),
|
||||
lockSha256: createHash('sha256').update(fs.readFileSync(path.join(root, 'yarn.lock'))).digest('hex'),
|
||||
}
|
||||
}
|
||||
|
||||
export function writeContext(root, env = process.env) {
|
||||
const context = ciContext(root, env)
|
||||
const directory = path.join(root, 'refactor/.cache/ci')
|
||||
fs.mkdirSync(directory, { recursive: true })
|
||||
fs.writeFileSync(path.join(directory, 'context.json'), `${JSON.stringify(context, null, 2)}\n`)
|
||||
for (const value of [context.yarnCache, context.browserCache]) assert(!/[\r\n]/.test(value), 'CI paths must fit one environment-file line')
|
||||
if (env.GITHUB_OUTPUT)
|
||||
fs.appendFileSync(env.GITHUB_OUTPUT, `node=${context.node}\nyarn=${context.yarn}\nplaywright=${context.playwright}\nyarn_cache=${context.yarnCache}\nbrowser_cache=${context.browserCache}\n`)
|
||||
if (env.GITHUB_ENV)
|
||||
fs.appendFileSync(env.GITHUB_ENV, `YARN_CACHE_FOLDER=${context.yarnCache}\nPLAYWRIGHT_BROWSERS_PATH=${context.browserCache}\n`)
|
||||
return context
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url))
|
||||
console.log(JSON.stringify(writeContext(process.cwd())))
|
||||
@@ -0,0 +1,65 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { ciContext } from './ci-context.mjs'
|
||||
|
||||
export const requiredJobs = ['checks', 'coverage', 'browser-smoke']
|
||||
|
||||
export function evaluateJobs(needs) {
|
||||
const object = needs !== null && typeof needs === 'object' && !Array.isArray(needs)
|
||||
const errors = []
|
||||
if (!object)
|
||||
errors.push('Job results must be an object')
|
||||
else if (Object.keys(needs).some(name => !requiredJobs.includes(name)))
|
||||
errors.push('Unexpected job entries; update the explicit required-job policy')
|
||||
const rows = requiredJobs.map((job) => {
|
||||
const value = object && Object.hasOwn(needs, job) ? needs[job]?.result : undefined
|
||||
const result = ['success', 'failure', 'cancelled', 'skipped'].includes(value) ? value : 'missing-or-invalid'
|
||||
return { job, result, passed: result === 'success' }
|
||||
})
|
||||
return { passed: errors.length === 0 && rows.every(row => row.passed), rows, errors }
|
||||
}
|
||||
|
||||
export function renderSummary(report) {
|
||||
return [
|
||||
'## ArtPlayer CI',
|
||||
'',
|
||||
`Result: **${report.passed ? 'passed' : 'failed'}**`,
|
||||
'',
|
||||
'| Required job | Result |',
|
||||
'| --- | --- |',
|
||||
...report.rows.map(row => `| ${row.job} | ${row.result} |`),
|
||||
'',
|
||||
'Success requires every configured matrix job to succeed. Skipped, cancelled and missing results do not pass.',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export function summarize(root, env = process.env) {
|
||||
let needs
|
||||
let invalidJSON = false
|
||||
try {
|
||||
needs = JSON.parse(env.ARTPLAYER_CI_NEEDS || '')
|
||||
}
|
||||
catch {
|
||||
invalidJSON = true
|
||||
}
|
||||
const report = { schemaVersion: 1, context: ciContext(root, env), ...evaluateJobs(needs) }
|
||||
if (invalidJSON)
|
||||
report.errors.push('Job results were not valid JSON')
|
||||
const directory = path.join(root, 'refactor/.cache/ci')
|
||||
fs.mkdirSync(directory, { recursive: true })
|
||||
fs.writeFileSync(path.join(directory, 'summary.json'), `${JSON.stringify(report, null, 2)}\n`)
|
||||
const markdown = renderSummary(report)
|
||||
fs.writeFileSync(path.join(directory, 'summary.md'), markdown)
|
||||
if (env.GITHUB_STEP_SUMMARY)
|
||||
fs.appendFileSync(env.GITHUB_STEP_SUMMARY, markdown)
|
||||
return report
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const report = summarize(process.cwd())
|
||||
console.log(renderSummary(report))
|
||||
process.exitCode = report.passed ? 0 : 1
|
||||
}
|
||||
Reference in new issue
Block a user