chore(repo): make the recovered production baseline portable

Record source and release ownership, keep source line endings consistent,
and exclude local build/config backups from version control.

Restore default Next type paths, normalize CI checks across line endings,
and use portable Playwright and postbuild entry points on Windows/Linux.
Align the stale timeout fixture with the existing production timeout policy.

Application runtime files remain identical to production-snapshot-48-20261030.
Production build and focused regressions passed; pre-existing full-repository
lint findings remain documented instead of relaxing lint rules.
This commit is contained in:
Codex 2026-10-30 16:19:08 +08:00
parent 03b8a0deb0
commit 9155682238
12 changed files with 227 additions and 42 deletions

19
.gitattributes vendored Normal file
View File

@ -0,0 +1,19 @@
# Keep source checkouts consistent across Windows and Linux.
.gitattributes text eol=lf
.gitignore text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.mjs text eol=lf
*.cjs text eol=lf
*.json text eol=lf
*.jsonc text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.css text eol=lf
*.html text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf

6
.gitignore vendored
View File

@ -13,6 +13,7 @@ coverage
.next/
.next-build/
.next-previous/
/.next-*/
out/
build
@ -67,6 +68,11 @@ migration-backup-complete/
migration-*.log
migration-*.txt
# Local release backups, model credentials and migration scratch data.
/.model-config-backups/
/backups/
/tmp/
# 本地抓包/竞品参考产物,不能提交
artifacts/liblib-*/
analysis/qiaodou-extract/

View File

@ -0,0 +1,62 @@
# 源码基线与发布约定
## 本次生产源码归档
2026 年 10 月 30 日完成只读核对:
- 归档前的 `main`:`e2a8f2dda3f190f78529b4419f295a356476c74b`。
- 原主线回退标签:`main-before-production-sync-20261030`。
- 生产源码分支:`codex/canvas-image-429-deploy-1001`。
- 生产源码提交:`439fd56fe120fdb9f855b50fa4df9ce2e71bb72a`。
- 实际运行构建 ID:`QhoygcZ4-x42PwCgCEj1d`。
- 生产目录另有 19 个已跟踪文件的修改和 6 个新增源码文件。
这些源码改动按原始内容归档在标签 `production-snapshot-48-20261030`,
25 个文件均经过 SHA256 校验。原始补丁和文件副本还保存在独立的本地备份中。
环境文件、模型配置备份和历史构建目录未加入源码归档。
源码归档标签表示核对时的服务器源码状态。实际运行的构建单独用构建 ID 标识,
不能仅凭服务器分支名或提交号认定某份构建已经部署。
接手分支 `codex/production-48-20261030` 在归档后只清理构建目录引用和仓库检查兼容性,
通过 PR 将已审查的生产源码同步回 `main`。本次源码同步不包含服务器部署或重启。
## 后续开发与合并
1. 从最新 `main` 创建任务分支,使用 `codex/<任务>` 命名。
2. 每次提交只暂存本次任务明确涉及的文件,检查暂存区差异和凭据。
3. 通过 PR 合入 `main`,记录验证结果;未通过或未执行的检查须写明原因。
4. 历史生产、试验和备份分支先保留。确认改动是否已收录后,再单独评审归档。
5. 服务器上的临时修复应及时取回源码、审查、提交并合入主线,避免再次形成服务器独有状态。
源码换行由 `.gitattributes` 固定为 LF,Windows 批处理文件保留 CRLF。
这样源码断言和检查在 Windows、Linux 上读取到相同内容,避免仅因换行差异误报。
## 构建与发布
- 从 `main` 的确定提交或对应发布标签构建,保留提交号与构建 ID 的对应关系。
- 默认构建目录为 `.next`;独立发布可设置 `NEXT_DIST_DIR`。
- `next-env.d.ts` 和 `tsconfig.json` 中不要提交某台服务器历史构建目录的引用。
- `.next-*`、模型配置备份、临时目录和备份文件留在运行环境,不纳入版本控制。
- 发布前保存可回滚版本,发布后核对实际运行构建、页面和必要业务流程。
- 合并源码与部署是两个操作;部署需要单独明确目标和发布范围。
`scripts/storage/` 下的迁移、审计脚本作为维护源码保存。
执行前须检查参数、目标存储和数据库配置;本次归档没有执行任何数据迁移。
## 验证入口
项目固定使用 pnpm 9.12.3,依赖按锁文件安装。
常规检查入口为 `pnpm check:branch`、`pnpm check:quality` 和 `pnpm build`,
运行环境与路由验收按 PR 模板记录。
`pnpm lint` 直接使用锁文件中的 Biome 版本执行现有检查,避免包装命令再次调用 `npx`。
历史 lint 或格式诊断应在 PR 中如实记录,不通过放宽规则或跳过检查掩盖。
Playwright 入口通过 Node 设置测试环境变量,辅助测试额外启用 `react-server` 模块条件,
可在 Windows 和 Linux 上使用同一条命令。
构建后的公共 source map 清理由 Node 完成,只处理工作区内当前构建目录的 `static` 文件。
原生 Agent 沙箱测试另外需要对应的运行时和隔离环境,应单独记录是否具备这些条件。
仓库工作流目前均为手动触发。若以后增加自动发布,须明确发布分支、目标环境、
审批和回滚方式,避免普通推送意外触发生产变更。
本次归档保留现有生产行为,历史质量问题和未执行的验收项目在合并 PR 中单独列明。

2
next-env.d.ts vendored
View File

@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next-agent-brain-icon-20261022/types/routes.d.ts";
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -20,9 +20,9 @@
"prebuild": "node scripts/security/auto-enable-perf-guard.mjs",
"build": "next build",
"build:with-migrate": "pnpm db:migrate && next build",
"postbuild": "find \"${NEXT_DIST_DIR:-.next}/static\" -type f -name '*.map' -delete 2>/dev/null || true",
"postbuild": "node scripts/postbuild-cleanup.mjs",
"start": "next start",
"lint": "pnpm exec ultracite check",
"lint": "pnpm exec biome check .",
"check:logs": "node scripts/check-log-hygiene.mjs && tsx scripts/check-log-redaction.ts",
"check:artifacts": "node scripts/check-tracked-artifacts.mjs",
"check:brand": "node scripts/check-brand-hygiene.mjs",
@ -32,9 +32,9 @@
"check:secrets": "node scripts/check-secrets.mjs",
"check:metadata": "node scripts/check-package-metadata.mjs",
"check:tests": "node scripts/check-test-hygiene.mjs",
"test:helpers": "PLAYWRIGHT=True pnpm exec playwright test --config=playwright.helpers.config.ts",
"test:helpers": "node --conditions=react-server scripts/run-playwright.mjs --config=playwright.helpers.config.ts",
"test:zcode-agent": "NODE_OPTIONS=--conditions=react-server tsx scripts/zcode-agent/task-fixture.ts && node scripts/zcode-agent/sandbox-fixture.mjs && node scripts/zcode-agent/native-fixture.mjs",
"test:canvas-agent": "PLAYWRIGHT=True pnpm exec playwright test --config=playwright.canvas-agent.config.ts",
"test:canvas-agent": "node scripts/run-playwright.mjs --config=playwright.canvas-agent.config.ts",
"check:quality": "pnpm lint && pnpm check:logs && pnpm check:brand && pnpm check:ci && pnpm check:env && pnpm check:secrets && pnpm check:metadata && pnpm check:tests && pnpm test:helpers && pnpm exec tsc --noEmit --pretty false && git diff --check && pnpm check:artifacts",
"audit:security": "pnpm audit --registry=https://registry.npmjs.org --audit-level high",
"audit:security:json": "pnpm audit --registry=https://registry.npmjs.org --audit-level high --json",
@ -54,7 +54,7 @@
"db:snapshot:system-settings": "pnpm exec tsx scripts/system-settings/create-snapshot.ts",
"db:restore:system-settings-snapshot": "pnpm exec tsx scripts/system-settings/restore-snapshot.ts",
"poc:nanobanana": "pnpm exec tsx scripts/nanobanana-browser-poc.ts",
"test": "export PLAYWRIGHT=True && pnpm exec playwright test",
"test": "node scripts/run-playwright.mjs",
"storage:configure-upload-cors": "pnpm exec tsx scripts/storage/configure-direct-upload-cors.ts",
"storage:configure-upload-lifecycle": "pnpm exec tsx scripts/storage/configure-multipart-lifecycle.ts",
"storage:repair-model3d-qiniu": "pnpm exec tsx scripts/storage/repair-model3d-qiniu-assets.ts",

View File

@ -26,7 +26,7 @@ const assertNotMatches = (file, source, pattern, reason) => {
};
for (const file of workflowFiles) {
const source = readFileSync(file, "utf8");
const source = readFileSync(file, "utf8").replace(/\r\n/g, "\n");
assertIncludes(file, source, "permissions:\n contents: read", "read-only workflow permissions");
assertIncludes(file, source, "concurrency:", "workflow concurrency");

View File

@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
const helpersConfig = readFileSync("playwright.helpers.config.ts", "utf8");
const testRunner = readFileSync("scripts/run-playwright.mjs", "utf8");
const violations = [];
const expectIncludes = (source, expected, reason) => {
@ -21,8 +22,9 @@ const expectNotIncludes = (source, forbidden, reason) => {
const testHelpersScript = packageJson.scripts?.["test:helpers"] ?? "";
const qualityScript = packageJson.scripts?.["check:quality"] ?? "";
expectIncludes(testHelpersScript, "PLAYWRIGHT=True", "Playwright test env flag");
expectIncludes(testHelpersScript, "playwright test --config=playwright.helpers.config.ts", "dedicated helper Playwright config");
expectIncludes(testRunner, 'PLAYWRIGHT: "True"', "Playwright test env flag");
expectIncludes(testHelpersScript, "node --conditions=react-server scripts/run-playwright.mjs", "cross-platform server-only helper runner");
expectIncludes(testHelpersScript, "--config=playwright.helpers.config.ts", "dedicated helper Playwright config");
expectIncludes(qualityScript, "pnpm test:helpers", "helper tests in quality gate");
expectIncludes(helpersConfig, 'testDir: "./tests"', "tests directory");

View File

@ -0,0 +1,43 @@
#!/usr/bin/env node
import { readdir, realpath, unlink } from "node:fs/promises";
import { isAbsolute, join, relative, resolve } from "node:path";
const workspace = await realpath(process.cwd());
const distDir = resolve(workspace, process.env.NEXT_DIST_DIR?.trim() || ".next");
function requireInsideWorkspace(path) {
const location = relative(workspace, path);
if (!location || location === ".." || location.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(location)) {
throw new Error("Source map cleanup requires a build directory inside the workspace.");
}
}
requireInsideWorkspace(distDir);
let staticDir;
try {
staticDir = await realpath(join(distDir, "static"));
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
let removed = 0;
async function clean(directory) {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory()) {
await clean(path);
} else if (entry.isFile() && entry.name.endsWith(".map")) {
await unlink(path);
removed++;
}
}
}
if (staticDir) {
requireInsideWorkspace(staticDir);
await clean(staticDir);
}
console.log(`Removed ${removed} public source map files.`);

View File

@ -0,0 +1,32 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const child = spawn(
process.execPath,
[
...process.execArgv,
require.resolve("@playwright/test/cli"),
"test",
...process.argv.slice(2),
],
{
env: { ...process.env, PLAYWRIGHT: "True" },
stdio: "inherit",
},
);
for (const signal of ["SIGINT", "SIGTERM"]) {
process.on(signal, () => child.kill(signal));
}
child.on("error", (error) => {
console.error(error.message);
process.exitCode = 1;
});
child.on("exit", (code) => {
process.exitCode = code ?? 1;
});

View File

@ -148,16 +148,16 @@ test.describe("VIP98K reference-image routing", () => {
test("loads bounded database runtime settings with a Pro timeout override", () => {
const defaults = normalizeVip98kRuntimeConfig(null);
expect(resolveVip98kSubmissionTimeoutMs("nano-banana-2", defaults)).toBe(
120_000,
300_000,
);
expect(resolveVip98kSubmissionTimeoutMs("nano-banana-pro", defaults)).toBe(
240_000,
300_000,
);
expect(defaults.referenceCacheTtlMs).toBe(7 * 24 * 60 * 60 * 1000);
const config = normalizeVip98kRuntimeConfig({
submissionTimeoutMs: 90_000,
modelSubmissionTimeoutMs: { "nano-banana-pro": 300_000 },
modelSubmissionTimeoutMs: { "nano-banana-pro": 420_000 },
maxSubmissionAttempts: 20,
referenceCacheTtlMs: 1,
});
@ -165,10 +165,10 @@ test.describe("VIP98K reference-image routing", () => {
expect(config.maxSubmissionAttempts).toBe(5);
expect(config.referenceCacheTtlMs).toBe(5 * 60_000);
expect(resolveVip98kSubmissionTimeoutMs("nano-banana-2", config)).toBe(
90_000,
300_000,
);
expect(resolveVip98kSubmissionTimeoutMs("NANO-BANANA-PRO", config)).toBe(
300_000,
420_000,
);
});

View File

@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { isAbsolute, join, relative, resolve } from "node:path";
import { test } from "node:test";
const workspace = process.cwd();
const script = resolve("scripts/postbuild-cleanup.mjs");
test("postbuild removes nested public maps and preserves runtime assets", async () => {
const qa = resolve(".qa");
await mkdir(qa, { recursive: true });
const directory = await mkdtemp(join(qa, "postbuild-cleanup-"));
const nested = join(directory, "static", "chunks");
try {
await mkdir(nested, { recursive: true });
await writeFile(join(nested, "runtime.js"), "runtime");
await writeFile(join(nested, "runtime.js.map"), "source map");
await writeFile(join(nested, "notes.txt"), "keep");
const result = spawnSync(process.execPath, [script], {
cwd: workspace,
env: { ...process.env, NEXT_DIST_DIR: relative(workspace, directory) },
encoding: "utf8",
});
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /Removed 1 public source map files/);
assert.equal(await readFile(join(nested, "runtime.js"), "utf8"), "runtime");
assert.equal(await readFile(join(nested, "notes.txt"), "utf8"), "keep");
await assert.rejects(readFile(join(nested, "runtime.js.map")), { code: "ENOENT" });
} finally {
const location = relative(qa, directory);
assert.ok(location.startsWith("postbuild-cleanup-") && !isAbsolute(location) && !location.includes(".."));
await rm(directory, { recursive: true, force: true });
}
});
test("postbuild refuses a build directory outside the workspace", () => {
const result = spawnSync(process.execPath, [script], {
cwd: workspace,
env: { ...process.env, NEXT_DIST_DIR: ".." },
encoding: "utf8",
});
assert.notEqual(result.status, 0);
assert.match(result.stderr, /inside the workspace/);
});

View File

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -23,9 +19,7 @@
}
],
"paths": {
"@/*": [
"./*"
]
"@/*": ["./*"]
},
"strictNullChecks": true
},
@ -35,25 +29,7 @@
"**/*.tsx",
".next/types/**/*.ts",
"next.config.js",
".next/dev/types/**/*.ts",
".next-build/types/**/*.ts",
".next-build/dev/types/**/*.ts",
".next-vip/types/**/*.ts",
".next-vip/dev/types/**/*.ts",
".next-build-hicad-20260728-135607/types/**/*.ts",
".next-build-hicad-20260728-135607/dev/types/**/*.ts",
".next-build-hicad-20260728-140320/types/**/*.ts",
".next-build-hicad-20260728-140320/dev/types/**/*.ts",
".next-candidate-home-banner-20260804-bg/types/**/*.ts",
".next-candidate-home-banner-20260804-bg/dev/types/**/*.ts",
".next-extend-vip98k-20261012-1791792190533/types/**/*.ts",
".next-extend-vip98k-20261012-1791792190533/dev/types/**/*.ts",
".next-psd-dimensions-e6dbb612-20261015-1792035218252/types/**/*.ts",
".next-psd-dimensions-e6dbb612-20261015-1792035218252/dev/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules",
"services/pascal-editor",
"backups"
]
"exclude": ["node_modules", "services/pascal-editor", "backups"]
}