From 3470b124e408501ed824b993bcb34dd40e77c293 Mon Sep 17 00:00:00 2001 From: malei <> Date: Sat, 1 Aug 2026 23:30:32 +0800 Subject: [PATCH] chore: guard admin production builds --- .env.production | 2 ++ README.md | 11 +++++++ package.json | 3 +- scripts/release-preflight.cjs | 56 +++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 .env.production create mode 100644 scripts/release-preflight.cjs diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..cdaf01b --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +# 安全占位符:正式构建必须用 .env.production.local 覆盖,并先运行发布门禁。 +VITE_PUBLIC_H5_ORIGIN=https://report.petstore.invalid diff --git a/README.md b/README.md index 6df1703..6e75f1e 100644 --- a/README.md +++ b/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/**` diff --git a/package.json b/package.json index 953b272..246f25c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/release-preflight.cjs b/scripts/release-preflight.cjs new file mode 100644 index 0000000..d58c5f6 --- /dev/null +++ b/scripts/release-preflight.cjs @@ -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')