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:
parent
03b8a0deb0
commit
9155682238
19
.gitattributes
vendored
Normal file
19
.gitattributes
vendored
Normal 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
6
.gitignore
vendored
@ -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/
|
||||
|
||||
62
docs/source-and-release-policy.md
Normal file
62
docs/source-and-release-policy.md
Normal 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
2
next-env.d.ts
vendored
@ -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.
|
||||
|
||||
10
package.json
10
package.json
@ -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",
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
43
scripts/postbuild-cleanup.mjs
Normal file
43
scripts/postbuild-cleanup.mjs
Normal 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.`);
|
||||
32
scripts/run-playwright.mjs
Normal file
32
scripts/run-playwright.mjs
Normal 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;
|
||||
});
|
||||
@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
45
tests/scripts/postbuild-cleanup.test.mjs
Normal file
45
tests/scripts/postbuild-cleanup.test.mjs
Normal 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/);
|
||||
});
|
||||
@ -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"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user