#!/usr/bin/env node /** * scripts/config.mjs —— 部署前缀的**唯一来源**,并把同一份配置写进三个地方。 * * 三处同源(pitfalls.md 第五条 / routing-best-practices.md 坑 4): * 1. ← index.html * 2. importmap 里的前缀 ← index.html(依赖 + 业务路径都在前缀下) * 3. OBS / vendor 上传前缀 ← deploy.sh 读同一个 prefix 文件 * * 做法:先写 deploy/prefix/.txt,再写 index.html。所有副本都由 prefix 派生, * 不存在「手工改了其中一处」的可能。check.mjs 会反向校验三者一致。 * * 用法:node scripts/config.mjs [--base ] * node scripts/config.mjs local # http://localhost:/ (由 serve.mjs 读取) * node scripts/config.mjs dev # /kangshifu/dev/ → https://kangshifu.fmode.cn/kangshifu/dev/ * node scripts/config.mjs prod # /kangshifu/ → https://kangshifu.fmode.cn/kangshifu/ * node scripts/config.mjs --prefix /a/b/ # 自定义前缀(必须以 / 开头、/ 结尾) * * 前缀可以是**完整 URL**(如 https://cdn.example.com/x/env/proj/):这时所有模块都以绝对 URL 加载, * 与页面所在的位置无关 —— 本地用 http.server 按同一目录结构起服务,就能验证「子路径部署」而不改任何源码。 */ import { writeFileSync, mkdirSync, readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; export const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const PREFIX_DIR = join(ROOT, 'deploy/prefix'); /** 环境 → 部署前缀。改部署位置只改这里。 */ export const ENVS = { local: '/', // 本地验证「子路径部署」用:同一套静态文件挂在二级路径下 'local-sub': '/kangshifu/dev/inno-board/', dev: '/kangshifu/dev/', prod: '/kangshifu/', }; export function normalizePrefix(p) { if (!p) throw new Error('prefix 不能为空(根部署用 "/")'); const s = String(p); if (/^https?:\/\//.test(s)) return s.endsWith('/') ? s : s + '/'; if (!s.startsWith('/')) throw new Error(`prefix 必须以 / 开头:${s}`); return s.endsWith('/') ? s : s + '/'; } /** 在 `` 之后按前缀生成 importmap 原文。依赖与业务都挂在前缀下(同源,无 CORS)。 */ export function importMapJson(prefix) { const j = (p) => JSON.stringify(prefix + p); return `{ "imports": { "lit": ${j('lib/vendor/lit/v3.3.3/index.js')}, "lit/": ${j('lib/vendor/lit/v3.3.3/')}, "lit-html": ${j('lib/vendor/lit-html/v3.3.3/lit-html.js')}, "lit-html/": ${j('lib/vendor/lit-html/v3.3.3/')}, "lit-element": ${j('lib/vendor/lit-element/v4.2.2/index.js')}, "lit-element/": ${j('lib/vendor/lit-element/v4.2.2/')}, "@lit/reactive-element": ${j('lib/vendor/@lit/reactive-element/v2.1.2/reactive-element.js')}, "@lit/reactive-element/": ${j('lib/vendor/@lit/reactive-element/v2.1.2/')}, "@lit/context": ${j('lib/vendor/@lit/context/v1.1.6/index.js')}, "@lit/context/": ${j('lib/vendor/@lit/context/v1.1.6/')}, "@lit-labs/signals": ${j('lib/vendor/@lit-labs/signals/v0.3.0/index.js')}, "@lit-labs/signals/": ${j('lib/vendor/@lit-labs/signals/v0.3.0/')}, "signal-polyfill": ${j('lib/vendor/signal-polyfill/v0.2.2/dist/index.js')}, "signal-polyfill/": ${j('lib/vendor/signal-polyfill/v0.2.2/dist/')} } }`; } /** index.html 原文:base href、importmap、模块入口全部由 prefix 派生。 */ export function indexHtml(prefix, { dev = false } = {}) { return ` 康师傅 · 产品创新看板 `; } /** 写出 index.html + 前缀文件,并自检三处同源。部署脚本与 verify 共用这一个入口。 */ export function writeConfig({ env = 'local', prefix: override = null } = {}) { let prefix; if (override) { prefix = normalizePrefix(override); env = env || 'custom'; } else { env = env || 'local'; if (!ENVS[env]) throw new Error(`未知环境 ${env};可选:${Object.keys(ENVS).join(' / ')},或 --prefix <路径>`); prefix = ENVS[env]; } const dev = env === 'local' || env === 'local-sub'; writeFileSync(join(ROOT, 'index.html'), indexHtml(prefix, { dev })); mkdirSync(PREFIX_DIR, { recursive: true }); writeFileSync(join(PREFIX_DIR, `${env}.txt`), prefix + '\n'); // 自检:写完后反向读取,三处必须同源 const html = readFileSync(join(ROOT, 'index.html'), 'utf8'); const base = html.match(/([\s\S]*?)<\/script>/)[1]); const stored = readFileSync(join(PREFIX_DIR, `${env}.txt`), 'utf8').trim(); const ok = base === prefix && stored === prefix && Object.values(map.imports).every((v) => v.startsWith(prefix)) && html.includes(`src="${prefix}src/app.js"`); if (!ok) throw new Error(`三处不同源:base=${base} stored=${stored} prefix=${prefix}`); return { env, prefix, base, stored, importCount: Object.keys(map.imports).length }; } function main() { const args = process.argv.slice(2); const envArg = args.find((a) => !a.startsWith('--')); const override = args.includes('--prefix') ? args[args.indexOf('--prefix') + 1] : null; const { env, prefix, base, stored, importCount } = writeConfig({ env: envArg, prefix: override }); const firstImport = JSON.parse(readFileSync(join(ROOT, 'index.html'), 'utf8') .match(/