Makelore 2.0 initial clean snapshot

This commit is contained in:
inman
2026-07-29 17:22:35 +08:00
commit b8ca3f8eea
694 changed files with 139782 additions and 0 deletions

View File

@@ -0,0 +1,461 @@
import { createHash } from 'node:crypto';
import { execFile } from 'node:child_process';
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { promisify } from 'node:util';
import { dirname, join } from 'node:path';
import { tmpdir } from 'node:os';
import { afterEach, describe, expect, it } from 'vitest';
import { readWorksDeployCheck } from '@electron/opencode/works-square-deploy-check';
import {
GAME_CANVAS_DYNAMIC_WORKS_DEPLOY_CHECK_ID,
REQUIRED_DYNAMIC_WORKS_DEPLOY_CHECK_IDS,
} from '../../shared/works-square-deploy-check';
const execFileAsync = promisify(execFile);
const projects: string[] = [];
function validCompose(volumes = ''): string {
return [
'services:',
' frontend:',
' build:',
' context: .',
' dockerfile: Dockerfile',
' ports:',
' - "127.0.0.1::8080"',
volumes ? ` volumes:\n${volumes}` : '',
].filter(Boolean).join('\n');
}
function validFiles(compose = validCompose(), overrides: Record<string, string> = {}): Record<string, string> {
return {
'niancode.yml': [
'runtime: compose',
'compose:',
' file: docker-compose.yml',
' public_service: frontend',
' public_port: 8080',
].join('\n'),
'docker-compose.yml': compose,
Dockerfile: [
'FROM node:20-alpine AS build',
'WORKDIR /app',
'COPY package.json package-lock.json ./',
'RUN npm ci',
'COPY . .',
'RUN npm run build',
'FROM nginx:1.27-alpine',
'COPY nginx.conf /etc/nginx/conf.d/default.conf',
'COPY --from=build /app/dist /usr/share/nginx/html',
'EXPOSE 8080',
'CMD ["nginx", "-g", "daemon off;"]',
].join('\n'),
'package.json': JSON.stringify({ scripts: { build: 'vite build' }, devDependencies: { vite: '^7.0.0' } }),
'package-lock.json': '{}',
'vite.config.js': 'export default { base: "./" };',
'index.html': '<script type="module" src="./assets/index.js"></script>',
'nginx.conf': 'server { listen 0.0.0.0:8080; }',
'src/main.ts': 'console.log("ready");',
...overrides,
};
}
type ProjectOptions = {
category?: string;
files?: Record<string, string>;
includeCanvasCheck?: boolean;
localSmokeUnavailable?: boolean;
reportChecks?: Record<string, { status: 'PASS' | 'SKIPPED' | 'BLOCKED'; detail: string; command: string }>;
reportStatus?: 'PASS' | 'SKIPPED' | 'BLOCKED';
};
async function createProject(compose = validCompose(), options: ProjectOptions = {}): Promise<{ projectPath: string; zipPath: string }> {
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-deploy-check-'));
projects.push(projectPath);
const zipPath = join(projectPath, 'project-upload.zip');
const sourcePath = join(projectPath, 'package-source');
await mkdir(sourcePath, { recursive: true });
for (const [name, content] of Object.entries(validFiles(compose, options.files))) {
const filePath = join(sourcePath, name);
await mkdir(dirname(filePath), { recursive: true });
await writeFile(filePath, content, 'utf8');
}
await execFileAsync('zip', ['-q', '-0', '-r', zipPath, '.'], { cwd: sourcePath });
const zipSha256 = createHash('sha256').update(await readFile(zipPath)).digest('hex');
const publish = {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: options.category ?? 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: zipPath,
};
await writeFile(join(projectPath, 'works-publish.json'), `${JSON.stringify(publish, null, 2)}\n`, 'utf8');
await writeFile(join(projectPath, '部署报告.md'), options.localSmokeUnavailable
? '# BLOCKED\n本机 Docker/浏览器不可用,交由 Works Square 云端构建验证。\n'
: options.includeCanvasCheck
? '# PASS\n逻辑分辨率800x450\n桌面视口1280x720主画布实际尺寸1280x720最大等比预期尺寸1280x720\n移动视口390x844主画布实际尺寸390x219.375最大等比预期尺寸390x219.375\nresize 复测:已重新布局\n'
: '# PASS\n', 'utf8');
const checks: Record<string, unknown> = Object.fromEntries(REQUIRED_DYNAMIC_WORKS_DEPLOY_CHECK_IDS.map((id) => [id, options.localSmokeUnavailable
? {
status: 'BLOCKED',
detail: `${id}:本机 Docker/浏览器不可用,交由 Works Square 云端构建验证`,
command: `check ${id}`,
execution: 'unavailable',
}
: {
status: 'PASS',
detail: `${id} 通过`,
command: `check ${id}`,
execution: 'executed',
}]));
if (options.includeCanvasCheck) {
checks[GAME_CANVAS_DYNAMIC_WORKS_DEPLOY_CHECK_ID] = {
status: 'PASS',
detail: '桌面、移动和 resize 复测通过',
command: 'playwright canvas viewport smoke',
canvas_evidence: {
logical_resolution: { width: 800, height: 450 },
desktop_viewport: { width: 1280, height: 720 },
mobile_viewport: { width: 390, height: 844 },
desktop_canvas: { width: 1280, height: 720 },
mobile_canvas: { width: 390, height: 219.375 },
expected_max_canvas: {
desktop: { width: 1280, height: 720 },
mobile: { width: 390, height: 219.375 },
},
resize_verified: true,
},
};
}
Object.assign(checks, options.reportChecks);
await writeFile(join(projectPath, 'works-deploy-check.json'), `${JSON.stringify({
schema_version: 1,
status: options.localSmokeUnavailable ? 'BLOCKED' : options.reportStatus ?? 'PASS',
checked_at: '2026-07-12T00:00:00.000Z',
zip_file_path: zipPath,
zip_sha256: zipSha256,
checks,
}, null, 2)}\n`, 'utf8');
return { projectPath, zipPath };
}
function readProjectCheck(projectPath: string) {
return readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
}
afterEach(async () => {
await Promise.all(projects.splice(0).map((projectPath) => rm(projectPath, { recursive: true, force: true })));
});
describe('Works Square deployment package checks', () => {
it('accepts SKIPPED for unavailable Docker checks', async () => {
const { projectPath } = await createProject(validCompose(), {
reportStatus: 'SKIPPED',
reportChecks: {
compose_build: {
status: 'SKIPPED',
detail: 'Docker not installed on current environment',
command: 'docker compose build --pull=false',
},
},
});
const result = await readProjectCheck(projectPath);
expect(result.report?.checks.compose_build?.status).toBe('SKIPPED');
});
it('normalizes legacy environment-only BLOCKED checks to SKIPPED', async () => {
const { projectPath } = await createProject(validCompose(), {
reportStatus: 'BLOCKED',
reportChecks: {
sandbox_browser_smoke: {
status: 'BLOCKED',
detail: 'No browser automation available (Playwright/Puppeteer)',
command: 'playwright test sandbox-smoke.spec.ts',
},
},
});
const result = await readProjectCheck(projectPath);
expect(result.report?.checks.sandbox_browser_smoke?.status).toBe('SKIPPED');
});
it.each([
['not verified', 'docker compose build --pull=false'],
['docker compose up exited 1', 'docker compose up -d'],
])('does not normalize a non-environment failure: %s', async (detail, command) => {
const { projectPath } = await createProject(validCompose(), {
reportStatus: 'BLOCKED',
reportChecks: {
compose_up: { status: 'BLOCKED', detail, command },
},
});
const result = await readProjectCheck(projectPath);
expect(result.report?.checks.compose_up?.status).toBe('BLOCKED');
});
it('returns warning when only environment-dependent checks are skipped', async () => {
const { projectPath } = await createProject(validCompose(), {
reportStatus: 'SKIPPED',
reportChecks: {
compose_build: {
status: 'SKIPPED',
detail: 'Docker not installed on current environment',
command: 'docker compose build --pull=false',
},
},
});
expect((await readProjectCheck(projectPath)).status).toBe('warning');
});
it('blocks a hash mismatch even when dynamic checks are skipped', async () => {
const { projectPath } = await createProject(validCompose(), {
reportStatus: 'SKIPPED',
reportChecks: {
compose_build: {
status: 'SKIPPED',
detail: 'Docker not installed on current environment',
command: 'docker compose build --pull=false',
},
},
});
await writeFile(join(projectPath, 'project-upload.zip'), Buffer.from('changed zip contents'));
const result = await readProjectCheck(projectPath);
expect(result.status).toBe('blocked');
expect(result.error).toContain('当前 zip');
});
it('blocks an executed dynamic failure', async () => {
const { projectPath } = await createProject(validCompose(), {
reportStatus: 'BLOCKED',
reportChecks: {
compose_up: {
status: 'BLOCKED',
detail: 'docker compose up exited 1',
command: 'docker compose up -d',
},
},
});
expect((await readProjectCheck(projectPath)).status).toBe('blocked');
});
it('passes a self-contained Compose/Vite package with a matching report', async () => {
const { projectPath } = await createProject();
const result = await readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
expect(result.status).toBe('pass');
expect(result.report?.status).toBe('PASS');
expect(Object.values(result.checks ?? {}).every((check) => check.status === 'PASS')).toBe(true);
});
it('blocks bind mounts even when the agent report says PASS', async () => {
const { projectPath } = await createProject('services:\n frontend:\n build: .\n ports:\n - "127.0.0.1::8080"\n volumes:\n - .:/app');
const result = await readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
expect(result.status).toBe('blocked');
expect(result.error).toContain('bind mount');
});
it('blocks a report that is no longer bound to the current ZIP', async () => {
const { projectPath } = await createProject();
await writeFile(join(projectPath, 'project-upload.zip'), Buffer.from('changed zip contents'));
const result = await readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
expect(result.status).toBe('blocked');
expect(result.error).toContain('当前 zip');
});
it('allows safe Compose defaults and requires an all-interface container listener', async () => {
const safeCompose = validCompose().replace(
' ports:',
' environment:\n API_URL: "${API_URL:-http://backend:8000}"\n ports:',
);
const { projectPath } = await createProject(safeCompose);
const safeResult = await readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
expect(safeResult.status).toBe('pass');
const safeEnvFileCompose = validCompose().replace(
' ports:',
' env_file:\n - .env.example\n ports:',
);
const { projectPath: safeEnvFileProjectPath } = await createProject(safeEnvFileCompose, {
files: { '.env.example': 'API_URL=http://backend:8000\n' },
});
const safeEnvFileResult = await readWorksDeployCheck(safeEnvFileProjectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(safeEnvFileProjectPath, 'project-upload.zip'),
});
expect(safeEnvFileResult.status).toBe('pass');
const { projectPath: unresolvedEnvProjectPath } = await createProject(
validCompose().replace(
' ports:',
' environment:\n API_URL: "${API_URL}"\n ports:',
),
);
const unresolvedEnvResult = await readWorksDeployCheck(unresolvedEnvProjectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(unresolvedEnvProjectPath, 'project-upload.zip'),
});
expect(unresolvedEnvResult.status).toBe('blocked');
expect(unresolvedEnvResult.error).toContain('未声明环境变量');
const { projectPath: blockedProjectPath } = await createProject(validCompose(), {
files: { 'nginx.conf': 'server { listen 127.0.0.1:8080; }' },
});
const blockedResult = await readWorksDeployCheck(blockedProjectPath, {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(blockedProjectPath, 'project-upload.zip'),
});
expect(blockedResult.status).toBe('blocked');
expect(blockedResult.error).toContain('0.0.0.0:8080');
});
it('requires responsive canvas evidence for game projects', async () => {
const files = {
'style.css': 'html, body, #app, #game-container { width: 100%; height: 100%; margin: 0; } canvas { display: block; }',
'src/game.ts': 'const config = { width: 800, height: 450, parent: "game-container", scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH } }; window.addEventListener("resize", () => config);',
};
const { projectPath } = await createProject(validCompose(), { category: 'game', files, includeCanvasCheck: true });
const result = await readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '游戏部署检查测试包',
summary: '用于测试游戏部署门禁。',
category: 'game',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
expect(result.status).toBe('pass');
expect(result.checks?.[GAME_CANVAS_DYNAMIC_WORKS_DEPLOY_CHECK_ID]?.status).toBe('PASS');
});
it('blocks a game project when canvas smoke evidence is missing', async () => {
const files = {
'style.css': 'html, body, #app, #game-container { width: 100%; height: 100%; margin: 0; } canvas { display: block; }',
'src/game.ts': 'const config = { scale: { mode: Phaser.Scale.FIT } };',
};
const { projectPath } = await createProject(validCompose(), { category: 'game', files });
const result = await readWorksDeployCheck(projectPath, {
app_id: 'deploy-check-fixture',
title: '游戏部署检查测试包',
summary: '用于测试游戏部署门禁。',
category: 'game',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
});
expect(result.status).toBe('blocked');
expect(result.error).toContain('game_canvas_smoke');
});
it('keeps local mode strict but allows explicitly unavailable local smoke in cloud mode', async () => {
const { projectPath } = await createProject(validCompose(), { localSmokeUnavailable: true });
const publish = {
app_id: 'deploy-check-fixture',
title: '部署检查测试包',
summary: '用于测试部署门禁。',
category: 'web',
age_band: '10-12',
difficulty: 'beginner',
version_name: 'v1.0.0',
change_log: '测试包',
zip_file_path: join(projectPath, 'project-upload.zip'),
};
const localResult = await readWorksDeployCheck(projectPath, publish);
expect(localResult.status).toBe('blocked');
const cloudResult = await readWorksDeployCheck(projectPath, publish, { mode: 'cloud' });
expect(cloudResult.status).toBe('pass');
expect(cloudResult.zip_sha256).toMatch(/^[a-f0-9]{64}$/);
});
});