实现 Makelore 一键提交审核

需求:让非专业用户在项目操作区一次提交,运营审核通过后直接发布。

实现:由 Electron Main 完成安全打包、自动版本、幂等重试和状态脱敏;补齐友好失败反馈、唯一提交入口及隔离 Electron E2E fixture。
This commit is contained in:
2026-08-08 14:44:08 +08:00
parent 7b23cce67a
commit 724290e864
18 changed files with 3056 additions and 31 deletions

View File

@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
createWorksProject,
downloadWorksAssetToProject,
fetchCurrentWorksProjectStatus,
fetchMyWorksProjectStatus,
fetchMyWorksProjects,
fetchWorksAsset,
@@ -9,6 +10,7 @@ import {
fetchWorksTokenUsage,
fetchWorksProjectVersions,
fetchWorksProjects,
publishWorksProjectSource,
toPlazaCard,
uploadWorksProjectZip,
WorksSquareApiError,
@@ -275,6 +277,34 @@ describe('works square client', () => {
);
});
it('loads the current project status without exposing a Works Square token', async () => {
const status = {
project: {
app_id: 'space-cleaner',
title: 'Space Cleaner',
summary: 'Catch space trash',
},
latest_version: {
id: 'version-new',
version_name: 'v1.1.0',
review_status: 'building',
change_log: '通过 Makelore 一键提交',
build_job_id: 'job-new',
build_status: 'failed',
build_error_code: 'BUILD_COMMAND_FAILED',
release_id: null,
created_at: '2026-08-07T10:30:00+08:00',
},
versions: [],
};
hostApiFetchMock.mockResolvedValueOnce({ success: true, status });
await expect(fetchCurrentWorksProjectStatus('space cleaner')).resolves.toEqual(status);
expect(hostApiFetchMock).toHaveBeenCalledWith(
'/api/works/projects/mine/space%20cleaner/status',
);
});
it('exposes an api error class for status-based recovery', () => {
const error = new WorksSquareApiError('Conflict', 409);
@@ -320,6 +350,87 @@ describe('works square client', () => {
);
});
it('asks Main to package and submit source without renderer-owned secrets or archive fields', async () => {
const publishResult = {
package: {
archiveName: 'project.zip',
sha256: 'a'.repeat(64),
fileCount: 18,
sourceBytes: 42_000,
archiveBytes: 12_000,
excludedCount: 3,
excludedPaths: ['.env', 'dist/', 'node_modules/'],
manifest: {
schema_version: 1,
kind: 'web',
runtime: 'static',
build: {
preset: 'vite',
package_manager: 'npm',
entry: 'index.html',
},
},
},
upload: {
version_id: 'version-1',
review_status: 'building',
build_job_id: 'job-1',
build_status: 'queued',
},
};
hostApiFetchMock.mockResolvedValueOnce({ success: true, ...publishResult });
const result = await publishWorksProjectSource({
projectId: 'project-1',
project: {
app_id: 'space-cleaner',
title: '太空清洁队',
summary: '收集漂浮垃圾的小游戏。',
category: 'game',
},
});
expect(result).toEqual(publishResult);
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/projects/publish-source', {
method: 'POST',
body: JSON.stringify({
projectId: 'project-1',
project: {
app_id: 'space-cleaner',
title: '太空清洁队',
summary: '收集漂浮垃圾的小游戏。',
category: 'game',
},
}),
});
expect(hostApiFetchMock.mock.calls[0]?.[1]?.body).not.toContain('accessToken');
expect(hostApiFetchMock.mock.calls[0]?.[1]?.body).not.toContain('zipFilePath');
expect(hostApiFetchMock.mock.calls[0]?.[1]?.body).not.toContain('versionName');
});
it('keeps the safe Main error code for understandable publish failures', async () => {
hostApiFetchMock.mockResolvedValueOnce({
success: false,
status: 400,
code: 'PROJECT_FILE_MISSING',
error: '项目根目录缺少 package-lock.json',
});
await expect(publishWorksProjectSource({
projectId: 'project-1',
project: {
app_id: 'space-cleaner',
title: '太空清洁队',
summary: '收集漂浮垃圾的小游戏。',
},
})).rejects.toMatchObject({
name: 'WorksSquareApiError',
code: 'PROJECT_FILE_MISSING',
statusCode: 400,
message: '项目根目录缺少 package-lock.json',
});
});
it('loads uploaded versions with the current access token', async () => {
hostApiFetchMock.mockResolvedValueOnce({
success: true,