82 lines
3.5 KiB
JavaScript
82 lines
3.5 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readdir, readFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import test from 'node:test'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const repositoryRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)))
|
|
const scriptsReadme = path.join(repositoryRoot, 'scripts', 'README.md')
|
|
const excludedDirectoryNames = new Set([
|
|
'.git', '.logs', 'dist', 'node_modules', 'output', 'outputs', 'reports', 'target', 'uni_modules',
|
|
])
|
|
const allowedDirectoryPrefixes = [
|
|
'scripts/',
|
|
'deploy/scripts/',
|
|
'deploy/rocketmq/',
|
|
'firstleg/scripts/',
|
|
'lastleg-service/scripts/',
|
|
'warehouse/scripts/',
|
|
]
|
|
|
|
async function executableFiles(directory, extensions, { excludeTests = true } = {}) {
|
|
const entries = await readdir(path.join(repositoryRoot, directory), { withFileTypes: true })
|
|
return entries
|
|
.filter((entry) => entry.isFile())
|
|
.filter((entry) => extensions.some((extension) => entry.name.endsWith(extension)))
|
|
.filter((entry) => !excludeTests || !entry.name.endsWith('.test.mjs'))
|
|
.map((entry) => `${directory}/${entry.name}`.replaceAll('\\', '/'))
|
|
}
|
|
|
|
async function discoverScriptFiles(directory = repositoryRoot) {
|
|
const discovered = []
|
|
const entries = await readdir(directory, { withFileTypes: true })
|
|
for (const entry of entries) {
|
|
if (entry.isDirectory() && (
|
|
excludedDirectoryNames.has(entry.name)
|
|
|| entry.name.startsWith('node_modules.')
|
|
|| entry.name.startsWith('.codex-')
|
|
|| entry.name.startsWith('.runtime-')
|
|
)) continue
|
|
const absolute = path.join(directory, entry.name)
|
|
if (entry.isDirectory()) {
|
|
discovered.push(...await discoverScriptFiles(absolute))
|
|
continue
|
|
}
|
|
if (!['.mjs', '.ps1', '.sh', '.groovy'].some((extension) => entry.name.endsWith(extension))) continue
|
|
discovered.push(path.relative(repositoryRoot, absolute).replaceAll('\\', '/'))
|
|
}
|
|
return discovered
|
|
}
|
|
|
|
test('executable scripts only live in documented directories', async () => {
|
|
const files = await discoverScriptFiles()
|
|
const misplaced = files.filter((file) => !allowedDirectoryPrefixes.some((prefix) => file.startsWith(prefix)))
|
|
assert.deepEqual(misplaced, [], `以下脚本位于未登记目录:\n${misplaced.join('\n')}`)
|
|
})
|
|
|
|
test('all directly runnable scripts are documented in scripts/README.md', async () => {
|
|
const readme = await readFile(scriptsReadme, 'utf8')
|
|
const files = [
|
|
...await executableFiles('scripts', ['.mjs', '.ps1', '.groovy']),
|
|
...await executableFiles('scripts/openapi-validation', ['.mjs']),
|
|
...await executableFiles('deploy/scripts', ['.sh']),
|
|
...await executableFiles('deploy/rocketmq', ['.sh']),
|
|
...await executableFiles('firstleg/scripts', ['.mjs']),
|
|
...await executableFiles('lastleg-service/scripts', ['.mjs']),
|
|
]
|
|
|
|
const undocumented = files.filter((file) => {
|
|
const relativeToScripts = file.startsWith('scripts/') ? file.slice('scripts/'.length) : file
|
|
return !readme.includes(file) && !readme.includes(relativeToScripts)
|
|
})
|
|
|
|
assert.deepEqual(undocumented, [], `以下可执行脚本未登记用途与边界:\n${undocumented.join('\n')}`)
|
|
})
|
|
|
|
test('README explains internal modules and test scripts are not standalone entries', async () => {
|
|
const readme = await readFile(scriptsReadme, 'utf8')
|
|
assert.match(readme, /\*\.test\.mjs.*离线 Node 静态合同/)
|
|
assert.match(readme, /admin-test-env-modules\/\*\.mjs.*逐模块说明/)
|
|
assert.match(readme, /warehouse\/scripts\/warehouse-public-master-data-contract\.test\.mjs/)
|
|
})
|