在 Makelore 构建并预检静态发布产物
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'http';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { lstat, mkdir, mkdtemp, open, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { app } from 'electron';
|
||||
import { lstat, mkdir, open, stat, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import type { HostApiContext } from '../context';
|
||||
import { parseJsonBody, sendJson } from '../route-utils';
|
||||
@@ -10,9 +10,9 @@ import { trustedWorksProjectPlayUrl } from '../works-play-url';
|
||||
import { proxyAwareFetch } from '../../utils/proxy-fetch';
|
||||
import { WORKS_SQUARE_CONFIG } from '../works-config';
|
||||
import {
|
||||
createStaticProjectPackage,
|
||||
ProjectPackageError,
|
||||
} from '../../services/project-packager';
|
||||
import { prepareProjectRelease, ProjectReleaseBuildError } from '../../services/project-release-builder';
|
||||
import { getValidWorksSquareAccessToken } from '../../services/works-square-session';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
@@ -126,14 +126,12 @@ function sendPublishSourceFailure(
|
||||
}
|
||||
|
||||
const SAFE_PUBLISH_PREFLIGHT_CODES = new Set([
|
||||
'PREVIEW_REQUIRED',
|
||||
'PUBLISH_PREFLIGHT_LOAD_FAILED',
|
||||
'PUBLISH_PREFLIGHT_RUNTIME_ERROR',
|
||||
'PUBLISH_PREFLIGHT_BLANK',
|
||||
'PUBLISH_PREFLIGHT_TIMEOUT',
|
||||
]);
|
||||
const SAFE_PUBLISH_PREFLIGHT_MESSAGES: Record<string, string> = {
|
||||
PREVIEW_REQUIRED: '请先在 Makelore 内置浏览器中打开当前项目预览。',
|
||||
PUBLISH_PREFLIGHT_LOAD_FAILED: '作品主页无法打开。',
|
||||
PUBLISH_PREFLIGHT_RUNTIME_ERROR: '作品打开时发生了运行错误。',
|
||||
PUBLISH_PREFLIGHT_BLANK: '作品打开后没有可见内容。',
|
||||
@@ -157,7 +155,17 @@ async function sendPublishSourceUpstreamError(
|
||||
): Promise<void> {
|
||||
let code = fallbackCode;
|
||||
let error = fallbackMessage;
|
||||
if (response.status === 401) {
|
||||
let payload: unknown = null;
|
||||
try { payload = await readResponsePayload(response); } catch { /* project only stable codes */ }
|
||||
const detail = isRecord(payload) && isRecord(payload.detail) ? payload.detail : null;
|
||||
const upstreamCode = isRecord(payload)
|
||||
? readOptionalString(payload.code) ?? (detail ? readOptionalString(detail.code) : undefined)
|
||||
: undefined;
|
||||
if (upstreamCode === 'CLIENT_BUILD_PROTOCOL_REQUIRED') {
|
||||
code = upstreamCode;
|
||||
error = '请升级 Makelore 并重新提交。';
|
||||
}
|
||||
else if (response.status === 401) {
|
||||
code = 'AUTH_REQUIRED';
|
||||
error = '登录状态已失效,请重新登录。';
|
||||
} else if (response.status === 403) {
|
||||
@@ -748,6 +756,9 @@ async function readAutomaticVersionName(projectPath: string): Promise<string> {
|
||||
function createSourceUploadForm(
|
||||
archiveBytes: Buffer,
|
||||
archiveName: string,
|
||||
builtArchiveBytes: Buffer,
|
||||
builtArchiveName: string,
|
||||
artifactContract: object,
|
||||
versionName: string,
|
||||
): FormData {
|
||||
const archiveBlob = new Blob([new Uint8Array(archiveBytes)], { type: 'application/zip' });
|
||||
@@ -755,6 +766,8 @@ function createSourceUploadForm(
|
||||
form.set('version_name', versionName);
|
||||
form.set('change_log', SOURCE_PUBLISH_CHANGE_LOG);
|
||||
form.set('archive', archiveBlob, archiveName);
|
||||
form.set('built_archive', new Blob([new Uint8Array(builtArchiveBytes)], { type: 'application/zip' }), builtArchiveName);
|
||||
form.set('artifact_contract', JSON.stringify(artifactContract));
|
||||
return form;
|
||||
}
|
||||
|
||||
@@ -763,6 +776,9 @@ async function uploadSourceProjectVersion(input: {
|
||||
appId: string;
|
||||
archiveBytes: Buffer;
|
||||
archiveName: string;
|
||||
builtArchiveBytes: Buffer;
|
||||
builtArchiveName: string;
|
||||
artifactContract: object;
|
||||
versionName: string;
|
||||
idempotencyKey: string;
|
||||
}): Promise<Response> {
|
||||
@@ -780,6 +796,9 @@ async function uploadSourceProjectVersion(input: {
|
||||
body: createSourceUploadForm(
|
||||
input.archiveBytes,
|
||||
input.archiveName,
|
||||
input.builtArchiveBytes,
|
||||
input.builtArchiveName,
|
||||
input.artifactContract,
|
||||
input.versionName,
|
||||
),
|
||||
},
|
||||
@@ -847,24 +866,11 @@ async function handlePublishProjectSource(
|
||||
return;
|
||||
}
|
||||
|
||||
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'makelore-publish-'));
|
||||
const archivePath = join(temporaryDirectory, 'project.zip');
|
||||
let prepared: Awaited<ReturnType<typeof prepareProjectRelease>> | null = null;
|
||||
try {
|
||||
if (!ctx.agentBrowser) {
|
||||
sendPublishSourceFailure(
|
||||
res,
|
||||
400,
|
||||
'PREVIEW_REQUIRED',
|
||||
'请先在 Makelore 内置浏览器中打开当前项目预览。',
|
||||
);
|
||||
return;
|
||||
}
|
||||
await ctx.agentBrowser.preflightCurrentProject(localProject.path);
|
||||
const packageSummary = await createStaticProjectPackage({
|
||||
projectPath: localProject.path,
|
||||
archivePath,
|
||||
});
|
||||
const archiveBytes = await readFile(archivePath);
|
||||
if (!ctx.agentBrowser) throw new ProjectReleaseBuildError('LOCAL_BUILD_RUNTIME_UNAVAILABLE');
|
||||
prepared = await prepareProjectRelease({ projectPath: localProject.path, clientVersion: app.getVersion() });
|
||||
await ctx.agentBrowser.preflightStaticArtifact(prepared.staticArtifact);
|
||||
const versionName = await readAutomaticVersionName(localProject.path);
|
||||
const idempotencyKey = `makelore-${randomUUID()}`;
|
||||
|
||||
@@ -910,8 +916,11 @@ async function handlePublishProjectSource(
|
||||
const uploadResponse = await uploadSourceProjectVersion({
|
||||
accessToken,
|
||||
appId,
|
||||
archiveBytes,
|
||||
archiveName: packageSummary.archiveName,
|
||||
archiveBytes: prepared.sourceArchive.bytes,
|
||||
archiveName: prepared.sourceArchive.name,
|
||||
builtArchiveBytes: prepared.builtArchive.bytes,
|
||||
builtArchiveName: prepared.builtArchive.name,
|
||||
artifactContract: prepared.contract,
|
||||
versionName,
|
||||
idempotencyKey,
|
||||
});
|
||||
@@ -943,7 +952,7 @@ async function handlePublishProjectSource(
|
||||
versionId: uploadPayload.version_id,
|
||||
versionName,
|
||||
reviewStatus: uploadPayload.review_status,
|
||||
zipSha256: packageSummary.sha256,
|
||||
zipSha256: prepared.contract.source_digest,
|
||||
});
|
||||
} catch {
|
||||
logger.warn('[works] One-click submission succeeded, but local preview mapping could not be saved');
|
||||
@@ -952,7 +961,7 @@ async function handlePublishProjectSource(
|
||||
} else {
|
||||
bindingWarning = LOCAL_PREVIEW_BINDING_WARNING;
|
||||
}
|
||||
const { archivePath: _archivePath, ...rendererPackageSummary } = packageSummary;
|
||||
const { archivePath: _archivePath, ...rendererPackageSummary } = prepared.sourceArchive.summary;
|
||||
sendJson(res, uploadResponse.status, {
|
||||
success: true,
|
||||
package: rendererPackageSummary,
|
||||
@@ -960,7 +969,7 @@ async function handlePublishProjectSource(
|
||||
...(bindingWarning ? { binding_warning: bindingWarning } : {}),
|
||||
});
|
||||
} finally {
|
||||
await rm(temporaryDirectory, { recursive: true, force: true }).catch(() => undefined);
|
||||
await prepared?.dispose().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1147,12 +1156,15 @@ export async function handleWorksRoutes(
|
||||
const isProjectStatus = /^\/api\/works\/projects\/mine\/[^/]+\/status$/.test(url.pathname);
|
||||
if (isProjectSourcePublish) {
|
||||
const isPackageError = error instanceof ProjectPackageError;
|
||||
const isLocalBuildError = error instanceof ProjectReleaseBuildError;
|
||||
sendPublishSourceFailure(
|
||||
res,
|
||||
isPackageError || isSafePublishPreflightError(error) ? 400 : 503,
|
||||
isPackageError || isSafePublishPreflightError(error) ? error.code : 'WORKS_SQUARE_UNAVAILABLE',
|
||||
isPackageError || isLocalBuildError || isSafePublishPreflightError(error) ? 400 : 503,
|
||||
isPackageError || isLocalBuildError || isSafePublishPreflightError(error) ? error.code : 'WORKS_SQUARE_UNAVAILABLE',
|
||||
isPackageError
|
||||
? error.message
|
||||
: isLocalBuildError
|
||||
? error.code
|
||||
: isSafePublishPreflightError(error)
|
||||
? SAFE_PUBLISH_PREFLIGHT_MESSAGES[error.code]
|
||||
: '发布服务暂时不可用,请稍后重试。',
|
||||
|
||||
Reference in New Issue
Block a user