Files
makelore/tests/unit/project-package-template.test.ts
2026-07-29 17:22:35 +08:00

111 lines
6.0 KiB
TypeScript

import { cp, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { afterEach, describe, expect, it } from 'vitest';
import { parse as parseYaml } from 'yaml';
import {
initializeWorksPackageTemplate,
resolveWorksPackageTemplateDirectory,
} from '@electron/opencode/project-package-template';
const temporaryDirectories: string[] = [];
const resourcesDirectory = join(process.cwd(), 'resources');
async function temporaryDirectory(prefix: string): Promise<string> {
const directory = await mkdtemp(join(tmpdir(), prefix));
temporaryDirectories.push(directory);
return directory;
}
afterEach(async () => {
await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })));
});
describe('Works Square project package template', () => {
it('copies the shared baseline and only the Web example for a standard project', async () => {
const projectPath = await temporaryDirectory('niancode-works-template-web-');
const result = await initializeWorksPackageTemplate(projectPath, 'standard-development', { resourcesDirectory });
expect(result.files).toEqual(expect.arrayContaining([
'.dockerignore',
'Dockerfile',
'docker-compose.yml',
'nginx.conf',
'niancode.yml',
'deploy/works-deploy-check.schema.json',
'deploy/examples/works-deploy-check.web.json',
'deploy/部署报告模板.md',
]));
expect((await stat(join(projectPath, 'deploy/examples/works-deploy-check.web.json'))).isFile()).toBe(true);
await expect(stat(join(projectPath, 'deploy/examples/works-deploy-check.game.json'))).rejects.toMatchObject({ code: 'ENOENT' });
for (const releaseArtifact of ['works-deploy-check.json', 'works-publish.json', '部署报告.md']) {
await expect(stat(join(projectPath, releaseArtifact))).rejects.toMatchObject({ code: 'ENOENT' });
}
});
it('copies only the complete game/canvas example for a game project', async () => {
const projectPath = await temporaryDirectory('niancode-works-template-game-');
await initializeWorksPackageTemplate(projectPath, 'game-development', { resourcesDirectory });
const example = JSON.parse(await readFile(join(projectPath, 'deploy/examples/works-deploy-check.game.json'), 'utf8'));
const evidence = example.checks.game_canvas_smoke.canvas_evidence;
expect(example.status).toBe('BLOCKED');
expect(evidence.resize_verified).toBe(false);
for (const measurement of [
evidence.logical_resolution,
evidence.desktop_viewport,
evidence.mobile_viewport,
evidence.desktop_canvas,
evidence.mobile_canvas,
evidence.expected_max_canvas.desktop,
evidence.expected_max_canvas.mobile,
]) {
expect(typeof measurement.width).toBe('number');
expect(typeof measurement.height).toBe('number');
expect(measurement.width).toBeGreaterThan(0);
expect(measurement.height).toBeGreaterThan(0);
}
await expect(stat(join(projectPath, 'deploy/examples/works-deploy-check.web.json'))).rejects.toMatchObject({ code: 'ENOENT' });
});
it('keeps the manifest, Compose, Nginx, schema, and examples parseable and contract-safe', async () => {
const templateRoot = resolveWorksPackageTemplateDirectory(resourcesDirectory);
const manifest = parseYaml(await readFile(join(templateRoot, 'niancode.yml'), 'utf8'));
const compose = parseYaml(await readFile(join(templateRoot, 'docker-compose.yml'), 'utf8'));
const nginx = await readFile(join(templateRoot, 'nginx.conf'), 'utf8');
const schema = JSON.parse(await readFile(join(templateRoot, 'deploy/works-deploy-check.schema.json'), 'utf8'));
const web = JSON.parse(await readFile(join(templateRoot, 'deploy/examples/works-deploy-check.web.json'), 'utf8'));
const game = JSON.parse(await readFile(join(templateRoot, 'deploy/examples/works-deploy-check.game.json'), 'utf8'));
expect(manifest).toEqual({ runtime: 'compose', compose: { file: 'docker-compose.yml', public_service: 'frontend', public_port: 8080 } });
expect(compose.services.frontend.ports).toContain('127.0.0.1::8080');
expect(nginx).toContain('listen 0.0.0.0:8080');
expect(schema.properties.schema_version.const).toBe(1);
expect(web.checks).not.toHaveProperty('game_canvas_smoke');
expect(game.checks).toHaveProperty('game_canvas_smoke');
});
it('rejects invalid JSON with a source-relative error and never overwrites a destination file', async () => {
const fixtureResources = await temporaryDirectory('niancode-works-template-fixture-');
await cp(resolveWorksPackageTemplateDirectory(resourcesDirectory), resolveWorksPackageTemplateDirectory(fixtureResources), { recursive: true });
await writeFile(join(resolveWorksPackageTemplateDirectory(fixtureResources), 'deploy/examples/works-deploy-check.web.json'), '{invalid', 'utf8');
const invalidProject = await temporaryDirectory('niancode-works-template-invalid-');
await expect(initializeWorksPackageTemplate(invalidProject, 'standard-development', { resourcesDirectory: fixtureResources }))
.rejects.toThrow('deploy/examples/works-deploy-check.web.json');
const existingProject = await temporaryDirectory('niancode-works-template-existing-');
await writeFile(join(existingProject, 'niancode.yml'), 'user-owned', 'utf8');
await expect(initializeWorksPackageTemplate(existingProject, 'standard-development', { resourcesDirectory }))
.rejects.toThrow('niancode.yml');
expect(await readFile(join(existingProject, 'niancode.yml'), 'utf8')).toBe('user-owned');
});
it('is included by the packaged resources rule', async () => {
const builderConfig = parseYaml(await readFile(join(process.cwd(), 'electron-builder.yml'), 'utf8'));
expect(builderConfig.extraResources).toEqual(expect.arrayContaining([
expect.objectContaining({ from: 'resources/', to: 'resources/' }),
]));
expect((await stat(resolveWorksPackageTemplateDirectory(resourcesDirectory))).isDirectory()).toBe(true);
});
});