实现项目真机预览与待审版本扫码验收

需求:接管客户端未提交 WIP,保留真机预览,移除旧登录原型并维持 2.0.0。

实现:由 Main 核对项目与精确 Release,签发短时 Owner preview;补充一键提交映射能力及 Windows ZIP 预检兼容。
This commit is contained in:
2026-08-08 17:57:36 +08:00
parent 7b23cce67a
commit 1a19ad9808
20 changed files with 1890 additions and 12 deletions

View File

@@ -1,17 +1,17 @@
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 { promisify } from 'node:util';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createWorksCloudDeployment } from '@electron/services/works-cloud-deployment';
import type { OpencodeProject } from '@electron/opencode/project-store';
import { WORKS_CLOUD_DEPLOYMENT_FILE_NAME } from '../../shared/works-cloud-deployment';
import { REQUIRED_DYNAMIC_WORKS_DEPLOY_CHECK_IDS } from '../../shared/works-square-deploy-check';
const execFileAsync = promisify(execFile);
const tempDirectories: string[] = [];
const execFileAsync = promisify(execFile);
const project: OpencodeProject = {
id: 'prj_cloud_deployment_test',
@@ -77,7 +77,10 @@ async function createReadyProject(): Promise<{ projectPath: string; zipPath: str
await mkdir(dirname(filePath), { recursive: true });
await writeFile(filePath, content, 'utf8');
}
await execFileAsync('zip', ['-q', '-0', '-r', zipPath, '.'], { cwd: sourcePath });
const zipCommand = process.platform === 'win32'
? { file: 'tar', args: ['-a', '-cf', zipPath, ...Object.keys(files)] }
: { file: 'zip', args: ['-q', '-0', '-r', zipPath, '.'] };
await execFileAsync(zipCommand.file, zipCommand.args, { cwd: sourcePath });
const zipSha256 = createHash('sha256').update(await readFile(zipPath)).digest('hex');
const publish = {
app_id: 'cloud-deployment-test',
@@ -110,16 +113,21 @@ async function createReadyProject(): Promise<{ projectPath: string; zipPath: str
}
async function waitForStatus(projectPath: string, expected: string): Promise<Record<string, unknown>> {
let lastRecord: Record<string, unknown> | undefined;
for (let attempt = 0; attempt < 100; attempt += 1) {
try {
const record = JSON.parse(await readFile(join(projectPath, WORKS_CLOUD_DEPLOYMENT_FILE_NAME), 'utf8')) as Record<string, unknown>;
lastRecord = record;
if (record.status === expected) return record;
if (record.status === 'failed') {
throw new Error(`Deployment failed while waiting for ${expected}: ${String(record.error)}`);
}
} catch {
// The coordinator may not have written the first status yet.
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
throw new Error(`Timed out waiting for ${expected}`);
throw new Error(`Timed out waiting for ${expected}; last record: ${JSON.stringify(lastRecord)}`);
}
afterEach(async () => {
@@ -127,6 +135,42 @@ afterEach(async () => {
});
describe('Main-owned Works Square cloud deployment', () => {
it('records an already submitted one-click release without starting another upload', async () => {
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-cloud-record-'));
tempDirectories.push(projectPath);
const projectRecord = { ...project, path: projectPath };
const fetchMock = vi.fn();
const coordinator = createWorksCloudDeployment(createStore(projectRecord), {
apiBaseUrl: 'https://square.test',
fetchImpl: fetchMock,
watchDirectory: () => ({ close: vi.fn() }),
});
const record = await coordinator.recordSubmitted(projectRecord.id, {
appId: 'makelore-project',
versionId: 'version-remote-7',
versionName: 'v2.0.0',
reviewStatus: 'building',
zipSha256: 'a'.repeat(64),
});
expect(record).toMatchObject({
project_id: projectRecord.id,
status: 'submitted',
app_id: 'makelore-project',
version_id: 'version-remote-7',
version_name: 'v2.0.0',
review_status: 'building',
zip_sha256: 'a'.repeat(64),
});
await expect(coordinator.get(projectRecord.id)).resolves.toEqual(record);
expect(JSON.parse(await readFile(
join(projectPath, WORKS_CLOUD_DEPLOYMENT_FILE_NAME),
'utf8',
))).toEqual(record);
expect(fetchMock).not.toHaveBeenCalled();
});
it('keeps an armed task waiting for the package instead of asking for server SSH', async () => {
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-cloud-waiting-'));
tempDirectories.push(projectPath);