chore: guard admin production builds
This commit is contained in:
parent
7e924fa79d
commit
3470b124e4
2
.env.production
Normal file
2
.env.production
Normal file
@ -0,0 +1,2 @@
|
||||
# 安全占位符:正式构建必须用 .env.production.local 覆盖,并先运行发布门禁。
|
||||
VITE_PUBLIC_H5_ORIGIN=https://report.petstore.invalid
|
||||
11
README.md
11
README.md
@ -19,9 +19,20 @@ npm run dev
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
npm run preflight:release
|
||||
npm run build
|
||||
```
|
||||
|
||||
仓库内 `.env.production` 使用 `.invalid` 安全占位符,默认门禁会失败。正式发布必须使用权限受控的域名文件:
|
||||
|
||||
```bash
|
||||
PETSTORE_ADMIN_ENV_FILE=/secure/path/petstore-admin.env npm run preflight:release
|
||||
cp /secure/path/petstore-admin.env .env.production.local
|
||||
npm run build
|
||||
```
|
||||
|
||||
`VITE_PUBLIC_H5_ORIGIN` 必须是本项目公开报告页的显式 HTTPS 源,不能是 localhost、`.invalid` 或已归属其他产品的 `www.s-good.com`。真实配置不得提交,发布后删除 `.env.production.local`。
|
||||
|
||||
## Owner
|
||||
|
||||
- Store Admin FE → `admin/**`
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"preflight:release": "node scripts/release-preflight.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
|
||||
56
scripts/release-preflight.cjs
Normal file
56
scripts/release-preflight.cjs
Normal file
@ -0,0 +1,56 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const envFile = process.env.PETSTORE_ADMIN_ENV_FILE || '.env.production'
|
||||
const absoluteEnvFile = path.resolve(process.cwd(), envFile)
|
||||
|
||||
function fail(message) {
|
||||
console.error(`[release-preflight] FAIL: ${message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(absoluteEnvFile)) {
|
||||
fail('environment file does not exist')
|
||||
}
|
||||
|
||||
const values = {}
|
||||
for (const rawLine of fs.readFileSync(absoluteEnvFile, 'utf8').split(/\r?\n/)) {
|
||||
const line = rawLine.trim()
|
||||
if (!line || line.startsWith('#')) continue
|
||||
const separator = line.indexOf('=')
|
||||
if (separator < 1) continue
|
||||
const key = line.slice(0, separator).trim().replace(/^export\s+/, '')
|
||||
const value = line.slice(separator + 1).trim().replace(/^(['"])(.*)\1$/, '$2')
|
||||
values[key] = value
|
||||
}
|
||||
|
||||
const name = 'VITE_PUBLIC_H5_ORIGIN'
|
||||
const value = values[name]
|
||||
if (!value) fail(`${name} is required`)
|
||||
|
||||
let url
|
||||
try {
|
||||
url = new URL(value)
|
||||
} catch {
|
||||
fail(`${name} must be a valid URL`)
|
||||
}
|
||||
|
||||
const hostname = url.hostname.toLowerCase()
|
||||
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
|
||||
const isPlaceholder = hostname.endsWith('.invalid')
|
||||
|| hostname.endsWith('.test')
|
||||
|| hostname.endsWith('.example')
|
||||
|| ['example.com', 'example.net', 'example.org'].some(
|
||||
(reserved) => hostname === reserved || hostname.endsWith(`.${reserved}`),
|
||||
)
|
||||
const hasExtraParts = Boolean(url.username || url.password || url.search || url.hash)
|
||||
|| (url.pathname !== '' && url.pathname !== '/')
|
||||
if (url.protocol !== 'https:' || isLocal || isPlaceholder || hasExtraParts) {
|
||||
fail(`${name} must be an explicit production HTTPS origin`)
|
||||
}
|
||||
if (hostname === 'www.s-good.com') {
|
||||
fail(`${name} uses a domain reserved by another product`)
|
||||
}
|
||||
|
||||
console.log(`[release-preflight] ${name}: PASS`)
|
||||
console.log('[release-preflight] admin release configuration PASS')
|
||||
Loading…
Reference in New Issue
Block a user