feat(plugins): bundle project scaffold marketplace plugin

This commit is contained in:
2026-09-04 22:21:24 +08:00
parent 2207806038
commit ddb678b46f
23 changed files with 218 additions and 86 deletions

View File

@@ -3,18 +3,22 @@
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 { 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 { loadBundledCodingPluginDefinitions } from '@electron/coding-plugins/manifest';
import { createCodingProjectMetadata } from '@electron/coding-projects/project-config';
import { createStaticProjectPackage } from '@electron/services/project-packager';
import type { ProjectType } from '../../shared/project-config';
import {
PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID,
PROJECT_SCAFFOLD_PLUGIN_ID,
isCodeOwnedOptionalBundledPluginId,
} from '../../shared/coding-plugins';
const pluginRoot = resolve('plugins/makelore-project-scaffold');
const pluginRoot = resolve('resources/coding-plugins/project-scaffold');
const scaffoldScript = join(
pluginRoot,
'skills',
@@ -42,23 +46,30 @@ afterEach(async () => {
});
describe('MakeLore project scaffold plugin', () => {
it('is discovered as a skill-only package with executable Skill scripts', async () => {
it('loads as a fixed code-owned bundled Skill while retaining portable plugin metadata', async () => {
const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8'));
const pluginManifest = JSON.parse(await readFile(
const portableManifest = JSON.parse(await readFile(
join(pluginRoot, '.codex-plugin', 'plugin.json'),
'utf8',
));
const marketplaceManifest = JSON.parse(await readFile(join(pluginRoot, 'plugin.json'), 'utf8'));
const inspected = await inspectDevicePackage(pluginRoot);
const definitions = await loadBundledCodingPluginDefinitions(resolve('resources/coding-plugins'));
const definition = definitions.find(({ id }) => id === PROJECT_SCAFFOLD_PLUGIN_ID);
expect({ name: packageJson.name, version: packageJson.version }).toEqual({
name: pluginManifest.name,
version: pluginManifest.version,
name: marketplaceManifest.name,
version: marketplaceManifest.version,
});
expect(pluginManifest.skills).toBe('./skills/');
expect({ name: portableManifest.name, version: portableManifest.version }).toEqual({
name: marketplaceManifest.name,
version: marketplaceManifest.version,
});
expect(portableManifest.skills).toBe('./skills/');
expect(devicePackageKind(inspected)).toBe('skill-only');
expect(inspected).toMatchObject({
packageId: 'makelore-project-scaffold',
resolvedVersion: '0.1.0',
packageId: PROJECT_SCAFFOLD_PLUGIN_ID,
resolvedVersion: '1.0.0',
extensionEntries: [],
hasSkillScripts: true,
ignoredLifecycleScripts: [],
@@ -67,30 +78,25 @@ describe('MakeLore project scaffold plugin', () => {
entryPath: 'skills/makelore-project-scaffold/SKILL.md',
}],
});
expect(definition).toMatchObject({
id: PROJECT_SCAFFOLD_PLUGIN_ID,
version: '1.0.0',
runtimeKind: 'skill_only',
acquisitionMode: 'user_acquired',
releaseId: PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID,
provenance: { source: 'bundled', packageRoot: 'project-scaffold' },
skills: [{
id: 'makelore-project-scaffold',
entryPath: 'skills/makelore-project-scaffold/SKILL.md',
grants: [],
}],
tools: [],
});
expect(isCodeOwnedOptionalBundledPluginId(PROJECT_SCAFFOLD_PLUGIN_ID)).toBe(true);
});
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));
it('ships the executable scaffold and every Skill resource in the bundled root', async () => {
const skillRoot = join(pluginRoot, 'skills', 'makelore-project-scaffold');
expect(await readFile(join(skillRoot, 'scripts', 'scaffold.mjs'), 'utf8'))
.toContain('schemaVersion: 1');
expect(await readFile(join(skillRoot, 'assets', 'templates', 'common', 'package-lock.json'), 'utf8'))
@@ -164,7 +170,7 @@ describe('MakeLore project scaffold plugin', () => {
});
it('creates an interactive AI application accepted by the existing source packager', async () => {
const projectType: ProjectType = 'interactive_ai_app';
const projectType = 'interactive_ai_app' as const;
const projectPath = await mkdtemp(join(tmpdir(), `makelore-${projectType}-`));
const outputPath = await mkdtemp(join(tmpdir(), 'makelore-scaffold-package-'));
tempDirectories.push(projectPath, outputPath);