正式构建拒绝源码非规范换行并补充回归门禁
This commit is contained in:
parent
70f19d09ff
commit
9e89edae28
@ -60,6 +60,8 @@ powershell -ExecutionPolicy Bypass -File .\build\build-release.ps1 -OutputDirect
|
|||||||
|
|
||||||
## 固定工具链与安装策略
|
## 固定工具链与安装策略
|
||||||
|
|
||||||
|
正式入口在安装依赖前以及构建结束时检查后端、前端受跟踪文件的实际换行,要求文本采用 LF,二进制和无换行文件不受影响。Git 干净状态可能忽略 CRLF/LF 差异,不能单独证明构建输入一致;发现 CRLF 或混合换行即拒绝发布,不自动改写源码。遇到此提示请从当前已提交版本重新克隆后构建,不要使用终端批量替换中文文件。检查结果及文件数量写入 manifest 的 build_environment.source_line_endings。
|
||||||
|
|
||||||
`toolchain.mjs` 是正式发布入口调用的构建检查器,`toolchain.test.mjs` 是纳入前端测试命令的正式回归,不是临时验收脚本。
|
`toolchain.mjs` 是正式发布入口调用的构建检查器,`toolchain.test.mjs` 是纳入前端测试命令的正式回归,不是临时验收脚本。
|
||||||
|
|
||||||
1. Go 精确版本以 `backend/go.mod` 为唯一来源;实际工具链不符则失败。`go.sum`、`go.mod` 固定 Go 依赖和数据库驱动,构建及归档使用 `-mod=readonly`;禁用外部 Go 工作区及 `GOFLAGS` 覆盖。
|
1. Go 精确版本以 `backend/go.mod` 为唯一来源;实际工具链不符则失败。`go.sum`、`go.mod` 固定 Go 依赖和数据库驱动,构建及归档使用 `-mod=readonly`;禁用外部 Go 工作区及 `GOFLAGS` 覆盖。
|
||||||
|
|||||||
@ -119,6 +119,29 @@ export function inputHashes(backend, frontend) {
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateSourceLineEndings(output) {
|
||||||
|
const records = output.split('\0');
|
||||||
|
requireCondition(records.pop() === '', '源码换行检查输出不完整。');
|
||||||
|
requireCondition(records.length > 0, '源码目录没有受跟踪文件,不能确认构建输入。');
|
||||||
|
const invalid = [];
|
||||||
|
for (const record of records) {
|
||||||
|
const match = /^i\/\S+\s+w\/(\S+)\s+attr\/[^\t]*\t([\s\S]+)$/.exec(record);
|
||||||
|
requireCondition(match, '无法解析源码换行检查结果。');
|
||||||
|
const [, ending, path] = match;
|
||||||
|
requireCondition(['lf', 'crlf', 'mixed', 'none', '-text'].includes(ending), '源码换行检查返回未知文件状态。');
|
||||||
|
if (ending === 'crlf' || ending === 'mixed') invalid.push(path);
|
||||||
|
}
|
||||||
|
requireCondition(invalid.length === 0,
|
||||||
|
`源码文件存在非规范换行,拒绝构建:${invalid.slice(0, 5).join('、')}。请从当前提交重新克隆后构建;工具不会改写源文件。`);
|
||||||
|
return { policy: 'lf', tracked_files: records.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inspectSourceLineEndings(directory) {
|
||||||
|
// Git 干净状态会忽略 CRLF/LF 差异;必须读取工作树的实际换行状态。
|
||||||
|
const result = execute('git', ['ls-files', '--eol', '-z', '--', '.'], directory);
|
||||||
|
return validateSourceLineEndings(result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
export function installDependencies(frontend) {
|
export function installDependencies(frontend) {
|
||||||
// 不信任使用者的 npm 全局/用户设置,命令行参数显式覆盖;保留构建所需的可选平台包。
|
// 不信任使用者的 npm 全局/用户设置,命令行参数显式覆盖;保留构建所需的可选平台包。
|
||||||
const result = execute(npmCommand, [
|
const result = execute(npmCommand, [
|
||||||
@ -132,6 +155,10 @@ export function installDependencies(frontend) {
|
|||||||
export function inspectToolchain(backend, frontend, install = false) {
|
export function inspectToolchain(backend, frontend, install = false) {
|
||||||
const host = { platform: platform(), arch: arch(), release: release(), version: version() };
|
const host = { platform: platform(), arch: arch(), release: release(), version: version() };
|
||||||
validateEnvironment(process.env, host);
|
validateEnvironment(process.env, host);
|
||||||
|
const sourceLineEndings = {
|
||||||
|
backend: inspectSourceLineEndings(backend),
|
||||||
|
frontend: inspectSourceLineEndings(frontend),
|
||||||
|
};
|
||||||
const pkg = JSON.parse(readFileSync(join(frontend, 'package.json'), 'utf8'));
|
const pkg = JSON.parse(readFileSync(join(frontend, 'package.json'), 'utf8'));
|
||||||
const lock = JSON.parse(readFileSync(join(frontend, 'package-lock.json'), 'utf8'));
|
const lock = JSON.parse(readFileSync(join(frontend, 'package-lock.json'), 'utf8'));
|
||||||
const npmrc = readFileSync(join(frontend, '.npmrc'), 'utf8');
|
const npmrc = readFileSync(join(frontend, '.npmrc'), 'utf8');
|
||||||
@ -153,6 +180,7 @@ export function inspectToolchain(backend, frontend, install = false) {
|
|||||||
dependency_scripts: 'disabled',
|
dependency_scripts: 'disabled',
|
||||||
dependency_install: install ? 'npm-ci' : 'not-run',
|
dependency_install: install ? 'npm-ci' : 'not-run',
|
||||||
input_sha256: hashes,
|
input_sha256: hashes,
|
||||||
|
source_line_endings: sourceLineEndings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,71 @@ import { tmpdir } from 'node:os';
|
|||||||
import { dirname, join, resolve } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { installDependencies, npmEnvironment, validateEnvironment, validateFrontend, validateGoVersion } from './toolchain.mjs';
|
import { inspectSourceLineEndings, installDependencies, npmEnvironment, validateEnvironment, validateFrontend, validateGoVersion, validateSourceLineEndings } from './toolchain.mjs';
|
||||||
|
|
||||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
|
|
||||||
|
test('源码换行检查接受规范文本、无换行文件及二进制', () => {
|
||||||
|
const records = [
|
||||||
|
'i/lf w/lf attr/text=auto eol=lf \t页面 空格.vue',
|
||||||
|
'i/none w/none attr/text=auto eol=lf \tempty.js',
|
||||||
|
'i/-text w/-text attr/text=auto eol=lf \timage.png',
|
||||||
|
'i/lf w/lf attr/text=auto eol=lf \t换行\n文件.js',
|
||||||
|
].join('\0') + '\0';
|
||||||
|
assert.deepEqual(validateSourceLineEndings(records), { policy: 'lf', tracked_files: 4 });
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const ending of ['crlf', 'mixed']) {
|
||||||
|
test(`源码换行检查拒绝 ${ending} 而不是自动修改文件`, () => {
|
||||||
|
assert.throws(() => validateSourceLineEndings(`i/lf w/${ending} attr/text=auto eol=lf \t页面.vue\0`), /非规范换行.*页面.vue/);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('源码换行检查拒绝空、截断、格式错误或未知状态', () => {
|
||||||
|
for (const output of ['', '\0', 'i/lf w/lf attr/text\tapp.js', 'invalid\0', 'i/lf w/unknown attr/text\tapp.js\0']) {
|
||||||
|
assert.throws(() => validateSourceLineEndings(output));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('实际 Git 干净差异仍可检测工作树换行,且不改写源文件', (t) => {
|
||||||
|
const directory = mkdtempSync(join(tmpdir(), 'scert-source-endings-'));
|
||||||
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
||||||
|
const run = (...args) => {
|
||||||
|
const result = spawnSync('git', args, { cwd: directory, encoding: 'utf8', windowsHide: true });
|
||||||
|
assert.equal(result.status, 0, `${result.error ?? ''}\n${result.stderr}`);
|
||||||
|
return result.stdout;
|
||||||
|
};
|
||||||
|
run('init', '--quiet');
|
||||||
|
writeFileSync(join(directory, '.gitattributes'), '* text=auto eol=lf\n');
|
||||||
|
const source = join(directory, '页面.vue');
|
||||||
|
writeFileSync(source, '<template>中文页面</template>\n');
|
||||||
|
writeFileSync(join(directory, 'image.png'), Buffer.from([0, 13, 10, 255]));
|
||||||
|
run('add', '--', '.gitattributes', '页面.vue', 'image.png');
|
||||||
|
assert.equal(inspectSourceLineEndings(directory).tracked_files, 3);
|
||||||
|
const changed = Buffer.from('<template>中文页面</template>\r\n');
|
||||||
|
writeFileSync(source, changed);
|
||||||
|
assert.equal(run('diff', '--exit-code'), '');
|
||||||
|
assert.throws(() => inspectSourceLineEndings(directory), /非规范换行/);
|
||||||
|
assert.deepEqual(readFileSync(source), changed);
|
||||||
|
const result = spawnSync(process.execPath, [join(root, 'build', 'toolchain.mjs'),
|
||||||
|
'--backend', directory, '--frontend', directory, '--install'], {
|
||||||
|
cwd: directory, encoding: 'utf8', windowsHide: true,
|
||||||
|
});
|
||||||
|
assert.equal(result.status, 1);
|
||||||
|
assert.match(result.stderr, /非规范换行/);
|
||||||
|
assert.equal(result.stdout, '');
|
||||||
|
assert.ok(!existsSync(join(directory, 'node_modules')));
|
||||||
|
assert.ok(!existsSync(join(directory, 'package-lock.json')));
|
||||||
|
assert.deepEqual(readFileSync(source), changed);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('非 Git 目录不能跳过源码换行检查', (t) => {
|
||||||
|
const directory = mkdtempSync(join(tmpdir(), 'scert-source-no-git-'));
|
||||||
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
||||||
|
assert.throws(() => inspectSourceLineEndings(directory), /git 执行失败/);
|
||||||
|
});
|
||||||
|
|
||||||
function fixture() {
|
function fixture() {
|
||||||
const frontend = join(root, 'frontend');
|
const frontend = join(root, 'frontend');
|
||||||
const pkg = JSON.parse(readFileSync(join(frontend, 'package.json'), 'utf8'));
|
const pkg = JSON.parse(readFileSync(join(frontend, 'package.json'), 'utf8'));
|
||||||
|
|||||||
@ -1,4 +1,13 @@
|
|||||||
# Linux 生产流程测试记录
|
# Linux 生产流程测试记录
|
||||||
|
## 构建整改补充(2026-09-30 20:58)
|
||||||
|
|
||||||
|
本批只修改正式构建工具与回归,不修改应用业务代码,也未重新部署。修改前检查点 70f19d0 已推送。
|
||||||
|
|
||||||
|
- 原工作区正式工具链拒绝非规范换行,发生在依赖安装之前,没有自动改写中文文件。规范独立克隆则通过检查,后端 547 个、前端 105 个受跟踪文件采用允许的换行状态。
|
||||||
|
- 工具链 31 项、前端全量 237 项测试通过;新增真实 Git 夹具证明 Git diff 可忽略 CRLF,但正式入口仍退出 1,且不生成 node_modules 或锁文件。
|
||||||
|
- 20:58 管理 API 返回 401,明确提示重新登录,确认此前浏览器会话已失效;公开管理入口和就绪检查仍正常。未把未认证接口的错误当成有效任务列表。
|
||||||
|
- 后续需在规范克隆用本批提交完成正式发布构建。P0-03 目录边界复审结论不变;P0-04 整项及生产完整门禁仍未完成。
|
||||||
|
|
||||||
## 最新实机与复审记录(2026-09-30 20:53)
|
## 最新实机与复审记录(2026-09-30 20:53)
|
||||||
|
|
||||||
### 正式页面配置校验失败与恢复
|
### 正式页面配置校验失败与恢复
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
# Scert 整改任务执行清单
|
# Scert 整改任务执行清单
|
||||||
|
## 构建换行整改任务更新(2026-09-30)
|
||||||
|
|
||||||
|
- [x] 已实现拒绝非规范换行,不替换用户中文源码;记录后端、前端检查策略及文件数量。
|
||||||
|
- [x] 正式回归覆盖 LF、无换行、二进制、中文和换行文件名、CRLF、混合换行、空输出、截断、无效格式、未知状态及非 Git 目录。
|
||||||
|
- [x] 实际 Git 夹具验证:Git diff 无差异时仍能拒绝 CRLF;正式工具链退出 1,不安装依赖、不生成锁文件、不改写源码。
|
||||||
|
- [x] 工具链 31 项和前端全量 237 项通过,规范克隆工具链检查通过。
|
||||||
|
- [~] 提交后完整构建验证;Linux 原生构建、双平台服务、数据库与生产业务门禁仍未完成。
|
||||||
|
|
||||||
## 最新任务状态(2026-09-30 20:53)
|
## 最新任务状态(2026-09-30 20:53)
|
||||||
|
|
||||||
- [✓] P0-03 按本项范围完成复审,不再与整体生产验收混为一谈。源码与产物隔离、独立克隆构建、忽略规则、索引与实际部署包边界均有本轮证据。
|
- [✓] P0-03 按本项范围完成复审,不再与整体生产验收混为一谈。源码与产物隔离、独立克隆构建、忽略规则、索引与实际部署包边界均有本轮证据。
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
# Scert 整改、合并与优化清单
|
# Scert 整改、合并与优化清单
|
||||||
|
## 最新开发进度(2026-09-30,构建输入换行门禁)
|
||||||
|
|
||||||
|
- [✓] P0-03 保持复审通过,证据见下节;不代表全部生产验收通过。
|
||||||
|
- [x] P0-04 子项:正式工具链在安装依赖前及构建后读取实际源码换行,拒绝 CRLF、混合换行、无受跟踪源码及 Git 检查失败;不会自动改写文件。后端和前端检查结果写入发布清单。
|
||||||
|
- [x] 工具链 31 项、前端全量 237 项测试通过;实际非规范工作区已被正式入口拒绝;规范独立克隆检查通过,核对后端 547 个、前端 105 个受跟踪文件。
|
||||||
|
- [~] 新工具链提交后仍需在规范克隆执行完整发布构建;Linux 原生及双平台完整门禁未完成,P0-04 整项仍为进行中。
|
||||||
|
- [!] 远端继续运行此前已验证的 0bb427d 正式候选,未将新的本地工具链或复审包冒充为已部署。其余生产门禁按下节继续。
|
||||||
|
|
||||||
## 最新进度(2026-09-30 20:53,回滚实机证据与 P0-03 独立复审)
|
## 最新进度(2026-09-30 20:53,回滚实机证据与 P0-03 独立复审)
|
||||||
|
|
||||||
本节及对应任务正文是当前状态;下方按日期保留的“总体验收仍为 P0-03”属于历史口径。任务复审和整体生产放行分开判断,不能将所有后续门禁都挂在目录整理任务下。
|
本节及对应任务正文是当前状态;下方按日期保留的“总体验收仍为 P0-03”属于历史口径。任务复审和整体生产放行分开判断,不能将所有后续门禁都挂在目录整理任务下。
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user