feat: plugin-registry 标准发布面(dsh.plugin.json + 双 client bundle + registry 打包),v0.1.2
- dsh.plugin.json 原生清单:id dsh-external/dsh-better-sidebar,version 0.1.2, main = lib/index.js,client.main = lib/client-registry.js,contributes 空声明 - tsdown client 构建参数化:lib/client.js(官方通道,id = 包名)+ lib/client-registry.js (registry 通道,id = 清单 id),同源不漂移 - scripts/package-registry.mjs 组装 registry/ 安装根(gitignore,不入库) - tests/manifest-consistency.spec.ts:id 两段校验 / version 同步 / 入口存在 / 双 bundle id 不互换 / contributes 空 - README:新增「通过 plugin-registry 安装(标准)」章节 + 保留官方 profile 安装 方案 + 通道互斥说明;架构表补 manifest 与双 client 行
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Registry manifest consistency spec: guards the plugin-registry release form
|
||||
* (`dsh.plugin.json`) against the build output and package.json, so the repo
|
||||
* cannot drift into an uninstallable / uneable state:
|
||||
* - `id` passes the registry's strict two-segment validation (native form),
|
||||
* - `version` stays in sync with package.json,
|
||||
* - the manifest entry files exist in the build output,
|
||||
* - the registry client bundle registers exactly the manifest id (the
|
||||
* browser-side `arrive()` contract), and the official client bundle keeps
|
||||
* the package-name id (the client-modules compose contract) — the two
|
||||
* bundles must never be swapped.
|
||||
*
|
||||
* Reads the built lib/ output, so run `pnpm build` first (the registry
|
||||
* install validates main/client.main existence the same way).
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(fileURLToPath(new URL('..', import.meta.url)))
|
||||
|
||||
/** The registry manifest face this spec touches (mirror of the registry's PluginManifest). */
|
||||
interface PluginManifest {
|
||||
id: string
|
||||
version: string
|
||||
main: string
|
||||
description?: string
|
||||
engines?: { dsh?: string }
|
||||
contributes?: { tools?: string[]; skills?: string[] }
|
||||
client?: { main?: string; inject?: string[]; immediately?: boolean }
|
||||
}
|
||||
|
||||
interface PackageJson {
|
||||
name: string
|
||||
version: string
|
||||
}
|
||||
|
||||
const manifest = JSON.parse(readFileSync(resolve(ROOT, 'dsh.plugin.json'), 'utf8')) as PluginManifest
|
||||
const pkg = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')) as PackageJson
|
||||
|
||||
/** The registry's strict id validation (manifest.ts / client-modules registerExternal): exactly two lowercase slash-separated segments. */
|
||||
const ID_PATTERN = /^(?!node_modules\/)(?:@[a-z0-9][a-z0-9-.]*\/[a-z0-9][a-z0-9-.]*|[a-z0-9][a-z0-9-.]*\/[a-z0-9][a-z0-9-.]*)$/
|
||||
|
||||
/** The registered `__ModuleLoader__.load({ id })` value of a built client bundle. */
|
||||
function bundleId(file: string): string {
|
||||
const source = readFileSync(resolve(ROOT, file), 'utf8')
|
||||
const match = /load\(\{\s*id:\s*"([^"]+)"/.exec(source)
|
||||
if (match === null) throw new Error(`${file} registers no __ModuleLoader__.load id — run \`pnpm build\` first`)
|
||||
return match[1]!
|
||||
}
|
||||
|
||||
describe('registry manifest consistency (dsh.plugin.json)', () => {
|
||||
it('id is a valid two-segment registry id (native form)', () => {
|
||||
expect(manifest.id).toMatch(ID_PATTERN)
|
||||
})
|
||||
|
||||
it('version matches package.json', () => {
|
||||
expect(manifest.version).toBe(pkg.version)
|
||||
})
|
||||
|
||||
it('main and client.main exist in the build output', () => {
|
||||
expect(existsSync(resolve(ROOT, manifest.main))).toBe(true)
|
||||
expect(manifest.client?.main).toBeDefined()
|
||||
expect(existsSync(resolve(ROOT, manifest.client!.main!))).toBe(true)
|
||||
})
|
||||
|
||||
it('the registry bundle registers exactly the manifest id and the official bundle the package name (no swap)', () => {
|
||||
const registryId = bundleId(manifest.client!.main!)
|
||||
expect(registryId).toBe(manifest.id)
|
||||
expect(bundleId('lib/client.js')).toBe(pkg.name)
|
||||
expect(registryId).not.toBe(pkg.name)
|
||||
})
|
||||
|
||||
it('contributes declares no tools or skills (the host half registers none)', () => {
|
||||
expect(manifest.contributes?.tools).toEqual([])
|
||||
expect(manifest.contributes?.skills).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user