实现 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

@@ -82,6 +82,8 @@ export type WorksProjectMetadataInput = {
export type WorksProjectVersionUpload = {
version_id: string;
review_status: string;
build_job_id?: string | null;
build_status?: string | null;
};
export type WorksProjectVersion = {
@@ -90,6 +92,10 @@ export type WorksProjectVersion = {
review_status: string;
change_log: string;
build_job_id: string | null;
build_status: string | null;
build_error_code: string | null;
release_id: string | null;
rejection_reason?: string | null;
created_at: string;
};
@@ -112,6 +118,36 @@ export type WorksProjectUploadInput = {
zipFilePath: string;
};
export type WorksStaticPackageSummary = {
archiveName: string;
sha256: string;
fileCount: number;
sourceBytes: number;
archiveBytes: number;
excludedCount: number;
excludedPaths: string[];
manifest: {
schema_version: 1;
kind: 'web';
runtime: 'static';
build: {
preset: 'vite';
package_manager: 'npm';
entry: 'index.html';
};
};
};
export type WorksProjectSourcePublishInput = {
projectId: string;
project: WorksProjectMetadataInput;
};
export type WorksProjectSourcePublishResult = {
package: WorksStaticPackageSummary;
upload: WorksProjectVersionUpload;
};
export type WorksSpeechTranscription = {
text: string;
model: string;
@@ -152,6 +188,7 @@ export type PlazaCard = {
type WorksActionResponse<TField extends string, TValue> = {
success: boolean;
status?: number;
code?: string;
error?: string;
} & {
[key in TField]?: TValue;
@@ -177,11 +214,13 @@ type FetchMyWorksProjectsInput = {
export class WorksSquareApiError extends Error {
readonly statusCode?: number;
readonly code?: string;
constructor(message: string, statusCode?: number) {
constructor(message: string, statusCode?: number, code?: string) {
super(message);
this.name = 'WorksSquareApiError';
this.statusCode = statusCode;
this.code = code;
}
}
@@ -191,7 +230,7 @@ function assertSuccess<TField extends string, TValue>(
fallback: string,
): TValue {
if (!response.success || response[field] === undefined) {
throw new WorksSquareApiError(response.error || fallback, response.status);
throw new WorksSquareApiError(response.error || fallback, response.status, response.code);
}
return response[field] as TValue;
}
@@ -366,6 +405,15 @@ export async function fetchMyWorksProjectStatus(
return assertSuccess(response, 'status', 'Failed to load Works Square project status');
}
export async function fetchCurrentWorksProjectStatus(
appId: string,
): Promise<WorksProjectStatusDetail> {
const response = await hostApiFetch<WorksActionResponse<'status', WorksProjectStatusDetail>>(
`/api/works/projects/mine/${encodeURIComponent(appId)}/status`,
);
return assertSuccess(response, 'status', 'Failed to load Works Square project status');
}
export async function uploadWorksProjectZip(
input: WorksProjectUploadInput,
): Promise<WorksProjectVersionUpload> {
@@ -385,6 +433,33 @@ export async function uploadWorksProjectZip(
return assertSuccess(response, 'upload', 'Failed to upload Works Square project zip');
}
export async function publishWorksProjectSource(
input: WorksProjectSourcePublishInput,
): Promise<WorksProjectSourcePublishResult> {
const response = await hostApiFetch<{
success: boolean;
status?: number;
code?: string;
error?: string;
package?: WorksStaticPackageSummary;
upload?: WorksProjectVersionUpload;
}>('/api/works/projects/publish-source', {
method: 'POST',
body: JSON.stringify({
projectId: input.projectId,
project: input.project,
}),
});
if (!response.success || !response.package || !response.upload) {
throw new WorksSquareApiError(
response.error || 'Failed to package and upload Works Square project',
response.status,
response.code,
);
}
return { package: response.package, upload: response.upload };
}
export async function armWorksCloudDeployment(projectId: string): Promise<WorksCloudDeploymentRecord> {
const response = await hostApiFetch<WorksActionResponse<'deployment', WorksCloudDeploymentRecord>>(
`/api/opencode/projects/${encodeURIComponent(projectId)}/works-cloud-deploy`,