75 行
2.5 KiB
JavaScript
75 行
2.5 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
import { spawnSync } from 'node:child_process'
|
||
|
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
||
|
|
import path from 'node:path'
|
||
|
|
import process from 'node:process'
|
||
|
|
|
||
|
|
const root = path.resolve(import.meta.dirname, '..')
|
||
|
|
const releaseId = 'cache-contract-test'
|
||
|
|
const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'
|
||
|
|
|
||
|
|
function run(args) {
|
||
|
|
const result = spawnSync(yarn, args, {
|
||
|
|
cwd: root,
|
||
|
|
env: { ...process.env, VITE_RELEASE_ID: releaseId },
|
||
|
|
stdio: 'inherit',
|
||
|
|
})
|
||
|
|
if (result.status !== 0) process.exit(result.status ?? 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
function files(directory, suffix) {
|
||
|
|
return readdirSync(directory, { withFileTypes: true }).flatMap(entry => {
|
||
|
|
const current = path.join(directory, entry.name)
|
||
|
|
if (entry.isDirectory()) return files(current, suffix)
|
||
|
|
return entry.name.endsWith(suffix) ? [current] : []
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
function requireText(file, values) {
|
||
|
|
const content = readFileSync(file, 'utf8')
|
||
|
|
for (const value of values) {
|
||
|
|
if (!content.includes(value)) {
|
||
|
|
throw new Error(`${path.relative(root, file)} 缺少缓存契约:${value}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function verifyBuild(output, publicPrefix) {
|
||
|
|
const assetDirectory = path.join(output, 'releases', releaseId, 'assets')
|
||
|
|
if (!existsSync(assetDirectory) || !statSync(assetDirectory).isDirectory()) {
|
||
|
|
throw new Error(`${path.relative(root, output)} 未生成版本化 assets 目录`)
|
||
|
|
}
|
||
|
|
const html = files(output, '.html').map(file => readFileSync(file, 'utf8')).join('\n')
|
||
|
|
const expected = `${publicPrefix}releases/${releaseId}/assets/`
|
||
|
|
if (!html.includes(expected)) {
|
||
|
|
throw new Error(`${path.relative(root, output)} 的 HTML 未引用 ${expected}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
run(['workspace', 'tenant-platform', 'build'])
|
||
|
|
run(['workspace', 'ops-platform', 'build'])
|
||
|
|
run(['workspace', '@xuqm/docs-site', 'build'])
|
||
|
|
|
||
|
|
verifyBuild(path.join(root, 'tenant-platform', 'dist'), '/')
|
||
|
|
verifyBuild(path.join(root, 'ops-platform', 'dist'), '/')
|
||
|
|
verifyBuild(path.join(root, 'docs-site', 'docs', '.vitepress', 'dist'), '/docs/')
|
||
|
|
|
||
|
|
for (const nginx of ['nginx/tenant.conf', 'nginx/ops.conf']) {
|
||
|
|
requireText(path.join(root, nginx), [
|
||
|
|
'no-cache, no-store, must-revalidate',
|
||
|
|
'public, max-age=31536000, immutable',
|
||
|
|
'try_files $uri =404',
|
||
|
|
])
|
||
|
|
}
|
||
|
|
for (const dockerfile of ['Dockerfile.tenant', 'Dockerfile.ops']) {
|
||
|
|
requireText(path.join(root, dockerfile), [
|
||
|
|
'ARG PREVIOUS_IMAGE=',
|
||
|
|
'/etc/xuqm_asset_layout_v2',
|
||
|
|
'COPY --from=previous',
|
||
|
|
'VITE_RELEASE_ID=${SERVICE_VERSION}',
|
||
|
|
])
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('Web 缓存与上一版本资源保留契约验证通过')
|