收敛客户端静态发布链路
This commit is contained in:
179
electron/services/works-submission-binding.ts
Normal file
179
electron/services/works-submission-binding.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import type { OpencodeProjectStore } from '../opencode/project-store';
|
||||
import { logger } from '../utils/logger';
|
||||
import {
|
||||
WORKS_SUBMISSION_BINDING_FILE_NAME,
|
||||
WORKS_SUBMISSION_BINDING_SCHEMA_VERSION,
|
||||
type WorksSubmissionBindingRecord,
|
||||
} from '../../shared/works-submission-binding';
|
||||
|
||||
const LEGACY_PENDING_STATUSES = new Set([
|
||||
'armed',
|
||||
'waiting_for_package',
|
||||
'waiting_for_login',
|
||||
'uploading',
|
||||
'failed',
|
||||
]);
|
||||
const LEGACY_RETIRED_MESSAGE = '旧版自动部署任务已停用,请在项目配置中点击“提交审核”重新提交。';
|
||||
|
||||
type ProjectStore = Pick<OpencodeProjectStore, 'listProjects'>;
|
||||
|
||||
type SubmittedBindingInput = {
|
||||
appId: string;
|
||||
versionId: string;
|
||||
versionName: string;
|
||||
reviewStatus: string;
|
||||
zipSha256?: string;
|
||||
};
|
||||
|
||||
type LegacyBindingRecord = {
|
||||
schema_version: 1;
|
||||
project_id: string;
|
||||
status: string;
|
||||
requested_at: string;
|
||||
updated_at: string;
|
||||
app_id?: string;
|
||||
version_name?: string;
|
||||
zip_sha256?: string;
|
||||
version_id?: string;
|
||||
review_status?: string;
|
||||
};
|
||||
|
||||
function readString(value: unknown): string | null {
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
function parseCurrentRecord(value: unknown): WorksSubmissionBindingRecord | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
if (
|
||||
record.schema_version !== WORKS_SUBMISSION_BINDING_SCHEMA_VERSION
|
||||
|| (record.status !== 'submitted' && record.status !== 'legacy_retired')
|
||||
) return null;
|
||||
const projectId = readString(record.project_id);
|
||||
const requestedAt = readString(record.requested_at);
|
||||
const updatedAt = readString(record.updated_at);
|
||||
if (!projectId || !requestedAt || !updatedAt) return null;
|
||||
return record as WorksSubmissionBindingRecord;
|
||||
}
|
||||
|
||||
function parseLegacyRecord(value: unknown): LegacyBindingRecord | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
const projectId = readString(record.project_id);
|
||||
const status = readString(record.status);
|
||||
const requestedAt = readString(record.requested_at);
|
||||
const updatedAt = readString(record.updated_at);
|
||||
if (record.schema_version !== 1 || !projectId || !status || !requestedAt || !updatedAt) {
|
||||
return null;
|
||||
}
|
||||
return record as LegacyBindingRecord;
|
||||
}
|
||||
|
||||
function migrateLegacyRecord(record: LegacyBindingRecord): WorksSubmissionBindingRecord {
|
||||
const now = new Date().toISOString();
|
||||
if (
|
||||
record.status === 'submitted'
|
||||
&& readString(record.app_id)
|
||||
&& readString(record.version_id)
|
||||
&& readString(record.version_name)
|
||||
) {
|
||||
return {
|
||||
schema_version: WORKS_SUBMISSION_BINDING_SCHEMA_VERSION,
|
||||
project_id: record.project_id,
|
||||
status: 'submitted',
|
||||
requested_at: record.requested_at,
|
||||
updated_at: now,
|
||||
app_id: record.app_id,
|
||||
version_id: record.version_id,
|
||||
version_name: record.version_name,
|
||||
...(readString(record.review_status) ? { review_status: record.review_status } : {}),
|
||||
...(readString(record.zip_sha256) ? { zip_sha256: record.zip_sha256 } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
schema_version: WORKS_SUBMISSION_BINDING_SCHEMA_VERSION,
|
||||
project_id: record.project_id,
|
||||
status: 'legacy_retired',
|
||||
requested_at: record.requested_at,
|
||||
updated_at: now,
|
||||
error_code: 'LEGACY_AUTO_DEPLOY_RETIRED',
|
||||
message: LEGACY_PENDING_STATUSES.has(record.status)
|
||||
? LEGACY_RETIRED_MESSAGE
|
||||
: '旧版部署记录已停用,请使用项目配置中的“提交审核”。',
|
||||
};
|
||||
}
|
||||
|
||||
async function persistRecord(
|
||||
projectPath: string,
|
||||
record: WorksSubmissionBindingRecord,
|
||||
): Promise<WorksSubmissionBindingRecord> {
|
||||
await writeFile(
|
||||
join(projectPath, WORKS_SUBMISSION_BINDING_FILE_NAME),
|
||||
`${JSON.stringify(record, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
return record;
|
||||
}
|
||||
|
||||
export async function readWorksSubmissionBinding(
|
||||
projectPath: string,
|
||||
): Promise<WorksSubmissionBindingRecord | null> {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(await readFile(
|
||||
join(projectPath, WORKS_SUBMISSION_BINDING_FILE_NAME),
|
||||
'utf8',
|
||||
)) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const current = parseCurrentRecord(parsed);
|
||||
if (current) return current;
|
||||
const legacy = parseLegacyRecord(parsed);
|
||||
if (!legacy) return null;
|
||||
return persistRecord(projectPath, migrateLegacyRecord(legacy));
|
||||
}
|
||||
|
||||
export function createWorksSubmissionBindingStore(projectStore: ProjectStore) {
|
||||
async function recordSubmitted(
|
||||
projectId: string,
|
||||
input: SubmittedBindingInput,
|
||||
): Promise<WorksSubmissionBindingRecord> {
|
||||
const project = (await projectStore.listProjects()).find((item) => item.id === projectId);
|
||||
if (!project) throw new Error('Project not found');
|
||||
const now = new Date().toISOString();
|
||||
return persistRecord(project.path, {
|
||||
schema_version: WORKS_SUBMISSION_BINDING_SCHEMA_VERSION,
|
||||
project_id: project.id,
|
||||
status: 'submitted',
|
||||
requested_at: now,
|
||||
updated_at: now,
|
||||
app_id: input.appId,
|
||||
version_id: input.versionId,
|
||||
version_name: input.versionName,
|
||||
review_status: input.reviewStatus,
|
||||
...(input.zipSha256 ? { zip_sha256: input.zipSha256 } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
async function get(projectId: string): Promise<WorksSubmissionBindingRecord | null> {
|
||||
const project = (await projectStore.listProjects()).find((item) => item.id === projectId);
|
||||
return project ? readWorksSubmissionBinding(project.path) : null;
|
||||
}
|
||||
|
||||
async function start(): Promise<void> {
|
||||
for (const project of await projectStore.listProjects()) {
|
||||
try {
|
||||
await readWorksSubmissionBinding(project.path);
|
||||
} catch (error) {
|
||||
logger.warn(`[works-submission-binding] Failed to migrate ${project.id}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { recordSubmitted, get, start };
|
||||
}
|
||||
Reference in New Issue
Block a user