Files
makelore/tests/unit/project-scaffold-plugin.test.ts

194 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @vitest-environment node
import { spawnSync } from 'node:child_process';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import {
devicePackageKind,
inspectDevicePackage,
} from '@electron/coding-packages/device-package-format';
import { DevicePackageManager } from '@electron/coding-packages/device-package-manager';
import { createCodingProjectMetadata } from '@electron/coding-projects/project-config';
import { createStaticProjectPackage } from '@electron/services/project-packager';
import type { ProjectType } from '../../shared/project-config';
const pluginRoot = resolve('plugins/makelore-project-scaffold');
const scaffoldScript = join(
pluginRoot,
'skills',
'makelore-project-scaffold',
'scripts',
'scaffold.mjs',
);
const tempDirectories: string[] = [];
function extractStringList(source: string, declaration: string): string[] {
const declarationStart = source.indexOf(`const ${declaration} =`);
expect(declarationStart).toBeGreaterThanOrEqual(0);
const listStart = source.indexOf('[', declarationStart);
const listEnd = source.indexOf(']', listStart);
expect(listStart).toBeGreaterThanOrEqual(0);
expect(listEnd).toBeGreaterThan(listStart);
return [...source.slice(listStart + 1, listEnd).matchAll(/'([^']+)'/g)]
.map((match) => match[1]);
}
afterEach(async () => {
await Promise.all(
tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })),
);
});
describe('MakeLore project scaffold plugin', () => {
it('is discovered as a skill-only package with executable Skill scripts', async () => {
const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8'));
const pluginManifest = JSON.parse(await readFile(
join(pluginRoot, '.codex-plugin', 'plugin.json'),
'utf8',
));
const inspected = await inspectDevicePackage(pluginRoot);
expect({ name: packageJson.name, version: packageJson.version }).toEqual({
name: pluginManifest.name,
version: pluginManifest.version,
});
expect(pluginManifest.skills).toBe('./skills/');
expect(devicePackageKind(inspected)).toBe('skill-only');
expect(inspected).toMatchObject({
packageId: 'makelore-project-scaffold',
resolvedVersion: '0.1.0',
extensionEntries: [],
hasSkillScripts: true,
ignoredLifecycleScripts: [],
skillEntries: [{
id: 'makelore-project-scaffold',
entryPath: 'skills/makelore-project-scaffold/SKILL.md',
}],
});
});
it('prepares, confirms, and installs the real package with every Skill resource', async () => {
const root = await mkdtemp(join(tmpdir(), 'makelore-scaffold-device-package-'));
tempDirectories.push(root);
const manager = new DevicePackageManager({
rootDir: join(root, 'store'),
now: () => Date.parse('2026-09-04T00:00:00.000Z'),
createId: () => 'project-scaffold-plan',
});
const preview = await manager.prepare(pluginRoot, 'prepare-turn');
expect(preview).toMatchObject({
kind: 'skill-only',
includesExecutableCode: true,
skillEntries: [{ id: 'makelore-project-scaffold' }],
extensionEntries: [],
});
expect(preview.warnings.join(' ')).toContain('可执行 Skill 脚本');
await manager.commit(preview.planId, true, 'confirm-turn');
const [resource] = (await manager.resolveEnabledResources()).skillEntries;
expect(resource?.id).toBe('makelore-project-scaffold');
const skillRoot = dirname(join(resource!.packageRoot, resource!.entryPath));
expect(await readFile(join(skillRoot, 'scripts', 'scaffold.mjs'), 'utf8'))
.toContain('schemaVersion: 1');
expect(await readFile(join(skillRoot, 'assets', 'templates', 'common', 'package-lock.json'), 'utf8'))
.toContain('"lockfileVersion": 3');
expect(await readFile(join(skillRoot, 'references', 'submission-requirements.md'), 'utf8'))
.toContain('提交审批要求');
});
it('carries the complete categorized release-readiness contract', async () => {
const skill = await readFile(
join(pluginRoot, 'skills', 'makelore-project-scaffold', 'SKILL.md'),
'utf8',
);
const requirements = await readFile(
join(
pluginRoot,
'skills',
'makelore-project-scaffold',
'references',
'submission-requirements.md',
),
'utf8',
);
const packagerSource = await readFile('electron/services/project-packager.ts', 'utf8');
expect(skill).toContain('Release-readiness workflow');
expect(skill).toContain('不能进入一键提交');
expect(skill).toContain('可进入一键提交,仍需运行期验证');
expect(skill).toContain('Do not infer an external-network blocker');
for (const heading of ['必须具备', '直接阻断', '打包排除', '运行时或平台确认']) {
expect(requirements).toContain(`**${heading}**`);
}
for (const value of [
'.makelore/project.json',
'schema v2',
'interactive_ai_app',
'mini_game',
'mini_program',
'custom',
'npm ci --ignore-scripts',
'lockfileVersion',
'2,000',
'200 MiB',
'50 MiB',
'release.json',
'CON',
'1280×720',
'390×844',
'30 秒',
'HTTP/HTTPS',
'.env',
'serviceaccount',
'works-',
'PNG、JPEG 或 WebP',
'1150',
'存在但不阻断',
]) {
expect(requirements).toContain(value);
}
const excludedValues = [
...extractStringList(packagerSource, 'EXCLUDED_DIRECTORY_NAMES'),
...extractStringList(packagerSource, 'EXCLUDED_FILE_NAMES'),
...extractStringList(packagerSource, 'EXCLUDED_FILE_SUFFIXES'),
];
expect(excludedValues.length).toBeGreaterThan(50);
for (const value of excludedValues) {
expect(requirements).toContain(value);
}
});
it('creates an interactive AI application accepted by the existing source packager', async () => {
const projectType: ProjectType = 'interactive_ai_app';
const projectPath = await mkdtemp(join(tmpdir(), `makelore-${projectType}-`));
const outputPath = await mkdtemp(join(tmpdir(), 'makelore-scaffold-package-'));
tempDirectories.push(projectPath, outputPath);
await createCodingProjectMetadata(projectPath, {
projectType,
now: '2026-09-04T00:00:00.000Z',
});
const scaffold = spawnSync(
process.execPath,
[scaffoldScript, '--project-root', projectPath],
{ encoding: 'utf8' },
);
expect(scaffold.status, scaffold.stderr).toBe(0);
const packaged = await createStaticProjectPackage({
projectPath,
archivePath: join(outputPath, 'project.zip'),
});
expect(packaged.manifest.project_type).toBe(projectType);
expect(packaged.archiveName).toBe('project.zip');
expect(packaged.fileCount).toBeGreaterThan(1);
expect(packaged.archiveBytes).toBeGreaterThan(0);
expect(packaged.excludedPaths).toEqual(expect.arrayContaining(['.makelore/', 'knowledge/']));
});
});