Ctrl K
ComunidadGuías…

private-deploy/deliver-public-inputs.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * 私有集成环节:把公共框架的根构建元数据逐字节交付到本仓库的 `tools/builtin-build/`。
 *
 * 交付纪律(设计 2.8):三个文件(package.json / pnpm-workspace.yaml / pnpm-lock.yaml)必须来自
 * **同一份公共框架输入**——一个公共检出或解包后的公共发行目录;这里不读私有 workspace、不生成、
 * 不裁剪。源码入口在站点侧按字节使用交付物:材料缺失、版本不匹配或含私有 workspace 痕迹时直接失败。
 *
 * 用法(在私有集成工作区、合并上游之后执行):
 *   node private-deploy/deliver-public-inputs.mjs --source <公共检出或发行目录> [--allow-dirty]
 *
 * 交付物:`tools/builtin-build/{package.json,pnpm-workspace.yaml,pnpm-lock.yaml}` 与一份
 * `input.json`(框架版本、来源提交、三个文件的摘要、换行规则)。三件套按 Git 规范形式(LF)落盘并
 * 记录 LF 摘要:Windows 检出的工作区是 CRLF,按工作区字节记摘要会在提交或换机器后不一致。集成提交
 * 后,服务器按 Gitee 集成版本取得这些文件,不需要访问任何本地公共仓库。
 */
import { execFileSync } from 'node:child_process';
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { publicInputFiles, PUBLIC_INPUT_NEWLINE, normalizeNewlines, verifyPublicInputRecord, writePublicInputRecord } from '../packages/plugin-manager/src/public-build-view.mjs';

export const INPUT_DIRECTORY = 'tools/builtin-build';
/** 交付的换行规则:三件套按 Git 规范形式(LF)落盘并记录摘要(与站点侧共用同一实现)。 */
export const NEWLINE = PUBLIC_INPUT_NEWLINE;

const readJson = path => JSON.parse(readFileSync(path, 'utf8'));

/** 公共输入里的元数据目录与版本来源:公共检出用根文件,解包发行目录用 source/ 与 tools/builtin-build/。 */
export function locatePublicInput(source) {
  const bundled = resolve(source, INPUT_DIRECTORY);
  const directory = existsSync(resolve(bundled, 'package.json')) ? bundled : source;
  const manifestPath = existsSync(resolve(directory, 'package.json')) ? resolve(directory, 'package.json') : resolve(source, 'source/package.json');
  if (!existsSync(manifestPath)) throw new Error(`公共输入缺少 package.json:${source};请指向公共检出或解包后的公共发行目录。`);
  for (const name of publicInputFiles) if (!existsSync(resolve(directory, name))) throw new Error(`公共输入不完整:${resolve(directory, name)}`);
  return { directory, manifestPath };
}

/** 公共输入必须是公共框架材料:私有集成树(含 plugins/external)不是那一份输入。 */
function assertPublicSource(source) {
  if (existsSync(resolve(source, 'plugins/external'))) throw new Error(`公共输入含 plugins/external,不是公共框架输入:${source};请指向公共检出或解包后的公共发行目录。`);
  if (!statSync(source).isDirectory()) throw new Error(`公共输入必须是目录:${source}`);
}

/** 来源提交与「交付的字节是否与提交一致」:检出可用 Git 时记录,发行目录只记录摘要。 */
function describeSource(source, directory, files) {
  if (!existsSync(resolve(source, '.git'))) return { sourceKind: 'release', sourceCommit: null, sourceModified: null };
  const git = (...args) => execFileSync('git', args, { cwd: source, encoding: 'utf8', windowsHide: true }).trim();
  const commit = git('rev-parse', 'HEAD');
  const modified = Object.keys(files).filter(name => {
    const relative = resolve(directory, name).slice(resolve(source).length + 1).split('\\').join('/');
    return git('status', '--porcelain', '--', relative) !== '';
  });
  return { sourceKind: 'checkout', sourceCommit: commit, sourceModified: modified };
}

export function deliverPublicInputs({ root, source, allowDirty = false } = {}) {
  if (!root || !source) throw new Error('用法:node private-deploy/deliver-public-inputs.mjs --source <公共检出或发行目录> [--allow-dirty]');
  root = resolve(root);
  source = resolve(source);
  if (!existsSync(source)) throw new Error(`公共输入不存在:${source}`);
  assertPublicSource(source);
  const { directory, manifestPath } = locatePublicInput(source);
  const expected = readJson(resolve(root, 'package.json')).version;
  const delivered = readJson(manifestPath).version;
  if (typeof delivered !== 'string' || delivered !== expected) throw new Error(`公共输入版本(${delivered ?? '(缺失)'})与本仓库框架版本(${expected})不一致;先完成公共框架集成再交付。`);
  const files = Object.fromEntries(publicInputFiles.map(name => [name, normalizeNewlines(readFileSync(resolve(directory, name)))]));
  const description = describeSource(source, directory, files);
  if (description.sourceModified?.length && !allowDirty) {
    throw new Error(`公共输入的这些文件与 ${description.sourceCommit?.slice(0, 12)} 的记录不同(工作区已修改):${description.sourceModified.join(', ')};请在公共框架提交后再交付,或显式加 --allow-dirty 并知悉这一差异。`);
  }
  const target = resolve(root, INPUT_DIRECTORY);
  mkdirSync(target, { recursive: true });
  for (const [name, bytes] of Object.entries(files)) {
    // 不用 cpSync 覆盖:Windows 上覆盖已存在文件遇到非 ASCII 路径会失败(libuv 扩展路径怪癖)。
    writeFileSync(resolve(target, name), bytes);
  }
  // 记录格式只有一份实现(公共模块的 writePublicInputRecord):发行包与集成交付写的必须是同一种记录。
  return writePublicInputRecord(target, { version: expected, sourceKind: description.sourceKind, sourceCommit: description.sourceCommit,
    sourceModified: description.sourceModified, deliveredAt: new Date().toISOString() });
}

/**
 * 私有门禁的入口:复用公共模块里的交付摘要校验(站点侧同一实现),只多一句「怎么补交付」的提示。
 */
export function verifyDeliveredInputs(root) {
  root = resolve(root);
  try { return verifyPublicInputRecord(root, resolve(root, INPUT_DIRECTORY)); }
  catch (error) {
    if (/缺少交付记录/u.test(error.message)) throw new Error(`${error.message};运行 node private-deploy/deliver-public-inputs.mjs --source <公共检出或发行目录>`);
    throw error;
  }
}

export function main(args = process.argv.slice(2)) {
  const options = { allowDirty: false };
  for (let index = 0; index < args.length; index += 1) {
    if (args[index] === '--allow-dirty') { options.allowDirty = true; continue; }
    if (['--root', '--source'].includes(args[index]) && args[index + 1] && !args[index + 1].startsWith('--')) { options[args[index].slice(2)] = args[++index]; continue; }
    throw new Error(`未知参数:${args[index]}。用法:node private-deploy/deliver-public-inputs.mjs --source <公共检出或发行目录> [--root <仓库根>] [--allow-dirty]`);
  }
  const root = options.root ? resolve(options.root) : fileURLToPath(new URL('../', import.meta.url));
  const record = deliverPublicInputs({ root, source: options.source, allowDirty: options.allowDirty });
  console.log(JSON.stringify(record, null, 2));
  if (record.sourceModified?.length) console.warn(`注意:交付的元数据与提交 ${record.sourceCommit?.slice(0, 12)} 的记录不同(${record.sourceModified.join(', ')});已记入 ${INPUT_DIRECTORY}/input.json。`);
  console.log(`已交付公开构建元数据:${resolve(root, INPUT_DIRECTORY)};随集成提交后,服务器无需访问公共仓库。`);
}

if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  try { main(); } catch (error) { console.error(error.message); process.exitCode = 1; }
}