| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- #!/usr/bin/env node
- /**
- * scripts/config.mjs —— 部署前缀的**唯一来源**,并把同一份配置写进三个地方。
- *
- * 三处同源(pitfalls.md 第五条 / routing-best-practices.md 坑 4):
- * 1. <base href> ← index.html
- * 2. importmap 里的前缀 ← index.html(依赖 + 业务路径都在前缀下)
- * 3. OBS / vendor 上传前缀 ← deploy.sh 读同一个 prefix 文件
- *
- * 做法:先写 deploy/prefix/<env>.txt,再写 index.html。所有副本都由 prefix 派生,
- * 不存在「手工改了其中一处」的可能。check.mjs 会反向校验三者一致。
- *
- * 用法:node scripts/config.mjs <env> [--base <url>]
- * node scripts/config.mjs local # http://localhost:<port>/ (由 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 + '/';
- }
- /** 在 `<base>` 之后按前缀生成 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 `<!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
- <meta name="color-scheme" content="light" />
- <meta name="theme-color" content="#e9edf7" />
- <title>康师傅 · 产品创新看板</title>
- <meta name="description" content="康师傅研发中心 AI 赋能实战营 · 多人工作台 + 机会卡片池" />
- <!-- 部署前缀唯一来源:scripts/config.mjs(与 importmap 前缀、上传前缀同源) -->
- <base href="${prefix}" />
- <link rel="preload" href="${prefix}src/assets/inter-latin.woff2" as="font" type="font/woff2" crossorigin />
- <script type="importmap">
- ${importMapJson(prefix)}
- </script>
- <script>${dev ? 'window.__KF_DEV__=true;window.__KF_PARSE_BASE__="https://kangshifu.fmode.cn/parse";' : ''}</script>
- <script type="module" src="${prefix}src/app.js"></script>
- </head>
- <body>
- <kf-app></kf-app>
- <noscript><p style="padding:24px;font:16px system-ui">这个看板需要 JavaScript 才能读取数据。请在浏览器中启用 JavaScript 后刷新。</p></noscript>
- </body>
- </html>
- `;
- }
- /** 写出 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(/<base href="([^"]+)"/)?.[1];
- const map = JSON.parse(html.match(/<script type="importmap">([\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(/<script type="importmap">([\s\S]*?)<\/script>/)[1]).imports.lit;
- console.log(`✓ env=${env} prefix=${prefix}`);
- console.log(` base href = ${base}`);
- console.log(` importmap 前缀 = ${firstImport.replace(/lib\/vendor.*$/, '')}(${importCount} 条映射)`);
- console.log(` deploy/prefix/${env}.txt = ${stored}`);
- }
- if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) main();
|