mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
Some checks failed
ci / Detect CI change scopes (push) Failing after 2s
landing-page-ci / Validate landing page (push) Failing after 1s
landing-page-deploy / Deploy landing page (push) Has been skipped
nix-check / build (push) Failing after 1s
ci / Preflight (push) Has been skipped
ci / Core package tests (push) Has been skipped
ci / Tools workspace tests (push) Has been skipped
ci / Daemon workspace tests (push) Has been skipped
ci / Web workspace tests (push) Has been skipped
ci / E2E vitest (push) Has been skipped
ci / Playwright critical (push) Has been skipped
ci / Build workspaces (push) Has been skipped
ci / App workspace tests (push) Has been skipped
ci / Validate workspace (push) Failing after 0s
Two-part fix for the Plugin folder card's "Open Design PR" button. The
flow was broken end-to-end: the CLI emitted a 404 URL, and the agent
prompt under-specified the contribution steps so the agent stalled
mid-turn or fell back to the legacy issue-URL path.
Catalog target — `apps/daemon/src/plugins/publish.ts`:
`buildPublishLink({ catalog: 'open-design' })` hardcoded a submission URL
at `github.com/open-design/plugin-registry`, the dedicated registry repo
proposed in docs/plans/plugin-registry.md §1.2. That repo doesn't exist
yet (P3.1 notes "creating the external GitHub repo is an operational
launch step, not a code blocker"), so every generated URL 404'd. Retarget
the catalog at the live `nexu-io/open-design` monorepo and update the
target-path hint in the PR body to `plugins/community/<plugin-name>/`
(the actual layout under main). Plan §1.2 stays the long-term goal — see
the code comment.
Also updates the matching `od plugin yank` issue URL in cli.ts.
Agent prompt — `apps/web/src/components/design-files/pluginFolderActions.ts`:
The `contribute` action prompt only said "use the supported `od plugin
publish` Open Design registry flow", which produces an issue URL (the
legacy path) and left the agent to invent the remaining steps. The agent
ended up calling `AskUserQuestion` mid-turn waiting for input that the
DesignFilesPanel buttons couldn't satisfy, then stalled for 600s.
Rewrite the `contribute` prompt to drive the full PR flow via raw `gh`
commands:
1. preflight `gh --version` / `gh auth status` (detect-and-instruct on
missing CLI, never auto-install anything)
2. read manifest, resolve author login
3. `gh repo fork nexu-io/open-design --remote=false`
4. clone fork, branch, cp plugin into `plugins/community/<name>/`,
commit/push using author's git identity (not a hardcoded bot)
5. `gh pr create ... --web` so the author reviews and clicks Create
Hard bans on `AskUserQuestion`, retry-on-failure, and the legacy
`od plugin publish --to open-design` CLI keep the turn fire-and-forget.
The `install` / `publish` action prompts are unchanged.
Tests:
* `apps/daemon/tests/plugins-publish.test.ts` — assert URL host +
catalogLabel match `nexu-io/open-design`, body contains the new path
hint.
* `apps/web/tests/components/pluginFolderActions.test.ts` (new) — lock
the contribute prompt's command surface, the `--web` review window,
the `AskUserQuestion` ban, the install-tool ban, and folder-path
interpolation. Nine assertions covering the prompt contract without
coupling to exact wording.
Validation:
* `pnpm --filter @open-design/daemon exec vitest run tests/plugins-publish.test.ts`
→ 11/11 passed
* `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts`
→ 9/9 passed
* `pnpm --filter @open-design/web typecheck` clean
* E2E: button click in DesignFilesPanel under namespace=e2e-pr-test ran
`gh --version` → fork → clone → branch → commit/push → ended at
`gh pr create --web` opening the GitHub PR draft in browser. No
AskUserQuestion stall this time.
Doesn't touch: `5f71968f` server-side endpoints (`/contribute-open-design`
+ `/publish-github` still in main as dead code — separate cleanup),
plan/spec docs (per maintainer call to keep the dedicated-repo as the
long-term goal), or `registry-backends.test.ts` (plan terminal-state
fixture; no production caller).
159 lines
5.8 KiB
TypeScript
159 lines
5.8 KiB
TypeScript
// Phase 4 / spec §14.1 — `od plugin publish` URL builder unit test.
|
|
//
|
|
// The PR-template launcher is purely string assembly; we lock the
|
|
// public contract here so a future spec patch that retargets a
|
|
// catalog (e.g. anthropics/skills moves to a /pulls path or grows a
|
|
// dedicated submission form) updates this fixture in the same PR.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
buildPublishLink,
|
|
PublishError,
|
|
PUBLISH_TARGETS,
|
|
upsertMarketplaceJsonEntry,
|
|
} from '../src/plugins/publish.js';
|
|
|
|
const META = {
|
|
pluginId: 'open-design/sample-plugin',
|
|
pluginVersion: '1.0.0',
|
|
pluginTitle: 'Sample Plugin',
|
|
pluginDescription: 'A fixture for the publish flow.',
|
|
repoUrl: 'https://github.com/open-design/sample-plugin',
|
|
};
|
|
|
|
describe('buildPublishLink', () => {
|
|
it('exports the four canonical catalog targets', () => {
|
|
expect(PUBLISH_TARGETS.sort()).toEqual([
|
|
'anthropics-skills',
|
|
'awesome-agent-skills',
|
|
'clawhub',
|
|
'open-design',
|
|
'skills-sh',
|
|
].sort());
|
|
});
|
|
|
|
it('builds a github-issue URL for anthropics/skills with title + body', () => {
|
|
const link = buildPublishLink({ catalog: 'anthropics-skills', meta: META });
|
|
expect(link.catalog).toBe('anthropics-skills');
|
|
expect(link.catalogLabel).toBe('anthropics/skills');
|
|
expect(link.url).toMatch(/^https:\/\/github\.com\/anthropics\/skills\/issues\/new\?/);
|
|
const params = new URLSearchParams(link.url.split('?')[1]);
|
|
expect(params.get('title')).toBe('Add Sample Plugin');
|
|
expect(params.get('body')).toContain('A fixture for the publish flow.');
|
|
expect(link.prBody).toContain('https://github.com/open-design/sample-plugin');
|
|
});
|
|
|
|
it('builds a github-issue URL for awesome-agent-skills', () => {
|
|
const link = buildPublishLink({ catalog: 'awesome-agent-skills', meta: META });
|
|
expect(link.url).toMatch(/^https:\/\/github\.com\/VoltAgent\/awesome-agent-skills\/issues\/new\?/);
|
|
});
|
|
|
|
it('builds a github-issue URL for clawhub', () => {
|
|
const link = buildPublishLink({ catalog: 'clawhub', meta: META });
|
|
expect(link.url).toMatch(/^https:\/\/github\.com\/openclaw\/clawhub\/issues\/new\?/);
|
|
});
|
|
|
|
it('points at skills.sh + the npx skills add command (no PR form there)', () => {
|
|
const link = buildPublishLink({ catalog: 'skills-sh', meta: META });
|
|
expect(link.url).toBe('https://skills.sh/');
|
|
expect(link.prBody).toContain('npx skills add open-design/sample-plugin');
|
|
});
|
|
|
|
it('builds an Open Design registry submission URL', () => {
|
|
// The dedicated `open-design/plugin-registry` repo per
|
|
// docs/plans/plugin-registry.md §1.2 is the long-term target; until that
|
|
// operational launch step happens, submissions land in `nexu-io/open-design`
|
|
// (plugins/community/<plugin-name>/), keeping contribution where stars and
|
|
// PR traffic already are.
|
|
const link = buildPublishLink({ catalog: 'open-design', meta: META });
|
|
expect(link.catalogLabel).toBe('nexu-io/open-design');
|
|
expect(link.url).toMatch(/^https:\/\/github\.com\/nexu-io\/open-design\/issues\/new\?/);
|
|
expect(link.prBody).toContain('plugins/community/<plugin-name>/open-design.json');
|
|
expect(link.prBody).toContain('plugins/registry/community/open-design-marketplace.json');
|
|
});
|
|
|
|
it('falls back to owner/repo placeholder when repoUrl is missing for skills-sh', () => {
|
|
const link = buildPublishLink({
|
|
catalog: 'skills-sh',
|
|
meta: { pluginId: 'sample-plugin', pluginVersion: '1.0.0' },
|
|
});
|
|
expect(link.prBody).toContain('npx skills add owner/repo');
|
|
});
|
|
|
|
it('rejects unknown catalogs', () => {
|
|
expect(() => buildPublishLink({ catalog: 'mystery' as never, meta: META })).toThrow(PublishError);
|
|
});
|
|
});
|
|
|
|
describe('upsertMarketplaceJsonEntry', () => {
|
|
it('adds a namespaced plugin entry with a reproducible github source', () => {
|
|
const outcome = upsertMarketplaceJsonEntry({
|
|
generatedAt: '2026-05-14T00:00:00.000Z',
|
|
manifest: {
|
|
specVersion: '1.0.0',
|
|
name: 'community',
|
|
version: '0.1.0',
|
|
plugins: [],
|
|
},
|
|
meta: META,
|
|
});
|
|
|
|
expect(outcome.inserted).toBe(true);
|
|
expect(outcome.entry).toMatchObject({
|
|
name: 'open-design/sample-plugin',
|
|
source: 'github:open-design/sample-plugin',
|
|
version: '1.0.0',
|
|
title: 'Sample Plugin',
|
|
publisher: {
|
|
github: 'open-design',
|
|
},
|
|
});
|
|
expect(outcome.manifest.plugins).toHaveLength(1);
|
|
expect(outcome.manifest.generatedAt).toBe('2026-05-14T00:00:00.000Z');
|
|
});
|
|
|
|
it('updates existing entries and preserves unrelated catalog metadata', () => {
|
|
const outcome = upsertMarketplaceJsonEntry({
|
|
generatedAt: '2026-05-14T00:00:00.000Z',
|
|
manifest: {
|
|
specVersion: '1.0.0',
|
|
name: 'community',
|
|
version: '0.1.0',
|
|
extra: true,
|
|
plugins: [
|
|
{
|
|
name: 'open-design/sample-plugin',
|
|
source: 'github:open-design/sample-plugin@old',
|
|
version: '0.9.0',
|
|
tags: ['kept'],
|
|
},
|
|
],
|
|
},
|
|
meta: {
|
|
...META,
|
|
pluginVersion: '1.1.0',
|
|
repoUrl: 'https://github.com/open-design/sample-plugin/tree/main/plugins/sample',
|
|
},
|
|
});
|
|
|
|
expect(outcome.inserted).toBe(false);
|
|
expect(outcome.manifest.extra).toBe(true);
|
|
expect(outcome.manifest.plugins[0]).toMatchObject({
|
|
name: 'open-design/sample-plugin',
|
|
source: 'github:open-design/sample-plugin@main/plugins/sample',
|
|
version: '1.1.0',
|
|
tags: ['kept'],
|
|
});
|
|
});
|
|
|
|
it('rejects flat ids for public marketplace JSON', () => {
|
|
expect(() => upsertMarketplaceJsonEntry({
|
|
manifest: { plugins: [] },
|
|
meta: {
|
|
pluginId: 'sample-plugin',
|
|
pluginVersion: '1.0.0',
|
|
repoUrl: 'https://github.com/open-design/sample-plugin',
|
|
},
|
|
})).toThrow(PublishError);
|
|
});
|
|
});
|