实现项目真机预览与待审版本扫码验收
需求:接管客户端未提交 WIP,保留真机预览,移除旧登录原型并维持 2.0.0。 实现:由 Main 核对项目与精确 Release,签发短时 Owner preview;补充一键提交映射能力及 Windows ZIP 预检兼容。
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user