修复 Jenkins 发布流水线与 macOS 包门禁
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 1m12s
Project CI / AI game creator shell Rust smoke (push) Failing after 1m12s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 1m12s
Project CI / AI game creator shell Rust crates (push) Successful in 1m37s
Project CI / Frontend tests (push) Successful in 3m37s
Project CI / Repository checks (push) Successful in 4m4s
Project CI / AI game creator shell web tests (push) Successful in 2m11s
Project CI / Backend tests (push) Successful in 6m31s
Project CI / Native shell tests (push) Successful in 7m13s

- 转义 Stdb Publish GString 内的 shell 命令替换,避免 Jenkinsfile 加载时 Groovy 编译失败
- 为 Stdb Publish 暂存清理命令补充生产运维回归门禁
- 抽离 macOS 包内容策略并放行随包 Claude Agent SDK 的受控 node_modules
- 违规资源现在会输出具体相对路径,并补充包内容白名单单测与排障记录
This commit is contained in:
2026-09-30 16:01:21 +08:00
parent c25fd47d16
commit 110f088d41
6 changed files with 127 additions and 16 deletions
@@ -5,6 +5,8 @@ import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { listForbiddenBundledResourceFiles } from './macos-bundle-policy.mjs';
// 只操作临时复制品;不启动 GUI、不读取开发机凭据、不访问 Provider。
assert.equal(process.platform, 'darwin', '此验证必须在 macOS 执行');
const source = path.resolve(process.argv[2] || '');
@@ -287,22 +289,32 @@ try {
]) {
assert.ok(fs.existsSync(path.join(plugin, file)), file);
}
// 随包 Node 的 npm 是包里唯一允许出现的 node_modules:除了 npm 目录自身与它的子项,
// 还要放行它的上级目录 `game-runtime/node/node_modules`(recursive readdir 会列出目录项,
// 少了这一条会让整个门禁对合法包失败——#439 引入后一直没被跑到,直到 2026-09-21 才暴露)。
const allowedNodeModules = (file) =>
file === 'game-runtime/node/node_modules' ||
file === 'game-runtime/node/node_modules/npm' ||
file.startsWith('game-runtime/node/node_modules/npm/');
const claudeAgentRoot = path.join(resources, 'claude-agent');
const claudeAgentSdk = path.join(
claudeAgentRoot,
'node_modules/@anthropic-ai/claude-agent-sdk',
);
const claudeAgentBinaryPackage = `claude-agent-sdk-darwin-${architecture === 'arm64' ? 'arm64' : 'x64'}`;
const claudeAgentBinary = path.join(
claudeAgentRoot,
'node_modules/@anthropic-ai',
claudeAgentBinaryPackage,
'claude',
);
for (const required of [
path.join(claudeAgentRoot, 'index.mjs'),
path.join(claudeAgentSdk, 'sdk.mjs'),
claudeAgentBinary,
]) {
assert.ok(fs.existsSync(required), required);
}
// 随包只允许 Node runtime 的 npm 与 Claude Agent SDK sidecar 这两棵
// node_modules 子树;任何其它 node_modules 都是构建残留或不可控依赖。
const packageFiles = fs.readdirSync(resources, { recursive: true });
assert.ok(
!packageFiles.some(
(file) =>
/(^|\/)(\.env[^/]*|auth\.json|target|\.git)(\/|$)|\.(exe|dll)$/.test(
file,
) ||
(/(^|\/)node_modules(\/|$)/.test(file) && !allowedNodeModules(file)),
),
assert.deepEqual(
listForbiddenBundledResourceFiles(packageFiles),
[],
'安装包包含禁止资源',
);
assert.equal(run(executable, ['--version']).stdout.trim(), manifest.version);
assert.equal(
@@ -0,0 +1,46 @@
/**
* macOS 安装包内容策略。
*
* `check-macos-bundle.mjs` 会扫描随包 resources。Node runtime 与 Claude Agent SDK
* sidecar 都需要 `node_modules` 形式的目录,但其它构建残留或第三方依赖不能被
* 静默带入安装包,因此这里只放行两棵明确的生产子树。
*/
const NODE_RUNTIME_NODE_MODULES = 'game-runtime/node/node_modules';
const CLAUDE_AGENT_NODE_MODULES = 'claude-agent/node_modules';
const CLAUDE_AGENT_SCOPE = `${CLAUDE_AGENT_NODE_MODULES}/@anthropic-ai`;
const CLAUDE_AGENT_PACKAGE_PATTERN =
/^(?:claude-agent-sdk|claude-agent-sdk-darwin-(?:arm64|x64))(?:\/.*)?$/u;
function isAllowedClaudeAgentNodeModulesPath(file) {
if (file === CLAUDE_AGENT_NODE_MODULES || file === CLAUDE_AGENT_SCOPE) {
return true;
}
if (!file.startsWith(`${CLAUDE_AGENT_SCOPE}/`)) {
return false;
}
return CLAUDE_AGENT_PACKAGE_PATTERN.test(
file.slice(`${CLAUDE_AGENT_SCOPE}/`.length),
);
}
export function isAllowedBundledNodeModulesPath(file) {
return (
file === NODE_RUNTIME_NODE_MODULES ||
file === `${NODE_RUNTIME_NODE_MODULES}/npm` ||
file.startsWith(`${NODE_RUNTIME_NODE_MODULES}/npm/`) ||
isAllowedClaudeAgentNodeModulesPath(file)
);
}
export function listForbiddenBundledResourceFiles(files) {
return files.filter(
(file) =>
/(^|\/)(\.env[^/]*|auth\.json|target|\.git)(\/|$)|\.(exe|dll)$/u.test(
file,
) ||
(/(^|\/)node_modules(\/|$)/u.test(file) &&
!isAllowedBundledNodeModulesPath(file)),
);
}
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { listForbiddenBundledResourceFiles } from './macos-bundle-policy.mjs';
import {
assertMacosAppMatchesChannelIdentity,
assertManifestArtifactMatchesExpected,
@@ -8,6 +9,42 @@ import {
readMacosAppInfoIdentity,
} from './macos-release-identity.mjs';
test('allows only the production node_modules subtrees in the macOS bundle', () => {
assert.deepEqual(
listForbiddenBundledResourceFiles([
'game-runtime/node/node_modules',
'game-runtime/node/node_modules/npm/bin/npm-cli.js',
'claude-agent/node_modules',
'claude-agent/node_modules/@anthropic-ai',
'claude-agent/node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs',
'claude-agent/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64/claude',
]),
[],
);
assert.deepEqual(
listForbiddenBundledResourceFiles([
'claude-agent/node_modules/unexpected/index.mjs',
'claude-agent/node_modules/@anthropic-ai/unexpected/index.mjs',
'plugins/agc-cocos-editor/node_modules/leftover/index.mjs',
'game-runtime/node/.env.local',
'claude-agent/auth.json',
'claude-agent/target/debug/claude',
'claude-agent/tool.exe',
'claude-agent/tool.dll',
]),
[
'claude-agent/node_modules/unexpected/index.mjs',
'claude-agent/node_modules/@anthropic-ai/unexpected/index.mjs',
'plugins/agc-cocos-editor/node_modules/leftover/index.mjs',
'game-runtime/node/.env.local',
'claude-agent/auth.json',
'claude-agent/target/debug/claude',
'claude-agent/tool.exe',
'claude-agent/tool.dll',
],
);
});
const DEV_IDENTITY = {
productName: '陶泥儿开发版',
identifier: 'world.genarrative.ai-game-creator',