- 建立 pnpm workspace + Turborepo 单仓、ESLint 9/Prettier/commitlint/husky 门禁 - NestJS + Fastify API:统一错误体、trace_id、Zod 管道、/health 真实依赖探活、OpenAPI 3.1 契约 - Prisma 基线:outbox_events/idempotency_keys/system_configs 与首版迁移、幂等种子 - shared 契约包:错误码、权限点、ULID、游标分页、命名/时间工具 - ui 包与 Web 壳层:Semi + Remix 图标统一出口、Cloud Blue 视觉、登录/工作台/错误页 - worker 骨架、Playwright 冒烟、Gitea CI、OpenAPI 快照与 oasdiff 回退脚本 - 补充 A0 实施报告与本地开发说明
This commit is contained in:
1 parent
9c3ab9768a
commit
04f49690f4
155 files changed
+18402
-28
No files matched your search
@@ -0,0 +1,10 @@
|
||||
const userAgent = process.env.npm_config_user_agent ?? '';
|
||||
|
||||
if (!userAgent.startsWith('pnpm/')) {
|
||||
console.error('');
|
||||
console.error('✗ 灵犀项目只允许使用 pnpm 10 安装依赖(见 AGENTS.md §3)。');
|
||||
console.error(' 请执行:corepack enable && pnpm install');
|
||||
console.error(' 或使用:npx pnpm@10.34.9 install');
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* OpenAPI 兼容性检查(A0 回退实现)。
|
||||
*
|
||||
* 优先使用 oasdiff 二进制;未安装时使用本脚本的结构化回退检查:
|
||||
* - 删除 path / operation
|
||||
* - 删除 components.schemas
|
||||
* - 请求体必填字段新增、字段类型变更
|
||||
*
|
||||
* 基线来自 git HEAD 中的快照;首次提交(快照尚未入库)时跳过。
|
||||
*/
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const SNAPSHOT = resolve(process.cwd(), 'openapi/lingxi.openapi.json');
|
||||
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
|
||||
|
||||
if (!existsSync(SNAPSHOT)) {
|
||||
console.error(`未找到 OpenAPI 快照:${SNAPSHOT},请先执行 pnpm openapi:export`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const current = JSON.parse(readFileSync(SNAPSHOT, 'utf8'));
|
||||
|
||||
let baseline = null;
|
||||
try {
|
||||
const raw = execFileSync('git', ['show', `HEAD:${'openapi/lingxi.openapi.json'}`], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
baseline = JSON.parse(raw);
|
||||
} catch {
|
||||
console.log('基线快照尚未入库,跳过 OpenAPI diff(首次提交)。');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const breaking = [];
|
||||
|
||||
for (const [path, baselinePath] of Object.entries(baseline.paths ?? {})) {
|
||||
const currentPath = current.paths?.[path];
|
||||
if (!currentPath) {
|
||||
breaking.push(`删除路径:${path}`);
|
||||
continue;
|
||||
}
|
||||
for (const method of HTTP_METHODS) {
|
||||
if (baselinePath[method] !== undefined && currentPath[method] === undefined) {
|
||||
breaking.push(`删除操作:${method.toUpperCase()} ${path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const schemaName of Object.keys(baseline.components?.schemas ?? {})) {
|
||||
if (current.components?.schemas?.[schemaName] === undefined) {
|
||||
breaking.push(`删除 schema:${schemaName}`);
|
||||
}
|
||||
}
|
||||
|
||||
const baselineSchemas = baseline.components?.schemas ?? {};
|
||||
const currentSchemas = current.components?.schemas ?? {};
|
||||
for (const [name, baselineSchema] of Object.entries(baselineSchemas)) {
|
||||
const currentSchema = currentSchemas[name];
|
||||
if (currentSchema === undefined) {
|
||||
continue;
|
||||
}
|
||||
const baselineRequired = new Set(baselineSchema.required ?? []);
|
||||
for (const required of currentSchema.required ?? []) {
|
||||
if (!baselineRequired.has(required)) {
|
||||
breaking.push(`schema ${name} 新增必填字段:${required}`);
|
||||
}
|
||||
}
|
||||
for (const [property, baselineProperty] of Object.entries(baselineSchema.properties ?? {})) {
|
||||
const currentProperty = currentSchema.properties?.[property];
|
||||
if (
|
||||
currentProperty !== undefined &&
|
||||
baselineProperty.type !== undefined &&
|
||||
currentProperty.type !== undefined &&
|
||||
baselineProperty.type !== currentProperty.type
|
||||
) {
|
||||
breaking.push(
|
||||
`schema ${name}.${property} 类型变更:${baselineProperty.type} -> ${currentProperty.type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (breaking.length > 0) {
|
||||
console.error('检测到破坏性 OpenAPI 变更:');
|
||||
for (const item of breaking) {
|
||||
console.error(` - ${item}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('OpenAPI 无破坏性变更。');
|
||||
Reference in new issue
Block a user